krilla 0.7.0

A high-level crate for creating PDF files.
Documentation
//! This example shows how to use parley to create advanced layouted text.

use std::borrow::Cow;
use std::collections::HashMap;
use std::path;

use krilla::color::rgb;
use krilla::geom::Point;
use krilla::num::NormalizedF32;
use krilla::page::PageSettings;
use krilla::paint::Fill;
use krilla::text::Font;
use krilla::text::{GlyphId, KrillaGlyph};
use krilla::Document;
use parley::layout::Alignment;
use parley::style::{FontFamily, FontStack, FontWeight, LineHeight, StyleProperty};
use parley::{FontContext, LayoutContext};

fn main() {
    // The text that we want to insert into the PDF.
    let text = String::from(
        "This is a long text. We want it to not be wider than 200pt, \
    so that it fits on the page. Let's intersperse some emojis 💩👻💀emojis🦩🌚😁😆\
    as well as complex scripts: हैलो वर्ल्ड and مرحبا بالعالم",
    );

    // Set up the properties of the text. See the documentation of parley for more information
    // on how you can configure its behavior.
    let max_advance = Some(200.0);
    let text_color = rgb::Color::new(0, 0, 0);
    let mut font_cx = FontContext::default();
    let mut layout_cx = LayoutContext::new();
    let mut builder = layout_cx.ranged_builder(&mut font_cx, &text, 1.0, false);
    let brush_style = StyleProperty::Brush(text_color);
    builder.push_default(brush_style);

    let font_stack = FontStack::List(Cow::Borrowed(&[
        FontFamily::Named(Cow::Borrowed("Noto Sans")),
        FontFamily::Named(Cow::Borrowed("Noto Sans Arabic")),
        FontFamily::Named(Cow::Borrowed("Noto Sans Devanagari")),
        FontFamily::Named(Cow::Borrowed("Noto Color Emoji")),
    ]));
    let font_stack_style = StyleProperty::FontStack(font_stack);
    builder.push_default(font_stack_style);
    builder.push_default(StyleProperty::LineHeight(LineHeight::FontSizeRelative(1.3)));
    builder.push_default(StyleProperty::FontSize(16.0));

    // In our case, we set the first four characters to bold and also make some
    // part of the text red.
    let bold = FontWeight::new(600.0);
    let bold_style = StyleProperty::FontWeight(bold);
    builder.push(bold_style, 0..4);

    let color_style = StyleProperty::Brush(rgb::Color::new(255, 0, 0));
    builder.push(color_style, 2..12);

    let mut layout = builder.build(&text);
    layout.break_all_lines(max_advance);
    layout.align(max_advance, Alignment::Start, Default::default());

    // After setting up everything, now starts the actual part where we use krilla to write
    // the text to a PDF.
    // We need to set up a font cache that converts from parley fonts to krilla fonts.
    let mut font_cache = HashMap::new();

    // The usual page setup.
    let mut document = Document::new();
    let mut page = document.start_page_with(PageSettings::from_wh(200.0, 300.0).unwrap());
    let mut surface = page.surface();

    for line in layout.lines() {
        let y = line.metrics().baseline;
        let mut x = 0.0;
        for run in line.runs() {
            let mut cur_x = x;
            let font = run.font().clone();
            let (font_data, id) = font.data.into_raw_parts();
            // Get the krilla font.
            let krilla_font = font_cache
                .entry(id)
                .or_insert_with(|| Font::new(font_data.into(), font.index).unwrap());
            let font_size = run.font_size();

            // This is part is somewhat convoluted, the reason being that each glyph might
            // have a different style than the previous one. So we always need to keep track
            // of the current style, and whenever we encounter a new style we "flush" all
            // current glyphs into the PDF, and build the next sequence of consecutive
            // glyphs.
            let mut cur_style = None;
            let mut glyphs = Vec::<KrillaGlyph>::new();

            for cluster in run.visual_clusters() {
                if cluster.is_ligature_continuation() {
                    if let Some(glyph) = glyphs.last_mut() {
                        glyph.text_range.end = cluster.text_range().end;
                    }
                    // Ligature continuations have no glyphs of their own.
                    continue;
                }

                for glyph in cluster.glyphs() {
                    let glyph_style = glyph.style_index;

                    if let Some(style) = cur_style {
                        if style != glyph_style {
                            // If style doesn't match, flush all glyphs up to now.
                            cur_style = Some(glyph_style);
                            let style = layout.styles()[style as usize].brush;
                            surface.set_fill(Some(Fill {
                                paint: style.into(),
                                opacity: NormalizedF32::ONE,
                                rule: Default::default(),
                            }));

                            surface.draw_glyphs(
                                Point { x: cur_x, y },
                                &glyphs,
                                krilla_font.clone(),
                                &text,
                                font_size,
                                false,
                            );
                            glyphs.clear();
                            cur_x = x;
                        }
                    } else {
                        cur_style = Some(glyph_style);
                    }

                    // Add the current glyph to our buffer of glyphs.
                    glyphs.push(KrillaGlyph::new(
                        GlyphId::new(glyph.id as u32),
                        glyph.advance / font_size,
                        glyph.x / font_size,
                        glyph.y / font_size,
                        0.0,
                        cluster.text_range(),
                        None,
                    ));
                    // And make sure keep track of the current x position.
                    x += glyph.advance;
                }
            }

            // Flush all remaining glyphs, if existing.
            if !glyphs.is_empty() {
                surface.set_fill(Some(Fill {
                    paint: layout.styles()[cur_style.unwrap() as usize].brush.into(),
                    opacity: NormalizedF32::ONE,
                    rule: Default::default(),
                }));
                surface.draw_glyphs(
                    Point::from_xy(cur_x, y),
                    &glyphs,
                    krilla_font.clone(),
                    &text,
                    font_size,
                    false,
                );
            }
        }
    }

    surface.finish();
    page.finish();

    let pdf = document.finish().unwrap();

    let path = path::absolute("parley.pdf").unwrap();
    eprintln!("Saved PDF to '{}'", path.display());

    // Write the PDF to a file.
    std::fs::write(path, &pdf).unwrap();
}