forked from 1b-agent/opencadstudio-upstream
DGNIMPORT: V8 text styles; 3D text ends at its byte count
A V8 file's text style table comes in as text styles (a style the drawing has keeps its own; Standard stays Standard), and each text or text node takes the style its text style linkage names. A 3D text's characters end at the byte count the element gives, so a byte after them is no longer read as a character. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
952cc0326d
commit
b686d5cbe8
4 files changed
+74
-12
No files matched your search
@@ -362,10 +362,20 @@ fn color_code(color: By<crate::scene::model::underlay_vector::DgnColor>) -> 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<EntityType> {
|
||||
fn mtext_of_text(geom: &DgnGeom, color: u16, style: &str) -> Option<EntityType> {
|
||||
let DgnGeom::Text {
|
||||
text,
|
||||
origin,
|
||||
@@ -477,7 +487,7 @@ fn mtext_of_text(geom: &DgnGeom, color: u16) -> Option<EntityType> {
|
||||
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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -279,6 +279,34 @@ fn f64_at(e: &[u8], at: usize) -> Option<f64> {
|
||||
}
|
||||
|
||||
/// 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<u32> {
|
||||
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<String> {
|
||||
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<DgnImport> {
|
||||
unit_names: (metres(4180), metres(4196)),
|
||||
v7: false,
|
||||
references,
|
||||
text_styles: text_style_table(&nm),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -615,6 +615,8 @@ pub enum DgnGeom {
|
||||
just: u8,
|
||||
/// Font number (V7); `None` when the file's font is not read.
|
||||
font: Option<u32>,
|
||||
/// Text style id (V8), an entry of [`DgnImport::text_styles`].
|
||||
style: Option<u32>,
|
||||
},
|
||||
/// 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<DgnReference>,
|
||||
/// 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
|
||||
|
||||
Reference in new issue
Block a user