summaryrefslogtreecommitdiff
path: root/src/library/text
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-04-08 15:08:26 +0200
committerLaurenz <laurmaedje@gmail.com>2022-04-08 15:45:14 +0200
commit712c00ecb72b67da2c0788e5d3eb4dcc6366b2a7 (patch)
treef5d7ef4341a4728c980d020cc173fa6bb70feaff /src/library/text
parent977ac77e6a3298be2644a8231e93acbef9f7f396 (diff)
Em units
Diffstat (limited to 'src/library/text')
-rw-r--r--src/library/text/deco.rs51
-rw-r--r--src/library/text/mod.rs77
-rw-r--r--src/library/text/par.rs24
-rw-r--r--src/library/text/shaping.rs18
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);
}
}
}