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(
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;

View File

@ -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))
}