feat(hatch): rasterise PAT pattern lines for paper canvas + PDF
Both the on-screen paper canvas and the PDF / print pipeline only knew how to draw the boundary outline of pattern hatches (ANSI31, ANSI37, dotted brick, etc.) — the actual hatch lines were a GPU-only feature, so any paper-space view or PDF export showed a hollow polygon where there should be ruled fill. Add a CPU rasteriser as `HatchModel::pattern_segments()`: - builds an edge list from the boundary, splitting at NaN sentinels and closing each sub-path so even-odd inside tests handle islands / holes; - for every PAT family computes the world-space step vector (`(dx,dy)` rotated into the family's frame, scaled by `model.scale`) and the inclusive k-range bounded by the boundary AABB plus a 4096- line safety cap; - intersects each line with every boundary edge, sorts the hit parameters, walks them in pairs (even-odd) and emits one segment per in/out interval. Dash patterns are walked along each clipped span with phase preserved via `t mod period`, matching the GPU shader's absolute-t dash evaluation; - pattern origin (`family.x0 / y0`) is rotated by `angle_offset` and scaled the same way as `hatch_batched.wgsl`, so PAT patterns with non-zero origins (brick / offset herringbone) line up with the GPU render. `paper_canvas::draw_hatch` and `pdf_export::emit_hatch` now route pattern hatches through this rasteriser; solid / gradient hatches keep their existing fast paths. The previous outline-only behaviour was a visible regression on every layout containing a ruled fill. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
c2f3844cb6
commit
30f7ad0918
3 changed files with 285 additions and 24 deletions
|
|
@ -290,9 +290,11 @@ fn emit_hatch(ops: &mut Vec<Op>, hatch: &HatchModel, ox: f32, oy: f32) {
|
|||
let (paint_mode, fill_color) = match &hatch.pattern {
|
||||
HatchPattern::Solid => (PaintMode::Fill, [r, g, b]),
|
||||
HatchPattern::Pattern(_) => {
|
||||
// Stroke the outline only — pattern lines themselves are
|
||||
// procedural and not synthesised on the CPU.
|
||||
(PaintMode::Stroke, [r, g, b])
|
||||
// Pattern fills are emitted as raster line segments below; the
|
||||
// outline polygon path itself is skipped because pattern
|
||||
// hatches in real DXF do not draw their boundary as part of
|
||||
// the fill.
|
||||
(PaintMode::Clip, [r, g, b]) // sentinel — handled below
|
||||
}
|
||||
HatchPattern::Gradient { color2, .. } => {
|
||||
// PDF gradients are stored in resource dictionaries; for the
|
||||
|
|
@ -306,21 +308,44 @@ fn emit_hatch(ops: &mut Vec<Op>, hatch: &HatchModel, ox: f32, oy: f32) {
|
|||
}
|
||||
};
|
||||
|
||||
match paint_mode {
|
||||
PaintMode::Fill | PaintMode::FillStroke => {
|
||||
ops.push(Op::SetFillColor {
|
||||
col: Color::Rgb(Rgb {
|
||||
r: fill_color[0],
|
||||
g: fill_color[1],
|
||||
b: fill_color[2],
|
||||
icc_profile: None,
|
||||
}),
|
||||
// Pattern hatches: rasterise the family lines clipped to the boundary
|
||||
// and emit each as a stroked line. Skips the polygon outline entirely.
|
||||
if matches!(hatch.pattern, HatchPattern::Pattern(_)) {
|
||||
let segments = hatch.pattern_segments();
|
||||
if segments.is_empty() {
|
||||
return;
|
||||
}
|
||||
ops.push(Op::SetOutlineColor {
|
||||
col: Color::Rgb(Rgb {
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
icc_profile: None,
|
||||
}),
|
||||
});
|
||||
for [a, b_pt] in segments {
|
||||
ops.push(Op::DrawLine {
|
||||
line: Line {
|
||||
points: vec![
|
||||
LinePoint {
|
||||
p: Point::new(Mm(a[0] + ox), Mm(a[1] + oy)),
|
||||
bezier: false,
|
||||
},
|
||||
LinePoint {
|
||||
p: Point::new(Mm(b_pt[0] + ox), Mm(b_pt[1] + oy)),
|
||||
bezier: false,
|
||||
},
|
||||
],
|
||||
is_closed: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
return;
|
||||
}
|
||||
if matches!(paint_mode, PaintMode::Stroke | PaintMode::FillStroke) {
|
||||
ops.push(Op::SetOutlineColor {
|
||||
|
||||
// Solid / gradient: filled polygon path.
|
||||
if matches!(paint_mode, PaintMode::Fill | PaintMode::FillStroke) {
|
||||
ops.push(Op::SetFillColor {
|
||||
col: Color::Rgb(Rgb {
|
||||
r: fill_color[0],
|
||||
g: fill_color[1],
|
||||
|
|
|
|||
Loading…
Reference in a new issue