mirror of
https://github.com/typst/typst
synced 2025-05-21 12:35:29 +08:00
Simplify shaping 🌱
This commit is contained in:
parent
d1c07260c0
commit
2df8b964d0
@ -116,11 +116,11 @@ impl<'a> TreeLayouter<'a> {
|
||||
|
||||
let boxed = shaping::shape(
|
||||
text,
|
||||
self.ctx.state.sys.primary,
|
||||
self.ctx.state.text.font_size(),
|
||||
variant,
|
||||
&self.ctx.state.text.fallback,
|
||||
self.ctx.state.sys.primary,
|
||||
&mut self.ctx.loader.borrow_mut(),
|
||||
&self.ctx.state.text.fallback,
|
||||
variant,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
176
src/shaping.rs
176
src/shaping.rs
@ -7,24 +7,12 @@
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use fontdock::{FaceId, FaceQuery, FallbackTree, FontVariant};
|
||||
use ttf_parser::GlyphId;
|
||||
use ttf_parser::{Face, GlyphId};
|
||||
|
||||
use crate::font::FontLoader;
|
||||
use crate::geom::{Point, Size};
|
||||
use crate::layout::{BoxLayout, Dir, LayoutElement};
|
||||
|
||||
/// Shape text into a box containing shaped runs.
|
||||
pub async fn shape(
|
||||
text: &str,
|
||||
dir: Dir,
|
||||
size: f64,
|
||||
variant: FontVariant,
|
||||
fallback: &FallbackTree,
|
||||
loader: &mut FontLoader,
|
||||
) -> BoxLayout {
|
||||
Shaper::new(text, dir, size, variant, fallback, loader).shape().await
|
||||
}
|
||||
|
||||
/// A shaped run of text.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Shaped {
|
||||
@ -70,113 +58,75 @@ impl Debug for Shaped {
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs super-basic text shaping.
|
||||
struct Shaper<'a> {
|
||||
text: &'a str,
|
||||
/// Shape text into a box containing [`Shaped`] runs.
|
||||
///
|
||||
/// [`Shaped`]: struct.Shaped.html
|
||||
pub async fn shape(
|
||||
text: &str,
|
||||
size: f64,
|
||||
dir: Dir,
|
||||
loader: &mut FontLoader,
|
||||
fallback: &FallbackTree,
|
||||
variant: FontVariant,
|
||||
fallback: &'a FallbackTree,
|
||||
loader: &'a mut FontLoader,
|
||||
shaped: Shaped,
|
||||
layout: BoxLayout,
|
||||
offset: f64,
|
||||
}
|
||||
) -> BoxLayout {
|
||||
let mut layout = BoxLayout::new(Size::new(0.0, size));
|
||||
let mut shaped = Shaped::new(FaceId::MAX, size);
|
||||
let mut offset = 0.0;
|
||||
|
||||
impl<'a> Shaper<'a> {
|
||||
fn new(
|
||||
text: &'a str,
|
||||
dir: Dir,
|
||||
size: f64,
|
||||
variant: FontVariant,
|
||||
fallback: &'a FallbackTree,
|
||||
loader: &'a mut FontLoader,
|
||||
) -> Self {
|
||||
Self {
|
||||
text,
|
||||
dir,
|
||||
variant,
|
||||
fallback,
|
||||
loader,
|
||||
shaped: Shaped::new(FaceId::MAX, size),
|
||||
layout: BoxLayout::new(Size::new(0.0, size)),
|
||||
offset: 0.0,
|
||||
}
|
||||
}
|
||||
// Create an iterator with conditional direction.
|
||||
let mut forwards = text.chars();
|
||||
let mut backwards = text.chars().rev();
|
||||
let chars: &mut dyn Iterator<Item = char> = if dir.is_positive() {
|
||||
&mut forwards
|
||||
} else {
|
||||
&mut backwards
|
||||
};
|
||||
|
||||
async fn shape(mut self) -> BoxLayout {
|
||||
// If the primary axis is negative, we layout the characters reversed.
|
||||
if self.dir.is_positive() {
|
||||
for c in self.text.chars() {
|
||||
self.shape_char(c).await;
|
||||
}
|
||||
} else {
|
||||
for c in self.text.chars().rev() {
|
||||
self.shape_char(c).await;
|
||||
}
|
||||
}
|
||||
|
||||
// Flush the last buffered parts of the word.
|
||||
if !self.shaped.text.is_empty() {
|
||||
let pos = Point::new(self.offset, 0.0);
|
||||
self.layout.push(pos, LayoutElement::Text(self.shaped));
|
||||
}
|
||||
|
||||
self.layout
|
||||
}
|
||||
|
||||
async fn shape_char(&mut self, c: char) {
|
||||
let (index, glyph, char_width) = match self.select_font(c).await {
|
||||
Some(selected) => selected,
|
||||
// TODO: Issue warning about missing character.
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Flush the buffer and issue a font setting action if the font differs
|
||||
// from the last character's one.
|
||||
if self.shaped.face != index {
|
||||
if !self.shaped.text.is_empty() {
|
||||
let shaped = std::mem::replace(
|
||||
&mut self.shaped,
|
||||
Shaped::new(FaceId::MAX, self.layout.size.height),
|
||||
);
|
||||
|
||||
let pos = Point::new(self.offset, 0.0);
|
||||
self.layout.push(pos, LayoutElement::Text(shaped));
|
||||
self.offset = self.layout.size.width;
|
||||
}
|
||||
|
||||
self.shaped.face = index;
|
||||
}
|
||||
|
||||
self.shaped.text.push(c);
|
||||
self.shaped.glyphs.push(glyph);
|
||||
self.shaped.offsets.push(self.layout.size.width - self.offset);
|
||||
|
||||
self.layout.size.width += char_width;
|
||||
}
|
||||
|
||||
async fn select_font(&mut self, c: char) -> Option<(FaceId, GlyphId, f64)> {
|
||||
let query = FaceQuery {
|
||||
fallback: self.fallback.iter(),
|
||||
variant: self.variant,
|
||||
c,
|
||||
};
|
||||
|
||||
if let Some((id, owned_face)) = self.loader.query(query).await {
|
||||
for c in chars {
|
||||
let query = FaceQuery { fallback: fallback.iter(), variant, c };
|
||||
if let Some((id, owned_face)) = loader.query(query).await {
|
||||
let face = owned_face.get();
|
||||
let (glyph, width) = match lookup_glyph(face, c, size) {
|
||||
Some(v) => v,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let units_per_em = face.units_per_em().unwrap_or(1000) as f64;
|
||||
let ratio = 1.0 / units_per_em;
|
||||
let font_size = self.layout.size.height;
|
||||
let to_raw = |x| ratio * x as f64 * font_size;
|
||||
// Flush the buffer if we change the font face.
|
||||
if shaped.face != id && !shaped.text.is_empty() {
|
||||
let pos = Point::new(layout.size.width, 0.0);
|
||||
layout.push(pos, LayoutElement::Text(shaped));
|
||||
layout.size.width += offset;
|
||||
shaped = Shaped::new(FaceId::MAX, size);
|
||||
offset = 0.0;
|
||||
}
|
||||
|
||||
// Determine the width of the char.
|
||||
let glyph = face.glyph_index(c)?;
|
||||
let glyph_width = to_raw(face.glyph_hor_advance(glyph)? as i32);
|
||||
|
||||
Some((id, glyph, glyph_width))
|
||||
} else {
|
||||
None
|
||||
shaped.face = id;
|
||||
shaped.text.push(c);
|
||||
shaped.glyphs.push(glyph);
|
||||
shaped.offsets.push(offset);
|
||||
offset += width;
|
||||
}
|
||||
}
|
||||
|
||||
// Flush the last buffered parts of the word.
|
||||
if !shaped.text.is_empty() {
|
||||
let pos = Point::new(layout.size.width, 0.0);
|
||||
layout.push(pos, LayoutElement::Text(shaped));
|
||||
layout.size.width += offset;
|
||||
}
|
||||
|
||||
layout
|
||||
}
|
||||
|
||||
/// Looks up the glyph for `c` and returns its index alongside its width at the
|
||||
/// given `size`.
|
||||
fn lookup_glyph(face: &Face, c: char, size: f64) -> Option<(GlyphId, f64)> {
|
||||
let glyph = face.glyph_index(c)?;
|
||||
|
||||
// Determine the width of the char.
|
||||
let units_per_em = face.units_per_em().unwrap_or(1000) as f64;
|
||||
let width_units = face.glyph_hor_advance(glyph)? as f64;
|
||||
let width = width_units / units_per_em * size;
|
||||
|
||||
Some((glyph, width))
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user