Simplify shaping 🌱

This commit is contained in:
Laurenz 2020-10-05 15:35:55 +02:00
parent d1c07260c0
commit 2df8b964d0
2 changed files with 66 additions and 116 deletions

View File

@ -116,11 +116,11 @@ impl<'a> TreeLayouter<'a> {
let boxed = shaping::shape( let boxed = shaping::shape(
text, text,
self.ctx.state.sys.primary,
self.ctx.state.text.font_size(), self.ctx.state.text.font_size(),
variant, self.ctx.state.sys.primary,
&self.ctx.state.text.fallback,
&mut self.ctx.loader.borrow_mut(), &mut self.ctx.loader.borrow_mut(),
&self.ctx.state.text.fallback,
variant,
) )
.await; .await;

View File

@ -7,24 +7,12 @@
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use fontdock::{FaceId, FaceQuery, FallbackTree, FontVariant}; use fontdock::{FaceId, FaceQuery, FallbackTree, FontVariant};
use ttf_parser::GlyphId; use ttf_parser::{Face, GlyphId};
use crate::font::FontLoader; use crate::font::FontLoader;
use crate::geom::{Point, Size}; use crate::geom::{Point, Size};
use crate::layout::{BoxLayout, Dir, LayoutElement}; 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. /// A shaped run of text.
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct Shaped { pub struct Shaped {
@ -70,113 +58,75 @@ impl Debug for Shaped {
} }
} }
/// Performs super-basic text shaping. /// Shape text into a box containing [`Shaped`] runs.
struct Shaper<'a> { ///
text: &'a str, /// [`Shaped`]: struct.Shaped.html
pub async fn shape(
text: &str,
size: f64,
dir: Dir, dir: Dir,
loader: &mut FontLoader,
fallback: &FallbackTree,
variant: FontVariant, variant: FontVariant,
fallback: &'a FallbackTree, ) -> BoxLayout {
loader: &'a mut FontLoader, let mut layout = BoxLayout::new(Size::new(0.0, size));
shaped: Shaped, let mut shaped = Shaped::new(FaceId::MAX, size);
layout: BoxLayout, let mut offset = 0.0;
offset: f64,
}
impl<'a> Shaper<'a> { // Create an iterator with conditional direction.
fn new( let mut forwards = text.chars();
text: &'a str, let mut backwards = text.chars().rev();
dir: Dir, let chars: &mut dyn Iterator<Item = char> = if dir.is_positive() {
size: f64, &mut forwards
variant: FontVariant, } else {
fallback: &'a FallbackTree, &mut backwards
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,
}
}
async fn shape(mut self) -> BoxLayout { for c in chars {
// If the primary axis is negative, we layout the characters reversed. let query = FaceQuery { fallback: fallback.iter(), variant, c };
if self.dir.is_positive() { if let Some((id, owned_face)) = loader.query(query).await {
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 {
let face = owned_face.get(); 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; // Flush the buffer if we change the font face.
let ratio = 1.0 / units_per_em; if shaped.face != id && !shaped.text.is_empty() {
let font_size = self.layout.size.height; let pos = Point::new(layout.size.width, 0.0);
let to_raw = |x| ratio * x as f64 * font_size; 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. shaped.face = id;
let glyph = face.glyph_index(c)?; shaped.text.push(c);
let glyph_width = to_raw(face.glyph_hor_advance(glyph)? as i32); shaped.glyphs.push(glyph);
shaped.offsets.push(offset);
Some((id, glyph, glyph_width)) offset += width;
} else {
None
} }
} }
// 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))
} }