cad-editor/tests/pdf_export_text_check.rs

116 lines
3.8 KiB
Rust
Raw Normal View History

fix(export): render SDF text as vectors in PDF/print export (#385) Since text became SDF-only (the legacy stroke-text path was removed), all text — standalone TEXT/MTEXT and dimension text — rendered on-screen only as GPU glyph quads carried on `WireModel::text_verts`. The CPU PDF/print exporter draws only wire stroke `points` and hatch fills, so it silently dropped every glyph: dimensions and text were missing from exported PDFs and prints (present in v0.7.6). Reconstruct vector text in the exporter instead of resurrecting the stroke path, so the SDF render path and its caching are untouched: - sdf_atlas: `GlyphAtlas::export_table()` snapshots each baked glyph's vector geometry (outline strokes + fill triangles, glyph space) keyed by its tile `uv_min`, re-resolved through `Face` so it honours the current TEXTFILL. - pdf_export: a new `emit_text` pass walks each wire's `text_verts` (one 6-vertex quad per glyph), recovers the glyph geometry by `uv_min`, and affine-maps it into the quad — `DrawLine` polylines for stroke (LFF/SHX) fonts, filled triangles for TrueType, plus solid decoration bars. Runs under the same rotation/scale/clip CTM as the wires, so it covers model space, paper space and windowed plots, and every SDF-text kind (text, dimension, multileader, table, tolerance). Text embedded in complex linetypes is SDF-only with no stroke fallback and stays out of scope. Tests: a unit test asserts a stroke glyph emits vector ops within the glyph's world bbox; an integration test drives a real TEXT + linear DIMENSION through scene.entity_wires() -> export_pdf and checks text reaches the file. Verified bug-first: with emit_text disabled the end-to-end test fails with the reported symptom (text adds no content). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:49:21 -07:00
// Regression for #385: text and dimension text must reach the PDF / print
// export. From v0.8.2 text renders on-screen only as GPU SDF glyph quads
// (`WireModel::text_verts`); the CPU PDF exporter used to draw only wire
// stroke `points`, so all text — standalone TEXT/MTEXT and dimension text —
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>
2026-07-16 10:06:15 +03:00
// vanished from exported PDFs (present in v0.7.6).
//
// The op-level placement checks live in `pdf_export`'s own unit tests, which can
// see `emit_text`'s ops. What only an integration test can cover is that the
// real entity -> scene.entity_wires() -> export_pdf path carries text at all,
// per entity kind — so that is what this asserts.
fix(export): render SDF text as vectors in PDF/print export (#385) Since text became SDF-only (the legacy stroke-text path was removed), all text — standalone TEXT/MTEXT and dimension text — rendered on-screen only as GPU glyph quads carried on `WireModel::text_verts`. The CPU PDF/print exporter draws only wire stroke `points` and hatch fills, so it silently dropped every glyph: dimensions and text were missing from exported PDFs and prints (present in v0.7.6). Reconstruct vector text in the exporter instead of resurrecting the stroke path, so the SDF render path and its caching are untouched: - sdf_atlas: `GlyphAtlas::export_table()` snapshots each baked glyph's vector geometry (outline strokes + fill triangles, glyph space) keyed by its tile `uv_min`, re-resolved through `Face` so it honours the current TEXTFILL. - pdf_export: a new `emit_text` pass walks each wire's `text_verts` (one 6-vertex quad per glyph), recovers the glyph geometry by `uv_min`, and affine-maps it into the quad — `DrawLine` polylines for stroke (LFF/SHX) fonts, filled triangles for TrueType, plus solid decoration bars. Runs under the same rotation/scale/clip CTM as the wires, so it covers model space, paper space and windowed plots, and every SDF-text kind (text, dimension, multileader, table, tolerance). Text embedded in complex linetypes is SDF-only with no stroke fallback and stays out of scope. Tests: a unit test asserts a stroke glyph emits vector ops within the glyph's world bbox; an integration test drives a real TEXT + linear DIMENSION through scene.entity_wires() -> export_pdf and checks text reaches the file. Verified bug-first: with emit_text disabled the end-to-end test fails with the reported symptom (text adds no content). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:49:21 -07:00
use acadrust::entities::{Dimension, DimensionLinear, Text};
use acadrust::types::Vector3;
use acadrust::EntityType;
use OpenCADStudio::io::pdf_export::{export_pdf, PdfPlotOptions, PlotWire};
fix(export): render SDF text as vectors in PDF/print export (#385) Since text became SDF-only (the legacy stroke-text path was removed), all text — standalone TEXT/MTEXT and dimension text — rendered on-screen only as GPU glyph quads carried on `WireModel::text_verts`. The CPU PDF/print exporter draws only wire stroke `points` and hatch fills, so it silently dropped every glyph: dimensions and text were missing from exported PDFs and prints (present in v0.7.6). Reconstruct vector text in the exporter instead of resurrecting the stroke path, so the SDF render path and its caching are untouched: - sdf_atlas: `GlyphAtlas::export_table()` snapshots each baked glyph's vector geometry (outline strokes + fill triangles, glyph space) keyed by its tile `uv_min`, re-resolved through `Face` so it honours the current TEXTFILL. - pdf_export: a new `emit_text` pass walks each wire's `text_verts` (one 6-vertex quad per glyph), recovers the glyph geometry by `uv_min`, and affine-maps it into the quad — `DrawLine` polylines for stroke (LFF/SHX) fonts, filled triangles for TrueType, plus solid decoration bars. Runs under the same rotation/scale/clip CTM as the wires, so it covers model space, paper space and windowed plots, and every SDF-text kind (text, dimension, multileader, table, tolerance). Text embedded in complex linetypes is SDF-only with no stroke fallback and stays out of scope. Tests: a unit test asserts a stroke glyph emits vector ops within the glyph's world bbox; an integration test drives a real TEXT + linear DIMENSION through scene.entity_wires() -> export_pdf and checks text reaches the file. Verified bug-first: with emit_text disabled the end-to-end test fails with the reported symptom (text adds no content). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:49:21 -07:00
use OpenCADStudio::scene::Scene;
#[test]
fn text_and_dim_reach_pdf_export() {
let mut scene = Scene::new();
let t = Text::with_value("HELLO", Vector3::new(10.0, 10.0, 0.0)).with_height(5.0);
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>
2026-07-16 10:06:15 +03:00
let text_h = scene.add_entity(EntityType::Text(t));
fix(export): render SDF text as vectors in PDF/print export (#385) Since text became SDF-only (the legacy stroke-text path was removed), all text — standalone TEXT/MTEXT and dimension text — rendered on-screen only as GPU glyph quads carried on `WireModel::text_verts`. The CPU PDF/print exporter draws only wire stroke `points` and hatch fills, so it silently dropped every glyph: dimensions and text were missing from exported PDFs and prints (present in v0.7.6). Reconstruct vector text in the exporter instead of resurrecting the stroke path, so the SDF render path and its caching are untouched: - sdf_atlas: `GlyphAtlas::export_table()` snapshots each baked glyph's vector geometry (outline strokes + fill triangles, glyph space) keyed by its tile `uv_min`, re-resolved through `Face` so it honours the current TEXTFILL. - pdf_export: a new `emit_text` pass walks each wire's `text_verts` (one 6-vertex quad per glyph), recovers the glyph geometry by `uv_min`, and affine-maps it into the quad — `DrawLine` polylines for stroke (LFF/SHX) fonts, filled triangles for TrueType, plus solid decoration bars. Runs under the same rotation/scale/clip CTM as the wires, so it covers model space, paper space and windowed plots, and every SDF-text kind (text, dimension, multileader, table, tolerance). Text embedded in complex linetypes is SDF-only with no stroke fallback and stays out of scope. Tests: a unit test asserts a stroke glyph emits vector ops within the glyph's world bbox; an integration test drives a real TEXT + linear DIMENSION through scene.entity_wires() -> export_pdf and checks text reaches the file. Verified bug-first: with emit_text disabled the end-to-end test fails with the reported symptom (text adds no content). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:49:21 -07:00
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>
2026-07-16 10:06:15 +03:00
// A linear dimension — its measurement value is synthesized as SDF text too,
// and dimension text is what #385 was actually filed about.
let dim = DimensionLinear::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(40.0, 0.0, 0.0));
let dim_h = scene.add_entity(EntityType::Dimension(Dimension::Linear(dim)));
fix(export): render SDF text as vectors in PDF/print export (#385) Since text became SDF-only (the legacy stroke-text path was removed), all text — standalone TEXT/MTEXT and dimension text — rendered on-screen only as GPU glyph quads carried on `WireModel::text_verts`. The CPU PDF/print exporter draws only wire stroke `points` and hatch fills, so it silently dropped every glyph: dimensions and text were missing from exported PDFs and prints (present in v0.7.6). Reconstruct vector text in the exporter instead of resurrecting the stroke path, so the SDF render path and its caching are untouched: - sdf_atlas: `GlyphAtlas::export_table()` snapshots each baked glyph's vector geometry (outline strokes + fill triangles, glyph space) keyed by its tile `uv_min`, re-resolved through `Face` so it honours the current TEXTFILL. - pdf_export: a new `emit_text` pass walks each wire's `text_verts` (one 6-vertex quad per glyph), recovers the glyph geometry by `uv_min`, and affine-maps it into the quad — `DrawLine` polylines for stroke (LFF/SHX) fonts, filled triangles for TrueType, plus solid decoration bars. Runs under the same rotation/scale/clip CTM as the wires, so it covers model space, paper space and windowed plots, and every SDF-text kind (text, dimension, multileader, table, tolerance). Text embedded in complex linetypes is SDF-only with no stroke fallback and stays out of scope. Tests: a unit test asserts a stroke glyph emits vector ops within the glyph's world bbox; an integration test drives a real TEXT + linear DIMENSION through scene.entity_wires() -> export_pdf and checks text reaches the file. Verified bug-first: with emit_text disabled the end-to-end test fails with the reported symptom (text adds no content). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:49:21 -07:00
let wires = scene.entity_wires();
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>
2026-07-16 10:06:15 +03:00
// Assert per entity, not "some wire somewhere has text": both asserts below
// would otherwise be satisfied by the TEXT alone, leaving a dimension-text
// regression — the actual reported bug — green.
// Tessellation names each wire after its entity handle (see the
// `w.name.parse::<u64>()` lookup in the render pipeline).
let has_text = |h: acadrust::types::Handle| {
let want = h.value().to_string();
wires
.iter()
.any(|w| w.name == want && !w.text_verts.is_empty())
};
assert!(has_text(text_h), "TEXT entity emitted no text glyphs");
fix(export): render SDF text as vectors in PDF/print export (#385) Since text became SDF-only (the legacy stroke-text path was removed), all text — standalone TEXT/MTEXT and dimension text — rendered on-screen only as GPU glyph quads carried on `WireModel::text_verts`. The CPU PDF/print exporter draws only wire stroke `points` and hatch fills, so it silently dropped every glyph: dimensions and text were missing from exported PDFs and prints (present in v0.7.6). Reconstruct vector text in the exporter instead of resurrecting the stroke path, so the SDF render path and its caching are untouched: - sdf_atlas: `GlyphAtlas::export_table()` snapshots each baked glyph's vector geometry (outline strokes + fill triangles, glyph space) keyed by its tile `uv_min`, re-resolved through `Face` so it honours the current TEXTFILL. - pdf_export: a new `emit_text` pass walks each wire's `text_verts` (one 6-vertex quad per glyph), recovers the glyph geometry by `uv_min`, and affine-maps it into the quad — `DrawLine` polylines for stroke (LFF/SHX) fonts, filled triangles for TrueType, plus solid decoration bars. Runs under the same rotation/scale/clip CTM as the wires, so it covers model space, paper space and windowed plots, and every SDF-text kind (text, dimension, multileader, table, tolerance). Text embedded in complex linetypes is SDF-only with no stroke fallback and stays out of scope. Tests: a unit test asserts a stroke glyph emits vector ops within the glyph's world bbox; an integration test drives a real TEXT + linear DIMENSION through scene.entity_wires() -> export_pdf and checks text reaches the file. Verified bug-first: with emit_text disabled the end-to-end test fails with the reported symptom (text adds no content). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:49:21 -07:00
assert!(
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>
2026-07-16 10:06:15 +03:00
has_text(dim_h),
"DIMENSION entity emitted no measurement-value text glyphs"
fix(export): render SDF text as vectors in PDF/print export (#385) Since text became SDF-only (the legacy stroke-text path was removed), all text — standalone TEXT/MTEXT and dimension text — rendered on-screen only as GPU glyph quads carried on `WireModel::text_verts`. The CPU PDF/print exporter draws only wire stroke `points` and hatch fills, so it silently dropped every glyph: dimensions and text were missing from exported PDFs and prints (present in v0.7.6). Reconstruct vector text in the exporter instead of resurrecting the stroke path, so the SDF render path and its caching are untouched: - sdf_atlas: `GlyphAtlas::export_table()` snapshots each baked glyph's vector geometry (outline strokes + fill triangles, glyph space) keyed by its tile `uv_min`, re-resolved through `Face` so it honours the current TEXTFILL. - pdf_export: a new `emit_text` pass walks each wire's `text_verts` (one 6-vertex quad per glyph), recovers the glyph geometry by `uv_min`, and affine-maps it into the quad — `DrawLine` polylines for stroke (LFF/SHX) fonts, filled triangles for TrueType, plus solid decoration bars. Runs under the same rotation/scale/clip CTM as the wires, so it covers model space, paper space and windowed plots, and every SDF-text kind (text, dimension, multileader, table, tolerance). Text embedded in complex linetypes is SDF-only with no stroke fallback and stays out of scope. Tests: a unit test asserts a stroke glyph emits vector ops within the glyph's world bbox; an integration test drives a real TEXT + linear DIMENSION through scene.entity_wires() -> export_pdf and checks text reaches the file. Verified bug-first: with emit_text disabled the end-to-end test fails with the reported symptom (text adds no content). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:49:21 -07:00
);
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>
2026-07-16 10:06:15 +03:00
// End-to-end: the same wire set with and without text. A private temp dir
// keeps concurrent runs (and other users on a shared build host) from
// colliding on a fixed name in the shared temp root.
let dir = std::env::temp_dir().join(format!("ocs385-{}", std::process::id()));
std::fs::create_dir_all(&dir).expect("temp dir");
let plot_wires: Vec<PlotWire> = wires
.iter()
.map(|w| PlotWire {
wire: w.clone(),
draw_depth: 0.0,
})
.collect();
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>
2026-07-16 10:06:15 +03:00
let p_text = dir.join("with_text.pdf");
export_pdf(
&plot_wires,
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>
2026-07-16 10:06:15 +03:00
&[],
&[],
210.0,
297.0,
0.0,
0.0,
0,
1.0,
None,
&p_text,
None,
2026-08-15 23:11:26 +03:00
PdfPlotOptions::default(),
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>
2026-07-16 10:06:15 +03:00
)
.expect("export with text");
fix(export): render SDF text as vectors in PDF/print export (#385) Since text became SDF-only (the legacy stroke-text path was removed), all text — standalone TEXT/MTEXT and dimension text — rendered on-screen only as GPU glyph quads carried on `WireModel::text_verts`. The CPU PDF/print exporter draws only wire stroke `points` and hatch fills, so it silently dropped every glyph: dimensions and text were missing from exported PDFs and prints (present in v0.7.6). Reconstruct vector text in the exporter instead of resurrecting the stroke path, so the SDF render path and its caching are untouched: - sdf_atlas: `GlyphAtlas::export_table()` snapshots each baked glyph's vector geometry (outline strokes + fill triangles, glyph space) keyed by its tile `uv_min`, re-resolved through `Face` so it honours the current TEXTFILL. - pdf_export: a new `emit_text` pass walks each wire's `text_verts` (one 6-vertex quad per glyph), recovers the glyph geometry by `uv_min`, and affine-maps it into the quad — `DrawLine` polylines for stroke (LFF/SHX) fonts, filled triangles for TrueType, plus solid decoration bars. Runs under the same rotation/scale/clip CTM as the wires, so it covers model space, paper space and windowed plots, and every SDF-text kind (text, dimension, multileader, table, tolerance). Text embedded in complex linetypes is SDF-only with no stroke fallback and stays out of scope. Tests: a unit test asserts a stroke glyph emits vector ops within the glyph's world bbox; an integration test drives a real TEXT + linear DIMENSION through scene.entity_wires() -> export_pdf and checks text reaches the file. Verified bug-first: with emit_text disabled the end-to-end test fails with the reported symptom (text adds no content). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:49:21 -07:00
let with_text = std::fs::read(&p_text).expect("read pdf");
assert!(with_text.starts_with(b"%PDF"), "not a PDF");
let stripped: Vec<_> = plot_wires
.into_iter()
fix(export): render SDF text as vectors in PDF/print export (#385) Since text became SDF-only (the legacy stroke-text path was removed), all text — standalone TEXT/MTEXT and dimension text — rendered on-screen only as GPU glyph quads carried on `WireModel::text_verts`. The CPU PDF/print exporter draws only wire stroke `points` and hatch fills, so it silently dropped every glyph: dimensions and text were missing from exported PDFs and prints (present in v0.7.6). Reconstruct vector text in the exporter instead of resurrecting the stroke path, so the SDF render path and its caching are untouched: - sdf_atlas: `GlyphAtlas::export_table()` snapshots each baked glyph's vector geometry (outline strokes + fill triangles, glyph space) keyed by its tile `uv_min`, re-resolved through `Face` so it honours the current TEXTFILL. - pdf_export: a new `emit_text` pass walks each wire's `text_verts` (one 6-vertex quad per glyph), recovers the glyph geometry by `uv_min`, and affine-maps it into the quad — `DrawLine` polylines for stroke (LFF/SHX) fonts, filled triangles for TrueType, plus solid decoration bars. Runs under the same rotation/scale/clip CTM as the wires, so it covers model space, paper space and windowed plots, and every SDF-text kind (text, dimension, multileader, table, tolerance). Text embedded in complex linetypes is SDF-only with no stroke fallback and stays out of scope. Tests: a unit test asserts a stroke glyph emits vector ops within the glyph's world bbox; an integration test drives a real TEXT + linear DIMENSION through scene.entity_wires() -> export_pdf and checks text reaches the file. Verified bug-first: with emit_text disabled the end-to-end test fails with the reported symptom (text adds no content). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:49:21 -07:00
.map(|mut w| {
w.wire.text_verts.clear();
fix(export): render SDF text as vectors in PDF/print export (#385) Since text became SDF-only (the legacy stroke-text path was removed), all text — standalone TEXT/MTEXT and dimension text — rendered on-screen only as GPU glyph quads carried on `WireModel::text_verts`. The CPU PDF/print exporter draws only wire stroke `points` and hatch fills, so it silently dropped every glyph: dimensions and text were missing from exported PDFs and prints (present in v0.7.6). Reconstruct vector text in the exporter instead of resurrecting the stroke path, so the SDF render path and its caching are untouched: - sdf_atlas: `GlyphAtlas::export_table()` snapshots each baked glyph's vector geometry (outline strokes + fill triangles, glyph space) keyed by its tile `uv_min`, re-resolved through `Face` so it honours the current TEXTFILL. - pdf_export: a new `emit_text` pass walks each wire's `text_verts` (one 6-vertex quad per glyph), recovers the glyph geometry by `uv_min`, and affine-maps it into the quad — `DrawLine` polylines for stroke (LFF/SHX) fonts, filled triangles for TrueType, plus solid decoration bars. Runs under the same rotation/scale/clip CTM as the wires, so it covers model space, paper space and windowed plots, and every SDF-text kind (text, dimension, multileader, table, tolerance). Text embedded in complex linetypes is SDF-only with no stroke fallback and stays out of scope. Tests: a unit test asserts a stroke glyph emits vector ops within the glyph's world bbox; an integration test drives a real TEXT + linear DIMENSION through scene.entity_wires() -> export_pdf and checks text reaches the file. Verified bug-first: with emit_text disabled the end-to-end test fails with the reported symptom (text adds no content). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:49:21 -07:00
w
})
.collect();
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>
2026-07-16 10:06:15 +03:00
let p_bare = dir.join("no_text.pdf");
export_pdf(
&stripped,
&[],
&[],
210.0,
297.0,
0.0,
0.0,
0,
1.0,
None,
&p_bare,
None,
2026-08-15 23:11:26 +03:00
PdfPlotOptions::default(),
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>
2026-07-16 10:06:15 +03:00
)
.expect("export without text");
fix(export): render SDF text as vectors in PDF/print export (#385) Since text became SDF-only (the legacy stroke-text path was removed), all text — standalone TEXT/MTEXT and dimension text — rendered on-screen only as GPU glyph quads carried on `WireModel::text_verts`. The CPU PDF/print exporter draws only wire stroke `points` and hatch fills, so it silently dropped every glyph: dimensions and text were missing from exported PDFs and prints (present in v0.7.6). Reconstruct vector text in the exporter instead of resurrecting the stroke path, so the SDF render path and its caching are untouched: - sdf_atlas: `GlyphAtlas::export_table()` snapshots each baked glyph's vector geometry (outline strokes + fill triangles, glyph space) keyed by its tile `uv_min`, re-resolved through `Face` so it honours the current TEXTFILL. - pdf_export: a new `emit_text` pass walks each wire's `text_verts` (one 6-vertex quad per glyph), recovers the glyph geometry by `uv_min`, and affine-maps it into the quad — `DrawLine` polylines for stroke (LFF/SHX) fonts, filled triangles for TrueType, plus solid decoration bars. Runs under the same rotation/scale/clip CTM as the wires, so it covers model space, paper space and windowed plots, and every SDF-text kind (text, dimension, multileader, table, tolerance). Text embedded in complex linetypes is SDF-only with no stroke fallback and stays out of scope. Tests: a unit test asserts a stroke glyph emits vector ops within the glyph's world bbox; an integration test drives a real TEXT + linear DIMENSION through scene.entity_wires() -> export_pdf and checks text reaches the file. Verified bug-first: with emit_text disabled the end-to-end test fails with the reported symptom (text adds no content). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:49:21 -07:00
let no_text = std::fs::read(&p_bare).expect("read pdf");
assert!(
with_text.len() > no_text.len(),
"text added no content to the PDF: {} !> {}",
with_text.len(),
no_text.len()
);
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>
2026-07-16 10:06:15 +03:00
let _ = std::fs::remove_dir_all(&dir);
fix(export): render SDF text as vectors in PDF/print export (#385) Since text became SDF-only (the legacy stroke-text path was removed), all text — standalone TEXT/MTEXT and dimension text — rendered on-screen only as GPU glyph quads carried on `WireModel::text_verts`. The CPU PDF/print exporter draws only wire stroke `points` and hatch fills, so it silently dropped every glyph: dimensions and text were missing from exported PDFs and prints (present in v0.7.6). Reconstruct vector text in the exporter instead of resurrecting the stroke path, so the SDF render path and its caching are untouched: - sdf_atlas: `GlyphAtlas::export_table()` snapshots each baked glyph's vector geometry (outline strokes + fill triangles, glyph space) keyed by its tile `uv_min`, re-resolved through `Face` so it honours the current TEXTFILL. - pdf_export: a new `emit_text` pass walks each wire's `text_verts` (one 6-vertex quad per glyph), recovers the glyph geometry by `uv_min`, and affine-maps it into the quad — `DrawLine` polylines for stroke (LFF/SHX) fonts, filled triangles for TrueType, plus solid decoration bars. Runs under the same rotation/scale/clip CTM as the wires, so it covers model space, paper space and windowed plots, and every SDF-text kind (text, dimension, multileader, table, tolerance). Text embedded in complex linetypes is SDF-only with no stroke fallback and stays out of scope. Tests: a unit test asserts a stroke glyph emits vector ops within the glyph's world bbox; an integration test drives a real TEXT + linear DIMENSION through scene.entity_wires() -> export_pdf and checks text reaches the file. Verified bug-first: with emit_text disabled the end-to-end test fails with the reported symptom (text adds no content). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:49:21 -07:00
}