// SPDX-FileCopyrightText: 2020 Robin Krahl // SPDX-License-Identifier: CC0-1.0 //! This example generates a demo PDF document and writes it to the path that was passed as the //! first command-line argument. You may have to adapt the `FONT_DIRS`, `DEFAULT_FONT_NAME` and //! `MONO_FONT_NAME` constants for your system so that these files exist: //! - `{FONT_DIR}/{name}-Regular.ttf` //! - `{FONT_DIR}/{name}-Bold.ttf` //! - `{FONT_DIR}/{name}-Italic.ttf` //! - `{FONT_DIR}/{name}-BoldItalic.ttf` //! for `name` in {`DEFAULT_FONT_NAME`, `MONO_FONT_NAME`}. //! //! The generated document using the latest `genpdf-rs` release is available //! [here](https://genpdf-rs.ireas.org/examples/demo.pdf). use std::env; use genpdf::Alignment; use genpdf::Element as _; use genpdf::{elements, fonts, style}; const FONT_DIRS: &[&str] = &[ "/usr/share/fonts/liberation", "/usr/share/fonts/truetype/liberation", ]; const DEFAULT_FONT_NAME: &'static str = "LiberationSans"; const MONO_FONT_NAME: &'static str = "LiberationMono"; const LOREM_IPSUM: &'static str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut \ labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco \ laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in \ voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat \ non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; fn main() { let args: Vec<_> = env::args().skip(1).collect(); if args.len() != 1 { panic!("Missing argument: output file"); } let output_file = &args[0]; let font_dir = FONT_DIRS .iter() .filter(|path| std::path::Path::new(path).exists()) .next() .expect("Could not find font directory"); let default_font = fonts::from_files(font_dir, DEFAULT_FONT_NAME, Some(fonts::Builtin::Helvetica)) .expect("Failed to load the default font family"); let monospace_font = fonts::from_files(font_dir, MONO_FONT_NAME, Some(fonts::Builtin::Courier)) .expect("Failed to load the monospace font family"); let mut doc = genpdf::Document::new(default_font); doc.set_title("genpdf Demo Document"); doc.set_minimal_conformance(); doc.set_line_spacing(1.25); let mut decorator = genpdf::SimplePageDecorator::new(); decorator.set_margins(10); decorator.set_header(|page| { let mut layout = elements::LinearLayout::vertical(); if page > 1 { layout.push( elements::Paragraph::new(format!("Page {}", page)).aligned(Alignment::Center), ); layout.push(elements::Break::new(1)); } layout.styled(style::Style::new().with_font_size(10)) }); doc.set_page_decorator(decorator); #[cfg(feature = "hyphenation")] { use hyphenation::Load; doc.set_hyphenator( hyphenation::Standard::from_embedded(hyphenation::Language::EnglishUS) .expect("Failed to load hyphenation data"), ); } let monospace = doc.add_font_family(monospace_font); let code = style::Style::from(monospace).bold(); let red = style::Color::Rgb(255, 0, 0); let blue = style::Color::Rgb(0, 0, 255); doc.push( elements::Paragraph::new("genpdf Demo Document") .aligned(Alignment::Center) .styled(style::Style::new().bold().with_font_size(20)), ); doc.push(elements::Break::new(1.5)); doc.push(elements::Paragraph::new( "This document demonstrates how the genpdf crate generates PDF documents. Currently, \ genpdf supports these elements:", )); let mut list = elements::UnorderedList::new(); list.push( elements::Paragraph::default() .styled_string("Text", code) .string(", a single line of formatted text without wrapping."), ); list.push( elements::Paragraph::default() .styled_string("Paragraph", code) .string( ", one or more lines of formatted text with wrapping and an alignment (left, \ center, right).", ), ); list.push( elements::Paragraph::default() .styled_string("FramedElement", code) .string(", a frame drawn around other elements."), ); list.push( elements::Paragraph::default() .styled_string("PaddedElement", code) .string(", an element with an additional padding."), ); list.push( elements::Paragraph::default() .styled_string("StyledElement", code) .string(", an element with new default style."), ); list.push( elements::Paragraph::default() .styled_string("UnorderedList", code) .string(", an unordered list of bullet points."), ); list.push( elements::LinearLayout::vertical() .element( elements::Paragraph::default() .styled_string("OrderedList", code) .string(", an ordered list of bullet points."), ) .element( elements::OrderedList::new() .element(elements::Paragraph::new("Just like this.")) .element(elements::Paragraph::new("And this.")), ), ); list.push( elements::LinearLayout::vertical() .element( elements::Paragraph::default() .styled_string("BulletPoint", code) .string(", an element with a bullet point, just like in this list."), ) .element(elements::BulletPoint::new(elements::Paragraph::new( "Of course, lists can also be nested.", ))) .element( elements::BulletPoint::new(elements::Paragraph::new( "And you can change the bullet symbol.", )) .with_bullet("•"), ), ); list.push( elements::Paragraph::default() .styled_string("LinearLayout", code) .string( ", a container that vertically stacks its elements. The root element of a \ document is always a LinearLayout.", ), ); list.push( elements::Paragraph::default() .styled_string("TableLayout", code) .string(", a container that arranges its elements in rows and columns."), ); list.push(elements::Paragraph::new("And some more utility elements …")); doc.push(list); doc.push(elements::Break::new(1.5)); doc.push(elements::Paragraph::new( "You already saw lists and formatted centered text. Here are some other examples:", )); doc.push(elements::Paragraph::new("This is right-aligned text.").aligned(Alignment::Right)); doc.push( elements::Paragraph::new("And this paragraph has a frame drawn around it and is colored.") .padded(genpdf::Margins::vh(0, 1)) .framed() .styled(red), ); doc.push( elements::Paragraph::new("You can also use other fonts if you want to.").styled(monospace), ); doc.push( elements::Paragraph::default() .string("You can also ") .styled_string("combine ", red) .styled_string("multiple ", style::Style::from(blue).italic()) .styled_string("formats", code) .string(" in one paragraph.") .styled(style::Style::new().with_font_size(16)), ); doc.push(elements::Break::new(1.5)); doc.push(elements::Paragraph::new( "Embedding images also works using the 'images' feature.", )); #[cfg(feature = "images")] images::do_image_test(&mut doc); doc.push(elements::Paragraph::new("Here is an example table:")); let mut table = elements::TableLayout::new(vec![1, 2]); table.set_cell_decorator(elements::FrameCellDecorator::new(true, false, false)); table .row() .element( elements::Paragraph::new("Header 1") .styled(style::Effect::Bold) .padded(1), ) .element(elements::Paragraph::new("Value 2").padded(1)) .push() .expect("Invalid table row"); table .row() .element( elements::Paragraph::new("Header 2") .styled(style::Effect::Bold) .padded(1), ) .element( elements::Paragraph::new( "A long paragraph to demonstrate how wrapping works in tables. Nice, right?", ) .padded(1), ) .push() .expect("Invalid table row"); let list_layout = elements::LinearLayout::vertical() .element(elements::Paragraph::new( "Of course, you can use all other elements inside a table.", )) .element( elements::UnorderedList::new() .element(elements::Paragraph::new("Even lists!")) .element( elements::Paragraph::new("And frames!") .padded(genpdf::Margins::vh(0, 1)) .framed(), ), ); table .row() .element( elements::Paragraph::new("Header 3") .styled(style::Effect::Bold) .padded(1), ) .element(list_layout.padded(1)) .push() .expect("Invalid table row"); doc.push(table); doc.push(elements::Break::new(1.5)); doc.push(elements::Paragraph::new( "Now let’s print a long table to demonstrate how page wrapping works:", )); let mut table = elements::TableLayout::new(vec![1, 5]); table.set_cell_decorator(elements::FrameCellDecorator::new(true, true, false)); table .row() .element( elements::Paragraph::new("Index") .styled(style::Effect::Bold) .padded(1), ) .element( elements::Paragraph::new("Text") .styled(style::Effect::Bold) .padded(1), ) .push() .expect("Invalid table row"); for i in 0..10 { table .row() .element(elements::Paragraph::new(format!("#{}", i)).padded(1)) .element(elements::Paragraph::new(LOREM_IPSUM).padded(1)) .push() .expect("Invalid table row"); } doc.push(table); doc.render_to_file(output_file) .expect("Failed to write output file"); } // Only import the images if the feature is enabled. This helps verify our handling of feature toggles. #[cfg(feature = "images")] mod images { use super::*; const IMAGE_PATH_JPG: &'static str = "examples/images/test_image.jpg"; pub fn do_image_test(doc: &mut genpdf::Document) { doc.push(elements::Paragraph::new( "Here is an example image with default position/scale:", )); doc.push(elements::Image::from_path(IMAGE_PATH_JPG).expect("Unable to load image")); doc.push(elements::Paragraph::new( "and here is one that is centered, rotated, and scaled some.", )); doc.push( elements::Image::from_path(IMAGE_PATH_JPG) .expect("Unable to load image") .with_alignment(Alignment::Center) .with_scale(genpdf::Scale::new(0.5, 2)) .with_clockwise_rotation(45.0), ); doc.push(elements::Paragraph::new( "For a full example of image functionality, please see images.pdf.", )); doc.push(elements::Break::new(1.5)); } }