Files
vectorcraft/crates/pdf/src/tests_textstroke.rs
bflatasticandClaude Opus 5.5 ffc60d5ff8 M4.59: pattern strokes and freeform gradients keep their look in PDF
Pattern strokes are written as the pattern's tiles clipped to the stroke's
outline (dashes, caps, profile, arrowheads and alignment included) instead
of mid-grey; freeform gradients on fills, strokes and type as an image of
their colour field at the document raster effects resolution, clipped to
what they paint, instead of their average colour. Both read back within
2% of the canvas at 150 ppi with no warning. Text strokes are covered for
cap, miter limit, dash array and alignment; knockout groups keep their
soft-masked form with a warning. The freeform box and grid sampling are
shared with the renderer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 11:52:35 +07:00

40 lines
2.0 KiB
Rust

//! Strokes on type keep every stroke option in PDF: the characters' cap, join, miter limit and
//! dash array, and the alignment of the object's own strokes.
use vectorcraft_color::{Color, Paint};
use vectorcraft_doc::{AppearanceItem, CharStyle, Dash, Document, LineCap, LineJoin, Node, NodeKind, StrokeAlign, StrokeLayer, TextObject};
use vectorcraft_geom::Point;
use crate::*;
#[test]
fn text_strokes_keep_cap_join_miter_dashes_and_alignment() {
let style = CharStyle {
size: 48.0,
stroke: Paint::solid(Color::BLACK),
stroke_width: 1.5,
stroke_cap: LineCap::Round,
stroke_join: LineJoin::Miter,
stroke_miter_limit: 7.0,
stroke_dash: Some(Dash { pattern: vec![4.0, 2.0], ..Default::default() }),
..Default::default()
};
let mut d = Document::new(240.0, 100.0);
let mut n = Node::new(d.alloc_id(), NodeKind::Text(Box::new(TextObject::point(Point::new(10.0, 70.0), "Wave", style))));
let mut own = StrokeLayer::new(Paint::solid(Color::rgb8(0, 0, 255)), 3.0);
own.align = StrokeAlign::Inside;
n.appearance.items.push(AppearanceItem::Stroke(own));
let l = d.layers[0].id;
d.insert(Some(l), 0, n).unwrap();
let pdf = export(&d, &PdfOptions::uncompressed()).unwrap();
assert!(String::from_utf8_lossy(&pdf).contains("7 M 1 J[4 2]0 d"), "the characters' miter limit, cap and dash array");
let back = import(&pdf).unwrap();
let mut strokes = vec![];
back.walk(|n| strokes.extend(n.appearance.items.iter().filter_map(|i| if let AppearanceItem::Stroke(s) = i { Some(s.clone()) } else { None })));
let chars = strokes.iter().find(|s| s.dash.is_some()).expect("the characters' stroke");
assert_eq!((chars.cap, chars.join), (LineCap::Round, LineJoin::Miter));
assert!((chars.miter_limit - 7.0).abs() < 1e-3, "miter limit {}", chars.miter_limit);
// An inside stroke is drawn twice as wide, clipped to the glyphs.
assert!(strokes.iter().any(|s| s.dash.is_none() && (s.width - 6.0).abs() < 1e-3), "{strokes:?}");
}