diff --git a/Cargo.lock b/Cargo.lock index ae2e85cc1..38394ed88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,7 +74,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "acadrust" version = "0.4.0" -source = "git+https://github.com/OpenAEC-Foundation/acadifc?branch=main#2e63e7053f0f245004b41c4b576a0529748f3f41" +source = "git+https://github.com/OpenAEC-Foundation/acadifc?branch=main#379ce12e55a8b5f3c32b9678cc348f0b4dfb82c7" dependencies = [ "ahash 0.8.12", "anyhow", @@ -3180,7 +3180,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b577e2d69827c4740cba2b52efaad1c4cc7c73042860b199710b3575c68438d" dependencies = [ "bytecount", - "memchr 1.0.2", + "memchr 2.8.3", "nom 8.0.0", ] diff --git a/src/entities/leader.rs b/src/entities/leader.rs index 32da37746..27bb0f1a3 100644 --- a/src/entities/leader.rs +++ b/src/entities/leader.rs @@ -761,14 +761,24 @@ impl LeaderTess for Leader { .or_else(|| style.map(|s| s.dimtad)) .unwrap_or(0); if dimtad != 0 { - if let Some(acadrust::entities::EntityType::MText(mt)) = - document.get_entity(self.annotation_handle) - { - if mt.extents_width > 0.0 { - let to_far_edge = - sign * (mt.insertion_point.x - last.x) + mt.extents_width; - land_len = land_len.max(to_far_edge); + let mt = match document.get_entity(self.annotation_handle) { + Some(acadrust::entities::EntityType::MText(mt)) => { + Some((mt.extents_width, mt.insertion_point.x)) } + _ => None, + }; + // Underline width: the annotation MTEXT's laid-out extents when + // known, otherwise the leader's own stored annotation width + // (DXF code 41) — a DXF-loaded MTEXT often has no computed + // extents (extents_width == 0), which left the hook too short. + let width = mt + .map(|(w, _)| w) + .filter(|w| *w > 0.0) + .unwrap_or(self.text_width); + if width > 0.0 { + let ins_x = mt.map(|(_, x)| x).unwrap_or(last.x); + let to_far_edge = sign * (ins_x - last.x) + width; + land_len = land_len.max(to_far_edge); } } points.push([f64::NAN; 3]); diff --git a/src/entities/shape.rs b/src/entities/shape.rs index 7295da882..56abe38e6 100644 --- a/src/entities/shape.rs +++ b/src/entities/shape.rs @@ -63,32 +63,69 @@ fn shx_polylines( shape: &Shape, document: &acadrust::CadDocument, ) -> Option { - let style = shape - .style_handle - .and_then(|h| document.text_styles.iter().find(|s| s.handle == h)) - .or_else(|| { - let name = shape.style_name.trim(); - (!name.is_empty()) - .then(|| { - document - .text_styles - .iter() - .find(|s| s.name.eq_ignore_ascii_case(name)) - }) - .flatten() - })?; - let font = style.font_file.trim(); - if font.is_empty() { - return None; - } let base = document .source_path .as_deref() .map(std::path::Path::new) .and_then(|p| p.parent()); - let resolved = crate::io::resolve_image_file(font, base)?; - let num = u16::try_from(shape.shape_number).ok()?; - crate::scene::text::shx::shape_polylines(&resolved, num) + let name = shape.shape_name.trim(); + + // Resolve the glyph from one style's shape (.shx) file, preferring the + // shape NUMBER but falling back to the NAME — a DXF SHAPE carries only the + // name (code 2), so its `shape_number` is 0. + let try_style = |style: &acadrust::tables::TextStyle| { + let font = style.font_file.trim(); + if font.is_empty() { + return None; + } + let resolved = crate::io::resolve_image_file(font, base)?; + if let Ok(num) = u16::try_from(shape.shape_number) { + if num != 0 { + if let Some(p) = crate::scene::text::shx::shape_polylines(&resolved, num) { + return Some(p); + } + } + } + if !name.is_empty() { + return crate::scene::text::shx::shape_polylines_by_name(&resolved, name); + } + None + }; + + // 1. Direct style referenced by the SHAPE (handle, then name). + let direct = shape + .style_handle + .and_then(|h| document.text_styles.iter().find(|s| s.handle == h)) + .or_else(|| { + let sn = shape.style_name.trim(); + (!sn.is_empty()) + .then(|| { + document + .text_styles + .iter() + .find(|s| s.name.eq_ignore_ascii_case(sn)) + }) + .flatten() + }); + if let Some(style) = direct { + if let Some(p) = try_style(style) { + return Some(p); + } + } + + // 2. No usable style reference (the DXF case): search every shape-file + // style — one with a real font file and an empty table name — for a + // glyph named like this shape. + if !name.is_empty() { + for style in document.text_styles.iter() { + if style.name.is_empty() && !style.font_file.trim().is_empty() { + if let Some(p) = try_style(style) { + return Some(p); + } + } + } + } + None } impl TruckConvertible for Shape {