From e0fb0160e53ae38f26ff2511433650de26eee047 Mon Sep 17 00:00:00 2001 From: liaoxianglian Date: Wed, 17 Sep 2025 14:22:51 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=A3=E5=86=B3Printer=20Command=20Language?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=8C=E8=A7=A3=E5=86=B3=E5=A4=9A=E8=A1=8C?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E6=97=8B=E8=BD=AC=E9=97=AE=E9=A2=98=EF=BC=8C?= =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=A4=9A=E8=A1=8C=E6=96=87=E6=9C=AC=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE=E5=81=8F=E7=A7=BB=E9=97=AE=E9=A2=98=EF=BC=88=E6=B2=A1?= =?UTF-8?q?=E5=AE=8C=E5=85=A8=E8=A7=A3=E5=86=B3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/qelmt/dynamictext.rs | 123 ++++++++++++++++++++++++++++++++++++-- src/qelmt/mod.rs | 125 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 241 insertions(+), 7 deletions(-) diff --git a/src/qelmt/dynamictext.rs b/src/qelmt/dynamictext.rs index a606d17..13a20ed 100644 --- a/src/qelmt/dynamictext.rs +++ b/src/qelmt/dynamictext.rs @@ -4,6 +4,7 @@ use hex_color::HexColor; use simple_xml_builder::XMLElement; use unicode_segmentation::UnicodeSegmentation; use uuid::Uuid; +use dxf::enums::AttachmentPoint; /*use parley::{ Alignment, FontContext, FontWeight, InlineBox, Layout, LayoutContext, PositionedLayoutItem, @@ -85,7 +86,20 @@ impl From<&DynamicText> for XMLElement { // } // }; + println!("文本:{}", txt.text); + println!("对齐方式:{}, {}", txt.h_alignment, txt.v_alignment); + println!("文本宽度: {}", txt_width); + println!("文本高度:{}", pt_size); + println!("初始位置: x={}, y={}", txt.x, txt.y); + println!("旋转角度: {}", txt.rotation); + + + + // 计算基础位置(不考虑旋转) + // 如果是mtext,是没有左下角的位置的,只有对齐点的位置, + // 所以需要根据对齐点和文本宽度来计算最终的位置 + let base_x = txt.x + 0.5 - (pt_size / 8.0) - 4.05; let base_y = { let base_y_pos = txt.y + 0.5 - (7.0 / 5.0 * pt_size + 26.0 / 5.0) + pt_size; @@ -115,6 +129,9 @@ impl From<&DynamicText> for XMLElement { (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)); @@ -241,11 +258,21 @@ impl<'a> DTextBuilder<'a> { VAlignment::from(txt.vertical_text_justification), 0.0, // as Placeholder: no "reference_rectangle_width" with Text!!! ), - TextEntity::MText(mtxt) => ( - mtxt.insertion_point.x, - -mtxt.insertion_point.y, + TextEntity::MText(mtxt) => { + let (adjusted_x, adjusted_y) = adjust_mtext_coordinates( + mtxt.insertion_point.x, + mtxt.insertion_point.y, + mtxt.attachment_point, + mtxt.initial_text_height, + mtxt.reference_rectangle_width + ); + // 计算实际的旋转角度,优先使用x_axis_direction向量 + let actual_rotation = calculate_mtext_rotation(mtxt.rotation_angle, &mtxt.x_axis_direction); + ( + adjusted_x, + -adjusted_y, mtxt.insertion_point.z, - mtxt.rotation_angle, + 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. @@ -269,12 +296,13 @@ impl<'a> DTextBuilder<'a> { { let mut val = mtxt.extended_text.join(""); val.push_str(&mtxt.text); - val.replace("\\P", "\n") + 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, - ), + )}, TextEntity::Attrib(attrib) => ( attrib.location.x, -attrib.location.y, @@ -358,3 +386,86 @@ impl<'a> DTextBuilder<'a> { } } } + + +/// 计算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) +} \ No newline at end of file diff --git a/src/qelmt/mod.rs b/src/qelmt/mod.rs index 32b585d..2399c75 100644 --- a/src/qelmt/mod.rs +++ b/src/qelmt/mod.rs @@ -986,7 +986,6 @@ impl<'a> ObjectsBuilder<'a> { //need to look up the proper way to get the color for the Attrib let mut dtext = DTextBuilder::from_attrib(attrib) .color({ - println!("color index:{:?}", self.ent.common.color.index()); // 优先使用 ACI 颜色索引 if let Some(aci_index) = self.ent.common.color.index() { aci_to_hex_color(aci_index) @@ -1707,4 +1706,128 @@ fn aci_to_rgb(aci: u8) -> (u8, u8, u8) { fn aci_to_hex_color(aci: u8) -> HexColor { let (r, g, b) = aci_to_rgb(aci); HexColor::rgb(r, g, b) +} + +/// 从PCL格式字符串中提取文本内容 +/// +/// 解析Printer Command Language (PCL)格式的字符串,提取其中的文本内容。 +/// 支持格式如:"\pxt1;{\T1.001;传感器S1}" -> "传感器S1" +/// 以及Unicode转义格式:"{\T1.00100;\U+4F20\U+611F\U+5668S2}" -> "传感器S2" +/// +/// # 参数 +/// * `pcl_string` - PCL格式的字符串 +/// +/// # 返回值 +/// 提取出的文本内容,如果解析失败则返回原字符串 +pub fn extract_text_from_pcl(pcl_string: &str) -> String { + // 移除开头和结尾的空白字符 + let trimmed = pcl_string.trim(); + + // 处理直接以大括号开始的格式:{\T1.00100;\U+4F20\U+611F\U+5668S2} + if trimmed.starts_with('{') && trimmed.ends_with('}') { + let content = &trimmed[1..trimmed.len() - 1]; + + // 查找\T命令 + if let Some(t_pos) = content.find("\\T") { + // 查找\T命令后的分号 + let t_content = &content[t_pos..]; + if let Some(t_semicolon_pos) = t_content.find(';') { + let text_content = &t_content[t_semicolon_pos + 1..]; + return decode_unicode_escapes(text_content); + } + } + + // 如果没有找到\T命令,返回大括号内的全部内容并解码Unicode + return decode_unicode_escapes(content); + } + + // 检查是否以\pxt开头 + if !trimmed.starts_with("\\pxt") { + return pcl_string.to_string(); + } + + // 查找第一个分号,这标志着\pxt命令的结束 + if let Some(semicolon_pos) = trimmed.find(';') { + let remaining = &trimmed[semicolon_pos + 1..]; + + // 检查是否有大括号包围的内容 + if remaining.starts_with('{') && remaining.ends_with('}') { + let content = &remaining[1..remaining.len() - 1]; + + // 查找\T命令 + if let Some(t_pos) = content.find("\\T") { + // 查找\T命令后的分号 + let t_content = &content[t_pos..]; + if let Some(t_semicolon_pos) = t_content.find(';') { + let text_content = &t_content[t_semicolon_pos + 1..]; + return decode_unicode_escapes(text_content); + } + } + + // 如果没有找到\T命令,返回大括号内的全部内容并解码Unicode + return decode_unicode_escapes(content); + } + } + + // 如果解析失败,返回原字符串 + pcl_string.to_string() +} + +/// 解码Unicode转义序列,如\U+4F20转换为对应的Unicode字符 +/// +/// # 参数 +/// * `text` - 包含Unicode转义序列的文本 +/// +/// # 返回值 +/// 解码后的文本 +fn decode_unicode_escapes(text: &str) -> String { + let mut result = String::new(); + let mut chars = text.chars().peekable(); + + while let Some(ch) = chars.next() { + if ch == '\\' { + // 检查是否是Unicode转义序列 \U+XXXX + if chars.peek() == Some(&'U') { + chars.next(); // 消费 'U' + if chars.peek() == Some(&'+') { + chars.next(); // 消费 '+' + + // 收集十六进制数字 + let mut hex_digits = String::new(); + while let Some(&next_ch) = chars.peek() { + if next_ch.is_ascii_hexdigit() { + hex_digits.push(chars.next().unwrap()); + } else { + break; + } + } + + // 尝试解析十六进制数字为Unicode码点 + if let Ok(code_point) = u32::from_str_radix(&hex_digits, 16) { + if let Some(unicode_char) = char::from_u32(code_point) { + result.push(unicode_char); + continue; + } + } + + // 如果解析失败,保留原始字符 + result.push('\\'); + result.push('U'); + result.push('+'); + result.push_str(&hex_digits); + } else { + // 不是 \U+ 格式,保留原始字符 + result.push('\\'); + result.push('U'); + } + } else { + // 不是Unicode转义,保留反斜杠 + result.push('\\'); + } + } else { + result.push(ch); + } + } + + result } \ No newline at end of file