diff --git a/src/layout/elements.rs b/src/layout/elements.rs index b4a6b31c8..dd148a242 100644 --- a/src/layout/elements.rs +++ b/src/layout/elements.rs @@ -41,11 +41,14 @@ pub enum LayoutElement { /// A shaped run of text. #[derive(Clone, PartialEq)] pub struct Shaped { + /// The shaped text. pub text: String, + /// The font face the text was shaped with. pub face: FaceId, + /// The shaped glyphs. pub glyphs: Vec, - /// The horizontal offsets of the glyphs with the same indices. Vertical - /// offets are not yet supported. + /// The horizontal offsets of the glyphs. This is indexed parallel to `glyphs`. + /// Vertical offets are not yet supported. pub offsets: Vec, /// The font size. pub size: f64, diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 4fdbdde1b..fcaab3723 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -3,8 +3,8 @@ pub mod elements; pub mod line; pub mod primitive; +pub mod shaping; pub mod stack; -pub mod text; mod tree; pub use primitive::*; diff --git a/src/layout/text.rs b/src/layout/shaping.rs similarity index 56% rename from src/layout/text.rs rename to src/layout/shaping.rs index 7dd557c9e..115a23e25 100644 --- a/src/layout/text.rs +++ b/src/layout/shaping.rs @@ -1,44 +1,33 @@ -//! Layouting of continous pieces of text into boxes. +//! Super-basic text shaping. //! -//! The layouter picks the most suitable font for each individual character. -//! When the primary layouting axis horizontally inversed, the word is spelled -//! backwards. Vertical word layout is not yet supported. +//! 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::geom::Size; use crate::style::TextStyle; /// Layouts text into a box. -pub async fn layout_text(text: &str, ctx: TextContext<'_>) -> BoxLayout { - TextLayouter::new(text, ctx).layout().await +pub async fn shape(text: &str, ctx: ShapeOptions<'_>) -> BoxLayout { + Shaper::new(text, ctx).layout().await } -/// Performs the text layouting. +/// Options for text shaping. #[derive(Debug)] -struct TextLayouter<'a> { - ctx: TextContext<'a>, - text: &'a str, - shaped: Shaped, - elements: LayoutElements, - start: f64, - width: f64, -} - -/// The context for text layouting. -#[derive(Debug)] -pub struct TextContext<'a> { - /// The font loader to retrieve fonts from when typesetting text with - /// `layout_text`. +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 word is laid out. For now, only horizontal + /// 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 @@ -47,21 +36,33 @@ pub struct TextContext<'a> { pub align: LayoutAlign, } -impl<'a> TextLayouter<'a> { - fn new(text: &'a str, ctx: TextContext<'a>) -> Self { +/// 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, ctx.style.font_size()), - elements: LayoutElements::new(), - start: 0.0, - width: 0.0, - ctx, + 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) -> BoxLayout { + async fn layout(mut self) -> Layout { // If the primary axis is negative, we layout the characters reversed. - if self.ctx.dir.is_positive() { + if self.opts.dir.is_positive() { for c in self.text.chars() { self.layout_char(c).await; } @@ -73,15 +74,11 @@ impl<'a> TextLayouter<'a> { // Flush the last buffered parts of the word. if !self.shaped.text.is_empty() { - let pos = Size::new(self.start, 0.0); - self.elements.push(pos, LayoutElement::Text(self.shaped)); + let pos = Size::new(self.offset, 0.0); + self.layout.elements.push(pos, LayoutElement::Text(self.shaped)); } - BoxLayout { - size: Size::new(self.width, self.ctx.style.font_size()), - align: self.ctx.align, - elements: self.elements, - } + self.layout } async fn layout_char(&mut self, c: char) { @@ -95,14 +92,14 @@ impl<'a> TextLayouter<'a> { // from the last character's one. if self.shaped.face != index { if !self.shaped.text.is_empty() { - let pos = Size::new(self.start, 0.0); let shaped = std::mem::replace( &mut self.shaped, - Shaped::new(FaceId::MAX, self.ctx.style.font_size()), + Shaped::new(FaceId::MAX, self.opts.style.font_size()), ); - self.elements.push(pos, LayoutElement::Text(shaped)); - self.start = self.width; + let pos = Size::new(self.offset, 0.0); + self.layout.elements.push(pos, LayoutElement::Text(shaped)); + self.offset = self.layout.size.x; } self.shaped.face = index; @@ -110,19 +107,19 @@ impl<'a> TextLayouter<'a> { self.shaped.text.push(c); self.shaped.glyphs.push(glyph); - self.shaped.offsets.push(self.width - self.start); + self.shaped.offsets.push(self.layout.size.x - self.offset); - self.width += char_width; + self.layout.size.x += char_width; } async fn select_font(&mut self, c: char) -> Option<(FaceId, GlyphId, f64)> { - let mut variant = self.ctx.style.variant; + let mut variant = self.opts.style.variant; - if self.ctx.style.bolder { + if self.opts.style.bolder { variant.weight = variant.weight.thicken(300); } - if self.ctx.style.italic { + if self.opts.style.italic { variant.style = match variant.style { FontStyle::Normal => FontStyle::Italic, FontStyle::Italic => FontStyle::Normal, @@ -131,24 +128,24 @@ impl<'a> TextLayouter<'a> { } let query = FaceQuery { - fallback: self.ctx.style.fallback.iter(), + fallback: self.opts.style.fallback.iter(), variant, c, }; - if let Some((id, owned_face)) = self.ctx.loader.query(query).await { + 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(); - // Determine the width of the char. 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; + 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 = face.glyph_hor_advance(glyph)?; - let char_width = to_raw(glyph_width) * self.ctx.style.font_size(); + let glyph_width = to_raw(face.glyph_hor_advance(glyph)? as i32); - Some((id, glyph, char_width)) + Some((id, glyph, glyph_width)) } else { None } diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 56313383c..14359b86d 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -1,7 +1,7 @@ //! Layouting of syntax trees. use super::line::{LineContext, LineLayouter}; -use super::text::{layout_text, TextContext}; +use super::shaping::{shape, ShapeOptions}; use super::*; use crate::style::LayoutStyle; use crate::syntax::{ @@ -103,7 +103,7 @@ impl<'a> TreeLayouter<'a> { async fn layout_text(&mut self, text: &str) { self.layouter.add( - layout_text(text, TextContext { + shape(text, ShapeOptions { loader: &mut self.ctx.loader.borrow_mut(), style: &self.style.text, dir: self.ctx.sys.primary,