|
|
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)
|
|
|
} |