summaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/frame.rs4
-rw-r--r--src/layout/par.rs16
-rw-r--r--src/layout/shaping.rs92
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);
+ }
+}