use oxidize_pdf::graphics::Color;
use oxidize_pdf::Page;
#[test]
fn interleaved_gfx_text_gfx_preserves_call_order() {
let mut page = Page::a4();
page.graphics()
.set_fill_color(Color::Rgb(0.0, 0.0, 1.0))
.rect(50.0, 700.0, 100.0, 50.0)
.fill();
page.text()
.set_font(oxidize_pdf::text::Font::Helvetica, 12.0)
.at(50.0, 680.0)
.write("Label")
.unwrap();
page.graphics()
.set_fill_color(Color::Rgb(1.0, 0.0, 0.0))
.rect(50.0, 640.0, 100.0, 30.0)
.fill();
let mut doc = oxidize_pdf::Document::new();
doc.set_compress(false); doc.add_page(page);
let bytes = doc.to_bytes().expect("to_bytes must succeed");
let s = String::from_utf8_lossy(&bytes);
let pos_blue = s
.find("0.000 0.000 1.000 rg")
.expect("blue rg must be emitted");
let pos_label = s.find("(Label)").expect("Label literal must be emitted");
let pos_red = s
.find("1.000 0.000 0.000 rg")
.expect("red rg must be emitted");
assert!(
pos_blue < pos_label,
"blue rg ({pos_blue}) must precede the text label ({pos_label}) — \
call order should win, content stream:\n{s}"
);
assert!(
pos_label < pos_red,
"text label ({pos_label}) must precede the red rg ({pos_red}) — \
call order should win, content stream:\n{s}"
);
}
#[test]
fn text_then_graphics_preserves_call_order() {
let mut page = Page::a4();
page.text()
.set_font(oxidize_pdf::text::Font::Helvetica, 12.0)
.at(50.0, 700.0)
.write("Underneath")
.unwrap();
page.graphics()
.set_fill_color(Color::Rgb(0.5, 0.5, 0.5))
.rect(40.0, 695.0, 200.0, 20.0)
.fill();
let mut doc = oxidize_pdf::Document::new();
doc.set_compress(false); doc.add_page(page);
let bytes = doc.to_bytes().expect("to_bytes must succeed");
let s = String::from_utf8_lossy(&bytes);
let pos_label = s
.find("(Underneath)")
.expect("text literal must be emitted");
let pos_rect_fill = s
.find("0.500 0.500 0.500 rg")
.expect("grey rg must be emitted");
assert!(
pos_label < pos_rect_fill,
"text-first call order must put `(Underneath) Tj` ({pos_label}) before \
the grey rectangle's `rg` ({pos_rect_fill}); content stream:\n{s}"
);
}
#[test]
fn pure_graphics_only_does_not_regress() {
let mut page = Page::a4();
page.graphics()
.move_to(0.0, 0.0)
.line_to(100.0, 100.0)
.stroke();
let mut doc = oxidize_pdf::Document::new();
doc.set_compress(false); doc.add_page(page);
let bytes = doc.to_bytes().expect("to_bytes must succeed");
let s = String::from_utf8_lossy(&bytes);
let pos_m = s.find("0.00 0.00 m").expect("m must be emitted");
let pos_l = s.find("100.00 100.00 l").expect("l must be emitted");
let pos_s = s.find("\nS\n").expect("S must be emitted");
assert!(pos_m < pos_l && pos_l < pos_s);
}
#[test]
fn pure_text_only_does_not_regress() {
let mut page = Page::a4();
page.text()
.set_font(oxidize_pdf::text::Font::Helvetica, 12.0)
.at(100.0, 700.0)
.write("Hello")
.unwrap();
let mut doc = oxidize_pdf::Document::new();
doc.set_compress(false); doc.add_page(page);
let bytes = doc.to_bytes().expect("to_bytes must succeed");
let s = String::from_utf8_lossy(&bytes);
let pos_bt = s.find("BT\n").expect("BT must be emitted");
let pos_label = s.find("(Hello)").expect("Hello literal must be emitted");
let pos_et = s.find("ET\n").expect("ET must be emitted");
assert!(pos_bt < pos_label && pos_label < pos_et);
}
#[test]
fn add_text_flow_between_gfx_preserves_call_order() {
use oxidize_pdf::page::Margins;
use oxidize_pdf::text::TextFlowContext;
let mut page = Page::a4();
page.graphics()
.set_fill_color(Color::Rgb(0.0, 0.0, 1.0))
.rect(50.0, 700.0, 100.0, 50.0)
.fill();
let mut flow = TextFlowContext::new(595.0, 842.0, Margins::default());
flow.set_font(oxidize_pdf::text::Font::Helvetica, 12.0);
flow.at(50.0, 680.0);
flow.write_wrapped("FlowLabel").unwrap();
page.add_text_flow(&flow);
page.graphics()
.set_fill_color(Color::Rgb(1.0, 0.0, 0.0))
.rect(50.0, 640.0, 100.0, 30.0)
.fill();
let mut doc = oxidize_pdf::Document::new();
doc.set_compress(false);
doc.add_page(page);
let bytes = doc.to_bytes().expect("to_bytes must succeed");
let s = String::from_utf8_lossy(&bytes);
let pos_blue = s.find("0.000 0.000 1.000 rg").expect("blue rg must exist");
let pos_flow = s.find("(FlowLabel)").expect("FlowLabel literal must exist");
let pos_red = s.find("1.000 0.000 0.000 rg").expect("red rg must exist");
assert!(
pos_blue < pos_flow,
"blue rg ({pos_blue}) must precede the flow text ({pos_flow}) — content stream:\n{s}"
);
assert!(
pos_flow < pos_red,
"flow text ({pos_flow}) must precede the red rg ({pos_red}) — content stream:\n{s}"
);
}
#[test]
fn add_text_flow_then_page_text_preserves_call_order() {
use oxidize_pdf::page::Margins;
use oxidize_pdf::text::TextFlowContext;
let mut page = Page::a4();
let mut flow = TextFlowContext::new(595.0, 842.0, Margins::default());
flow.set_font(oxidize_pdf::text::Font::Helvetica, 12.0);
flow.at(50.0, 700.0);
flow.write_wrapped("Flow").unwrap();
page.add_text_flow(&flow);
page.text()
.set_font(oxidize_pdf::text::Font::Helvetica, 12.0)
.at(50.0, 680.0)
.write("PageText")
.unwrap();
let mut doc = oxidize_pdf::Document::new();
doc.set_compress(false);
doc.add_page(page);
let bytes = doc.to_bytes().expect("to_bytes must succeed");
let s = String::from_utf8_lossy(&bytes);
let pos_flow = s.find("(Flow)").expect("(Flow) literal must exist");
let pos_text = s.find("(PageText)").expect("(PageText) literal must exist");
assert!(
pos_flow < pos_text,
"flow text added first ({pos_flow}) must precede page text ({pos_text}); content stream:\n{s}"
);
}
#[test]
fn nan_font_size_sanitised_at_emission_via_text_context() {
let mut page = Page::a4();
page.text()
.set_font(oxidize_pdf::text::Font::Helvetica, f64::NAN)
.at(100.0, 700.0)
.write("hello")
.unwrap();
let mut doc = oxidize_pdf::Document::new();
doc.set_compress(false);
doc.add_page(page);
let bytes = doc.to_bytes().expect("to_bytes must succeed");
let s = String::from_utf8_lossy(&bytes);
assert!(
!s.contains("NaN") && !s.contains("/Helvetica inf"),
"NaN/inf must not appear in `Tf` emission, content stream:\n{s}"
);
assert!(
s.contains("/Helvetica 0 Tf") || s.contains("/Helvetica 0.0 Tf"),
"NaN font size must clamp to 0 in `Tf`, content stream:\n{s}"
);
}