use hipdf::fonts::{utils, Font, FontManager, StandardFont, TextBuilder, TextRenderingMode};
use hipdf::lopdf::{content::Content, dictionary, Document, Object, Stream};
fn create_font_showcase_pdf() -> Result<(), Box<dyn std::error::Error>> {
let mut doc = Document::with_version("1.7");
let mut font_manager = FontManager::new();
let helvetica_bold = Font::standard(StandardFont::HelveticaBold);
let inter_font = Font::from_file("examples/assets/fonts/Inter-Variable.ttf")
.expect("Failed to load Inter font. Make sure it's in examples/assets/fonts/");
let jetbrains_font = Font::from_file("examples/assets/fonts/JetBrainsMono-Variable.ttf")
.expect("Failed to load JetBrains Mono. Make sure it's in examples/assets/fonts/");
let (_, helvetica_res_name) = font_manager.embed_font(&mut doc, helvetica_bold)?;
let (_, inter_res_name) = font_manager.embed_font(&mut doc, inter_font.clone())?;
let (_, jetbrains_res_name) = font_manager.embed_font(&mut doc, jetbrains_font.clone())?;
let pages_id = doc.add_object(dictionary! {
"Type" => "Pages",
"Count" => 1,
});
let mut resources = dictionary! {};
for (_font, font_id, resource_name) in font_manager.fonts() {
font_manager.add_to_resources(&mut resources, *font_id, resource_name);
}
let mut operations = Vec::new();
operations.extend(
TextBuilder::new()
.begin_text()
.set_font(&helvetica_res_name, 24.0)
.position(50.0, 780.0)
.show("Font Showcase")
.end_text()
.build(),
);
let simple_text = "The quick brown fox jumps over the lazy dog.";
operations.extend(
TextBuilder::new()
.begin_text()
.set_font(&inter_res_name, 14.0)
.position(50.0, 720.0)
.show_encoded(inter_font.encode_text(&format!("Inter: {}", simple_text)))
.next_line(0.0, -20.0) .set_font(&jetbrains_res_name, 12.0)
.show_encoded(jetbrains_font.encode_text(&format!("JetBrains Mono: {}", simple_text)))
.end_text()
.build(),
);
let paragraph_text = "This is a longer paragraph that demonstrates the automatic word-wrapping feature provided by the `create_paragraph` utility function. It makes handling blocks of text much simpler by calculating line breaks based on a maximum width.";
operations.extend(utils::create_paragraph(
&inter_res_name,
&inter_font,
paragraph_text,
50.0, 650.0, 12.0, 500.0, 15.0, ));
let aligned_text = "Aligned Text Example";
operations.extend(utils::create_centered_text(
&inter_res_name,
&inter_font,
aligned_text,
297.5, 550.0,
16.0,
));
operations.extend(utils::create_right_aligned_text(
&inter_res_name,
&inter_font,
aligned_text,
545.0, 520.0,
16.0,
));
operations.extend(
TextBuilder::new()
.begin_text()
.set_fill_color(0.8, 0.1, 0.1)
.set_font(&inter_res_name, 14.0)
.position(50.0, 480.0)
.show_encoded(inter_font.encode_text("This text is red."))
.set_rendering_mode(TextRenderingMode::Stroke)
.set_stroke_color(0.1, 0.1, 0.8)
.next_line(0.0, -25.0)
.show_encoded(inter_font.encode_text("This text is stroked and blue."))
.set_rendering_mode(TextRenderingMode::FillThenStroke)
.set_fill_color(1.0, 0.9, 0.2) .set_stroke_color(0.8, 0.1, 0.1) .next_line(0.0, -25.0)
.show_encoded(inter_font.encode_text("Fill, then stroke!"))
.end_text()
.build(),
);
let unicode_text = "Unicode: αβγδε, áéíóú, こんにちは, 🚀";
let code_text = "fn main() { println!(\"Hello, PDF!\"); }";
operations.extend(
TextBuilder::new()
.begin_text()
.set_rendering_mode(TextRenderingMode::Fill) .set_fill_color(0.0, 0.0, 0.0) .set_font(&inter_res_name, 14.0)
.position(50.0, 380.0)
.show_encoded(inter_font.encode_text(unicode_text))
.next_line(0.0, -25.0)
.set_font(&jetbrains_res_name, 12.0)
.show_encoded(jetbrains_font.encode_text(code_text))
.end_text()
.build(),
);
let content = Content { operations };
let content_stream = Stream::new(dictionary! {}, content.encode()?);
let content_id = doc.add_object(content_stream);
let page_id = doc.add_object(dictionary! {
"Type" => "Page",
"Parent" => pages_id,
"MediaBox" => vec![0.into(), 0.into(), 595.into(), 842.into()],
"Contents" => content_id,
"Resources" => resources,
});
let pages_dict = doc
.get_object_mut(pages_id)
.and_then(Object::as_dict_mut)
.unwrap();
pages_dict.set("Kids", vec![Object::Reference(page_id)]);
let catalog_id = doc.add_object(dictionary! {
"Type" => "Catalog",
"Pages" => Object::Reference(pages_id),
});
doc.trailer.set("Root", Object::Reference(catalog_id));
doc.save("fonts_showcase.pdf")?;
println!("✅ PDF saved to fonts_showcase.pdf");
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
create_font_showcase_pdf()
}