use super::{two_dec, FontInfo, ScaleEntity, TextEntity}; use dxf::entities::{self, AttributeDefinition}; use hex_color::HexColor; use simple_xml_builder::XMLElement; use unicode_segmentation::UnicodeSegmentation; use uuid::Uuid; use dxf::enums::AttachmentPoint; use dxf::enums::{HorizontalTextJustification, VerticalTextJustification}; /*use parley::{ Alignment, FontContext, FontWeight, InlineBox, Layout, LayoutContext, PositionedLayoutItem, StyleProperty, };*/ use super::{HAlignment, VAlignment}; #[derive(Debug)] pub struct DynamicText { text: String, info_name: Option, pub x: f64, pub y: f64, z: f64, rotation: f64, uuid: Uuid, h_alignment: HAlignment, font: FontInfo, text_from: String, v_alignment: VAlignment, frame: bool, text_width: i32, keep_visual_rotation: bool, color: HexColor, reference_rectangle_width: f64, pub align_x: f64, pub align_y: f64, } impl From<&DynamicText> for XMLElement { fn from(txt: &DynamicText) -> Self { let mut dtxt_xml = XMLElement::new("dynamic_text"); // taken from QET_ElementScaler: "ElmtDynText::AsSVGstring" // // Position und Rotationspunkt berechnen: // posx = x + (size/8.0)+4.05 - 0.5; // posy = y + (7.0/5.0*size + 26.0/5.0) - 0.5; // rotx = (-1) * (((size/8.0)+4.05) - 0.5); // roty = (-1) * ((7.0/5.0*size + 26.0/5.0) - 0.5); // // reversed and slightly modified after looking at the result in element-editor: // let pt_size: f64 = txt.font.point_size; // // we need the horizontal alignment and the text-width to move to right x-position: // txt.reference_rectangle_width, // should be text-width (Group code 41) // txt.attachment_point, // Group code 71 // // 1 = Top left; 2 = Top center; 3 = Top right // // 4 = Middle left; 5 = Middle center; 6 = Middle right // // 7 = Bottom left; 8 = Bottom center; 9 = Bottom right // // // it's just annoying if the value for "reference_rectangle_width" in the dxf is “0.0”... // // o.k. ... as long as we do not know the real width: // "guess" the width by number of characters and font-size: // let graphene_count = txt.text.graphemes(true).count(); let txt_width = if txt.reference_rectangle_width > 2.0 { txt.reference_rectangle_width } else { // 不同的字母,宽度与高度的比例并不相同,0.75只是一个平均值 (graphene_count as f64) * pt_size * 0.75 }; println!("文本:{}", txt.text); println!("对齐方式:{}, {}", txt.h_alignment, txt.v_alignment); println!("文本宽度: {}", txt_width); println!("文本高度:{}", pt_size); println!("初始左下角位置: x={}, y={}", txt.x, txt.y); println!("初始对齐位置: x={}, y={}", txt.align_x, txt.align_y); println!("旋转角度: {}", txt.rotation); // 计算基础位置(不考虑旋转) // txt.x和txt.y现在是左下角位置 let left_x = txt.x; // align_x 是对齐点的x坐标,后面加的数字是考虑到QET中boundingRect的没有完全贴合文本内容 let mut align_x = match txt.h_alignment { HAlignment::Left => txt.align_x - 0.5, HAlignment::Center => txt.align_x - 1.0, HAlignment::Right => txt.align_x - 2.0 }; // 根据垂直对齐方式计算顶部的y坐标 let top_y = match txt.v_alignment { VAlignment::Top => txt.y + pt_size / 2.0, VAlignment::Center => txt.y - pt_size / 2.0, VAlignment::Bottom => txt.y - pt_size + pt_size / 2.0, }; // align_y 是对齐点的y坐标,后面加的数字是考虑到QET中boundingRect的没有完全贴合文本内容 let mut align_y = match txt.v_alignment { VAlignment::Top => txt.align_y - 0.5, VAlignment::Center => txt.align_y + 0.5, VAlignment::Bottom => txt.align_y + 1.0 }; if(txt.h_alignment == HAlignment::Left && txt.v_alignment==VAlignment::Bottom) { align_x = txt.x-0.5; align_y = txt.y+1.0; } println!("左上角位置: x={}, y={}", left_x, top_y); let base_x = left_x; let base_y = top_y; // 如果有旋转角度,应用旋转变换 let (x_pos, y_pos) = if txt.rotation != 0.0 { let rotation_rad = txt.rotation.to_radians(); let cos_r = rotation_rad.cos(); let sin_r = rotation_rad.sin(); // 以对齐点为中心进行旋转变换 let dx = base_x - txt.x; let dy = base_y - txt.y; let rotated_dx = dx * cos_r - dy * sin_r; let rotated_dy = dx * sin_r + dy * cos_r; (txt.x + rotated_dx, txt.y + rotated_dy) } else { (base_x, base_y) }; println!("最终位置: x={}, y={}", x_pos, y_pos); dtxt_xml.add_attribute("x", two_dec(x_pos)); dtxt_xml.add_attribute("y", two_dec(y_pos)); dtxt_xml.add_attribute("z", two_dec(txt.z)); dtxt_xml.add_attribute("rotation", two_dec(txt.rotation)); dtxt_xml.add_attribute("uuid", format!("{{{}}}", txt.uuid)); dtxt_xml.add_attribute("font", &txt.font); dtxt_xml.add_attribute("Halignment", &txt.h_alignment); dtxt_xml.add_attribute("Valignment", &txt.v_alignment); dtxt_xml.add_attribute("text_from", &txt.text_from); dtxt_xml.add_attribute("frame", txt.frame); dtxt_xml.add_attribute("text_width", txt.text_width); dtxt_xml.add_attribute("color", txt.color.display_rgb()); dtxt_xml.add_attribute("align_x", two_dec(align_x)); dtxt_xml.add_attribute("align_y", two_dec(align_y)); //If I ever add support for other text_from types, element and composite text //I'll need to add more smarts here, as there may be some other children components //for now since it only supports user_text I'm just statically adding the single child //component needed //match txt.text_from let mut text_xml = XMLElement::new("text"); text_xml.add_text(&txt.text); dtxt_xml.add_child(text_xml); // 添加颜色 let mut color_xml = XMLElement::new("color"); color_xml.add_text(&txt.color.display_rgb()); dtxt_xml.add_child(color_xml); if let Some(i_name) = &txt.info_name { dtxt_xml.add_attribute("info_name", i_name); } if txt.keep_visual_rotation { dtxt_xml.add_attribute("keep_visual_rotation", txt.keep_visual_rotation); } dtxt_xml } } impl ScaleEntity for DynamicText { fn scale(&mut self, fact_x: f64, fact_y: f64) { self.x *= fact_x; self.y *= fact_y; self.align_x *= fact_x; self.align_y *= fact_y; //self.font.pixel_size *= fact; self.font.point_size *= fact_x; } fn left_bound(&self) -> f64 { self.x } fn right_bound(&self) -> f64 { //todo!() 1.0 } fn top_bound(&self) -> f64 { self.y } fn bot_bound(&self) -> f64 { //todo!() 1.0 } } pub struct DTextBuilder<'a> { text: TextEntity<'a>, color: Option, } impl<'a> DTextBuilder<'a> { pub fn from_text(text: &'a entities::Text) -> Self { Self { text: TextEntity::Text(text), color: None, } } pub fn from_mtext(text: &'a entities::MText) -> Self { Self { text: TextEntity::MText(text), color: None, } } pub fn from_attrib(attrib: &'a AttributeDefinition) -> Self { Self { text: TextEntity::Attrib(attrib), color: None, } } pub fn color(self, color: HexColor) -> Self { Self { color: Some(color), ..self } } pub fn build(self) -> DynamicText { let ( x, y, z, rotation, style_name, text_height, value, h_alignment, v_alignment, reference_rectangle_width, align_x, align_y, ) = match self.text { TextEntity::Text(txt) => { // let (x, y) = if txt.horizontal_text_justification == HorizontalTextJustification::Left && (txt.vertical_text_justification == VerticalTextJustification::Bottom || txt.vertical_text_justification == VerticalTextJustification::Baseline) { // (txt.location.x, -txt.location.y) // } else { // (txt.second_alignment_point.x, -txt.second_alignment_point.y) // }; ( txt.location.x, -txt.location.y, txt.location.z, txt.rotation, &txt.text_style_name, txt.text_height, txt.value.clone(), HAlignment::from(txt.horizontal_text_justification), VAlignment::from_text_entity(txt.vertical_text_justification, false), 0.0, // as Placeholder: no "reference_rectangle_width" with Text!!! txt.second_alignment_point.x, -txt.second_alignment_point.y )}, TextEntity::MText(mtxt) => { // 计算实际的旋转角度,优先使用x_axis_direction向量 let actual_rotation = calculate_mtext_rotation(mtxt.rotation_angle, &mtxt.x_axis_direction); ( mtxt.insertion_point.x, -mtxt.insertion_point.y, mtxt.insertion_point.z, actual_rotation, &mtxt.text_style_name, //I'm not sure what the proper value is here for Mtext //becuase I haven't actually finished supporting it. //I'll put initial text height for now. But i'm not certain //exactly what this correlates to. There is also vertical_height, //which I would guess is the total vertical height for all the lines //it's possible I would need to take the vertical height and divide //by the number of lines to get the value I need....I'm not sure yet mtxt.initial_text_height, //There are 2 text fields on MTEXT, .text a String and .extended_text a Vec //Most of the example files I have at the moment are single line MTEXT. //I edited one of them in QCad, and added a few lines. The value came through in the text field //with extended_text being empty, and the newlines were deliniated by '\\P'...I might need to look //the spec a bit to determine what it says for MTEXT, but for now, I'll just assume this is correct //So looking at the spec, yes '\P' is the MTEXT newline essentially. There is a bunch of MTEXT //inline codes that can be found at https://ezdxf.readthedocs.io/en/stable/dxfentities/mtext.html //The extended text is code point 3 in the dxf spec which just says: "Additional text (always in 250-character chunks) (optional)" //and Code point 1 the normal text value says: "Text string. If the text string is less than 250 characters, all characters appear //in group 1. If the text string is greater than 250 characters, the string is divided into 250-character chunks, which appear in //one or more group 3 codes. If group 3 codes are used, the last group is a group 1 and has fewer than 250 characters" { let mut val = mtxt.extended_text.join(""); val.push_str(&mtxt.text); let processed_val = val.replace("\\P", "\n"); super::extract_text_from_pcl(&processed_val) }, HAlignment::from(mtxt.attachment_point), VAlignment::from(mtxt.attachment_point), mtxt.reference_rectangle_width, mtxt.insertion_point.x, -mtxt.insertion_point.y, )}, TextEntity::Attrib(attrib) => { // 根据DXF文本对齐规则,当水平对齐不是Left时,使用second_alignment_point // let (x, y) = if attrib.horizontal_text_justification == HorizontalTextJustification::Left && (attrib.vertical_text_justification == VerticalTextJustification::Bottom || attrib.vertical_text_justification == VerticalTextJustification::Baseline) { // println!("{}",111); // (attrib.location.x, -attrib.location.y) // } else { // println!("{}",222); // (attrib.second_alignment_point.x, -attrib.second_alignment_point.y) // }; ( attrib.location.x, -attrib.location.y, attrib.location.z, attrib.rotation, &attrib.text_style_name, attrib.text_height, attrib.text_tag.clone(), HAlignment::from(attrib.horizontal_text_justification), VAlignment::from_text_entity(attrib.vertical_text_justification, true), 0.0, // as Placeholder: not need to check if Attrib has something similar attrib.second_alignment_point.x, -attrib.second_alignment_point.y ) }, }; // Create a FontContext (font database) and LayoutContext (scratch space). // These are both intended to be constructed rarely (perhaps even once per app): /*let mut font_cx = FontContext::new(); let mut layout_cx = LayoutContext::new(); // Create a `RangedBuilder` or a `TreeBuilder`, which are used to construct a `Layout`. const DISPLAY_SCALE : f32 = 1.0; let mut builder = layout_cx.ranged_builder(&mut font_cx, &value, DISPLAY_SCALE); // Set default styles that apply to the entire layout builder.push_default(StyleProperty::LineHeight(1.3)); builder.push_default(StyleProperty::FontSize((text_height * self.txt_sc_factor.unwrap()).round() as f32)); // Build the builder into a Layout let mut layout: Layout<()> = builder.build(&value); // Run line-breaking and alignment on the Layout const MAX_WIDTH : Option = Some(1000.0); layout.break_all_lines(MAX_WIDTH); layout.align(MAX_WIDTH, Alignment::Start); let calc_width = layout.width(); let calc_height = layout.height(); dbg!(&value); dbg!(calc_width); dbg!(calc_height);*/ /*dbg!(&value); dbg!(&y); dbg!(&self.text);*/ DynamicText { //x: x - (calc_width as f64/2.0), x, y, z, rotation: if rotation.abs().round() as i64 % 360 != 0 { rotation - 180.0 } else { 0.0 }, uuid: Uuid::new_v4(), font: if style_name == "STANDARD" { FontInfo { point_size: text_height, ..Default::default() } } else { //clearly right now this is exactly the same as the main body of the if block //I'm jus putting this in for now, to compile while I get the font handling //working correctly FontInfo { point_size: text_height, ..Default::default() } }, reference_rectangle_width, //liest aus der dxf-Datei!!! h_alignment, v_alignment, text_from: "UserText".into(), frame: false, text_width: -1, color: self.color.unwrap_or(HexColor::BLACK), text: value, keep_visual_rotation: false, info_name: None, align_x, align_y, } } } /// 计算MText的实际旋转角度 /// /// # Arguments /// * `rotation_angle` - DXF中的rotation_angle字段(编号50) /// * `x_axis_direction` - DXF中的x_axis_direction向量(编号11,21,31) /// /// # Returns /// 返回实际的旋转角度(弧度) fn calculate_mtext_rotation(rotation_angle: f64, x_axis_direction: &dxf::Vector) -> f64 { // 检查x_axis_direction是否为默认值(1.0, 0.0, 0.0) let default_x_axis = (x_axis_direction.x - 1.0).abs() < 1e-10 && x_axis_direction.y.abs() < 1e-10 && x_axis_direction.z.abs() < 1e-10; if default_x_axis { // 如果x_axis_direction是默认值,使用rotation_angle rotation_angle } else { // 如果x_axis_direction不是默认值,从向量计算角度 // atan2返回弧度值,需要转换为度数 let angle_radians = x_axis_direction.y.atan2(x_axis_direction.x); // 将弧度转换为度数,这里rotation字段期望度数 angle_radians * 180.0 / std::f64::consts::PI } } /// 将MText的对齐点坐标调整为左下角坐标 /// /// # 参数 /// * `x` - MText的insertion_point.x(对齐点X坐标) /// * `y` - MText的insertion_point.y(对齐点Y坐标) /// * `attachment_point` - MText的对齐方式 /// * `text_height` - 文本高度 /// * `text_width` - 文本宽度(reference_rectangle_width) /// /// # 返回值 /// 返回调整后的(x, y)坐标,对应文本的左下角位置 pub fn adjust_mtext_coordinates( x: f64, y: f64, attachment_point: AttachmentPoint, text_height: f64, text_width: f64, ) -> (f64, f64) { let mut adjusted_x = x; let mut adjusted_y = y; // 根据水平对齐方式调整X坐标 match attachment_point { // 左对齐:无需调整X坐标 AttachmentPoint::TopLeft | AttachmentPoint::MiddleLeft | AttachmentPoint::BottomLeft => { // X坐标已经是左边界,无需调整 } // 中心对齐:向左偏移文本宽度的一半 AttachmentPoint::TopCenter | AttachmentPoint::MiddleCenter | AttachmentPoint::BottomCenter => { adjusted_x -= text_width / 2.0; // 如果文本旋转了,那就不是这么算了 } // 右对齐:向左偏移整个文本宽度 AttachmentPoint::TopRight | AttachmentPoint::MiddleRight | AttachmentPoint::BottomRight => { adjusted_x -= text_width; } } // 根据垂直对齐方式调整Y坐标 match attachment_point { // 底部对齐:无需调整Y坐标(已经是底边界) AttachmentPoint::BottomLeft | AttachmentPoint::BottomCenter | AttachmentPoint::BottomRight => { // Y坐标已经是底边界,无需调整 } // 中心对齐:向下偏移文本高度的一半 AttachmentPoint::MiddleLeft | AttachmentPoint::MiddleCenter | AttachmentPoint::MiddleRight => { adjusted_y -= text_height / 2.0; } // 顶部对齐:向下偏移整个文本高度 AttachmentPoint::TopLeft | AttachmentPoint::TopCenter | AttachmentPoint::TopRight => { adjusted_y -= text_height; } } (adjusted_x, adjusted_y) }