Le Rêfectéur

This commit is contained in:
Martin Haug 2022-02-04 11:07:33 +01:00
parent 9a9c6f22c4
commit 144c2957e1
2 changed files with 102 additions and 134 deletions

View File

@ -48,8 +48,9 @@ impl Frame {
self.elements.len() self.elements.len()
} }
/// Insert an element at the given layer in the Frame. This method panics if /// Insert an element at the given layer in the frame.
/// the layer is greater than the number of layers present. ///
/// This panics if the layer is greater than the number of layers present.
pub fn insert(&mut self, layer: usize, pos: Point, element: Element) { pub fn insert(&mut self, layer: usize, pos: Point, element: Element) {
self.elements.insert(layer, (pos, element)); self.elements.insert(layer, (pos, element));
} }

View File

@ -5,7 +5,7 @@ use std::convert::TryInto;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::ops::{BitXor, Range}; use std::ops::{BitXor, Range};
use kurbo::{BezPath, Line, ParamCurve, Point as KPoint}; use kurbo::{BezPath, Line, ParamCurve};
use rustybuzz::{Feature, UnicodeBuffer}; use rustybuzz::{Feature, UnicodeBuffer};
use ttf_parser::{GlyphId, OutlineBuilder, Tag}; use ttf_parser::{GlyphId, OutlineBuilder, Tag};
@ -816,9 +816,10 @@ impl<'a> ShapedText<'a> {
let text_layer = frame.layer(); let text_layer = frame.layer();
let width = text.width(); let width = text.width();
self.add_line_decos( // Apply line decorations.
&mut frame, fonts, &text, face_id, size, fill, pos, width, for deco in self.styles.get_cloned(TextNode::LINES) {
); self.add_line_decos(&mut frame, &deco, fonts, &text, pos, width);
}
frame.insert(text_layer, pos, Element::Text(text)); frame.insert(text_layer, pos, Element::Text(text));
@ -837,17 +838,13 @@ impl<'a> ShapedText<'a> {
fn add_line_decos( fn add_line_decos(
&self, &self,
frame: &mut Frame, frame: &mut Frame,
deco: &Decoration,
fonts: &FontStore, fonts: &FontStore,
text: &Text, text: &Text,
face_id: FaceId,
size: Length,
fill: Paint,
pos: Point, pos: Point,
width: Length, width: Length,
) { ) {
// Apply line decorations. let face = fonts.get(text.face_id);
for deco in self.styles.get_cloned(TextNode::LINES) {
let face = fonts.get(face_id);
let metrics = match deco.line { let metrics = match deco.line {
DecoLine::Underline => face.underline, DecoLine::Underline => face.underline,
DecoLine::Strikethrough => face.strikethrough, DecoLine::Strikethrough => face.strikethrough,
@ -855,131 +852,101 @@ impl<'a> ShapedText<'a> {
}; };
let evade = deco.evade && deco.line != DecoLine::Strikethrough; let evade = deco.evade && deco.line != DecoLine::Strikethrough;
let extent = deco.extent.resolve(text.size);
let extent = deco.extent.resolve(size);
let offset = deco let offset = deco
.offset .offset
.map(|s| s.resolve(size)) .map(|s| s.resolve(text.size))
.unwrap_or(-metrics.position.resolve(size)); .unwrap_or(-metrics.position.resolve(text.size));
let stroke = Stroke { let stroke = Stroke {
paint: deco.stroke.unwrap_or(fill), paint: deco.stroke.unwrap_or(text.fill),
thickness: deco thickness: deco
.thickness .thickness
.map(|s| s.resolve(size)) .map(|s| s.resolve(text.size))
.unwrap_or(metrics.thickness.resolve(size)), .unwrap_or(metrics.thickness.resolve(text.size)),
}; };
let line_y = pos.y + offset; let gap_padding = 0.08 * text.size;
let gap_padding = size * 0.08; let min_width = 0.162 * text.size;
let mut start = pos.x - extent;
let end = pos.x + (width + 2.0 * extent);
let mut push_segment = |from: Length, to: Length| {
let origin = Point::new(from, pos.y + offset);
let target = Point::new(to - from, Length::zero());
if target.x >= min_width || !evade {
let shape = Shape::stroked(Geometry::Line(target), stroke);
frame.push(origin, Element::Shape(shape));
}
};
if !evade {
push_segment(start, end);
return;
}
let gaps = if evade {
let line = Line::new( let line = Line::new(
KPoint::new(pos.x.to_raw(), offset.to_raw()), kurbo::Point::new(pos.x.to_raw(), offset.to_raw()),
KPoint::new((pos.x + width).to_raw(), offset.to_raw()), kurbo::Point::new((pos.x + width).to_raw(), offset.to_raw()),
); );
let mut x_advance = pos.x; let mut x = pos.x;
let mut intersections = vec![]; let mut intersections = vec![];
for glyph in text.glyphs.iter() { for glyph in text.glyphs.iter() {
let local_offset = glyph.x_offset.resolve(size) + x_advance; let dx = glyph.x_offset.resolve(text.size) + x;
let mut builder =
KurboPathBuilder::new(face.units_per_em, text.size, dx.to_raw());
let mut builder = KurboOutlineBuilder::new(
face.units_per_em,
size,
local_offset.to_raw(),
);
let bbox = face.ttf().outline_glyph(GlyphId(glyph.id), &mut builder); let bbox = face.ttf().outline_glyph(GlyphId(glyph.id), &mut builder);
let path = builder.finish();
x_advance += glyph.x_advance.resolve(size); x += glyph.x_advance.resolve(text.size);
let path = match bbox {
Some(bbox) => {
let y_min = -face.to_em(bbox.y_max).resolve(size);
let y_max = -face.to_em(bbox.y_min).resolve(size);
// The line does not intersect the glyph, continue // Only do the costly segments intersection test if the line
// with the next one. // intersects the bounding box.
if offset < y_min || offset > y_max { if bbox.map_or(false, |bbox| {
continue; let y_min = -face.to_em(bbox.y_max).resolve(text.size);
} let y_max = -face.to_em(bbox.y_min).resolve(text.size);
builder.finish() offset >= y_min && offset <= y_max
} }) {
None => continue, // Find all intersections of segments with the line.
};
// Collect all intersections of segments with the line and sort them.
intersections.extend( intersections.extend(
path.segments() path.segments()
.flat_map(|seg| seg.intersect_line(line)) .flat_map(|seg| seg.intersect_line(line))
.map(|is| Length::raw(line.eval(is.line_t).x)), .map(|is| Length::raw(line.eval(is.line_t).x)),
); );
} }
}
// When emitting the decorative line segments, we move from left to
// right. The intersections are not necessarily in this order, yet.
intersections.sort(); intersections.sort();
let mut gaps = vec![]; for gap in intersections.chunks_exact(2) {
let mut inside = None; let l = gap[0] - gap_padding;
let r = gap[1] + gap_padding;
// Alternate between outside and inside and collect the gaps
// into the gap vector.
for intersection in intersections {
match inside {
Some(start) => {
gaps.push((start, intersection));
inside = None;
}
None => inside = Some(intersection),
}
}
gaps
} else {
vec![]
};
let mut start = pos.x - extent;
let end = pos.x + (width + 2.0 * extent);
let min_width = 0.162 * size;
let mut push_segment = |from: Length, to: Length| {
let origin = Point::new(from, line_y);
let target = Point::new(to - from, Length::zero());
if target.x < min_width {
return;
}
let shape = Shape::stroked(Geometry::Line(target), stroke);
frame.push(origin, Element::Shape(shape));
};
if evade {
for gap in
gaps.into_iter().map(|(a, b)| (a - gap_padding, b + gap_padding))
{
if start >= end { if start >= end {
break; break;
} }
if start >= gap.0 { if start >= l {
start = gap.1; start = r;
continue; continue;
} }
push_segment(start, gap.0); push_segment(start, l);
start = gap.1; start = r;
}
} }
if start < end { if start < end {
push_segment(start, end); push_segment(start, end);
} }
} }
}
/// Reshape a range of the shaped text, reusing information from this /// Reshape a range of the shaped text, reusing information from this
@ -1069,15 +1036,15 @@ enum Side {
Right, Right,
} }
struct KurboOutlineBuilder { struct KurboPathBuilder {
path: BezPath, path: BezPath,
units_per_em: f64, units_per_em: f64,
font_size: Length, font_size: Length,
x_offset: f64, x_offset: f64,
} }
impl KurboOutlineBuilder { impl KurboPathBuilder {
pub fn new(units_per_em: f64, font_size: Length, x_offset: f64) -> Self { fn new(units_per_em: f64, font_size: Length, x_offset: f64) -> Self {
Self { Self {
path: BezPath::new(), path: BezPath::new(),
units_per_em, units_per_em,
@ -1086,12 +1053,12 @@ impl KurboOutlineBuilder {
} }
} }
pub fn finish(self) -> BezPath { fn finish(self) -> BezPath {
self.path self.path
} }
fn p(&self, x: f32, y: f32) -> KPoint { fn p(&self, x: f32, y: f32) -> kurbo::Point {
KPoint::new(self.s(x) + self.x_offset, -self.s(y)) kurbo::Point::new(self.s(x) + self.x_offset, -self.s(y))
} }
fn s(&self, v: f32) -> f64 { fn s(&self, v: f32) -> f64 {
@ -1099,7 +1066,7 @@ impl KurboOutlineBuilder {
} }
} }
impl OutlineBuilder for KurboOutlineBuilder { impl OutlineBuilder for KurboPathBuilder {
fn move_to(&mut self, x: f32, y: f32) { fn move_to(&mut self, x: f32, y: f32) {
self.path.move_to(self.p(x, y)); self.path.move_to(self.p(x, y));
} }