diff options
Diffstat (limited to 'src/layout')
| -rw-r--r-- | src/layout/frame.rs | 4 | ||||
| -rw-r--r-- | src/layout/par.rs | 16 | ||||
| -rw-r--r-- | src/layout/shaping.rs | 92 |
3 files changed, 99 insertions, 13 deletions
diff --git a/src/layout/frame.rs b/src/layout/frame.rs index 6cecc7a3..119aeea6 100644 --- a/src/layout/frame.rs +++ b/src/layout/frame.rs @@ -92,12 +92,14 @@ pub enum Shape { Rect(Size), /// An ellipse with its origin in the center. Ellipse(Size), + /// A line to a `Point` (relative to its position) with a stroke width. + Line(Point, Length), /// A bezier path. Path(Path), } /// How text and shapes are filled. -#[derive(Debug, Copy, Clone, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] pub enum Fill { /// A solid color. Color(Color), diff --git a/src/layout/par.rs b/src/layout/par.rs index f21778de..8b3cbf8b 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -190,7 +190,7 @@ impl<'a> ParLayout<'a> { while !stack.regions.current.height.fits(line.size.height) && !stack.regions.in_full_last() { - stack.finish_region(); + stack.finish_region(ctx); } // If the line does not fit horizontally or we have a mandatory @@ -217,7 +217,7 @@ impl<'a> ParLayout<'a> { stack.push(line); } - stack.finish() + stack.finish(ctx) } /// Find the index of the item whose range contains the `text_offset`. @@ -302,7 +302,7 @@ impl<'a> LineStack<'a> { self.lines.push(line); } - fn finish_region(&mut self) { + fn finish_region(&mut self, ctx: &LayoutContext) { if self.regions.fixed.horizontal { self.size.width = self.regions.current.width; } @@ -312,7 +312,7 @@ impl<'a> LineStack<'a> { let mut first = true; for line in std::mem::take(&mut self.lines) { - let frame = line.build(self.size.width); + let frame = line.build(ctx, self.size.width); let pos = Point::new(Length::zero(), offset); if first { @@ -329,8 +329,8 @@ impl<'a> LineStack<'a> { self.size = Size::zero(); } - fn finish(mut self) -> Vec<Frame> { - self.finish_region(); + fn finish(mut self, ctx: &LayoutContext) -> Vec<Frame> { + self.finish_region(ctx); self.finished } } @@ -447,7 +447,7 @@ impl<'a> LineLayout<'a> { } /// Build the line's frame. - fn build(&self, width: Length) -> Frame { + fn build(&self, ctx: &LayoutContext, width: Length) -> Frame { let size = Size::new(self.size.width.max(width), self.size.height); let free = size.width - self.size.width; @@ -463,7 +463,7 @@ impl<'a> LineLayout<'a> { } ParItem::Text(ref shaped, align) => { ruler = ruler.max(align); - shaped.build() + shaped.build(ctx) } ParItem::Frame(ref frame, align) => { ruler = ruler.max(align); diff --git a/src/layout/shaping.rs b/src/layout/shaping.rs index 14ea8611..232e9fc5 100644 --- a/src/layout/shaping.rs +++ b/src/layout/shaping.rs @@ -1,13 +1,14 @@ use std::borrow::Cow; use std::fmt::{self, Debug, Formatter}; -use std::ops::Range; +use std::ops::{Add, Range}; use rustybuzz::UnicodeBuffer; use super::{Element, Frame, Glyph, LayoutContext, Text}; use crate::exec::FontProps; -use crate::font::{Face, FaceId}; +use crate::font::{Em, Face, FaceId, VerticalFontMetric}; use crate::geom::{Dir, Length, Point, Size}; +use crate::layout::Shape; use crate::util::SliceExt; /// The result of shaping text. @@ -59,12 +60,13 @@ enum Side { impl<'a> ShapedText<'a> { /// Build the shaped text's frame. - pub fn build(&self) -> Frame { + pub fn build(&self, ctx: &LayoutContext) -> Frame { let mut frame = Frame::new(self.size, self.baseline); let mut offset = Length::zero(); for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) { let pos = Point::new(offset, self.baseline); + let mut text = Text { face_id, size: self.props.size, @@ -72,16 +74,20 @@ impl<'a> ShapedText<'a> { glyphs: vec![], }; + let mut width = Length::zero(); for glyph in group { text.glyphs.push(Glyph { id: glyph.glyph_id, x_advance: glyph.x_advance, x_offset: glyph.x_offset, }); - offset += glyph.x_advance; + width += glyph.x_advance; } frame.push(pos, Element::Text(text)); + decorate(ctx, &mut frame, &self.props, face_id, pos, width); + + offset += width; } frame @@ -364,3 +370,81 @@ fn measure( (Size::new(width, top + bottom), top) } + +/// Add underline, strikthrough and overline decorations. +fn decorate( + ctx: &LayoutContext, + frame: &mut Frame, + props: &FontProps, + face_id: FaceId, + pos: Point, + width: Length, +) { + let mut apply = |strength, position, extent, fill| { + let pos = Point::new(pos.x - extent, pos.y - position); + let target = Point::new(width + 2.0 * extent, Length::zero()); + frame.push(pos, Element::Geometry(Shape::Line(target, strength), fill)); + }; + + if let Some(strikethrough) = props.strikethrough { + let face = ctx.cache.font.get(face_id); + + let strength = strikethrough.strength.unwrap_or_else(|| { + face.ttf() + .strikeout_metrics() + .or_else(|| face.ttf().underline_metrics()) + .map_or(Em::new(0.06), |m| face.to_em(m.thickness)) + .to_length(props.size) + }); + + let position = strikethrough.position.unwrap_or_else(|| { + face.ttf() + .strikeout_metrics() + .map_or(Em::new(0.25), |m| face.to_em(m.position)) + .to_length(props.size) + }); + + apply(strength, position, strikethrough.extent, strikethrough.fill); + } + + if let Some(underline) = props.underline { + let face = ctx.cache.font.get(face_id); + + let strength = underline.strength.unwrap_or_else(|| { + face.ttf() + .underline_metrics() + .or_else(|| face.ttf().strikeout_metrics()) + .map_or(Em::new(0.06), |m| face.to_em(m.thickness)) + .to_length(props.size) + }); + + let position = underline.position.unwrap_or_else(|| { + face.ttf() + .underline_metrics() + .map_or(Em::new(-0.2), |m| face.to_em(m.position)) + .to_length(props.size) + }); + + apply(strength, position, underline.extent, underline.fill); + } + + if let Some(overline) = props.overline { + let face = ctx.cache.font.get(face_id); + + let strength = overline.strength.unwrap_or_else(|| { + face.ttf() + .underline_metrics() + .or_else(|| face.ttf().strikeout_metrics()) + .map_or(Em::new(0.06), |m| face.to_em(m.thickness)) + .to_length(props.size) + }); + + let position = overline.position.unwrap_or_else(|| { + face.vertical_metric(VerticalFontMetric::CapHeight) + .add(Em::new(0.1)) + .to_length(props.size) + }); + + apply(strength, position, overline.extent, overline.fill); + } +} |
