fix(export): project viewport text into paper space
Review follow-ups to #390, which fixed PDF/print text for model space but left the layout-plot path — the one issue #385 was filed from — broken. viewport_content_wires rewrote points/snap_pts/key_vertices/aabb and then cloned text_verts through untouched, so paper-space viewport text stayed at model (UTM) coordinates: a dimension at model (25000, 12000) plotted its lines onto the sheet and its glyphs kilometres off the page. A text-only wire fared worse — TEXT/MTEXT carry no stroke points since text went SDF-only, so it was dropped outright by the empty-clip and AABB rejections. Project the glyphs through the same proj_abs the snap points use (via the existing map_text_verts helper), cull per glyph on the quad centroid, count them towards the paper AABB, and dim them like the wire colour. Also in emit_text, which diverged from the wire pass it mirrors: - divide the pen by `scale`, or a Fit/windowed plot prints text as near-invisible hairlines while its own lines stay correct; - apply the CTB plot style, or a monochrome.ctb plot renders the lines black and leaves the text on its screen colour; - reset the dash pattern, which is persistent PDF state the wire pass leaves set — a HIDDEN-linetype last wire printed glyph outlines dashed. export_table's doc claimed it honoured TEXTFILL and widened the bold pen itself; it did neither. Gate fill_tris on textfill() so hollow-on-screen text exports hollow, carry the bold flag and widen the pen by the 1.7x the bake uses, and resolve Face once per family instead of once per glyph (the atlas mutex is held for that whole walk). uv_key identifies a glyph by its tile's uv_min, but grow_height rescales every entry's V and reset rewinds the packer, leaving already-built quads addressing the wrong tile — garbage on screen and a silent miss in the export table, i.e. #385 again under #347's grow-the-atlas conditions. Bump an atlas generation on both and fold it into the tessellation memo guard so the text re-lays-out instead. Quads built earlier in the same pass that grows are still stale for that pass; the guard heals them on the next one. Tests: the integration test's "some wire has text_verts" assert passed on pre-fix code too (the exporter ignored them; the scene always carried them), and its dimension half was satisfied by the TEXT alone — assert per entity handle instead, and stop colliding on fixed names in the shared temp root. The unit test's bbox came from the same quads the mapping reads, so it held by construction; pin the run's own world box instead. That still cannot catch a mirrored corner assignment — verified by mirroring it — so say so rather than claim coverage the assert doesn't have. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5bdfe483da
commit
5018963d3d
6 changed files with 257 additions and 72 deletions
|
|
@ -179,6 +179,33 @@ impl Scene {
|
|||
})
|
||||
.collect();
|
||||
|
||||
// SDF glyph quads ride `text_verts` in absolute WCS, so they need
|
||||
// the same projection the points get — a bare clone leaves the
|
||||
// text at model (UTM) coordinates while its own dimension lines
|
||||
// move to the sheet, putting the glyphs kilometres off the page
|
||||
// (issue #385 for layout plots). Cull per glyph on the quad's
|
||||
// centroid: the text analogue of `clip_polyline_to_rect`, since a
|
||||
// glyph can't be split at the viewport border.
|
||||
let mut projected_text = if wire.text_verts.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
model::wire_model::map_text_verts(&wire.text_verts, |x, y, z| {
|
||||
let p = proj_abs(x, y, z);
|
||||
(p[0] as f64, p[1] as f64, p[2] as f64)
|
||||
})
|
||||
.chunks_exact(6)
|
||||
.filter(|quad| {
|
||||
let (sx, sy) = quad.iter().fold((0.0f32, 0.0f32), |(ax, ay), v| {
|
||||
(ax + v.pos[0] + v.pos_low[0], ay + v.pos[1] + v.pos_low[1])
|
||||
});
|
||||
let (cx, cy) = (sx / 6.0, sy / 6.0);
|
||||
cx.is_finite() && cy.is_finite() && in_vp(cx, cy)
|
||||
})
|
||||
.flatten()
|
||||
.copied()
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
// Fast AABB pre-reject.
|
||||
let any_near = projected_pts.iter().any(|&[x, y, _]| {
|
||||
x.is_finite()
|
||||
|
|
@ -202,13 +229,16 @@ impl Scene {
|
|||
);
|
||||
let aabb_hits =
|
||||
max_x >= vp_x0 && min_x <= vp_x1 && max_y >= vp_y0 && min_y <= vp_y1;
|
||||
if !any_near && !aabb_hits {
|
||||
// A text-only wire (TEXT / MTEXT carry no stroke `points` since
|
||||
// text went SDF-only) has nothing to pre-reject on, so gate both
|
||||
// rejections on the glyphs too or it never reaches the sheet.
|
||||
if !any_near && !aabb_hits && projected_text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let clipped =
|
||||
clip_polyline_to_rect(&projected_pts, vp_x0, vp_y0, vp_x1, vp_y1, pcz);
|
||||
if clipped.is_empty() {
|
||||
if clipped.is_empty() && projected_text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -226,6 +256,17 @@ impl Scene {
|
|||
pmxx = pmxx.max(x);
|
||||
pmxy = pmxy.max(y);
|
||||
}
|
||||
// Glyphs count towards the paper AABB as well — a text-only wire
|
||||
// would otherwise stay UNBOUNDED and never pick.
|
||||
for v in &projected_text {
|
||||
let (x, y) = (v.pos[0] + v.pos_low[0], v.pos[1] + v.pos_low[1]);
|
||||
if x.is_finite() && y.is_finite() {
|
||||
pmnx = pmnx.min(x);
|
||||
pmny = pmny.min(y);
|
||||
pmxx = pmxx.max(x);
|
||||
pmxy = pmxy.max(y);
|
||||
}
|
||||
}
|
||||
|
||||
// Project snap points + key vertices into the same paper frame,
|
||||
// keeping only those inside the viewport rect, so endpoint /
|
||||
|
|
@ -252,8 +293,16 @@ impl Scene {
|
|||
|
||||
let adapted = view::render::adapt_to_bg(wire.color, self.paper_bg_color);
|
||||
let [r, g, b, a] = adapted;
|
||||
// Glyphs carry their own per-vertex colour, so dim them through
|
||||
// the same adapt + 0.80/0.85 the wire colour below gets, or the
|
||||
// text reads brighter than its own dimension lines.
|
||||
for v in &mut projected_text {
|
||||
let [tr, tg, tb, ta] = view::render::adapt_to_bg(v.color, self.paper_bg_color);
|
||||
v.color = [tr * 0.80, tg * 0.80, tb * 0.80, ta * 0.85];
|
||||
}
|
||||
let mut out = wire.clone();
|
||||
out.points = clipped;
|
||||
out.text_verts = projected_text;
|
||||
// Paper coordinates are small sheet units — no relative-to-eye
|
||||
// residual is needed, and keeping the model wire's points_low
|
||||
// here would add a model-scale offset to the paper points.
|
||||
|
|
|
|||
Loading…
Reference in a new issue