From 262a8fa36a09527b4e257c175b12c8437279cf66 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 4 Oct 2020 18:01:56 +0200 Subject: =?UTF-8?q?Refactor=20and=20move=20shaping=20out=20of=20the=20layo?= =?UTF-8?q?ut=20module=20=E2=9B=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout/mod.rs | 1 - src/layout/shaping.rs | 152 ------------------------------------------------- src/layout/tree.rs | 15 ++--- src/lib.rs | 1 + src/shaping.rs | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 162 insertions(+), 160 deletions(-) delete mode 100644 src/layout/shaping.rs create mode 100644 src/shaping.rs (limited to 'src') diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 11c03b0b..c9abb165 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -3,7 +3,6 @@ pub mod elements; pub mod line; pub mod primitive; -pub mod shaping; pub mod stack; mod tree; diff --git a/src/layout/shaping.rs b/src/layout/shaping.rs deleted file mode 100644 index 4de30a9e..00000000 --- a/src/layout/shaping.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! Super-basic text shaping. -//! -//! The layouter picks the most suitable font for each individual character. When the -//! direction is right-to-left, the word is spelled backwards. Vertical shaping is not yet -//! supported. - -use fontdock::{FaceId, FaceQuery, FontStyle}; -use ttf_parser::GlyphId; - -use super::elements::{LayoutElement, Shaped}; -use super::BoxLayout as Layout; -use super::*; -use crate::font::FontLoader; -use crate::style::TextStyle; - -/// Shape text into a box. -pub async fn shape(text: &str, ctx: ShapeOptions<'_>) -> BoxLayout { - Shaper::new(text, ctx).layout().await -} - -/// Options for text shaping. -#[derive(Debug)] -pub struct ShapeOptions<'a> { - /// The font loader to retrieve fonts from. - pub loader: &'a mut FontLoader, - /// The style for text: Font selection with classes, weights and variants, - /// font sizes, spacing and so on. - pub style: &'a TextStyle, - /// The direction into which the text is laid out. Currently, only horizontal - /// directions are supported. - pub dir: Dir, - /// The alignment of the _resulting_ layout. This does not effect the line - /// layouting itself, but rather how the finished layout will be positioned - /// in a parent layout. - pub align: LayoutAlign, -} - -/// Performs super-basic text shaping. -struct Shaper<'a> { - opts: ShapeOptions<'a>, - text: &'a str, - shaped: Shaped, - layout: Layout, - offset: f64, -} - -impl<'a> Shaper<'a> { - fn new(text: &'a str, opts: ShapeOptions<'a>) -> Self { - Self { - text, - shaped: Shaped::new(FaceId::MAX, opts.style.font_size()), - layout: BoxLayout { - size: Size::new(0.0, opts.style.font_size()), - align: opts.align, - elements: LayoutElements::new(), - }, - offset: 0.0, - opts, - } - } - - async fn layout(mut self) -> Layout { - // If the primary axis is negative, we layout the characters reversed. - if self.opts.dir.is_positive() { - for c in self.text.chars() { - self.layout_char(c).await; - } - } else { - for c in self.text.chars().rev() { - self.layout_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.elements.push(pos, LayoutElement::Text(self.shaped)); - } - - self.layout - } - - async fn layout_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.opts.style.font_size()), - ); - - let pos = Point::new(self.offset, 0.0); - self.layout.elements.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 mut variant = self.opts.style.variant; - - if self.opts.style.strong { - variant.weight = variant.weight.thicken(300); - } - - if self.opts.style.emph { - variant.style = match variant.style { - FontStyle::Normal => FontStyle::Italic, - FontStyle::Italic => FontStyle::Normal, - FontStyle::Oblique => FontStyle::Normal, - } - } - - let query = FaceQuery { - fallback: self.opts.style.fallback.iter(), - variant, - c, - }; - - if let Some((id, owned_face)) = self.opts.loader.query(query).await { - let face = owned_face.get(); - let font_size = self.opts.style.font_size(); - - let units_per_em = face.units_per_em().unwrap_or(1000) as f64; - let ratio = 1.0 / units_per_em; - let to_raw = |x| ratio * x as f64 * font_size; - - // 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 - } - } -} diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 18156e97..dfc1c464 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -1,8 +1,8 @@ //! Layouting of syntax trees. use super::line::{LineContext, LineLayouter}; -use super::shaping::{shape, ShapeOptions}; use super::*; +use crate::shaping; use crate::style::LayoutStyle; use crate::syntax::{ Decoration, Expr, NodeHeading, NodeRaw, Span, SpanWith, Spanned, SynNode, SynTree, @@ -103,12 +103,13 @@ impl<'a> TreeLayouter<'a> { async fn layout_text(&mut self, text: &str) { self.layouter.add( - shape(text, ShapeOptions { - loader: &mut self.ctx.loader.borrow_mut(), - style: &self.style.text, - dir: self.ctx.sys.primary, - align: self.ctx.align, - }) + shaping::shape( + text, + self.ctx.sys.primary, + self.ctx.align, + &self.style.text, + &mut self.ctx.loader.borrow_mut(), + ) .await, ); } diff --git a/src/lib.rs b/src/lib.rs index c47f7f51..43868cd4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,7 @@ pub mod library; pub mod paper; pub mod parse; pub mod prelude; +pub mod shaping; pub mod style; pub mod syntax; diff --git a/src/shaping.rs b/src/shaping.rs new file mode 100644 index 00000000..01739db3 --- /dev/null +++ b/src/shaping.rs @@ -0,0 +1,153 @@ +//! Super-basic text shaping. +//! +//! This is really only suited for simple Latin text. It picks the most suitable +//! font for each individual character. When the direction is right-to-left, the +//! word is spelled backwards. Vertical shaping is not supported. + +use fontdock::{FaceId, FaceQuery, FallbackTree, FontStyle, FontVariant}; +use ttf_parser::GlyphId; + +use crate::font::FontLoader; +use crate::geom::{Point, Size}; +use crate::layout::elements::{LayoutElement, LayoutElements, Shaped}; +use crate::layout::{BoxLayout, Dir, LayoutAlign}; +use crate::style::TextStyle; + +/// Shape text into a box. +pub async fn shape( + text: &str, + dir: Dir, + align: LayoutAlign, + style: &TextStyle, + loader: &mut FontLoader, +) -> BoxLayout { + Shaper::new(text, dir, align, style, loader).shape().await +} + +/// Performs super-basic text shaping. +struct Shaper<'a> { + text: &'a str, + dir: Dir, + variant: FontVariant, + fallback: &'a FallbackTree, + loader: &'a mut FontLoader, + shaped: Shaped, + layout: BoxLayout, + offset: f64, +} + +impl<'a> Shaper<'a> { + fn new( + text: &'a str, + dir: Dir, + align: LayoutAlign, + style: &'a TextStyle, + loader: &'a mut FontLoader, + ) -> Self { + let mut variant = style.variant; + + if style.strong { + variant.weight = variant.weight.thicken(300); + } + + if style.emph { + variant.style = match variant.style { + FontStyle::Normal => FontStyle::Italic, + FontStyle::Italic => FontStyle::Normal, + FontStyle::Oblique => FontStyle::Normal, + } + } + + Self { + text, + dir, + variant, + fallback: &style.fallback, + loader, + shaped: Shaped::new(FaceId::MAX, style.font_size()), + layout: BoxLayout { + size: Size::new(0.0, style.font_size()), + align, + elements: LayoutElements::new(), + }, + offset: 0.0, + } + } + + 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.elements.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.elements.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. + 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 + } + } +} -- cgit v1.2.3