Merge fix/621 into batch 13

This commit is contained in:
bflatastic
2026-10-17 17:19:48 +07:00
4 changed files with 41 additions and 9 deletions
+1 -1
View File
@@ -116,7 +116,7 @@ pub(crate) fn rule(r: figcraft_render::FillRule) -> FillRule {
}
/// The renderer's stroke fill rule: even-odd ring paths for aligned strokes, else non-zero.
fn stroke_rule(n: &Node, closed: bool) -> FillRule {
pub(crate) fn stroke_rule(n: &Node, closed: bool) -> FillRule {
if n.stroke.sides.is_some() || (n.stroke.dashes.is_empty() && closed && n.stroke.align != StrokeAlign::Center) {
FillRule::EvenOdd
} else {
+5 -5
View File
@@ -5,7 +5,7 @@ use std::collections::BTreeSet;
use std::ops::Range;
use std::sync::Arc;
use figcraft_doc::{Node, NodeKind, StrokeAlign, StrokeProps};
use figcraft_doc::{Node, NodeKind};
use figcraft_geom::{Affine, BezPath};
use figcraft_text::fontdb::FontBytes;
use figcraft_text::{Face, Glyph, TextLayout};
@@ -16,7 +16,7 @@ use krilla::text::{Font, GlyphId, KrillaGlyph};
use figcraft_color::{GradientKind, Paint, PaintKind};
use crate::draw::{Exporter, above, affine_ok, blend, to_xf, vector_paint};
use crate::draw::{Exporter, above, affine_ok, blend, stroke_rule, to_xf, vector_paint};
use crate::raster::Raster;
/// Glyphs of one face and size on one line, written as one text run.
@@ -84,11 +84,11 @@ pub(crate) fn draw_text(ex: &mut Exporter, s: &mut Surface, n: &Node, xf: Affine
}
}
ex.place_inner(s, inner);
let props = StrokeProps { align: StrokeAlign::Center, ..n.stroke.clone() };
// Glyph outlines are closed: inside and outside strokes clip to them, as on the canvas (#615).
for outlines in &stroked {
let area = figcraft_pathops::stroke_center(outlines, props.weight, &props);
let area = figcraft_render::draw::stroke_area(n, outlines, true);
for p in &n.strokes {
ex.fill(s, &area, FillRule::NonZero, p, w, h, gxf);
ex.fill(s, &area, stroke_rule(n, true), p, w, h, gxf);
}
}
}
+3 -3
View File
@@ -621,11 +621,11 @@ fn draw_content(ctx: &Ctx, pix: &mut Pixmap, n: &Node, xf: Affine, depth: usize,
f(pix);
}
if n.has_visible_stroke() {
let props = figcraft_doc::StrokeProps { align: StrokeAlign::Center, ..n.stroke.clone() };
// Glyph outlines are closed: inside and outside strokes clip to them (#615).
for path in keys.iter().filter_map(|k| by_style.get(k)) {
let s = figcraft_pathops::stroke_center(path, props.weight, &props);
let s = stroke_area(n, path, true);
if let Some(ts) = ts_path(&s) {
fill_with(pix, &ts, &n.strokes, w, h, gxf, FillRule::Winding, ctx, None);
fill_with(pix, &ts, &n.strokes, w, h, gxf, stroke_rule(n, true), ctx, None);
}
}
}
@@ -630,3 +630,35 @@ fn changing_the_colour_profile_converts_or_keeps_values() {
let seen = draw(&kept);
assert!(seen[0] > before[0] && seen[2] < before[2], "kept values look more saturated: {seen:?} vs {before:?}");
}
// ---- strokes on text ----
#[test]
fn text_strokes_follow_their_alignment() {
// #615: as on other shapes (and in SVG export), an inside stroke stays in the glyphs, a centre
// one reaches half its weight past them and an outside one all of it.
figcraft_text::scan_system_fonts();
let left_edge = |align: StrokeAlign| -> Option<u32> {
let (mut d, p) = page();
let mut td = figcraft_doc::TextData::new("I");
td.style.font_size = 120.0;
let t = d.create(p, NodeKind::Text(td), "T", 60.0, 30.0, 1.0, 1.0).unwrap();
let n = d.get_mut(t).unwrap();
n.fills.clear();
n.strokes = vec![Paint::solid(hex("#000000"))];
n.stroke.weight = 12.0;
n.stroke.align = align;
let (w, h) = figcraft_text::auto_size(n)?;
(n.width, n.height) = (w, h);
let pix = render(&d);
(0..200).find(|x| (0..200).any(|y| px(&pix, *x, y)[0] < 128))
};
let (Some(inside), Some(centre), Some(outside)) =
(left_edge(StrokeAlign::Inside), left_edge(StrokeAlign::Center), left_edge(StrokeAlign::Outside))
else {
eprintln!("no font to draw text with; skipping");
return;
};
assert!((5..=7).contains(&(inside - centre)), "centre reaches half the weight past inside: {inside} → {centre}");
assert!((5..=7).contains(&(centre - outside)), "outside reaches the rest: {centre} → {outside}");
}