mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Kerned PDF output
This commit is contained in:
parent
c0377de653
commit
0806af4aec
@ -24,7 +24,7 @@ fxhash = "0.2.1"
|
||||
image = { version = "0.23", default-features = false, features = ["png", "jpeg"] }
|
||||
itertools = "0.10"
|
||||
miniz_oxide = "0.4"
|
||||
pdf-writer = "0.3"
|
||||
pdf-writer = { git = "https://github.com/typst/pdf-writer", rev = "818659b" }
|
||||
rand = "0.8"
|
||||
rustybuzz = "0.4"
|
||||
serde = { version = "1", features = ["derive", "rc"] }
|
||||
|
@ -14,8 +14,8 @@ use pdf_writer::{
|
||||
use ttf_parser::{name_id, GlyphId};
|
||||
|
||||
use crate::color::Color;
|
||||
use crate::font::{Em, FaceId, FontStore};
|
||||
use crate::geom::{self, Length, Size};
|
||||
use crate::font::{FaceId, FontStore};
|
||||
use crate::geom::{self, Em, Length, Size};
|
||||
use crate::image::{Image, ImageId, ImageStore};
|
||||
use crate::layout::{Element, Frame, Geometry, Paint};
|
||||
use crate::Context;
|
||||
@ -140,7 +140,7 @@ impl<'a> PdfExporter<'a> {
|
||||
|
||||
// We only write font switching actions when the used face changes. To
|
||||
// do that, we need to remember the active face.
|
||||
let mut face = None;
|
||||
let mut face_id = None;
|
||||
let mut size = Length::zero();
|
||||
let mut fill: Option<Paint> = None;
|
||||
|
||||
@ -159,17 +159,50 @@ impl<'a> PdfExporter<'a> {
|
||||
|
||||
// Then, also check if we need to issue a font switching
|
||||
// action.
|
||||
if face != Some(shaped.face_id) || shaped.size != size {
|
||||
face = Some(shaped.face_id);
|
||||
if face_id != Some(shaped.face_id) || shaped.size != size {
|
||||
face_id = Some(shaped.face_id);
|
||||
size = shaped.size;
|
||||
|
||||
let name = format!("F{}", self.font_map.map(shaped.face_id));
|
||||
text.font(Name(name.as_bytes()), size.to_pt() as f32);
|
||||
}
|
||||
|
||||
// TODO: Respect individual glyph offsets.
|
||||
let face = self.fonts.get(shaped.face_id);
|
||||
|
||||
// Position the text.
|
||||
text.matrix(1.0, 0.0, 0.0, 1.0, x, y);
|
||||
text.show(Str(&shaped.encode_glyphs_be()));
|
||||
|
||||
let mut positioned = text.show_positioned();
|
||||
let mut adjustment = Em::zero();
|
||||
let mut encoded = vec![];
|
||||
|
||||
// Write the glyphs with kerning adjustments.
|
||||
for glyph in &shaped.glyphs {
|
||||
adjustment += glyph.x_offset;
|
||||
|
||||
if !adjustment.is_zero() {
|
||||
if !encoded.is_empty() {
|
||||
positioned.show(Str(&encoded));
|
||||
encoded.clear();
|
||||
}
|
||||
|
||||
positioned.adjust(-adjustment.to_pdf());
|
||||
adjustment = Em::zero();
|
||||
}
|
||||
|
||||
encoded.push((glyph.id >> 8) as u8);
|
||||
encoded.push((glyph.id & 0xff) as u8);
|
||||
|
||||
if let Some(advance) = face.advance(glyph.id) {
|
||||
adjustment += glyph.x_advance - advance;
|
||||
}
|
||||
|
||||
adjustment -= glyph.x_offset;
|
||||
}
|
||||
|
||||
if !encoded.is_empty() {
|
||||
positioned.show(Str(&encoded));
|
||||
}
|
||||
}
|
||||
|
||||
Element::Geometry(ref geometry, paint) => {
|
||||
|
54
src/font.rs
54
src/font.rs
@ -2,15 +2,13 @@
|
||||
|
||||
use std::collections::{hash_map::Entry, HashMap};
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::ops::Add;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
|
||||
use decorum::N64;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ttf_parser::name_id;
|
||||
use ttf_parser::{name_id, GlyphId};
|
||||
|
||||
use crate::geom::Length;
|
||||
use crate::geom::Em;
|
||||
use crate::loading::{FileHash, Loader};
|
||||
|
||||
/// A unique identifier for a loaded font face.
|
||||
@ -255,6 +253,13 @@ impl Face {
|
||||
Em::from_units(units, self.units_per_em)
|
||||
}
|
||||
|
||||
/// Look up the horizontal advance width of a glyph.
|
||||
pub fn advance(&self, glyph: u16) -> Option<Em> {
|
||||
self.ttf
|
||||
.glyph_hor_advance(GlyphId(glyph))
|
||||
.map(|units| self.to_em(units))
|
||||
}
|
||||
|
||||
/// Look up a vertical metric.
|
||||
pub fn vertical_metric(&self, metric: VerticalFontMetric) -> Em {
|
||||
match metric {
|
||||
@ -267,47 +272,6 @@ impl Face {
|
||||
}
|
||||
}
|
||||
|
||||
/// A length in em units.
|
||||
///
|
||||
/// `1em` is the same as the font size.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct Em(N64);
|
||||
|
||||
impl Em {
|
||||
/// The zero length.
|
||||
pub fn zero() -> Self {
|
||||
Self(N64::from(0.0))
|
||||
}
|
||||
|
||||
/// Create an em length.
|
||||
pub fn new(em: f64) -> Self {
|
||||
Self(N64::from(em))
|
||||
}
|
||||
|
||||
/// Convert units to an em length at the given units per em.
|
||||
pub fn from_units(units: impl Into<f64>, units_per_em: f64) -> Self {
|
||||
Self(N64::from(units.into() / units_per_em))
|
||||
}
|
||||
|
||||
/// The number of em units.
|
||||
pub fn get(self) -> f64 {
|
||||
self.0.into()
|
||||
}
|
||||
|
||||
/// Convert to a length at the given font size.
|
||||
pub fn to_length(self, font_size: Length) -> Length {
|
||||
self.get() * font_size
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Em {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifies a vertical metric of a font.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum VerticalFontMetric {
|
||||
|
@ -1,6 +1,3 @@
|
||||
use decorum::N64;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// An angle.
|
||||
|
112
src/geom/em.rs
Normal file
112
src/geom/em.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use super::*;
|
||||
|
||||
/// A length that is relative to the font size.
|
||||
///
|
||||
/// `1em` is the same as the font size.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Em(N64);
|
||||
|
||||
impl Em {
|
||||
/// The zero length.
|
||||
pub fn zero() -> Self {
|
||||
Self(N64::from(0.0))
|
||||
}
|
||||
|
||||
/// The font size.
|
||||
pub fn one() -> Self {
|
||||
Self(N64::from(1.0))
|
||||
}
|
||||
|
||||
/// Create an font-relative length.
|
||||
pub fn new(em: f64) -> Self {
|
||||
Self(N64::from(em))
|
||||
}
|
||||
|
||||
/// Create font units at the given units per em.
|
||||
pub fn from_units(units: impl Into<f64>, units_per_em: f64) -> Self {
|
||||
Self(N64::from(units.into() / units_per_em))
|
||||
}
|
||||
|
||||
/// Convert to a length at the given font size.
|
||||
pub fn to_length(self, font_size: Length) -> Length {
|
||||
self.get() * font_size
|
||||
}
|
||||
|
||||
/// The number of em units.
|
||||
pub fn get(self) -> f64 {
|
||||
self.0.into()
|
||||
}
|
||||
|
||||
/// Whether the length is zero.
|
||||
pub fn is_zero(self) -> bool {
|
||||
self.0 == 0.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Em {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Em {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}em", self.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Em {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
Self(-self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Em {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
sub_impl!(Em - Em -> Em);
|
||||
|
||||
impl Mul<f64> for Em {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: f64) -> Self {
|
||||
Self(self.0 * other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Em> for f64 {
|
||||
type Output = Em;
|
||||
|
||||
fn mul(self, other: Em) -> Em {
|
||||
other * self
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Em {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, other: f64) -> Self {
|
||||
Self(self.0 / other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div for Em {
|
||||
type Output = f64;
|
||||
|
||||
fn div(self, other: Self) -> f64 {
|
||||
self.get() / other.get()
|
||||
}
|
||||
}
|
||||
|
||||
assign_impl!(Em += Em);
|
||||
assign_impl!(Em -= Em);
|
||||
assign_impl!(Em *= f64);
|
||||
assign_impl!(Em /= f64);
|
@ -1,5 +1,3 @@
|
||||
use decorum::N64;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A fractional length.
|
||||
|
@ -1,6 +1,3 @@
|
||||
use decorum::N64;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// An absolute length.
|
||||
|
@ -5,6 +5,7 @@ mod macros;
|
||||
mod align;
|
||||
mod angle;
|
||||
mod dir;
|
||||
mod em;
|
||||
mod fr;
|
||||
mod gen;
|
||||
mod length;
|
||||
@ -19,6 +20,7 @@ mod spec;
|
||||
pub use align::*;
|
||||
pub use angle::*;
|
||||
pub use dir::*;
|
||||
pub use em::*;
|
||||
pub use fr::*;
|
||||
pub use gen::*;
|
||||
pub use length::*;
|
||||
@ -35,6 +37,9 @@ use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::iter::Sum;
|
||||
use std::ops::*;
|
||||
|
||||
use decorum::N64;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Generic access to a structure's components.
|
||||
pub trait Get<Index> {
|
||||
/// The structure's component type.
|
||||
|
@ -1,7 +1,5 @@
|
||||
use super::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A bezier path.
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
|
@ -1,7 +1,5 @@
|
||||
use super::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A point in 2D.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub struct Point {
|
||||
|
@ -1,5 +1,3 @@
|
||||
use decorum::N64;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A relative length.
|
||||
|
@ -1,7 +1,5 @@
|
||||
use super::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A size in 2D.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub struct Size {
|
||||
|
@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||
use super::{Constrained, Constraints};
|
||||
use crate::color::Color;
|
||||
use crate::font::FaceId;
|
||||
use crate::geom::{Length, Path, Point, Size};
|
||||
use crate::geom::{Em, Length, Path, Point, Size};
|
||||
use crate::image::ImageId;
|
||||
|
||||
/// A finished layout with elements at fixed positions.
|
||||
@ -130,27 +130,15 @@ pub struct Text {
|
||||
pub glyphs: Vec<Glyph>,
|
||||
}
|
||||
|
||||
impl Text {
|
||||
/// Encode the glyph ids into a big-endian byte buffer.
|
||||
pub fn encode_glyphs_be(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(2 * self.glyphs.len());
|
||||
for glyph in &self.glyphs {
|
||||
bytes.push((glyph.id >> 8) as u8);
|
||||
bytes.push((glyph.id & 0xff) as u8);
|
||||
}
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
/// A glyph in a run of shaped text.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Glyph {
|
||||
/// The glyph's index in the face.
|
||||
pub id: u16,
|
||||
/// The advance width of the glyph.
|
||||
pub x_advance: Length,
|
||||
pub x_advance: Em,
|
||||
/// The horizontal offset of the glyph.
|
||||
pub x_offset: Length,
|
||||
pub x_offset: Em,
|
||||
}
|
||||
|
||||
/// A geometric shape.
|
||||
|
@ -6,7 +6,7 @@ use rustybuzz::UnicodeBuffer;
|
||||
use super::{Element, Frame, Glyph, LayoutContext, Text};
|
||||
use crate::eval::{FontState, LineState};
|
||||
use crate::font::{Face, FaceId, FontVariant, LineMetrics};
|
||||
use crate::geom::{Dir, Length, Point, Size};
|
||||
use crate::geom::{Dir, Em, Length, Point, Size};
|
||||
use crate::layout::Geometry;
|
||||
use crate::util::SliceExt;
|
||||
|
||||
@ -72,9 +72,9 @@ pub struct ShapedGlyph {
|
||||
/// The glyph's index in the face.
|
||||
pub glyph_id: u16,
|
||||
/// The advance width of the glyph.
|
||||
pub x_advance: Length,
|
||||
pub x_advance: Em,
|
||||
/// The horizontal offset of the glyph.
|
||||
pub x_offset: Length,
|
||||
pub x_offset: Em,
|
||||
/// The start index of the glyph in the source text.
|
||||
pub text_index: usize,
|
||||
/// Whether splitting the shaping result before this glyph would yield the
|
||||
@ -106,7 +106,7 @@ impl<'a> ShapedText<'a> {
|
||||
x_advance: glyph.x_advance,
|
||||
x_offset: glyph.x_offset,
|
||||
});
|
||||
width += glyph.x_advance;
|
||||
width += glyph.x_advance.to_length(text.size);
|
||||
}
|
||||
|
||||
frame.push(pos, Element::Text(text));
|
||||
@ -267,8 +267,8 @@ fn shape_segment<'a>(
|
||||
glyphs.push(ShapedGlyph {
|
||||
face_id,
|
||||
glyph_id: info.glyph_id as u16,
|
||||
x_advance: face.to_em(pos[i].x_advance).to_length(size),
|
||||
x_offset: face.to_em(pos[i].x_offset).to_length(size),
|
||||
x_advance: face.to_em(pos[i].x_advance),
|
||||
x_offset: face.to_em(pos[i].x_offset),
|
||||
text_index: base + cluster,
|
||||
safe_to_break: !info.unsafe_to_break(),
|
||||
});
|
||||
@ -342,7 +342,9 @@ fn measure(
|
||||
let mut width = Length::zero();
|
||||
let mut top = Length::zero();
|
||||
let mut bottom = Length::zero();
|
||||
let mut expand_vertical = |face: &Face| {
|
||||
|
||||
// Expand top and bottom by reading the face's vertical metrics.
|
||||
let mut expand = |face: &Face| {
|
||||
top.set_max(face.vertical_metric(state.top_edge).to_length(state.size));
|
||||
bottom.set_max(-face.vertical_metric(state.bottom_edge).to_length(state.size));
|
||||
};
|
||||
@ -352,17 +354,17 @@ fn measure(
|
||||
// first available font.
|
||||
for family in state.families() {
|
||||
if let Some(face_id) = ctx.fonts.select(family, state.variant) {
|
||||
expand_vertical(ctx.fonts.get(face_id));
|
||||
expand(ctx.fonts.get(face_id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (face_id, group) in glyphs.group_by_key(|g| g.face_id) {
|
||||
let face = ctx.fonts.get(face_id);
|
||||
expand_vertical(face);
|
||||
expand(face);
|
||||
|
||||
for glyph in group {
|
||||
width += glyph.x_advance;
|
||||
width += glyph.x_advance.to_length(state.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -444,7 +444,7 @@ fn draw_text(canvas: &mut sk::Pixmap, ts: sk::Transform, ctx: &Context, text: &T
|
||||
for glyph in &text.glyphs {
|
||||
let units_per_em = ttf.units_per_em();
|
||||
let s = text.size.to_pt() as f32 / units_per_em as f32;
|
||||
let dx = glyph.x_offset.to_pt() as f32;
|
||||
let dx = glyph.x_offset.to_length(text.size).to_pt() as f32;
|
||||
let ts = ts.pre_translate(x + dx, 0.0);
|
||||
|
||||
// Try drawing SVG if present.
|
||||
@ -481,7 +481,7 @@ fn draw_text(canvas: &mut sk::Pixmap, ts: sk::Transform, ctx: &Context, text: &T
|
||||
}
|
||||
}
|
||||
|
||||
x += glyph.x_advance.to_pt() as f32;
|
||||
x += glyph.x_advance.to_length(text.size).to_pt() as f32;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user