Files
OneCAD-Draft/tests/pdf_export_text_check.rs
T
Hakan SevenandClaude Opus 5.5 e4d04ef132 Cargo: codec e023bc3 (acadrust sync), kernel abca4e8, graph c63556f
The codec now holds wide EntityType variants boxed (MText, Helix,
Dimension, Hatch, Viewport, MultiLeader, Surface, Table, Ole2Frame,
Light, Extended). Constructors box their entity; nested patterns reach
a dimension through entities::dimension::as_dimension(_mut). Also
follows BlockAction::action_ids, LineTypeComplexData::source_flags and
the decimal tab stop with an explicit character.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-16 09:17:01 +03:00

109 lines
4.0 KiB
Rust

// 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 —
// 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.
use codec::entities::{Dimension, DimensionLinear, Text};
use codec::types::Vector3;
use codec::EntityType;
use OpenCADStudio::io::pdf_export::{
export_pdf, PdfPageInput, PdfPlotOptions, PlotContent, PlotWire,
};
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);
let text_h = scene.add_entity(EntityType::Text(t));
// 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(Box::new(Dimension::Linear(dim))));
let wires = scene.entity_wires();
// 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: codec::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");
assert!(
has_text(dim_h),
"DIMENSION entity emitted no measurement-value text glyphs"
);
// 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();
let p_text = dir.join("with_text.pdf");
let mut page = PdfPageInput {
content: PlotContent {
wires: std::sync::Arc::new(plot_wires),
..Default::default()
},
paper_w: 210.0,
paper_h: 297.0,
offset_x: 0.0,
offset_y: 0.0,
rotation_deg: 0,
scale: 1.0,
clip: None,
options: PdfPlotOptions::default(),
plot_style: None,
};
export_pdf(&page, &p_text).expect("export with text");
let with_text = std::fs::read(&p_text).expect("read pdf");
assert!(with_text.starts_with(b"%PDF"), "not a PDF");
let stripped: Vec<_> = page
.content
.wires
.iter()
.cloned()
.map(|mut w| {
w.wire.text_verts.clear();
w.wire.searchable_text.clear();
w
})
.collect();
let p_bare = dir.join("no_text.pdf");
page.content.wires = std::sync::Arc::new(stripped);
export_pdf(&page, &p_bare).expect("export without text");
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()
);
let _ = std::fs::remove_dir_all(&dir);
}