diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-04-08 15:08:26 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-04-08 15:45:14 +0200 |
| commit | 712c00ecb72b67da2c0788e5d3eb4dcc6366b2a7 (patch) | |
| tree | f5d7ef4341a4728c980d020cc173fa6bb70feaff /src/library/text | |
| parent | 977ac77e6a3298be2644a8231e93acbef9f7f396 (diff) | |
Em units
Diffstat (limited to 'src/library/text')
| -rw-r--r-- | src/library/text/deco.rs | 51 | ||||
| -rw-r--r-- | src/library/text/mod.rs | 77 | ||||
| -rw-r--r-- | src/library/text/par.rs | 24 | ||||
| -rw-r--r-- | src/library/text/shaping.rs | 18 |
4 files changed, 90 insertions, 80 deletions
diff --git a/src/library/text/deco.rs b/src/library/text/deco.rs index da1a1141..f5ed4744 100644 --- a/src/library/text/deco.rs +++ b/src/library/text/deco.rs @@ -23,16 +23,16 @@ impl<const L: DecoLine> DecoNode<L> { /// Stroke color of the line, defaults to the text color if `None`. #[property(shorthand)] pub const STROKE: Option<Paint> = None; - /// Thickness of the line's strokes (dependent on scaled font size), read - /// from the font tables if `None`. - #[property(shorthand)] - pub const THICKNESS: Option<Relative<Length>> = None; - /// Position of the line relative to the baseline (dependent on scaled font - /// size), read from the font tables if `None`. - pub const OFFSET: Option<Relative<Length>> = None; - /// Amount that the line will be longer or shorter than its associated text - /// (dependent on scaled font size). - pub const EXTENT: Relative<Length> = Relative::zero(); + /// Thickness of the line's strokes, read from the font tables if `auto`. + #[property(shorthand, resolve)] + pub const THICKNESS: Smart<RawLength> = Smart::Auto; + /// Position of the line relative to the baseline, read from the font tables + /// if `auto`. + #[property(resolve)] + pub const OFFSET: Smart<RawLength> = Smart::Auto; + /// Amount that the line will be longer or shorter than its associated text. + #[property(resolve)] + pub const EXTENT: RawLength = RawLength::zero(); /// Whether the line skips sections in which it would collide /// with the glyphs. Does not apply to strikethrough. pub const EVADE: bool = true; @@ -66,9 +66,9 @@ impl<const L: DecoLine> Show for DecoNode<L> { pub struct Decoration { pub line: DecoLine, pub stroke: Option<Paint>, - pub thickness: Option<Relative<Length>>, - pub offset: Option<Relative<Length>>, - pub extent: Relative<Length>, + pub thickness: Smart<Length>, + pub offset: Smart<Length>, + pub extent: Length, pub evade: bool, } @@ -102,25 +102,18 @@ pub fn decorate( }; let evade = deco.evade && deco.line != STRIKETHROUGH; - let extent = deco.extent.resolve(text.size); - let offset = deco - .offset - .map(|s| s.resolve(text.size)) - .unwrap_or(-metrics.position.resolve(text.size)); + let offset = deco.offset.unwrap_or(-metrics.position.at(text.size)); let stroke = Stroke { paint: deco.stroke.unwrap_or(text.fill), - thickness: deco - .thickness - .map(|s| s.resolve(text.size)) - .unwrap_or(metrics.thickness.resolve(text.size)), + thickness: deco.thickness.unwrap_or(metrics.thickness.at(text.size)), }; let gap_padding = 0.08 * text.size; let min_width = 0.162 * text.size; - let mut start = pos.x - extent; - let end = pos.x + (width + 2.0 * extent); + let mut start = pos.x - deco.extent; + let end = pos.x + (width + 2.0 * deco.extent); let mut push_segment = |from: Length, to: Length| { let origin = Point::new(from, pos.y + offset); @@ -146,20 +139,20 @@ pub fn decorate( let mut intersections = vec![]; for glyph in text.glyphs.iter() { - let dx = glyph.x_offset.resolve(text.size) + x; + let dx = glyph.x_offset.at(text.size) + x; let mut builder = BezPathBuilder::new(face_metrics.units_per_em, text.size, dx.to_raw()); let bbox = face.ttf().outline_glyph(GlyphId(glyph.id), &mut builder); let path = builder.finish(); - x += glyph.x_advance.resolve(text.size); + x += glyph.x_advance.at(text.size); // Only do the costly segments intersection test if the line // intersects the bounding box. if bbox.map_or(false, |bbox| { - let y_min = -face.to_em(bbox.y_max).resolve(text.size); - let y_max = -face.to_em(bbox.y_min).resolve(text.size); + let y_min = -face.to_em(bbox.y_max).at(text.size); + let y_max = -face.to_em(bbox.y_min).at(text.size); offset >= y_min && offset <= y_max }) { @@ -225,7 +218,7 @@ impl BezPathBuilder { } fn s(&self, v: f32) -> f64 { - Em::from_units(v, self.units_per_em).resolve(self.font_size).to_raw() + Em::from_units(v, self.units_per_em).at(self.font_size).to_raw() } } diff --git a/src/library/text/mod.rs b/src/library/text/mod.rs index 4a139fb3..b5ccc636 100644 --- a/src/library/text/mod.rs +++ b/src/library/text/mod.rs @@ -16,7 +16,9 @@ use std::borrow::Cow; use ttf_parser::Tag; -use crate::font::{Face, FontStretch, FontStyle, FontWeight, VerticalFontMetric}; +use crate::font::{ + Face, FaceMetrics, FontStretch, FontStyle, FontWeight, VerticalFontMetric, +}; use crate::library::prelude::*; use crate::util::EcoString; @@ -39,23 +41,25 @@ impl TextNode { pub const WEIGHT: FontWeight = FontWeight::REGULAR; /// The width of the glyphs. pub const STRETCH: FontStretch = FontStretch::NORMAL; + /// The size of the glyphs. #[property(shorthand, fold)] - pub const SIZE: FontSize = Length::pt(11.0); + pub const SIZE: TextSize = Length::pt(11.0); /// The glyph fill color. #[property(shorthand)] pub const FILL: Paint = Color::BLACK.into(); - /// The amount of space that should be added between characters. - pub const TRACKING: Em = Em::zero(); - /// The ratio by which spaces should be stretched. - pub const SPACING: Ratio = Ratio::one(); + #[property(resolve)] + pub const TRACKING: RawLength = RawLength::zero(); + /// The width of spaces relative to the default space width. + #[property(resolve)] + pub const SPACING: Relative<RawLength> = Relative::one(); /// Whether glyphs can hang over into the margin. pub const OVERHANG: bool = true; /// The top end of the text bounding box. - pub const TOP_EDGE: VerticalFontMetric = VerticalFontMetric::CapHeight; + pub const TOP_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::CapHeight); /// The bottom end of the text bounding box. - pub const BOTTOM_EDGE: VerticalFontMetric = VerticalFontMetric::Baseline; + pub const BOTTOM_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::Baseline); /// Whether to apply kerning ("kern"). pub const KERNING: bool = true; @@ -188,44 +192,53 @@ castable! { /// The size of text. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct FontSize(pub Relative<Length>); +pub struct TextSize(pub RawLength); -impl Fold for FontSize { +impl Fold for TextSize { type Output = Length; fn fold(self, outer: Self::Output) -> Self::Output { - self.0.rel.resolve(outer) + self.0.abs + self.0.em.at(outer) + self.0.length } } castable! { - FontSize, - Expected: "relative length", - Value::Length(v) => Self(v.into()), - Value::Ratio(v) => Self(v.into()), - Value::Relative(v) => Self(v), + TextSize, + Expected: "length", + Value::Length(v) => Self(v), } -castable! { - Em, - Expected: "float", - Value::Float(v) => Self::new(v), +/// Specifies the bottom or top edge of text. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum TextEdge { + /// An edge specified using one of the well-known font metrics. + Metric(VerticalFontMetric), + /// An edge specified as a length. + Length(RawLength), +} + +impl TextEdge { + /// Resolve the value of the text edge given a font face. + pub fn resolve(self, styles: StyleChain, metrics: &FaceMetrics) -> Length { + match self { + Self::Metric(metric) => metrics.vertical(metric).resolve(styles), + Self::Length(length) => length.resolve(styles), + } + } } castable! { - VerticalFontMetric, - Expected: "string or relative length", - Value::Length(v) => Self::Relative(v.into()), - Value::Ratio(v) => Self::Relative(v.into()), - Value::Relative(v) => Self::Relative(v), - Value::Str(string) => match string.as_str() { - "ascender" => Self::Ascender, - "cap-height" => Self::CapHeight, - "x-height" => Self::XHeight, - "baseline" => Self::Baseline, - "descender" => Self::Descender, + TextEdge, + Expected: "string or length", + Value::Length(v) => Self::Length(v), + Value::Str(string) => Self::Metric(match string.as_str() { + "ascender" => VerticalFontMetric::Ascender, + "cap-height" => VerticalFontMetric::CapHeight, + "x-height" => VerticalFontMetric::XHeight, + "baseline" => VerticalFontMetric::Baseline, + "descender" => VerticalFontMetric::Descender, _ => Err("unknown font metric")?, - }, + }), } /// A stylistic set in a font face. diff --git a/src/library/text/par.rs b/src/library/text/par.rs index dc7c9dcf..57e2b45d 100644 --- a/src/library/text/par.rs +++ b/src/library/text/par.rs @@ -42,12 +42,15 @@ impl ParNode { /// Whether to hyphenate text to improve line breaking. When `auto`, words /// will will be hyphenated if and only if justification is enabled. pub const HYPHENATE: Smart<bool> = Smart::Auto; - /// The spacing between lines (dependent on scaled font size). - pub const LEADING: Relative<Length> = Ratio::new(0.65).into(); - /// The extra spacing between paragraphs (dependent on scaled font size). - pub const SPACING: Relative<Length> = Ratio::new(0.55).into(); + /// The spacing between lines. + #[property(resolve)] + pub const LEADING: RawLength = Em::new(0.65).into(); + /// The extra spacing between paragraphs. + #[property(resolve)] + pub const SPACING: RawLength = Em::new(0.55).into(); /// The indent the first line of a consecutive paragraph should have. - pub const INDENT: Relative<Length> = Relative::zero(); + #[property(resolve)] + pub const INDENT: RawLength = RawLength::zero(); fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { // The paragraph constructor is special: It doesn't create a paragraph @@ -370,7 +373,7 @@ fn prepare<'a>( } ParChild::Spacing(spacing) => match *spacing { Spacing::Relative(v) => { - let resolved = v.resolve(regions.base.x); + let resolved = v.resolve(styles).relative_to(regions.base.x); items.push(ParItem::Absolute(resolved)); ranges.push(range); } @@ -772,8 +775,7 @@ fn stack( regions: &Regions, styles: StyleChain, ) -> Vec<Arc<Frame>> { - let em = styles.get(TextNode::SIZE); - let leading = styles.get(ParNode::LEADING).resolve(em); + let leading = styles.get(ParNode::LEADING); let align = styles.get(ParNode::ALIGN); let justify = styles.get(ParNode::JUSTIFY); @@ -837,7 +839,7 @@ fn commit( if text.styles.get(TextNode::OVERHANG) { let start = text.dir.is_positive(); let em = text.styles.get(TextNode::SIZE); - let amount = overhang(glyph.c, start) * glyph.x_advance.resolve(em); + let amount = overhang(glyph.c, start) * glyph.x_advance.at(em); offset -= amount; remaining += amount; } @@ -852,7 +854,7 @@ fn commit( { let start = !text.dir.is_positive(); let em = text.styles.get(TextNode::SIZE); - let amount = overhang(glyph.c, start) * glyph.x_advance.resolve(em); + let amount = overhang(glyph.c, start) * glyph.x_advance.at(em); remaining += amount; } } @@ -887,7 +889,7 @@ fn commit( match item { ParItem::Absolute(v) => offset += *v, - ParItem::Fractional(v) => offset += v.resolve(line.fr, remaining), + ParItem::Fractional(v) => offset += v.share(line.fr, remaining), ParItem::Text(shaped) => position(shaped.build(fonts, justification)), ParItem::Frame(frame) => position(frame.clone()), } diff --git a/src/library/text/shaping.rs b/src/library/text/shaping.rs index d398e56d..32177f0a 100644 --- a/src/library/text/shaping.rs +++ b/src/library/text/shaping.rs @@ -132,7 +132,7 @@ impl<'a> ShapedText<'a> { .filter(|g| g.is_justifiable()) .map(|g| g.x_advance) .sum::<Em>() - .resolve(self.styles.get(TextNode::SIZE)) + .at(self.styles.get(TextNode::SIZE)) } /// Reshape a range of the shaped text, reusing information from this @@ -168,7 +168,7 @@ impl<'a> ShapedText<'a> { let glyph_id = ttf.glyph_index('-')?; let x_advance = face.to_em(ttf.glyph_hor_advance(glyph_id)?); let cluster = self.glyphs.last().map(|g| g.cluster).unwrap_or_default(); - self.size.x += x_advance.resolve(size); + self.size.x += x_advance.at(size); self.glyphs.to_mut().push(ShapedGlyph { face_id, glyph_id: glyph_id.0, @@ -443,8 +443,10 @@ fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, face_id: FaceI /// Apply tracking and spacing to a slice of shaped glyphs. fn track_and_space(ctx: &mut ShapingContext) { - let tracking = ctx.styles.get(TextNode::TRACKING); - let spacing = ctx.styles.get(TextNode::SPACING); + let em = ctx.styles.get(TextNode::SIZE); + let tracking = Em::from_length(ctx.styles.get(TextNode::TRACKING), em); + let spacing = ctx.styles.get(TextNode::SPACING).map(|abs| Em::from_length(abs, em)); + if tracking.is_zero() && spacing.is_one() { return; } @@ -452,7 +454,7 @@ fn track_and_space(ctx: &mut ShapingContext) { let mut glyphs = ctx.glyphs.iter_mut().peekable(); while let Some(glyph) = glyphs.next() { if glyph.is_space() { - glyph.x_advance *= spacing.get(); + glyph.x_advance = spacing.relative_to(glyph.x_advance); } if glyphs.peek().map_or(false, |next| glyph.cluster != next.cluster) { @@ -479,8 +481,8 @@ fn measure( // Expand top and bottom by reading the face's vertical metrics. let mut expand = |face: &Face| { let metrics = face.metrics(); - top.set_max(metrics.vertical(top_edge, size)); - bottom.set_max(-metrics.vertical(bottom_edge, size)); + top.set_max(top_edge.resolve(styles, metrics)); + bottom.set_max(-bottom_edge.resolve(styles, metrics)); }; if glyphs.is_empty() { @@ -499,7 +501,7 @@ fn measure( expand(face); for glyph in group { - width += glyph.x_advance.resolve(size); + width += glyph.x_advance.at(size); } } } |
