use codec::objects::ObjectType; use codec::tables::{DimStyle, TextStyle}; use codec::{CadDocument, EntityType, Handle}; #[derive(Debug, Clone)] pub struct TextCreationDefaults { pub style_name: String, pub height: f64, pub width_factor: f64, pub oblique_angle: f64, } #[derive(Debug, Clone)] pub struct DimensionCreationDefaults { pub style_name: String, pub style_handle: Handle, pub text_style_name: String, pub text_height: f64, pub gap: f64, pub arrow_size: f64, pub jog_angle: f64, pub scale: f64, pub annotative: bool, } fn named_text_style(doc: &CadDocument, name: &str) -> Option { doc.text_styles .iter() .find(|style| style.name.eq_ignore_ascii_case(name)) .or_else(|| { doc.text_styles .iter() .find(|style| style.name.eq_ignore_ascii_case("Standard")) }) .cloned() } /// Adds the reference's ISO-25 dimension style (the current style of its /// metric template) when the drawing has none of that name, and hands back its /// handle: the header records the current dimension style by handle as well as /// by name, and a DWG carries only the handle. pub fn ensure_iso_dim_style(doc: &mut CadDocument) -> Handle { if let Some(style) = doc .dim_styles .iter() .find(|style| style.name.eq_ignore_ascii_case("ISO-25")) { return style.handle; } let mut style = DimStyle::new("ISO-25"); style.handle = doc.allocate_handle(); style.dimtxt = 2.5; style.dimasz = 2.5; style.dimcen = 2.5; style.dimexe = 1.25; style.dimexo = 0.625; style.dimgap = 0.625; style.dimdli = 3.75; style.dimtad = 1; style.dimtih = false; style.dimtoh = false; style.dimtofl = true; style.dimtix = false; style.dimsoxd = false; style.dimdsep = i16::from(b','); style.dimlunit = 2; style.dimdec = 2; style.dimtdec = 2; style.dimaunit = 0; style.dimadec = 0; style.dimazin = 0; style.dimzin = 8; style.dimtzin = 8; style.dimatfit = 3; style.dimtmove = 0; style.dimscale = 1.0; style.dimlfac = 1.0; style.dimtfac = 1.0; style.dimaltf = 0.03937007874016; style.dimaltd = 3; style.dimalttd = 3; style.dimaltu = 2; style.dimfxl = 1.0; style.dimjogang = std::f64::consts::FRAC_PI_2; style.dimmzf = 100.0; style.dimaltmzf = 100.0; let handle = style.handle; let _ = doc.dim_styles.add(style); handle } fn named_dim_style(doc: &CadDocument, name: &str) -> Option { doc.dim_styles .iter() .find(|style| style.name.eq_ignore_ascii_case(name)) .or_else(|| { doc.dim_styles .iter() .find(|style| style.name.eq_ignore_ascii_case("Standard")) }) .cloned() } pub fn current_text_defaults(doc: &CadDocument) -> TextCreationDefaults { let requested = doc.header.current_text_style_name.trim(); let style = named_text_style(doc, requested); let variable_height = if doc.header.text_height > 1.0e-9 { doc.header.text_height } else { style .as_ref() .map(|style| style.last_height) .filter(|height| *height > 1.0e-9) .unwrap_or(2.5) }; TextCreationDefaults { style_name: style .as_ref() .map(|style| style.name.clone()) .filter(|name| !name.trim().is_empty()) .unwrap_or_else(|| "Standard".to_string()), height: style .as_ref() .map(|style| style.height) .filter(|height| *height > 1.0e-9) .unwrap_or(variable_height), width_factor: style.as_ref().map_or(1.0, |style| style.width_factor), oblique_angle: style.as_ref().map_or(0.0, |style| style.oblique_angle), } } pub fn current_dimension_defaults(doc: &CadDocument) -> DimensionCreationDefaults { let requested = doc.header.current_dimstyle_name.trim(); let style = named_dim_style(doc, requested); DimensionCreationDefaults { style_name: style .as_ref() .map(|style| style.name.clone()) .filter(|name| !name.trim().is_empty()) .unwrap_or_else(|| "Standard".to_string()), style_handle: style.as_ref().map_or(Handle::NULL, |style| style.handle), text_style_name: style .as_ref() .map(|style| style.dimtxsty.clone()) .filter(|name| !name.trim().is_empty()) .unwrap_or_else(|| current_text_defaults(doc).style_name), text_height: style.as_ref().map_or(2.5, |style| style.dimtxt), gap: style.as_ref().map_or(0.625, |style| style.dimgap), arrow_size: style.as_ref().map_or(2.5, |style| style.dimasz), jog_angle: style .as_ref() .map_or(std::f64::consts::FRAC_PI_4, |style| style.dimjogang), scale: style.as_ref().map_or(1.0, |style| { if style.annotative { 1.0 } else if style.dimscale > 1.0e-9 { style.dimscale } else { 1.0 } }), annotative: style.as_ref().is_some_and(|style| style.annotative), } } fn entity_uses_default_style(name: &str) -> bool { name.trim().is_empty() || name.eq_ignore_ascii_case("Standard") } fn apply_text_defaults(doc: &CadDocument, entity: &mut EntityType) { let active = current_text_defaults(doc); let style_name = match entity { EntityType::Text(text) => &mut text.style, EntityType::MText(text) => &mut text.style, EntityType::AttributeEntity(attribute) => &mut attribute.text_style, EntityType::AttributeDefinition(attribute) => &mut attribute.text_style, _ => return, }; if style_name.trim().is_empty() { style_name.clone_from(&active.style_name); } let resolved = named_text_style(doc, style_name) .unwrap_or_else(|| TextStyle::new(style_name.clone())); match entity { EntityType::Text(text) => { if resolved.height > 1.0e-9 { text.height = resolved.height; } if (text.width_factor - 1.0).abs() <= 1.0e-9 { text.width_factor = resolved.width_factor; } if text.oblique_angle.abs() <= 1.0e-9 { text.oblique_angle = resolved.oblique_angle; } } EntityType::MText(text) => { if resolved.height > 1.0e-9 { text.height = resolved.height; } } EntityType::AttributeEntity(attribute) => { if resolved.height > 1.0e-9 { attribute.height = resolved.height; } if (attribute.width_factor - 1.0).abs() <= 1.0e-9 { attribute.width_factor = resolved.width_factor; } if attribute.oblique_angle.abs() <= 1.0e-9 { attribute.oblique_angle = resolved.oblique_angle; } if attribute.text_generation_flags == 0 { attribute.text_generation_flags = (if resolved.flags.backward { 2 } else { 0 }) | (if resolved.flags.upside_down { 4 } else { 0 }); } } EntityType::AttributeDefinition(attribute) => { if resolved.height > 1.0e-9 { attribute.height = resolved.height; } if (attribute.width_factor - 1.0).abs() <= 1.0e-9 { attribute.width_factor = resolved.width_factor; } if attribute.oblique_angle.abs() <= 1.0e-9 { attribute.oblique_angle = resolved.oblique_angle; } if attribute.text_generation_flags == 0 { attribute.text_generation_flags = (if resolved.flags.backward { 2 } else { 0 }) | (if resolved.flags.upside_down { 4 } else { 0 }); } } _ => {} } } fn apply_dimension_defaults(doc: &CadDocument, entity: &mut EntityType) { let active = current_dimension_defaults(doc); match entity { EntityType::Dimension(dimension) => { if entity_uses_default_style(&dimension.base().style_name) { dimension.base_mut().style_name.clone_from(&active.style_name); } } EntityType::Leader(leader) => { if entity_uses_default_style(&leader.dimension_style) { leader.dimension_style.clone_from(&active.style_name); leader.text_height = active.text_height; leader.dimension_gap = active.gap; leader.arrow_size = active.arrow_size; } } EntityType::Tolerance(tolerance) => { if entity_uses_default_style(&tolerance.dimension_style_name) { tolerance.dimension_style_name.clone_from(&active.style_name); tolerance.dimension_style_handle = (!active.style_handle.is_null()) .then_some(active.style_handle); tolerance.text_height = active.text_height; tolerance.dimension_gap = active.gap; } } _ => {} } } fn apply_object_defaults(doc: &CadDocument, entity: &mut EntityType) { match entity { EntityType::MultiLeader(leader) if leader.style_handle.is_none() => { let name = doc.header.current_mleader_style_name.trim(); if let Some(style) = doc.objects.iter().find_map(|(handle, object)| match object { ObjectType::MultiLeaderStyle(style) if style.name.eq_ignore_ascii_case(name) => { let mut style = style.clone(); style.handle = *handle; Some(style) } _ => None, }) { crate::scene::annotative::apply_mleader_style(leader, &style); } } EntityType::Table(table) if table.table_style_handle.is_none() => { let name = doc.header.current_table_style_name.trim(); table.table_style_handle = doc.objects.iter().find_map(|(handle, object)| match object { ObjectType::TableStyle(style) if style.name.eq_ignore_ascii_case(name) => { Some(*handle) } _ => None, }); } EntityType::MLine(mline) if mline.style_handle.is_none() => { let name = doc.header.multiline_style.trim(); if let Some((handle, style_name)) = doc.objects.iter().find_map(|(handle, object)| match object { ObjectType::MLineStyle(style) if style.name.eq_ignore_ascii_case(name) => { Some((*handle, style.name.clone())) } _ => None, }) { mline.style_handle = Some(handle); mline.style_name = style_name; } } _ => {} } } pub fn apply_current_creation_styles(doc: &CadDocument, entity: &mut EntityType) { entity.common_mut().transparency = doc.current_entity_transparency(); apply_text_defaults(doc, entity); apply_dimension_defaults(doc, entity); apply_object_defaults(doc, entity); } pub(crate) fn parse_current_transparency(value: &str) -> Option { use codec::types::Transparency; match value.trim().to_ascii_uppercase().as_str() { "BYLAYER" | "-1" => Some(Transparency::ByLayer), "BYBLOCK" | "-2" => Some(Transparency::ByBlock), value => value.parse::().ok().filter(|value| *value <= 90) .map(|value| Transparency::from_percent(value as f64 / 100.0)), } } pub(crate) fn current_transparency_label(value: codec::types::Transparency) -> String { use codec::types::Transparency; match value { Transparency::ByLayer => "ByLayer".into(), Transparency::ByBlock => "ByBlock".into(), Transparency::Explicit(_) => format!("{:.0}", value.as_percent() * 100.0), } } #[cfg(test)] mod transparency_tests { use super::*; use codec::types::Transparency; #[test] fn parses_supported_current_transparency_values() { assert_eq!(parse_current_transparency("ByLayer"), Some(Transparency::ByLayer)); assert_eq!(parse_current_transparency("-1"), Some(Transparency::ByLayer)); assert_eq!(parse_current_transparency("ByBlock"), Some(Transparency::ByBlock)); assert_eq!(parse_current_transparency("-2"), Some(Transparency::ByBlock)); assert_eq!(parse_current_transparency("25"), Some(Transparency::Explicit(64))); assert!(parse_current_transparency("91").is_none()); assert!(parse_current_transparency("25.5").is_none()); assert!(parse_current_transparency("invalid").is_none()); } #[test] fn labels_transparency_for_command_and_properties_inputs() { assert_eq!(current_transparency_label(Transparency::ByLayer), "ByLayer"); assert_eq!(current_transparency_label(Transparency::ByBlock), "ByBlock"); assert_eq!(current_transparency_label(Transparency::Explicit(64)), "25"); } }