mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-12 08:52:32 +00:00
175 lines
6.9 KiB
Rust
175 lines
6.9 KiB
Rust
//! 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, FontFamilyName, 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 = FontFamily::List(Cow::Borrowed(&[
|
|
FontFamilyName::Named(Cow::Borrowed("Noto Sans")),
|
|
FontFamilyName::Named(Cow::Borrowed("Noto Sans Arabic")),
|
|
FontFamilyName::Named(Cow::Borrowed("Noto Sans Devanagari")),
|
|
FontFamilyName::Named(Cow::Borrowed("Noto Color Emoji")),
|
|
]));
|
|
let font_stack_style = StyleProperty::FontFamily(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();
|
|
}
|