diff --git a/src/modules/insert/dgn_import.rs b/src/modules/insert/dgn_import.rs index 2135d82dd..e437d5c4a 100644 --- a/src/modules/insert/dgn_import.rs +++ b/src/modules/insert/dgn_import.rs @@ -362,10 +362,20 @@ fn color_code(color: By) -> u16 } } -/// The text style of a line of text (V7: one per font number, "dgnNNN"), -/// added to `styles` with its font file the first time. -fn text_style(geom: &DgnGeom, styles: &mut Vec<(String, String)>) -> String { +/// The text style of a line of text (V7: one per font number, "dgnNNN"; +/// V8: the style the text names), added to `styles` with its font file +/// the first time. +fn text_style(geom: &DgnGeom, import: &DgnImport, styles: &mut Vec<(String, String)>) -> String { match geom { + DgnGeom::Text { style: Some(id), .. } => match import.text_styles.iter().find(|(i, _)| i == id) { + Some((_, name)) if !name.eq_ignore_ascii_case("Standard") => { + if !styles.iter().any(|s| s.0 == *name) { + styles.push((name.clone(), "txt.shx".to_string())); + } + name.clone() + } + _ => "Standard".to_string(), + }, DgnGeom::Text { font: Some(font), .. } => { @@ -435,7 +445,7 @@ fn text_line(g: &DgnGeom) -> Option<(&str, P3, f64, f64, f64)> { /// attached at its bottom left a third of the height below the baseline /// (exact line spacing 0.8), any other at its justification point; its /// contents carry its colour (and width factor). -fn mtext_of_text(geom: &DgnGeom, color: u16) -> Option { +fn mtext_of_text(geom: &DgnGeom, color: u16, style: &str) -> Option { let DgnGeom::Text { text, origin, @@ -477,7 +487,7 @@ fn mtext_of_text(geom: &DgnGeom, color: u16) -> Option { m.rotation = rotation.rem_euclid(std::f64::consts::TAU); m.rectangle_width = 0.0; m.drawing_direction = DrawingDirection::ByStyle; - m.style = "Standard".to_string(); + m.style = style.to_string(); Some(EntityType::MText(Box::new(m))) } @@ -768,12 +778,15 @@ impl<'a> Converter<'a> { } // V7 text is TEXT, V8 text an MTEXT. DgnGeom::Text { font: Some(_), .. } => { - let style = text_style(geom, &mut self.out.styles); + let style = text_style(geom, self.import, &mut self.out.styles); text_entity(geom, &style).into_iter().collect() } - DgnGeom::Text { .. } => mtext_of_text(geom, color_code(element.color)) - .into_iter() - .collect(), + DgnGeom::Text { .. } => { + let style = text_style(geom, self.import, &mut self.out.styles); + mtext_of_text(geom, color_code(element.color), &style) + .into_iter() + .collect() + } // Exploded, each line converts as a text element of its own. DgnGeom::TextNode { lines, .. } if self.explode_text_nodes => lines .iter() @@ -788,7 +801,7 @@ impl<'a> Converter<'a> { let Some(first) = lines.first() else { return Vec::new(); }; - let style = text_style(first, &mut self.out.styles); + let style = text_style(first, self.import, &mut self.out.styles); let color = (!self.import.v7).then(|| color_code(element.color)); mtext_of_node(lines, *origin, *just, *line_spacing, style, color) .into_iter() @@ -905,6 +918,12 @@ pub fn convert(import: &DgnImport, sub_units: bool, explode_text_nodes: bool) -> c.layer(&element, false); } } + // Every text style of the file's table comes in, used or not. + for (_, name) in &import.text_styles { + if !name.eq_ignore_ascii_case("Standard") && !c.out.styles.iter().any(|s| s.0 == *name) { + c.out.styles.push((name.clone(), "txt.shx".to_string())); + } + } c.in_definition = true; for (name, elements) in &cells { let entities = c.elements(elements, true, CellCopy::default(), 0); diff --git a/src/scene/model/underlay_vector/dgn7.rs b/src/scene/model/underlay_vector/dgn7.rs index a72343bf9..1a626eec7 100644 --- a/src/scene/model/underlay_vector/dgn7.rs +++ b/src/scene/model/underlay_vector/dgn7.rs @@ -533,6 +533,7 @@ fn read_run(run: &[&[u8]], colors: &[[u8; 3]], d3: bool) -> Raw { rotation: text.rotation, just: e[37], font: Some(e[36] as u32), + style: None, })); raw.texts.push(text); } diff --git a/src/scene/model/underlay_vector/dgn8.rs b/src/scene/model/underlay_vector/dgn8.rs index 730fe07ba..29ecd4de8 100644 --- a/src/scene/model/underlay_vector/dgn8.rs +++ b/src/scene/model/underlay_vector/dgn8.rs @@ -279,6 +279,34 @@ fn f64_at(e: &[u8], at: usize) -> Option { } /// A string stored after the "ff fe 01 00" marker, when the element has one. +/// The text style a text element uses: the id in its text style linkage +/// (the four bytes after `04 20 e6 04 00 00`). +fn text_style_id(e: &[u8]) -> Option { + let at = e.windows(6).position(|w| w == [0x04, 0x20, 0xe6, 0x04, 0x00, 0x00])? + 6; + u32_at(e, at) +} + +/// The text style table (the table entries after a header of table 3): +/// each style's id and name. +fn text_style_table(nm: &[&[u8]]) -> Vec<(u32, String)> { + let mut out = Vec::new(); + let mut table = 0; + for e in nm { + match kind(e) { + 96 => table = u32_at(e, 12).unwrap_or(0), + 95 if table == 3 => { + if let (Some(id), Some(name)) = (u32_at(e, 32), marked_string(e, 32)) { + if !out.iter().any(|(i, _)| *i == id) { + out.push((id, name)); + } + } + } + _ => {} + } + } + out +} + fn marked_string(e: &[u8], from: usize) -> Option { let i = e .get(from..)? @@ -861,7 +889,8 @@ fn read_run(run: &[&[u8]], styles: &Styles, xf: &Xf) -> Raw { } 17 => { // 3D: width 112, height 120, quaternion 144, origin 176, the - // characters after the string marker. 2D: angle 144, origin + // characters after the string marker, as many as the byte + // count at 110 holds. 2D: angle 144, origin // 152, a byte count (with its 4-byte marker) at 110 and the // characters from 174. let placed = if d3 { @@ -871,7 +900,12 @@ fn read_run(run: &[&[u8]], styles: &Styles, xf: &Xf) -> Raw { f64_at(e, 120)?, quat_axes(e, 144)?, p3(176)?, - marked_string(e, 200)?, + // The byte count at 110 (with the 4-byte marker) + // ends the characters. + { + let count = u16::from_le_bytes(e.get(110..112)?.try_into().ok()?) as usize; + marked_string(e, 200)?.chars().take(count.saturating_sub(4)).collect() + }, )) })() } else { @@ -921,6 +955,7 @@ fn read_run(run: &[&[u8]], styles: &Styles, xf: &Xf) -> Raw { rotation: pu[1].atan2(pu[0]), just, font: None, + style: text_style_id(e), })); raw.texts.push(Text { text, @@ -1524,6 +1559,7 @@ pub fn import(bytes: &[u8], name: &str) -> Option { unit_names: (metres(4180), metres(4196)), v7: false, references, + text_styles: text_style_table(&nm), }) } diff --git a/src/scene/model/underlay_vector/model.rs b/src/scene/model/underlay_vector/model.rs index c7b526179..ea98c7d81 100644 --- a/src/scene/model/underlay_vector/model.rs +++ b/src/scene/model/underlay_vector/model.rs @@ -615,6 +615,8 @@ pub enum DgnGeom { just: u8, /// Font number (V7); `None` when the file's font is not read. font: Option, + /// Text style id (V8), an entry of [`DgnImport::text_styles`]. + style: Option, }, /// A text node: its lines, its origin (the justification point, when /// read), its justification and (V7) the space between its lines. @@ -698,6 +700,7 @@ impl DgnGeom { rotation, just, font, + style, } => DgnGeom::Text { text: text.clone(), origin: p(*origin), @@ -707,6 +710,7 @@ impl DgnGeom { rotation: *rotation, just: *just, font: *font, + style: *style, }, DgnGeom::Cell { name, @@ -786,6 +790,8 @@ pub struct DgnImport { pub v7: bool, /// The model's reference attachments. pub references: Vec, + /// The text style table (V8): each style's id and name. + pub text_styles: Vec<(u32, String)>, } /// A reference attachment of a DGN model: the file and model it shows and