Expand description
Access glyphs outlines. Requires the outline
cargo feature (enabled by default).
This module is used to access the outlines of glyphs as a series of foundational drawing
instruction callbacks on implementors of the OutlineSink
trait. Outlines from glyf
and
CFF
tables can be accessed.
Example
This is a fairly complete example of mapping some glyphs and then visiting their outlines with
support for TrueType and CFF fonts. It accumulates the drawing operations into a String
.
In a real application you’d probably make calls to a graphics library instead.
use std::fmt::Write;
use allsorts::binary::read::ReadScope;
use allsorts::cff::CFF;
use allsorts::font::{GlyphTableFlags, MatchingPresentation};
use allsorts::font_data::FontData;
use allsorts::gsub::RawGlyph;
use allsorts::outline::{OutlineBuilder, OutlineSink};
use allsorts::pathfinder_geometry::line_segment::LineSegment2F;
use allsorts::pathfinder_geometry::vector::Vector2F;
use allsorts::tables::glyf::GlyfTable;
use allsorts::tables::loca::LocaTable;
use allsorts::tables::{FontTableProvider, SfntVersion};
use allsorts::{tag, Font};
struct DebugVisitor {
outlines: String,
}
impl OutlineSink for DebugVisitor {
fn move_to(&mut self, to: Vector2F) {
writeln!(&mut self.outlines, "move_to({}, {})", to.x(), to.y()).unwrap();
}
fn line_to(&mut self, to: Vector2F) {
writeln!(&mut self.outlines, "line_to({}, {})", to.x(), to.y()).unwrap();
}
fn quadratic_curve_to(&mut self, control: Vector2F, to: Vector2F) {
writeln!(
&mut self.outlines,
"quad_to({}, {}, {}, {})",
control.x(),
control.y(),
to.x(),
to.y()
)
.unwrap();
}
fn cubic_curve_to(&mut self, control: LineSegment2F, to: Vector2F) {
writeln!(
&mut self.outlines,
"curve_to({}, {}, {}, {}, {}, {})",
control.from_x(),
control.from_y(),
control.to_x(),
control.to_y(),
to.x(),
to.y()
)
.unwrap();
}
fn close(&mut self) {
writeln!(&mut self.outlines, "close()").unwrap();
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let script = tag::LATN;
let buffer = std::fs::read("tests/fonts/opentype/Klei.otf")?;
let scope = ReadScope::new(&buffer);
let font_file = scope.read::<FontData<'_>>()?;
let provider = font_file.table_provider(0)?;
let mut font = Font::new(provider)?.ok_or("cmap missing")?;
let mut sink = DebugVisitor {
outlines: String::new(),
};
// Map text to glyphs
let glyphs = font.map_glyphs("+", script, MatchingPresentation::NotRequired);
// Visit the outlines of each glyph. Read tables depending on the type of font
if font.glyph_table_flags.contains(GlyphTableFlags::CFF)
&& font.font_table_provider.sfnt_version() == tag::OTTO
{
let cff_data = font.font_table_provider.read_table_data(tag::CFF)?;
let mut cff = ReadScope::new(&cff_data).read::<CFF<'_>>()?;
sink.glyphs_to_path(&mut cff, &glyphs)?;
} else if font.glyph_table_flags.contains(GlyphTableFlags::GLYF) {
let loca_data = font.font_table_provider.read_table_data(tag::LOCA)?;
let loca = ReadScope::new(&loca_data).read_dep::<LocaTable<'_>>((
usize::from(font.maxp_table.num_glyphs),
font.head_table()?
.ok_or("missing head table")?
.index_to_loc_format,
))?;
let glyf_data = font.font_table_provider.read_table_data(tag::GLYF)?;
let mut glyf = ReadScope::new(&glyf_data).read_dep::<GlyfTable<'_>>(&loca)?;
sink.glyphs_to_path(&mut glyf, &glyphs)?;
} else {
return Err("no glyf or CFF table".into());
}
let expected = "move_to(225, 152)
line_to(225, 269)
curve_to(225, 274, 228, 276, 232, 276)
line_to(341, 276)
curve_to(346, 276, 347, 285, 347, 295)
curve_to(347, 307, 345, 320, 341, 320)
line_to(232, 320)
curve_to(226, 320, 226, 325, 226, 328)
line_to(226, 432)
curve_to(220, 435, 214, 437, 206, 437)
curve_to(198, 437, 190, 435, 181, 432)
line_to(181, 329)
curve_to(181, 326, 180, 320, 172, 320)
line_to(68, 320)
curve_to(62, 320, 59, 311, 59, 300)
curve_to(59, 289, 62, 278, 68, 276)
line_to(174, 276)
curve_to(179, 276, 181, 271, 181, 267)
line_to(181, 152)
curve_to(181, 147, 193, 144, 204, 144)
curve_to(215, 144, 225, 147, 225, 152)
close()
";
assert_eq!(sink.outlines, expected);
Ok(())
}
impl DebugVisitor {
pub fn glyphs_to_path<T>(
&mut self,
builder: &mut T,
glyphs: &[RawGlyph<()>],
) -> Result<(), Box<dyn std::error::Error>>
where
T: OutlineBuilder,
<T as OutlineBuilder>::Error: 'static,
{
for glyph in glyphs {
builder.visit(glyph.glyph_index, self)?;
}
Ok(())
}
}
Traits
Trait for visiting a glyph outline and delivering drawing commands to an
OutlineSink
.A trait for visiting a glyph outline