You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

515 lines
20 KiB
Rust

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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<String>,
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,
}
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 mut cjk_char_count = 0;
let mut graphene_count = 0;
for grapheme in txt.text.graphemes(true) {
graphene_count += 1;
if grapheme.chars().any(|c| {
// 检查是否为中文字符(包括中日韩统一表意文字)
(c >= '\u{4E00}' && c <= '\u{9FFF}') || // CJK统一表意文字
(c >= '\u{3400}' && c <= '\u{4DBF}') || // CJK扩展A
(c >= '\u{20000}' && c <= '\u{2A6DF}') || // CJK扩展B
(c >= '\u{2A700}' && c <= '\u{2B73F}') || // CJK扩展C
(c >= '\u{2B740}' && c <= '\u{2B81F}') || // CJK扩展D
(c >= '\u{2B820}' && c <= '\u{2CEAF}') || // CJK扩展E
(c >= '\u{F900}' && c <= '\u{FAFF}') || // CJK兼容表意文字
(c >= '\u{2F800}' && c <= '\u{2FA1F}') // CJK兼容表意文字补充
}) {
cjk_char_count += 1;
}
}
let txt_width = if txt.reference_rectangle_width > 2.0 {
txt.reference_rectangle_width
} else {
(graphene_count as f64) * pt_size * 0.75
};
// let x_pos = {
// let x_pos = txt.x + 0.5 - (pt_size / 8.0) - 4.05;
// // match txt.h_alignment {
// // HAlignment::Left => x_pos,
// // HAlignment::Center => x_pos - txt_width / 2.0,
// // HAlignment::Right => x_pos - txt_width,
// // }
// x_pos // 直接返回基础x_pos不进行水平对齐调整
// };
// // let y_pos = txt.y + 0.5 - (7.0 / 5.0 * pt_size + 26.0 / 5.0) + pt_size;
// let y_pos = {
// let base_y_pos = txt.y + 0.5 - (7.0 / 5.0 * pt_size + 26.0 / 5.0) + pt_size;
// match txt.v_alignment {
// VAlignment::Top => base_y_pos,
// VAlignment::Center => base_y_pos - pt_size / 2.0,
// VAlignment::Bottom => base_y_pos - pt_size,
// }
// };
// 如果采用计算对齐的方式,可能还要考虑字体宽度的偏差。。
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);
println!("cjk_char_count: {}", cjk_char_count);
// 计算基础位置(不考虑旋转)
// txt.x和txt.y现在是对齐点坐标需要根据对齐方式计算出左上角位置
// 首先根据水平对齐方式计算左边界的x坐标
let left_x = match txt.h_alignment {
HAlignment::Left => txt.x,
HAlignment::Center => txt.x - txt_width / 2.0,
HAlignment::Right => txt.x - txt_width,
};
// 根据垂直对齐方式计算顶部的y坐标
let top_y = txt.y;
// 根据是否包含中文字符调整字体大小相关的偏移量
let cjk_offset:f64 = (cjk_char_count as f64) * pt_size * 0.75;
// 左对齐是不需要中文偏移的,居中对齐需要偏移,右对齐要偏移
let base_x = left_x + 0.5 - (pt_size / 8.0) - 4.05
- match txt.h_alignment {
HAlignment::Left => 0.0,
HAlignment::Center => cjk_offset / 2.0,
HAlignment::Right => cjk_offset,
};
let base_y = top_y + 0.5 - (7.0 / 5.0 * pt_size + 26.0 / 5.0)
+ match txt.v_alignment {
VAlignment::Top => pt_size / 2.0,
VAlignment::Center => pt_size,
VAlignment::Bottom => pt_size / 2.0,
};
// 如果有旋转角度,应用旋转变换
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());
//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.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<HexColor>,
}
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,
) = 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)
};
(
x,
y,
txt.location.z,
txt.rotation,
&txt.text_style_name,
txt.text_height,
txt.value.clone(),
HAlignment::from(txt.horizontal_text_justification),
VAlignment::from(txt.vertical_text_justification),
0.0, // as Placeholder: no "reference_rectangle_width" with Text!!!
)},
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<String>
//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,
)},
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)
};
(
x,
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(attrib.vertical_text_justification),
0.0, // as Placeholder: not need to check if Attrib has something similar
)
},
};
// 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<f32> = 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,
}
}
}
/// 计算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)
}