Skip to content

Commit

Permalink
Font provider changes
Browse files Browse the repository at this point in the history
  • Loading branch information
laurmaedje committed May 15, 2024
1 parent 342bb90 commit c11f38e
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 238 deletions.
126 changes: 59 additions & 67 deletions crates/usvg/src/text/flatten.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use rustybuzz::ttf_parser;
use rustybuzz::ttf_parser::{GlyphId, RasterImageFormat};
use tiny_skia_path::{NonZeroRect, Size, Transform};

use crate::text::FontProviderExt;
use crate::*;

fn resolve_rendering_mode(text: &Text) -> ShapeRendering {
Expand Down Expand Up @@ -207,91 +208,82 @@ fn outline(
glyph_id: GlyphId,
font_provider: &dyn FontProvider,
) -> Option<tiny_skia_path::Path> {
font_provider.fontdb().with_face_data(
id,
|data, face_index| -> Option<tiny_skia_path::Path> {
let font = ttf_parser::Face::parse(data, face_index).ok()?;
font_provider.with_face_data(id, |data, face_index| -> Option<tiny_skia_path::Path> {
let font = ttf_parser::Face::parse(data, face_index).ok()?;

let mut builder = PathBuilder {
builder: tiny_skia_path::PathBuilder::new(),
};
let mut builder = PathBuilder {
builder: tiny_skia_path::PathBuilder::new(),
};

font.outline_glyph(glyph_id, &mut builder)?;
builder.builder.finish()
},
)?
font.outline_glyph(glyph_id, &mut builder)?;
builder.builder.finish()
})?
}

fn raster(id: ID, glyph_id: GlyphId, font_provider: &dyn FontProvider) -> Option<BitmapImage> {
font_provider
.fontdb()
.with_face_data(id, |data, face_index| -> Option<BitmapImage> {
let font = ttf_parser::Face::parse(data, face_index).ok()?;
let image = font.glyph_raster_image(glyph_id, u16::MAX)?;

if image.format == RasterImageFormat::PNG {
let bitmap_image = BitmapImage {
image: Image {
id: String::new(),
visibility: Visibility::Visible,
size: Size::from_wh(image.width as f32, image.height as f32)?,
rendering_mode: ImageRendering::OptimizeQuality,
kind: ImageKind::PNG(Arc::new(image.data.into())),
abs_transform: Transform::default(),
abs_bounding_box: NonZeroRect::from_xywh(
0.0,
0.0,
image.width as f32,
image.height as f32,
)?,
},
x: image.x,
y: image.y,
pixels_per_em: image.pixels_per_em,
glyph_bbox: font.glyph_bounding_box(glyph_id),
// ttf-parser always checks sbix first, so if this table exists, it was used.
is_sbix: font.tables().sbix.is_some(),
};
font_provider.with_face_data(id, |data, face_index| -> Option<BitmapImage> {
let font = ttf_parser::Face::parse(data, face_index).ok()?;
let image = font.glyph_raster_image(glyph_id, u16::MAX)?;

if image.format == RasterImageFormat::PNG {
let bitmap_image = BitmapImage {
image: Image {
id: String::new(),
visibility: Visibility::Visible,
size: Size::from_wh(image.width as f32, image.height as f32)?,
rendering_mode: ImageRendering::OptimizeQuality,
kind: ImageKind::PNG(Arc::new(image.data.into())),
abs_transform: Transform::default(),
abs_bounding_box: NonZeroRect::from_xywh(
0.0,
0.0,
image.width as f32,
image.height as f32,
)?,
},
x: image.x,
y: image.y,
pixels_per_em: image.pixels_per_em,
glyph_bbox: font.glyph_bounding_box(glyph_id),
// ttf-parser always checks sbix first, so if this table exists, it was used.
is_sbix: font.tables().sbix.is_some(),
};

return Some(bitmap_image);
}
return Some(bitmap_image);
}

None
})?
None
})?
}

fn svg(id: ID, glyph_id: GlyphId, font_provider: &dyn FontProvider) -> Option<Tree> {
// TODO: Technically not 100% accurate because the SVG format in a OTF font
// is actually a subset/superset of a normal SVG, but it seems to work fine
// for Twitter Color Emoji, so might as well use what we already have.
font_provider
.fontdb()
.with_face_data(id, |data, face_index| -> Option<Tree> {
let font = ttf_parser::Face::parse(data, face_index).ok()?;
let image = font.glyph_svg_image(glyph_id)?;
Tree::from_data(image.data, &Options::default(), &fontdb::Database::new()).ok()
})?
font_provider.with_face_data(id, |data, face_index| -> Option<Tree> {
let font = ttf_parser::Face::parse(data, face_index).ok()?;
let image = font.glyph_svg_image(glyph_id)?;
Tree::from_data(image.data, &Options::default(), &fontdb::Database::new()).ok()
})?
}

fn colr(id: ID, glyph_id: GlyphId, font_provider: &dyn FontProvider) -> Option<Vec<Path>> {
font_provider
.fontdb()
.with_face_data(id, |data, face_index| -> Option<Vec<Path>> {
let font = ttf_parser::Face::parse(data, face_index).ok()?;

let mut paths = vec![];
let mut glyph_painter = GlyphPainter {
face: &font,
paths: &mut paths,
builder: PathBuilder {
builder: tiny_skia_path::PathBuilder::new(),
},
};
font_provider.with_face_data(id, |data, face_index| -> Option<Vec<Path>> {
let font = ttf_parser::Face::parse(data, face_index).ok()?;

let mut paths = vec![];
let mut glyph_painter = GlyphPainter {
face: &font,
paths: &mut paths,
builder: PathBuilder {
builder: tiny_skia_path::PathBuilder::new(),
},
};

font.paint_color_glyph(glyph_id, 0, &mut glyph_painter)?;
font.paint_color_glyph(glyph_id, 0, &mut glyph_painter)?;

Some(paths)
})?
Some(paths)
})?
}

struct GlyphPainter<'a> {
Expand Down
108 changes: 12 additions & 96 deletions crates/usvg/src/text/fontdb.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use crate::layout::ResolvedFont;
use crate::{Font, FontProvider, FontStretch, FontStyle};
use fontdb::{Database, ID};
use rustybuzz::ttf_parser;
use std::num::NonZeroU16;
use svgtypes::FontFamily;

impl FontProvider for Database {
Expand Down Expand Up @@ -79,7 +77,16 @@ impl FontProvider for Database {
continue;
}

if !self.has_char(face.id, c) {
let has_char = self
.with_face_data(face.id, |font_data, face_index| -> Option<bool> {
let font = ttf_parser::Face::parse(font_data, face_index).ok()?;
font.glyph_index(c)?;
Some(true)
})
.flatten()
.unwrap_or(false);

if !has_char {
continue;
}

Expand All @@ -102,98 +109,7 @@ impl FontProvider for Database {
None
}

fn fontdb(&self) -> &Database {
&self
}
}

pub(crate) trait DatabaseExt {
fn load_font(&self, id: ID) -> Option<ResolvedFont>;
fn has_char(&self, id: ID, c: char) -> bool;
}

impl DatabaseExt for Database {
#[inline(never)]
fn load_font(&self, id: ID) -> Option<ResolvedFont> {
self.with_face_data(id, |data, face_index| -> Option<ResolvedFont> {
let font = ttf_parser::Face::parse(data, face_index).ok()?;

let units_per_em = NonZeroU16::new(font.units_per_em())?;

let ascent = font.ascender();
let descent = font.descender();

let x_height = font
.x_height()
.and_then(|x| u16::try_from(x).ok())
.and_then(NonZeroU16::new);
let x_height = match x_height {
Some(height) => height,
None => {
// If not set - fallback to height * 45%.
// 45% is what Firefox uses.
u16::try_from((f32::from(ascent - descent) * 0.45) as i32)
.ok()
.and_then(NonZeroU16::new)?
}
};

let line_through = font.strikeout_metrics();
let line_through_position = match line_through {
Some(metrics) => metrics.position,
None => x_height.get() as i16 / 2,
};

let (underline_position, underline_thickness) = match font.underline_metrics() {
Some(metrics) => {
let thickness = u16::try_from(metrics.thickness)
.ok()
.and_then(NonZeroU16::new)
// `ttf_parser` guarantees that units_per_em is >= 16
.unwrap_or_else(|| NonZeroU16::new(units_per_em.get() / 12).unwrap());

(metrics.position, thickness)
}
None => (
-(units_per_em.get() as i16) / 9,
NonZeroU16::new(units_per_em.get() / 12).unwrap(),
),
};

// 0.2 and 0.4 are generic offsets used by some applications (Inkscape/librsvg).
let mut subscript_offset = (units_per_em.get() as f32 / 0.2).round() as i16;
let mut superscript_offset = (units_per_em.get() as f32 / 0.4).round() as i16;
if let Some(metrics) = font.subscript_metrics() {
subscript_offset = metrics.y_offset;
}

if let Some(metrics) = font.superscript_metrics() {
superscript_offset = metrics.y_offset;
}

Some(ResolvedFont {
id,
units_per_em,
ascent,
descent,
x_height,
underline_position,
underline_thickness,
line_through_position,
subscript_offset,
superscript_offset,
})
})?
}

#[inline(never)]
fn has_char(&self, id: ID, c: char) -> bool {
let res = self.with_face_data(id, |font_data, face_index| -> Option<bool> {
let font = ttf_parser::Face::parse(font_data, face_index).ok()?;
font.glyph_index(c)?;
Some(true)
});

res == Some(Some(true))
fn with_database(&self, f: &mut dyn FnMut(&Database)) {
f(self);
}
}
Loading

0 comments on commit c11f38e

Please sign in to comment.