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
dir: Dir, pub async fn shape(
variant: FontVariant, text: &str,
fallback: &'a FallbackTree,
loader: &'a mut FontLoader,
shaped: Shaped,
layout: BoxLayout,
offset: f64,
}
impl<'a> Shaper<'a> {
fn new(
text: &'a str,
dir: Dir,
size: f64, size: f64,
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));
) -> Self { let mut shaped = Shaped::new(FaceId::MAX, size);
Self { let mut offset = 0.0;
text,
dir, // Create an iterator with conditional direction.
variant, let mut forwards = text.chars();
fallback, let mut backwards = text.chars().rev();
loader, let chars: &mut dyn Iterator<Item = char> = if dir.is_positive() {
shaped: Shaped::new(FaceId::MAX, size), &mut forwards
layout: BoxLayout::new(Size::new(0.0, size)), } else {
offset: 0.0, &mut backwards
} };
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,
};
// 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;
} }
async fn shape(mut self) -> BoxLayout { shaped.face = id;
// If the primary axis is negative, we layout the characters reversed. shaped.text.push(c);
if self.dir.is_positive() { shaped.glyphs.push(glyph);
for c in self.text.chars() { shaped.offsets.push(offset);
self.shape_char(c).await; offset += width;
}
} else {
for c in self.text.chars().rev() {
self.shape_char(c).await;
} }
} }
// Flush the last buffered parts of the word. // Flush the last buffered parts of the word.
if !self.shaped.text.is_empty() { if !shaped.text.is_empty() {
let pos = Point::new(self.offset, 0.0); let pos = Point::new(layout.size.width, 0.0);
self.layout.push(pos, LayoutElement::Text(self.shaped)); layout.push(pos, LayoutElement::Text(shaped));
layout.size.width += offset;
} }
self.layout layout
} }
async fn shape_char(&mut self, c: char) { /// Looks up the glyph for `c` and returns its index alongside its width at the
let (index, glyph, char_width) = match self.select_font(c).await { /// given `size`.
Some(selected) => selected, fn lookup_glyph(face: &Face, c: char, size: f64) -> Option<(GlyphId, f64)> {
// TODO: Issue warning about missing character. let glyph = face.glyph_index(c)?;
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 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;
// Determine the width of the char. // Determine the width of the char.
let glyph = face.glyph_index(c)?; let units_per_em = face.units_per_em().unwrap_or(1000) as f64;
let glyph_width = to_raw(face.glyph_hor_advance(glyph)? as i32); let width_units = face.glyph_hor_advance(glyph)? as f64;
let width = width_units / units_per_em * size;
Some((id, glyph, glyph_width)) Some((glyph, width))
} else {
None
}
}
} }