mirror of
https://github.com/typst/typst
synced 2025-05-18 11:05:28 +08:00
Deduplicate ttf-parser and rustybuzz face 🥞
This commit is contained in:
parent
de20a21a58
commit
464a6ff75e
@ -27,7 +27,7 @@ image = { version = "0.23", default-features = false, features = ["jpeg", "png"]
|
|||||||
miniz_oxide = "0.3"
|
miniz_oxide = "0.3"
|
||||||
pdf-writer = { path = "../pdf-writer" }
|
pdf-writer = { path = "../pdf-writer" }
|
||||||
rustybuzz = { git = "https://github.com/laurmaedje/rustybuzz" }
|
rustybuzz = { git = "https://github.com/laurmaedje/rustybuzz" }
|
||||||
ttf-parser = "0.9"
|
ttf-parser = "0.12"
|
||||||
unicode-bidi = "0.3"
|
unicode-bidi = "0.3"
|
||||||
unicode-xid = "0.2"
|
unicode-xid = "0.2"
|
||||||
xi-unicode = "0.3"
|
xi-unicode = "0.3"
|
||||||
|
60
src/font.rs
60
src/font.rs
@ -10,8 +10,7 @@ use crate::geom::Length;
|
|||||||
pub struct FaceBuf {
|
pub struct FaceBuf {
|
||||||
data: Box<[u8]>,
|
data: Box<[u8]>,
|
||||||
index: u32,
|
index: u32,
|
||||||
ttf: ttf_parser::Face<'static>,
|
inner: rustybuzz::Face<'static>,
|
||||||
buzz: rustybuzz::Face<'static>,
|
|
||||||
units_per_em: f64,
|
units_per_em: f64,
|
||||||
ascender: f64,
|
ascender: f64,
|
||||||
cap_height: f64,
|
cap_height: f64,
|
||||||
@ -30,23 +29,16 @@ impl FaceBuf {
|
|||||||
self.index
|
self.index
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to the underlying ttf-parser face.
|
/// Get a reference to the underlying ttf-parser/rustybuzz face.
|
||||||
pub fn ttf(&self) -> &ttf_parser::Face<'_> {
|
pub fn ttf(&self) -> &rustybuzz::Face<'_> {
|
||||||
// We can't implement Deref because that would leak the internal 'static
|
// We can't implement Deref because that would leak the internal 'static
|
||||||
// lifetime.
|
// lifetime.
|
||||||
&self.ttf
|
&self.inner
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to the underlying rustybuzz face.
|
/// Look up a vertical metric.
|
||||||
pub fn buzz(&self) -> &rustybuzz::Face<'_> {
|
pub fn vertical_metric(&self, metric: VerticalFontMetric) -> EmLength {
|
||||||
// We can't implement Deref because that would leak the internal 'static
|
self.convert(match metric {
|
||||||
// lifetime.
|
|
||||||
&self.buzz
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Look up a vertical metric at a given font size.
|
|
||||||
pub fn vertical_metric(&self, size: Length, metric: VerticalFontMetric) -> Length {
|
|
||||||
self.convert(size, match metric {
|
|
||||||
VerticalFontMetric::Ascender => self.ascender,
|
VerticalFontMetric::Ascender => self.ascender,
|
||||||
VerticalFontMetric::CapHeight => self.cap_height,
|
VerticalFontMetric::CapHeight => self.cap_height,
|
||||||
VerticalFontMetric::XHeight => self.x_height,
|
VerticalFontMetric::XHeight => self.x_height,
|
||||||
@ -55,9 +47,9 @@ impl FaceBuf {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert from font units to a length at a given font size.
|
/// Convert from font units to an em length length.
|
||||||
pub fn convert(&self, size: Length, units: impl Into<f64>) -> Length {
|
pub fn convert(&self, units: impl Into<f64>) -> EmLength {
|
||||||
units.into() / self.units_per_em * size
|
EmLength(units.into() / self.units_per_em)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,21 +62,19 @@ impl FaceFromVec for FaceBuf {
|
|||||||
let slice: &'static [u8] =
|
let slice: &'static [u8] =
|
||||||
unsafe { std::slice::from_raw_parts(data.as_ptr(), data.len()) };
|
unsafe { std::slice::from_raw_parts(data.as_ptr(), data.len()) };
|
||||||
|
|
||||||
let ttf = ttf_parser::Face::from_slice(slice, index).ok()?;
|
let inner = rustybuzz::Face::from_slice(slice, index)?;
|
||||||
let buzz = rustybuzz::Face::from_slice(slice, index)?;
|
|
||||||
|
|
||||||
// Look up some metrics we may need often.
|
// Look up some metrics we may need often.
|
||||||
let units_per_em = ttf.units_per_em().unwrap_or(1000);
|
let units_per_em = inner.units_per_em();
|
||||||
let ascender = ttf.typographic_ascender().unwrap_or(ttf.ascender());
|
let ascender = inner.typographic_ascender().unwrap_or(inner.ascender());
|
||||||
let cap_height = ttf.capital_height().filter(|&h| h > 0).unwrap_or(ascender);
|
let cap_height = inner.capital_height().filter(|&h| h > 0).unwrap_or(ascender);
|
||||||
let x_height = ttf.x_height().filter(|&h| h > 0).unwrap_or(ascender);
|
let x_height = inner.x_height().filter(|&h| h > 0).unwrap_or(ascender);
|
||||||
let descender = ttf.typographic_descender().unwrap_or(ttf.descender());
|
let descender = inner.typographic_descender().unwrap_or(inner.descender());
|
||||||
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
data,
|
data,
|
||||||
index,
|
index,
|
||||||
ttf,
|
inner,
|
||||||
buzz,
|
|
||||||
units_per_em: f64::from(units_per_em),
|
units_per_em: f64::from(units_per_em),
|
||||||
ascender: f64::from(ascender),
|
ascender: f64::from(ascender),
|
||||||
cap_height: f64::from(cap_height),
|
cap_height: f64::from(cap_height),
|
||||||
@ -94,6 +84,22 @@ impl FaceFromVec for FaceBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A length in resolved em units.
|
||||||
|
#[derive(Default, Debug, Copy, Clone, PartialEq, PartialOrd)]
|
||||||
|
pub struct EmLength(f64);
|
||||||
|
|
||||||
|
impl EmLength {
|
||||||
|
/// Convert to a length at the given font size.
|
||||||
|
pub fn scale(self, size: Length) -> Length {
|
||||||
|
self.0 * size
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the number of em units.
|
||||||
|
pub fn get(self) -> f64 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Identifies a vertical metric of a font.
|
/// Identifies a vertical metric of a font.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
pub enum VerticalFontMetric {
|
pub enum VerticalFontMetric {
|
||||||
|
@ -39,7 +39,7 @@ pub struct ShapedGlyph {
|
|||||||
/// The font face the glyph is contained in.
|
/// The font face the glyph is contained in.
|
||||||
pub face_id: FaceId,
|
pub face_id: FaceId,
|
||||||
/// The glyph's ID in the face.
|
/// The glyph's ID in the face.
|
||||||
pub id: GlyphId,
|
pub glyph_id: GlyphId,
|
||||||
/// The advance width of the glyph.
|
/// The advance width of the glyph.
|
||||||
pub x_advance: i32,
|
pub x_advance: i32,
|
||||||
/// The horizontal offset of the glyph.
|
/// The horizontal offset of the glyph.
|
||||||
@ -59,8 +59,6 @@ impl<'a> ShapedText<'a> {
|
|||||||
let mut x = Length::ZERO;
|
let mut x = Length::ZERO;
|
||||||
|
|
||||||
for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) {
|
for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) {
|
||||||
let face = loader.face(face_id);
|
|
||||||
|
|
||||||
let pos = Point::new(x, self.baseline);
|
let pos = Point::new(x, self.baseline);
|
||||||
let mut text = Text {
|
let mut text = Text {
|
||||||
face_id,
|
face_id,
|
||||||
@ -69,10 +67,11 @@ impl<'a> ShapedText<'a> {
|
|||||||
glyphs: vec![],
|
glyphs: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let face = loader.face(face_id);
|
||||||
for glyph in group {
|
for glyph in group {
|
||||||
let x_advance = face.convert(self.props.size, glyph.x_advance);
|
let x_advance = face.convert(glyph.x_advance).scale(self.props.size);
|
||||||
let x_offset = face.convert(self.props.size, glyph.x_offset);
|
let x_offset = face.convert(glyph.x_offset).scale(self.props.size);
|
||||||
text.glyphs.push(Glyph { id: glyph.id, x_advance, x_offset });
|
text.glyphs.push(Glyph { id: glyph.glyph_id, x_advance, x_offset });
|
||||||
x += x_advance;
|
x += x_advance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,7 +217,7 @@ fn shape_segment<'a>(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Shape!
|
// Shape!
|
||||||
let buffer = rustybuzz::shape(loader.face(face_id).buzz(), &[], buffer);
|
let buffer = rustybuzz::shape(loader.face(face_id).ttf(), &[], buffer);
|
||||||
let infos = buffer.glyph_infos();
|
let infos = buffer.glyph_infos();
|
||||||
let pos = buffer.glyph_positions();
|
let pos = buffer.glyph_positions();
|
||||||
|
|
||||||
@ -233,7 +232,7 @@ fn shape_segment<'a>(
|
|||||||
// TODO: Don't ignore y_advance and y_offset.
|
// TODO: Don't ignore y_advance and y_offset.
|
||||||
glyphs.push(ShapedGlyph {
|
glyphs.push(ShapedGlyph {
|
||||||
face_id,
|
face_id,
|
||||||
id: GlyphId(info.codepoint as u16),
|
glyph_id: GlyphId(info.codepoint as u16),
|
||||||
x_advance: pos[i].x_advance,
|
x_advance: pos[i].x_advance,
|
||||||
x_offset: pos[i].x_offset,
|
x_offset: pos[i].x_offset,
|
||||||
text_index: base + cluster,
|
text_index: base + cluster,
|
||||||
@ -306,8 +305,8 @@ fn measure(
|
|||||||
let mut bottom = Length::ZERO;
|
let mut bottom = Length::ZERO;
|
||||||
let mut width = Length::ZERO;
|
let mut width = Length::ZERO;
|
||||||
let mut vertical = |face: &FaceBuf| {
|
let mut vertical = |face: &FaceBuf| {
|
||||||
top = top.max(face.vertical_metric(props.size, props.top_edge));
|
top = top.max(face.vertical_metric(props.top_edge).scale(props.size));
|
||||||
bottom = bottom.max(-face.vertical_metric(props.size, props.bottom_edge));
|
bottom = bottom.max(-face.vertical_metric(props.bottom_edge).scale(props.size));
|
||||||
};
|
};
|
||||||
|
|
||||||
if glyphs.is_empty() {
|
if glyphs.is_empty() {
|
||||||
@ -325,7 +324,7 @@ fn measure(
|
|||||||
vertical(face);
|
vertical(face);
|
||||||
|
|
||||||
for glyph in group {
|
for glyph in group {
|
||||||
width += face.convert(props.size, glyph.x_advance);
|
width += face.convert(glyph.x_advance).scale(props.size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ use ttf_parser::{name_id, GlyphId};
|
|||||||
|
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
use crate::env::{Env, ImageResource, ResourceId};
|
use crate::env::{Env, ImageResource, ResourceId};
|
||||||
|
use crate::font::{EmLength, VerticalFontMetric};
|
||||||
use crate::geom::{self, Length, Size};
|
use crate::geom::{self, Length, Size};
|
||||||
use crate::layout::{Element, Fill, Frame, Image, Shape};
|
use crate::layout::{Element, Fill, Frame, Image, Shape};
|
||||||
|
|
||||||
@ -234,24 +235,18 @@ impl<'a> PdfExporter<'a> {
|
|||||||
flags.insert(FontFlags::SYMBOLIC);
|
flags.insert(FontFlags::SYMBOLIC);
|
||||||
flags.insert(FontFlags::SMALL_CAP);
|
flags.insert(FontFlags::SMALL_CAP);
|
||||||
|
|
||||||
// Convert from OpenType font units to PDF glyph units.
|
|
||||||
let em_per_unit = 1.0 / ttf.units_per_em().unwrap_or(1000) as f32;
|
|
||||||
let convert = |font_unit: f32| (1000.0 * em_per_unit * font_unit).round();
|
|
||||||
let convert_i16 = |font_unit: i16| convert(font_unit as f32);
|
|
||||||
let convert_u16 = |font_unit: u16| convert(font_unit as f32);
|
|
||||||
|
|
||||||
let global_bbox = ttf.global_bounding_box();
|
let global_bbox = ttf.global_bounding_box();
|
||||||
let bbox = Rect::new(
|
let bbox = Rect::new(
|
||||||
convert_i16(global_bbox.x_min),
|
face.convert(global_bbox.x_min).to_pdf(),
|
||||||
convert_i16(global_bbox.y_min),
|
face.convert(global_bbox.y_min).to_pdf(),
|
||||||
convert_i16(global_bbox.x_max),
|
face.convert(global_bbox.x_max).to_pdf(),
|
||||||
convert_i16(global_bbox.y_max),
|
face.convert(global_bbox.y_max).to_pdf(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let italic_angle = ttf.italic_angle().unwrap_or(0.0);
|
let italic_angle = ttf.italic_angle().unwrap_or(0.0);
|
||||||
let ascender = convert_i16(ttf.typographic_ascender().unwrap_or(0));
|
let ascender = face.vertical_metric(VerticalFontMetric::Ascender).to_pdf();
|
||||||
let descender = convert_i16(ttf.typographic_descender().unwrap_or(0));
|
let descender = face.vertical_metric(VerticalFontMetric::Descender).to_pdf();
|
||||||
let cap_height = ttf.capital_height().map(convert_i16);
|
let cap_height = face.vertical_metric(VerticalFontMetric::CapHeight).to_pdf();
|
||||||
let stem_v = 10.0 + 0.244 * (f32::from(ttf.weight().to_number()) - 50.0);
|
let stem_v = 10.0 + 0.244 * (f32::from(ttf.weight().to_number()) - 50.0);
|
||||||
|
|
||||||
// Write the base font object referencing the CID font.
|
// Write the base font object referencing the CID font.
|
||||||
@ -272,8 +267,8 @@ impl<'a> PdfExporter<'a> {
|
|||||||
.individual(0, {
|
.individual(0, {
|
||||||
let num_glyphs = ttf.number_of_glyphs();
|
let num_glyphs = ttf.number_of_glyphs();
|
||||||
(0 .. num_glyphs).map(|g| {
|
(0 .. num_glyphs).map(|g| {
|
||||||
let advance = ttf.glyph_hor_advance(GlyphId(g));
|
let x = ttf.glyph_hor_advance(GlyphId(g)).unwrap_or(0);
|
||||||
convert_u16(advance.unwrap_or(0))
|
face.convert(x).to_pdf()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -286,7 +281,7 @@ impl<'a> PdfExporter<'a> {
|
|||||||
.italic_angle(italic_angle)
|
.italic_angle(italic_angle)
|
||||||
.ascent(ascender)
|
.ascent(ascender)
|
||||||
.descent(descender)
|
.descent(descender)
|
||||||
.cap_height(cap_height.unwrap_or(ascender))
|
.cap_height(cap_height)
|
||||||
.stem_v(stem_v)
|
.stem_v(stem_v)
|
||||||
.font_file2(refs.data);
|
.font_file2(refs.data);
|
||||||
|
|
||||||
@ -571,3 +566,15 @@ where
|
|||||||
self.to_layout.iter().copied()
|
self.to_layout.iter().copied()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Additional methods for [`EmLength`].
|
||||||
|
trait EmLengthExt {
|
||||||
|
/// Convert an em length to a number of PDF font units.
|
||||||
|
fn to_pdf(self) -> f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EmLengthExt for EmLength {
|
||||||
|
fn to_pdf(self) -> f32 {
|
||||||
|
1000.0 * self.get() as f32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -418,9 +418,8 @@ fn draw_text(canvas: &mut Pixmap, env: &Env, ts: Transform, shaped: &Text) {
|
|||||||
let mut x = 0.0;
|
let mut x = 0.0;
|
||||||
|
|
||||||
for glyph in &shaped.glyphs {
|
for glyph in &shaped.glyphs {
|
||||||
let units_per_em = ttf.units_per_em().unwrap_or(1000);
|
let units_per_em = ttf.units_per_em();
|
||||||
|
let s = shaped.size.to_pt() as f32 / units_per_em as f32;
|
||||||
let s = (shaped.size / units_per_em as f64).to_pt() as f32;
|
|
||||||
let dx = glyph.x_offset.to_pt() as f32;
|
let dx = glyph.x_offset.to_pt() as f32;
|
||||||
let ts = ts.pre_translate(x + dx, 0.0);
|
let ts = ts.pre_translate(x + dx, 0.0);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user