use printpdf::*;
fn main() {
let mut doc = PdfDocument::new("Advanced Text Positioning Example");
let mut ops = Vec::new();
ops.extend_from_slice(&[
Op::StartTextSection,
Op::SetTextCursor {
pos: Point::new(Mm(20.0), Mm(280.0)),
},
Op::SetFont {
font: PdfFontHandle::Builtin(BuiltinFont::HelveticaBold),
size: Pt(24.0),
},
Op::SetLineHeight { lh: Pt(28.0) },
Op::SetFillColor {
col: Color::Rgb(Rgb {
r: 0.0,
g: 0.0,
b: 0.0,
icc_profile: None,
}),
},
Op::ShowText {
items: vec![TextItem::Text(
"Advanced Text Positioning and Styling".to_string(),
)],
},
Op::EndTextSection,
]);
ops.extend_from_slice(&[
Op::SaveGraphicsState,
Op::StartTextSection,
Op::SetTextCursor {
pos: Point::new(Mm(20.0), Mm(260.0)),
},
Op::SetFont {
font: PdfFontHandle::Builtin(BuiltinFont::Helvetica),
size: Pt(12.0),
},
Op::SetLineHeight { lh: Pt(14.0) },
Op::ShowText {
items: vec![TextItem::Text(
"1. Normal text without spacing:".to_string(),
)],
},
Op::AddLineBreak,
Op::ShowText {
items: vec![TextItem::Text("CHARACTERSPACING".to_string())],
},
Op::AddLineBreak,
Op::AddLineBreak,
Op::ShowText {
items: vec![TextItem::Text(
"Text with added character spacing:".to_string(),
)],
},
Op::AddLineBreak,
Op::SetCharacterSpacing { multiplier: 2.0 },
Op::ShowText {
items: vec![TextItem::Text("CHARACTERSPACING".to_string())],
},
Op::SetCharacterSpacing { multiplier: 0.0 },
Op::EndTextSection,
Op::RestoreGraphicsState,
]);
ops.extend_from_slice(&[
Op::SaveGraphicsState,
Op::StartTextSection,
Op::SetTextCursor {
pos: Point::new(Mm(20.0), Mm(230.0)),
},
Op::SetFont {
font: PdfFontHandle::Builtin(BuiltinFont::Helvetica),
size: Pt(12.0),
},
Op::SetLineHeight { lh: Pt(14.0) },
Op::ShowText {
items: vec![TextItem::Text("2. Rotated text:".to_string())],
},
Op::EndTextSection,
Op::RestoreGraphicsState,
]);
ops.extend(
(0..=7)
.flat_map(|i| {
let angle = i as f32 * 45.0; vec![
Op::SaveGraphicsState,
Op::StartTextSection,
Op::SetTextCursor {
pos: Point::new(Mm(50.0), Mm(210.0)),
},
Op::SetTextMatrix {
matrix: TextMatrix::TranslateRotate(
Pt(50.0 + i as f32 * 50.0),
Pt(600.0),
angle,
),
},
Op::SetFont {
font: PdfFontHandle::Builtin(BuiltinFont::TimesRoman),
size: Pt(10.0),
},
Op::SetLineHeight { lh: Pt(12.0) },
Op::SetFillColor {
col: Color::Rgb(Rgb {
r: 0.0,
g: 0.0,
b: i as f32 / 7.0,
icc_profile: None,
}),
},
Op::ShowText {
items: vec![TextItem::Text(format!("{}deg", angle))],
},
Op::EndTextSection,
Op::RestoreGraphicsState,
]
})
.collect::<Vec<_>>()
.iter()
.cloned(),
);
ops.extend_from_slice(&[
Op::SaveGraphicsState,
Op::StartTextSection,
Op::SetTextCursor {
pos: Point::new(Mm(20.0), Mm(190.0)),
},
Op::SetFont {
font: PdfFontHandle::Builtin(BuiltinFont::Helvetica),
size: Pt(12.0),
},
Op::SetLineHeight { lh: Pt(14.0) },
Op::ShowText {
items: vec![TextItem::Text("3. Text on a curved path:".to_string())],
},
Op::EndTextSection,
Op::RestoreGraphicsState,
]);
let center_x = 300.0;
let center_y = 460.0;
let radius = 100.0;
let text = "Curved Text Around A Circle Path";
for (i, c) in text.chars().rev().enumerate() {
let angle = 180.0 - (i as f32 * 8.0);
let radians = angle.to_radians();
let x = center_x + radius * radians.cos();
let y = center_y + radius * radians.sin();
ops.extend_from_slice(&[
Op::SaveGraphicsState,
Op::StartTextSection,
Op::SetTextCursor {
pos: Point {
x: Pt(0.0),
y: Pt(0.0),
},
},
Op::SetTextMatrix {
matrix: TextMatrix::TranslateRotate(Pt(x), Pt(y), angle + 90.0),
},
Op::SetFont {
font: PdfFontHandle::Builtin(BuiltinFont::Helvetica),
size: Pt(12.0),
},
Op::SetLineHeight { lh: Pt(14.0) },
Op::SetFillColor {
col: Color::Rgb(Rgb {
r: i as f32 / text.len() as f32,
g: 0.3,
b: 1.0 - i as f32 / text.len() as f32,
icc_profile: None,
}),
},
Op::ShowText {
items: vec![TextItem::Text(c.to_string())],
},
Op::EndTextSection,
Op::RestoreGraphicsState,
]);
}
ops.extend_from_slice(&[
Op::SaveGraphicsState,
Op::StartTextSection,
Op::SetTextCursor {
pos: Point::new(Mm(20.0), Mm(150.0)),
},
Op::SetFont {
font: PdfFontHandle::Builtin(BuiltinFont::Helvetica),
size: Pt(12.0),
},
Op::SetLineHeight { lh: Pt(14.0) },
Op::ShowText {
items: vec![TextItem::Text(
"4. Kerned text with manual adjustments:".to_string(),
)],
},
Op::AddLineBreak,
Op::ShowText {
items: vec![TextItem::Text("AV AWAY WAVE To Va".to_string())],
},
Op::AddLineBreak,
Op::ShowText {
items: vec![
TextItem::Text("A".to_string()),
TextItem::Offset(-30.0), TextItem::Text("V".to_string()),
TextItem::Offset(10.0), TextItem::Text("A".to_string()),
TextItem::Offset(-30.0), TextItem::Text("W".to_string()),
TextItem::Offset(-30.0), TextItem::Text("A".to_string()),
TextItem::Offset(-20.0), TextItem::Text("Y".to_string()),
TextItem::Offset(20.0), TextItem::Text("W".to_string()),
TextItem::Offset(-30.0), TextItem::Text("A".to_string()),
TextItem::Offset(-30.0), TextItem::Text("V".to_string()),
TextItem::Offset(-30.0), TextItem::Text("E".to_string()),
TextItem::Offset(40.0), TextItem::Text("T".to_string()),
TextItem::Offset(-20.0), TextItem::Text("o".to_string()),
TextItem::Offset(20.0), TextItem::Text("V".to_string()),
TextItem::Offset(-40.0), TextItem::Text("a".to_string()),
],
},
Op::EndTextSection,
Op::RestoreGraphicsState,
]);
ops.extend_from_slice(&[
Op::SaveGraphicsState,
Op::StartTextSection,
Op::SetTextCursor {
pos: Point::new(Mm(20.0), Mm(130.0)),
},
Op::SetFont {
font: PdfFontHandle::Builtin(BuiltinFont::Helvetica),
size: Pt(12.0),
},
Op::SetLineHeight { lh: Pt(14.0) },
Op::SetFillColor {
col: Color::Rgb(Rgb {
r: 0.0,
g: 0.0,
b: 0.0,
icc_profile: None,
}),
},
Op::ShowText {
items: vec![TextItem::Text(
"5. Text with different rendering modes:".to_string(),
)],
},
Op::AddLineBreak,
Op::SetTextRenderingMode {
mode: TextRenderingMode::Fill,
},
Op::SetFillColor {
col: Color::Rgb(Rgb {
r: 0.0,
g: 0.0,
b: 0.8,
icc_profile: None,
}),
},
Op::ShowText {
items: vec![TextItem::Text("Fill mode".to_string())],
},
Op::AddLineBreak,
Op::SetTextRenderingMode {
mode: TextRenderingMode::Stroke,
},
Op::SetOutlineColor {
col: Color::Rgb(Rgb {
r: 0.8,
g: 0.0,
b: 0.0,
icc_profile: None,
}),
},
Op::SetOutlineThickness { pt: Pt(0.5) },
Op::ShowText {
items: vec![TextItem::Text("Stroke mode".to_string())],
},
Op::AddLineBreak,
Op::SetTextRenderingMode {
mode: TextRenderingMode::FillStroke,
},
Op::SetFillColor {
col: Color::Rgb(Rgb {
r: 0.0,
g: 0.8,
b: 0.0,
icc_profile: None,
}),
},
Op::SetOutlineColor {
col: Color::Rgb(Rgb {
r: 0.0,
g: 0.3,
b: 0.0,
icc_profile: None,
}),
},
Op::ShowText {
items: vec![TextItem::Text("Fill and stroke mode".to_string())],
},
Op::EndTextSection,
Op::RestoreGraphicsState,
]);
ops.extend_from_slice(&[
Op::SaveGraphicsState,
Op::StartTextSection,
Op::SetTextCursor {
pos: Point::new(Mm(20.0), Mm(100.0)),
},
Op::SetFont {
font: PdfFontHandle::Builtin(BuiltinFont::Helvetica),
size: Pt(12.0),
},
Op::SetLineHeight { lh: Pt(14.0) },
Op::SetFillColor {
col: Color::Rgb(Rgb {
r: 0.0,
g: 0.0,
b: 0.0,
icc_profile: None,
}),
},
Op::SetTextRenderingMode {
mode: TextRenderingMode::Fill,
},
Op::ShowText {
items: vec![TextItem::Text(
"6. Text with horizontal scaling:".to_string(),
)],
},
Op::AddLineBreak,
Op::SetHorizontalScaling { percent: 100.0 },
Op::ShowText {
items: vec![TextItem::Text("Normal text (100% scaling)".to_string())],
},
Op::AddLineBreak,
Op::SetHorizontalScaling { percent: 75.0 },
Op::ShowText {
items: vec![TextItem::Text("Condensed text (75% scaling)".to_string())],
},
Op::AddLineBreak,
Op::SetHorizontalScaling { percent: 150.0 },
Op::ShowText {
items: vec![TextItem::Text("Expanded text (150% scaling)".to_string())],
},
Op::EndTextSection,
Op::RestoreGraphicsState,
]);
ops.extend_from_slice(&[
Op::SaveGraphicsState,
Op::StartTextSection,
Op::SetTextCursor {
pos: Point::new(Mm(20.0), Mm(80.0)),
},
Op::SetFont {
font: PdfFontHandle::Builtin(BuiltinFont::Helvetica),
size: Pt(12.0),
},
Op::SetLineHeight { lh: Pt(14.0) },
Op::SetHorizontalScaling { percent: 100.0 }, Op::ShowText {
items: vec![TextItem::Text("7. Text with word spacing:".to_string())],
},
Op::AddLineBreak,
Op::SetWordSpacing { pt: Pt(0.0) },
Op::ShowText {
items: vec![TextItem::Text(
"This is text with normal word spacing.".to_string(),
)],
},
Op::AddLineBreak,
Op::SetWordSpacing { pt: Pt(10.0) },
Op::ShowText {
items: vec![TextItem::Text(
"This is text with extra word spacing.".to_string(),
)],
},
Op::AddLineBreak,
Op::SetWordSpacing { pt: Pt(20.0) },
Op::ShowText {
items: vec![TextItem::Text(
"This is text with even more word spacing.".to_string(),
)],
},
Op::EndTextSection,
Op::RestoreGraphicsState,
]);
ops.extend_from_slice(&[
Op::SaveGraphicsState,
Op::StartTextSection,
Op::SetTextCursor {
pos: Point::new(Mm(20.0), Mm(60.0)),
},
Op::SetFont {
font: PdfFontHandle::Builtin(BuiltinFont::Helvetica),
size: Pt(12.0),
},
Op::SetLineHeight { lh: Pt(14.0) },
Op::SetWordSpacing { pt: Pt(0.0) }, Op::ShowText {
items: vec![TextItem::Text(
"8. Mixed fonts in a single line:".to_string(),
)],
},
Op::AddLineBreak,
Op::SetFont {
font: PdfFontHandle::Builtin(BuiltinFont::Helvetica),
size: Pt(12.0),
},
Op::ShowText {
items: vec![TextItem::Text("This is in ".to_string())],
},
Op::SetFont {
font: PdfFontHandle::Builtin(BuiltinFont::TimesRoman),
size: Pt(12.0),
},
Op::ShowText {
items: vec![TextItem::Text("Times Roman".to_string())],
},
Op::SetFont {
font: PdfFontHandle::Builtin(BuiltinFont::Courier),
size: Pt(12.0),
},
Op::ShowText {
items: vec![TextItem::Text(" and this is in Courier".to_string())],
},
Op::EndTextSection,
Op::RestoreGraphicsState,
]);
ops.extend_from_slice(&[
Op::SaveGraphicsState,
Op::StartTextSection,
Op::SetTextCursor {
pos: Point::new(Mm(20.0), Mm(40.0)),
},
Op::SetFont {
font: PdfFontHandle::Builtin(BuiltinFont::Helvetica),
size: Pt(12.0),
},
Op::SetLineHeight { lh: Pt(14.0) },
Op::ShowText {
items: vec![TextItem::Text(
"9. Azul Text3 Integration for Complex Scripts:".to_string(),
)],
},
Op::AddLineBreak,
Op::ShowText {
items: vec![TextItem::Text(
"[OK] TextMatrix provides absolute positioning".to_string(),
)],
},
Op::AddLineBreak,
Op::ShowText {
items: vec![TextItem::Text(
"[OK] TextItem::GlyphIds preserves exact glyph positioning".to_string(),
)],
},
Op::AddLineBreak,
Op::ShowText {
items: vec![TextItem::Text(
"[OK] Perfect for Arabic/Indic script shaping via azul-layout".to_string(),
)],
},
Op::AddLineBreak,
Op::AddLineBreak,
Op::ShowText {
items: vec![TextItem::Text(
"Architecture: ParsedFont → UnifiedLayout → Vec<Op>".to_string(),
)],
},
Op::AddLineBreak,
Op::ShowText {
items: vec![TextItem::Text(
"See printpdf::html::from_html() for full implementation".to_string(),
)],
},
Op::EndTextSection,
Op::RestoreGraphicsState,
]);
let page = PdfPage::new(Mm(210.0), Mm(297.0), ops);
let bytes = doc
.with_pages(vec![page])
.save(&PdfSaveOptions::default(), &mut Vec::new());
std::fs::write("./text_positioning_example.pdf", bytes).unwrap();
println!("Created text_positioning_example.pdf");
}