处理is_invisible和标注文本位置

master
liaoxianglian 5 months ago
commit 5ce0de699a

@ -0,0 +1,3 @@
# These are supported funding model platforms
ko_fi: vadoola
buy_me_a_coffee: vadoola

@ -0,0 +1,79 @@
name: Package
on:
workflow_dispatch:
jobs:
build:
runs-on: ${{ matrix.os }}-latest
strategy:
fail-fast: true
matrix:
include:
- os: ubuntu
arch: i386
target: i686-unknown-linux-gnu
- os: ubuntu
arch: armhf
target: armv7-unknown-linux-gnueabihf
- os: ubuntu
arch: amd64
target: x86_64-unknown-linux-gnu
- os: ubuntu
arch: arm64
target: aarch64-unknown-linux-gnu
- os: macos
arch: amd64
target: x86_64-apple-darwin
- os: macos
arch: arm64
target: aarch64-apple-darwin
- os: windows
arch: i386
target: i686-pc-windows-msvc
- os: windows
arch: amd64
target: x86_64-pc-windows-msvc
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Check crate
if: matrix.os == 'macos' || matrix.os == 'windows' || matrix.os == 'ubuntu' && matrix.arch == 'amd64'
run: cargo publish --dry-run --target ${{ matrix.target }}
- name: Clippy (release mode)
run: cargo clippy --release -- -D warnings
- name: Test (release mode)
if: matrix.os == 'macos' || matrix.os == 'ubuntu' || matrix.os == 'windows' && matrix.arch == 'amd64'
run: |
cargo test --release --verbose -- --nocapture &&
cargo clean
- name: Install Cross
if: matrix.os == 'ubuntu'
run: cargo install cross --git https://github.com/cross-rs/cross
- name: Build binary (Linux)
if: matrix.os == 'ubuntu'
run: cross build --release --target ${{ matrix.target }}
- name: Build binary (macOS/Windows)
if: matrix.os == 'macos' || matrix.os == 'windows'
run: cargo build --release --target ${{ matrix.target }}
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-${{ matrix.os }}-${{ matrix.target }}
path: |
target/*/release/dxf2elmt
target/*/release/dxf2elmt.exe
if-no-files-found: error

@ -0,0 +1,43 @@
name: Rust
on:
push:
tags: ["[0-9]+.[0-9]+.[0-9]+*"]
env:
CARGO_TERM_COLOR: always
# Linters inspired from here: https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md
jobs:
rust:
name: ${{ matrix.os }}-latest
runs-on: ${{ matrix.os }}-latest
strategy:
fail-fast: true
matrix:
include:
- os: ubuntu
- os: macos
- os: windows
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: fmt
run: cargo fmt --all -- --check
- name: build
if: matrix.os != 'windows' || github.event_name != 'pull_request'
run: cargo build --verbose
- name: clippy
if: matrix.os != 'windows' || github.event_name != 'pull_request'
#run: cargo clippy -- -D warnings
run: cargo clippy --
- name: test
if: matrix.os != 'windows' || github.event_name != 'pull_request'
run: cargo test --verbose -- --nocapture

11
.gitignore vendored

@ -0,0 +1,11 @@
/target
Dockerfile
*.dxf
*.elmt
cross.toml
/.cargo
*.txt
*.exe
/.vscode
Cargo.lock
.DS_Store

@ -0,0 +1,50 @@
[package]
name = "dxf2elmt"
version = "0.5.0"
edition = "2021"
description = "A CLI program to convert .dxf files into .elmt files"
authors = ["Vadoola <github: vadoola>", "Antonio Aguilar <github: antonioaja>"]
readme = "README.md"
repository = "https://github.com/Vadoola/dxf2elmt"
license = "MIT"
rust-version = "1.79.0"
[profile.release]
strip = true
lto = true
[profile.dev.package."*"]
opt-level = 3
[dependencies]
dxf = "0.6.0"
simple-xml-builder = "1.1.0"
bspline = "1.1.0"
uuid = { version = "1.16", features = ["serde", "v4"] }
tempfile = "3.15"
clap = { version = "4.5", features = ["derive"] }
anyhow = "1.0.97"
wild = "2.2"
rayon = "1.10.0"
hex_color = "3.0.0"
itertools = "0.14"
parley = "0.2.0"
unicode-segmentation = "1.12.0"
tracing = "0.1"
venator = { version = "1.1", optional = true }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
#[lints.clippy]
#unwrap_used = "deny"
#undecided on how I want to handle logging in this crate right now.
#I do want to do a bit of tracing debugging on some of these recursive blocks though
#for now I'll add tracing and venator under a trace feature that is disabled by default
#maybe a log feature which uses trace to log to a text file or syslog or something
#and a seperate feature that logs to venator? Also figure out how best to isolate
#it to a module, so I don' thave #[cfg(feature = ...)] all over the place
#https://www.shuttle.dev/blog/2024/01/09/getting-started-tracing-rust#instrumentation-in-tracing
[features]
default = []
venator = ["dep:venator"]

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Antonio Aguilar
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,58 @@
# dxf2elmt
dxf2elmt is CLI program which can convert .dxf files into [QElectroTech](https://qelectrotech.org/) .elmt files. The program supports both ascii and binary .dxf files.
The goal of this program is to create a fast and accurate conversion tool to be used with [QElectroTech](https://qelectrotech.org/).
## How to Use
dxf2elmt requires only one input from the user, the input file.
For example:
```bash
./dxf2elmt my_file.dxf
```
The .elmt file will be output into the same directory as the executable. It will retain the name of the .dxf file.
If you wish to forgo creating an .elmt file, you can use the "-v" argument for verbose output. This will output the contents of the .elmt file to stdout without actually creating the file. For example:
```bash
./dxf2elmt my_file.dxf -v
```
## Supported Entities
* Lines
* Circles
* Arcs
* Texts
* Ellipses
* Polylines
* LwPolylines
* Solids
* Splines
* Blocks (there are still some known issues for deeply nested block)
* MText (partial support)
* Leader
## To Do
* Support for the following
* Remaining 2d entities
* Styling (such as Dimension Styles)
* Better error messages
* Logging
## Compiling
Compiled using Rust (MSRV 1.79.0).
## Credits
* [Antonioaja](https://github.com/antonioaja) for creating the initial versions of [dxf2elmt](https://github.com/antonioaja/dxf2elmt). Thank you for all your work.
* [QElectroTech](https://qelectrotech.org/)
* [dxf-rs](https://github.com/IxMilia/dxf-rs)
* [simple-xml-builder](https://github.com/Accelbread/simple-xml-builder)
* [bspline](https://github.com/Twinklebear/bspline)
* [tempfile](https://github.com/Stebalien/tempfile)

@ -0,0 +1,26 @@
extern crate tempfile;
use anyhow::Context;
use std::fs::File;
use std::path::{Path, PathBuf};
use tempfile::tempfile;
pub fn create_file(
verbose_output: bool,
_info: bool,
file_name: &Path,
) -> Result<File, anyhow::Error> {
let old_file_name = file_name.to_string_lossy();
let mut file_name = PathBuf::from(file_name);
file_name.set_extension("elmt");
let friendly_file_name = file_name.to_string_lossy();
let mut out_file = tempfile().context("Could not create temporary file");
if !verbose_output {
out_file = File::create(&file_name).context("Could not create output file");
println!("{friendly_file_name} was created... \nNow converting {old_file_name}...",);
}
out_file.context("Could not return output file")
}

@ -0,0 +1,207 @@
#![warn(
clippy::all,
clippy::pedantic,
//clippy::cargo,
//rust_2024_compatibility,
)]
//#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
extern crate dxf;
extern crate simple_xml_builder;
extern crate unicode_segmentation;
use anyhow::{Context, Ok, Result};
use clap::Parser;
use dxf::entities::EntityType;
use dxf::Drawing;
use qelmt::Definition;
//use rayon::prelude::*;
use simple_xml_builder::XMLElement;
use std::time::Instant;
use std::{io, path::PathBuf};
use tracing::{span, trace, warn, Level};
use tracing_subscriber::prelude::*;
#[cfg(feature = "venator")]
use venator::Venator;
mod qelmt;
#[derive(Parser, Debug)]
#[command(name = "dxf2elmt")]
#[command(author, version, about = "A CLI program to convert .dxf files into .elmt files", long_about = None)]
struct Args {
/// The .dxf file to convert
//#[clap(short, long, value_parser)]
file_names: Vec<PathBuf>,
/// Activates verbose output, eliminates .elmt file writing
#[clap(short, long, value_parser, default_value_t = false)]
verbose: bool,
/// Converts text entities into dynamic text instead of the default text box
#[clap(short, long, value_parser, default_value_t = false)]
dtext: bool,
/// Determine the number of lines you want each spline to have (more lines = greater resolution)
#[clap(short, long, value_parser, default_value_t = 20)]
spline_step: u32,
/// Toggles information output... defaults to off
#[clap(short, long, value_parser, default_value_t = false)]
info: bool,
}
pub mod file_writer;
#[allow(clippy::too_many_lines)]
fn main() -> Result<()> {
#[cfg(feature = "venator")]
let tr_reg = tracing_subscriber::registry()
.with(Venator::default())
.with(tracing_subscriber::fmt::Layer::default());
#[cfg(not(feature = "venator"))]
let tr_reg = {
let file_layer =
if let std::result::Result::Ok(file) = std::fs::File::create("dxf2elmt.log") {
//if we can create a log file use it
Some(tracing_subscriber::fmt::layer().with_writer(std::sync::Arc::new(file)))
} else {
None
};
let stde_layer = tracing_subscriber::fmt::layer()
.pretty()
.with_writer(io::stderr);
tracing_subscriber::registry()
.with(file_layer.map(|fl| {
fl.with_file(true)
.with_line_number(true)
.with_thread_ids(true)
.with_ansi(false)
.with_filter(tracing_subscriber::EnvFilter::from_env("DXF2E_LOG"))
}))
.with(
stde_layer
.with_file(true)
.with_line_number(true)
.with_thread_ids(true)
.with_filter(tracing_subscriber::EnvFilter::from_env("DXF2E_LOG")),
)
};
tr_reg.init();
trace!("Starting dxf2elmt");
// Start recording time
let now: Instant = Instant::now();
// Collect arguments
let args: Args = Args::parse_from(wild::args());
// Load dxf file
let dxf_loop_span = span!(Level::TRACE, "Looping over dxf files");
let dxf_loop_guard = dxf_loop_span.enter();
for file_name in args.file_names {
let friendly_file_name = file_name
.file_stem()
.unwrap_or_else(|| file_name.as_os_str())
.to_string_lossy();
let drawing: Drawing = Drawing::load_file(&file_name).context(format!(
"Failed to load {friendly_file_name}...\n\tMake sure the file is a valid .dxf file.",
))?;
let q_elmt = Definition::new(friendly_file_name.clone(), args.spline_step, &drawing);
if !args.verbose && args.info {
println!("{friendly_file_name} loaded...");
}
// Initialize counts
let mut circle_count: u32 = 0;
let mut line_count: u32 = 0;
let mut arc_count: u32 = 0;
let mut spline_count: u32 = 0;
let mut text_count: u32 = 0;
let mut ellipse_count: u32 = 0;
let mut polyline_count: u32 = 0;
let mut lwpolyline_count: u32 = 0;
let mut solid_count: u32 = 0;
let mut block_count: u32 = 0;
let mut other_count: u32 = 0;
// Loop through all entities, counting the element types
//drawing.entities().for_each(|e| match e.specific {
drawing.entities().for_each(|e| match e.specific {
EntityType::Circle(ref _circle) => {
circle_count += 1;
}
EntityType::Line(ref _line) => {
line_count += 1;
}
EntityType::Arc(ref _arc) => {
arc_count += 1;
}
EntityType::Spline(ref _spline) => {
spline_count += 1;
}
EntityType::Text(ref _text) => {
text_count += 1;
}
EntityType::Ellipse(ref _ellipse) => {
ellipse_count += 1;
}
EntityType::Polyline(ref _polyline) => {
polyline_count += 1;
}
EntityType::LwPolyline(ref _lwpolyline) => {
lwpolyline_count += 1;
}
EntityType::Solid(ref _solid) => {
solid_count += 1;
}
EntityType::Insert(ref _insert) => {
block_count += 1;
}
_ => {
other_count += 1;
}
});
// Create output file for .elmt
let out_file = file_writer::create_file(args.verbose, args.info, &file_name)?;
// Write to output file
let out_xml = XMLElement::from(&q_elmt);
out_xml
.write(&out_file)
.context("Failed to write output file.")?;
if args.info {
println!("Conversion complete!\n");
// Print stats
println!("STATS");
println!("~~~~~~~~~~~~~~~");
println!("Circles: {circle_count}");
println!("Lines: {line_count}");
println!("Arcs: {arc_count}");
println!("Splines: {spline_count}");
println!("Texts: {text_count}");
println!("Ellipses: {ellipse_count}");
println!("Polylines: {polyline_count}");
println!("LwPolylines: {lwpolyline_count}");
println!("Solids: {solid_count}");
println!("Blocks: {block_count}");
println!("Currently Unsupported: {other_count}");
println!("\nTime Elapsed: {} ms", now.elapsed().as_millis());
}
if args.verbose {
print!("{out_xml}");
}
}
drop(dxf_loop_guard);
Ok(())
}

@ -0,0 +1,94 @@
use super::{two_dec, ScaleEntity};
use dxf::entities;
use simple_xml_builder::XMLElement;
#[derive(Debug)]
pub struct Arc {
//need to brush up on my Rust scoping rules, isn't there a way to make this pub to just the module?
pub x: f64,
pub y: f64,
width: f64,
height: f64,
start: f64,
angle: f64,
style: String,
antialias: bool,
}
impl From<&entities::Arc> for Arc {
fn from(arc: &entities::Arc) -> Self {
let temp_angle = if arc.start_angle > arc.end_angle {
(360.0 - arc.start_angle) + arc.end_angle
} else {
arc.end_angle - arc.start_angle
};
Arc {
x: arc.center.x - arc.radius,
y: -arc.center.y - arc.radius,
height: arc.radius * 2.0,
width: arc.radius * 2.0,
start: if arc.start_angle < 0.0 {
-arc.start_angle
} else {
arc.start_angle
},
angle: if temp_angle < 0.0 {
-temp_angle
} else {
temp_angle
},
//in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false,
style: if arc.thickness > 0.1 {
"line-style:normal;line-weight:normal;filling:none;color:black"
} else {
"line-style:normal;line-weight:thin;filling:none;color:black"
}
.into(),
}
}
}
impl From<&Arc> for XMLElement {
fn from(arc: &Arc) -> Self {
let mut arc_xml: XMLElement = XMLElement::new("arc");
arc_xml.add_attribute("x", two_dec(arc.x));
arc_xml.add_attribute("y", two_dec(arc.y));
arc_xml.add_attribute("width", two_dec(arc.width));
arc_xml.add_attribute("height", two_dec(arc.height));
arc_xml.add_attribute("start", arc.start.round());
arc_xml.add_attribute("angle", arc.angle.round());
arc_xml.add_attribute("antialias", arc.antialias);
arc_xml.add_attribute("style", &arc.style);
arc_xml
}
}
impl ScaleEntity for Arc {
fn scale(&mut self, fact_x: f64, fact_y: f64) {
self.x *= fact_x;
self.y *= fact_y;
self.width *= fact_x;
self.height *= fact_y;
}
fn left_bound(&self) -> f64 {
self.x
}
fn right_bound(&self) -> f64 {
self.x + self.width
}
fn top_bound(&self) -> f64 {
self.y
}
fn bot_bound(&self) -> f64 {
self.y + self.height
}
}

@ -0,0 +1,353 @@
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 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 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,
// }
// };
// 计算基础位置(不考虑旋转)
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;
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,
}
};
// 如果有旋转角度,应用旋转变换
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)
};
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);
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) => (
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(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,
mtxt.insertion_point.z,
mtxt.rotation_angle,
&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);
val.replace("\\P", "\n")
},
HAlignment::from(mtxt.attachment_point),
VAlignment::from(mtxt.attachment_point),
mtxt.reference_rectangle_width,
),
TextEntity::Attrib(attrib) => (
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(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,
}
}
}

@ -0,0 +1,177 @@
use super::{two_dec, Circularity, ScaleEntity};
use dxf::entities::{self, Circle, LwPolyline, Polyline};
use simple_xml_builder::XMLElement;
#[derive(Debug)]
pub struct Ellipse {
height: f64,
width: f64,
style: String,
//need to brush up on my Rust scoping rules, isn't there a way to make this pub to just the module?
pub x: f64,
pub y: f64,
antialias: bool,
}
impl From<&Circle> for Ellipse {
fn from(circ: &Circle) -> Self {
Ellipse {
x: circ.center.x - circ.radius,
y: -circ.center.y - circ.radius,
height: circ.radius * 2.0,
width: circ.radius * 2.0,
//in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false,
style: if circ.thickness > 0.5 {
"line-style:normal;line-weight:normal;filling:none;color:black"
} else {
"line-style:normal;line-weight:thin;filling:none;color:black"
}
.into(),
}
}
}
impl From<&entities::Ellipse> for Ellipse {
fn from(ellipse: &entities::Ellipse) -> Self {
Ellipse {
x: ellipse.center.x - ellipse.major_axis.x,
y: -ellipse.center.y - ellipse.major_axis.x * ellipse.minor_axis_ratio,
height: ellipse.major_axis.x * 2.0,
width: ellipse.major_axis.x * 2.0 * ellipse.minor_axis_ratio,
//in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false,
style: "line-style:normal;line-weight:thin;filling:none;color:black".into(),
}
}
}
impl TryFrom<&Polyline> for Ellipse {
type Error = &'static str; //add better error later
fn try_from(poly: &Polyline) -> Result<Self, Self::Error> {
if !poly.is_circular() {
return Err("Polyline has poor circularity, can't convert");
}
//I did this fold because min requires the vertex to have the Ordering trait
//but I forogot min_by exists taking a lambda, so I could compare them using
//the value I need. However my first quick attemp wasn't working
//Using min_by would probably be more effecietn than the fold
//So this is probably worth coming back to...but it's a low priority
//because the below code works.
let x = poly
.vertices()
.fold(f64::MAX, |min_x, vtx| min_x.min(vtx.location.x));
let max_x = poly
.vertices()
.fold(f64::MIN, |max_x, vtx| max_x.max(vtx.location.x));
let y = poly
.vertices()
.fold(f64::MAX, |min_y, vtx| min_y.min(vtx.location.y));
let max_y = poly
.vertices()
.fold(f64::MIN, |max_y, vtx| max_y.max(vtx.location.y));
Ok(Ellipse {
x,
y: -max_y,
height: max_y - y,
width: max_x - x,
//in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false,
style: "line-style:normal;line-weight:thin;filling:none;color:black".into(),
})
}
}
impl TryFrom<&LwPolyline> for Ellipse {
type Error = &'static str; //add better error later
fn try_from(poly: &LwPolyline) -> Result<Self, Self::Error> {
if !poly.is_circular() {
return Err("Polyline has poor circularity, can't convert");
}
let x = poly
.vertices
.iter()
.fold(f64::MAX, |min_x, vtx| min_x.min(vtx.x));
let max_x = poly
.vertices
.iter()
.fold(f64::MIN, |max_x, vtx| max_x.max(vtx.x));
let y = poly
.vertices
.iter()
.fold(f64::MAX, |min_y, vtx| min_y.min(vtx.y));
let max_y = poly
.vertices
.iter()
.fold(f64::MIN, |max_y, vtx| max_y.max(vtx.y));
Ok(Ellipse {
x,
y: -max_y,
height: max_y - y,
width: max_x - x,
//in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false,
style: "line-style:normal;line-weight:thin;filling:none;color:black".into(),
})
}
}
impl From<&Ellipse> for XMLElement {
fn from(ell: &Ellipse) -> Self {
let mut ell_xml: XMLElement = XMLElement::new("ellipse");
ell_xml.add_attribute("x", two_dec(ell.x));
ell_xml.add_attribute("y", two_dec(ell.y));
ell_xml.add_attribute("width", two_dec(ell.width));
ell_xml.add_attribute("height", two_dec(ell.height));
ell_xml.add_attribute("antialias", ell.antialias);
ell_xml.add_attribute("style", &ell.style);
ell_xml
}
}
impl ScaleEntity for Ellipse {
fn scale(&mut self, fact_x: f64, fact_y: f64) {
self.x *= fact_x;
self.y *= fact_y;
self.width *= fact_x;
self.height *= fact_y;
}
fn left_bound(&self) -> f64 {
self.x
}
fn right_bound(&self) -> f64 {
self.x + self.width
}
fn top_bound(&self) -> f64 {
self.y
}
fn bot_bound(&self) -> f64 {
self.y + self.height
}
}

@ -0,0 +1,211 @@
use super::two_dec;
use super::LineEnd;
use super::ScaleEntity;
use dxf::entities::{self, LwPolyline, Polyline};
use simple_xml_builder::XMLElement;
#[derive(Debug)]
pub struct Line {
length2: f64,
end2: LineEnd,
length1: f64,
//need to brush up on my Rust scoping rules, isn't there a way to make this pub to just the module?
pub x1: f64,
pub y1: f64,
pub x2: f64,
pub y2: f64,
style: String,
end1: LineEnd,
antialias: bool,
}
pub struct Leader(pub Vec<Line>);
impl From<&entities::Line> for Line {
fn from(line: &entities::Line) -> Self {
Line {
x1: line.p1.x,
y1: -line.p1.y,
length1: 1.5, //why is this statically set at 1.5?
end1: LineEnd::None,
x2: line.p2.x,
y2: -line.p2.y,
length2: 1.5, //why is this statically set at 1.5?
end2: LineEnd::None,
//in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false,
style: if line.thickness > 0.5 {
"line-style:normal;line-weight:normal;filling:none;color:black"
} else {
"line-style:normal;line-weight:thin;filling:none;color:black"
}
.into(),
}
}
}
impl TryFrom<&Polyline> for Line {
type Error = &'static str; //add better error later
fn try_from(poly: &Polyline) -> Result<Self, Self::Error> {
if poly.__vertices_and_handles.len() != 2 {
return Err("Error can't convert polyline with more than 2 points into a Line");
}
Ok(Line {
x1: poly.__vertices_and_handles[0].0.location.x,
y1: -poly.__vertices_and_handles[0].0.location.y,
length1: 1.5, //why is this statically set at 1.5?
end1: LineEnd::None,
x2: poly.__vertices_and_handles[1].0.location.x,
y2: -poly.__vertices_and_handles[1].0.location.y,
length2: 1.5, //why is this statically set at 1.5?
end2: LineEnd::None,
//in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false,
style: if poly.thickness > 0.5 {
"line-style:normal;line-weight:normal;filling:none;color:black"
} else {
"line-style:normal;line-weight:thin;filling:none;color:black"
}
.into(),
})
}
}
impl TryFrom<&LwPolyline> for Line {
type Error = &'static str; //add better error later
fn try_from(poly: &LwPolyline) -> Result<Self, Self::Error> {
if poly.vertices.len() != 2 {
return Err("Error can't convert polyline with more than 2 points into a Line");
}
Ok(Line {
x1: poly.vertices[0].x,
y1: -poly.vertices[0].y,
length1: 1.5, //why is this statically set at 1.5?
end1: LineEnd::None,
x2: poly.vertices[1].x,
y2: -poly.vertices[1].y,
length2: 1.5, //why is this statically set at 1.5?
end2: LineEnd::None,
//in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false,
style: if poly.thickness > 0.1 {
"line-style:normal;line-weight:normal;filling:none;color:black"
} else {
"line-style:normal;line-weight:thin;filling:none;color:black"
}
.into(),
})
}
}
impl From<&entities::Leader> for Leader {
fn from(leader: &entities::Leader) -> Self {
Leader(
leader
.vertices
.windows(2)
.enumerate()
.map(|(cnt, pt_slice)| {
let end1 = if leader.use_arrowheads && cnt == 0 {
LineEnd::SimpleArrow
} else {
LineEnd::None
};
Line {
x1: pt_slice[0].x,
y1: -pt_slice[0].y,
length1: 1.5, //In order to get the arrow sizing, I need to read in the dimension styling first
end1,
x2: pt_slice[1].x,
y2: -pt_slice[1].y,
length2: 1.5, //In order to get the arrow sizing, I need to read in the dimension styling first
end2: LineEnd::None,
//in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false,
//looks like line thickenss and color information I *might* need to grab from a dimension style
//entity which I haven't implemented yet
/*style: if line.thickness > 0.5 {
"line-style:normal;line-weight:normal;filling:none;color:black"
} else {
"line-style:normal;line-weight:thin;filling:none;color:black"
}
.into(),*/
style: "line-style:normal;line-weight:normal;filling:none;color:black"
.into(),
}
})
.collect(),
)
}
}
impl From<&Line> for XMLElement {
fn from(line: &Line) -> Self {
let mut line_xml: XMLElement = XMLElement::new("line");
line_xml.add_attribute("x1", two_dec(line.x1));
line_xml.add_attribute("y1", two_dec(line.y1));
line_xml.add_attribute("length1", two_dec(line.length1));
line_xml.add_attribute("end1", &line.end1);
line_xml.add_attribute("x2", two_dec(line.x2));
line_xml.add_attribute("y2", two_dec(line.y2));
line_xml.add_attribute("length2", two_dec(line.length2));
line_xml.add_attribute("end2", &line.end2);
line_xml.add_attribute("antialias", line.antialias);
line_xml.add_attribute("style", &line.style);
line_xml
}
}
impl ScaleEntity for Line {
fn scale(&mut self, fact_x: f64, fact_y: f64) {
self.x1 *= fact_x;
self.x2 *= fact_x;
self.y1 *= fact_y;
self.y2 *= fact_y;
//while writing this scaling code, I'm looking at
//QET_ElementScaler from plc-user to see if there are
//any easy to overlook mistakes that I might make
//doing the scaling. It seems they limit these lengths
//to 99.0, but I'm not sure why at the moment. I'll go
//ahead and limit them as well, and try to come back to
//figure out what the purpose here is
self.length1 *= fact_x.min(fact_y);
self.length1 = self.length1.min(99.0);
self.length2 *= fact_x.min(fact_y);
self.length2 = self.length2.min(99.0);
}
fn left_bound(&self) -> f64 {
self.x1.min(self.x2)
}
fn right_bound(&self) -> f64 {
self.x1.max(self.x2)
}
fn top_bound(&self) -> f64 {
self.y1.min(self.y2)
}
fn bot_bound(&self) -> f64 {
self.y1.max(self.y2)
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,282 @@
use super::{two_dec, ScaleEntity};
use dxf::entities::{LwPolyline, Polyline, Solid, Spline};
use simple_xml_builder::XMLElement;
use std::ops::{Add, Mul};
//wait Why do I have a coordinate AND a Point struct, that are
//essentially the same. It's been a couple of months, but I'm not
//seeing why I would have done this....almost makes me wondering
//if I started, then stopped, and then didn't realize where I left off
//and started again but used a different name...?
//Might need to take a closer look and clean this up.
#[derive(Debug)]
pub struct Coordinate {
pub x: f64,
pub y: f64,
}
#[derive(Copy, Clone, Debug)]
pub struct Point {
pub x: f64,
pub y: f64,
}
impl Point {
pub fn new(x: f64, y: f64) -> Point {
Point { x, y }
}
}
impl Mul<f64> for Point {
type Output = Point;
fn mul(self, rhs: f64) -> Point {
Point {
x: self.x * rhs,
y: self.y * rhs,
}
}
}
impl Add for Point {
type Output = Point;
fn add(self, rhs: Point) -> Point {
Point {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
#[derive(Debug)]
pub struct Polygon {
style: String,
antialias: bool,
pub coordinates: Vec<Coordinate>,
closed: bool,
}
impl From<&Polyline> for Polygon {
fn from(poly: &Polyline) -> Self {
Polygon {
coordinates: poly
.__vertices_and_handles
.iter()
.map(|(vertex, _handle)| Coordinate {
x: vertex.location.x,
y: -vertex.location.y,
})
.collect(),
closed: poly.is_closed(),
//in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false,
style: if poly.thickness > 0.1 {
"line-style:normal;line-weight:normal;filling:none;color:black"
} else {
"line-style:normal;line-weight:thin;filling:none;color:black"
}
.into(),
}
}
}
impl From<&LwPolyline> for Polygon {
fn from(poly: &LwPolyline) -> Self {
Polygon {
coordinates: poly
.vertices
.iter()
.map(|vertex| Coordinate {
x: vertex.x,
y: -vertex.y,
})
.collect(),
closed: poly.is_closed(),
//in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false,
style: if poly.thickness > 0.1 {
"line-style:normal;line-weight:normal;filling:none;color:black"
} else {
"line-style:normal;line-weight:thin;filling:none;color:black"
}
.into(),
}
}
}
impl From<(&Spline, u32)> for Polygon {
fn from((spline, spline_step): (&Spline, u32)) -> Self {
let mut i: usize = 0;
let mut points: Vec<Point> = Vec::new();
for _a in &spline.control_points {
points.push(Point::new(
spline.control_points[i].x,
spline.control_points[i].y,
));
i += 1;
}
i = 0;
let mut knots: Vec<f64> = Vec::new();
for _a in &spline.knot_values {
knots.push(spline.knot_values[i]);
i += 1;
}
let curr_spline = bspline::BSpline::new(
spline.degree_of_curve.unsigned_abs() as usize,
points,
knots,
);
let step: f64 =
(curr_spline.knot_domain().1 - curr_spline.knot_domain().0) / (spline_step as f64);
//there is probably a way to clean up some of this logic and use iterators
//although it looks like step_by doesn't work on a f64 range...hmmm
//but I haven't inspected it too closely, and for now am pretty much just duplicating
//it as antonioaja had it
let coordinates = {
let mut coords = Vec::with_capacity(
((curr_spline.knot_domain().1 - curr_spline.knot_domain().0) / step) as usize + 1,
);
let mut j: f64 = curr_spline.knot_domain().0;
i = 0;
while j < curr_spline.knot_domain().1 {
coords.push(Coordinate {
x: curr_spline.point(j).x,
y: -curr_spline.point(j).y,
});
j += step;
i += 1;
}
coords
};
Polygon {
coordinates,
closed: spline.is_closed(),
//in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false,
style: "line-style:normal;line-weight:thin;filling:none;color:black".into(),
}
}
}
impl From<&Solid> for Polygon {
fn from(solid: &Solid) -> Self {
Polygon {
coordinates: vec![
Coordinate {
x: solid.first_corner.x,
y: -solid.first_corner.y,
},
Coordinate {
x: solid.second_corner.x,
y: -solid.second_corner.y,
},
Coordinate {
x: solid.third_corner.x,
y: -solid.third_corner.y,
},
Coordinate {
x: solid.fourth_corner.x,
y: -solid.fourth_corner.y,
},
],
closed: true,
//in the original code antialias is always set to false...I'm guessing for performance
//reasons...I'm trying to think if there is a time we might want to turn it on?
antialias: false,
style: if solid.thickness > 0.5 {
"line-style:normal;line-weight:normal;filling:none;color:black"
} else {
"line-style:normal;line-weight:thin;filling:none;color:black"
}
.into(),
}
}
}
impl From<&Polygon> for XMLElement {
fn from(poly: &Polygon) -> Self {
let mut poly_xml: XMLElement = XMLElement::new("polygon");
for (count, coord) in poly.coordinates.iter().enumerate() {
poly_xml.add_attribute(format!("x{}", (count + 1)), two_dec(coord.x));
poly_xml.add_attribute(format!("y{}", (count + 1)), two_dec(coord.y));
}
//closed defaults to true, don't need to write it out unless it's false
if !poly.closed {
poly_xml.add_attribute("closed", poly.closed);
}
poly_xml.add_attribute("antialias", poly.antialias);
poly_xml.add_attribute("style", &poly.style);
poly_xml
}
}
impl ScaleEntity for Polygon {
fn scale(&mut self, fact_x: f64, fact_y: f64) {
self.coordinates.iter_mut().for_each(|coord| {
coord.x *= fact_x;
coord.y *= fact_y;
});
}
fn left_bound(&self) -> f64 {
let min_coord = self.coordinates.iter().min_by(|c1, c2| {
//if we get a None for the compare, then just returns Greater which will ignore it
//for finding the minimum
c1.x.partial_cmp(&c2.x)
.unwrap_or(std::cmp::Ordering::Greater)
});
if let Some(min_coord) = min_coord {
min_coord.x
} else {
0.0
}
}
fn right_bound(&self) -> f64 {
let max_coord = self.coordinates.iter().max_by(|c1, c2| {
//if we get a None for the compare, then just returns Less which will ignore it
//for finding the maximum
c1.x.partial_cmp(&c2.x).unwrap_or(std::cmp::Ordering::Less)
});
if let Some(max_coord) = max_coord {
max_coord.x
} else {
0.0
}
}
fn top_bound(&self) -> f64 {
let min_coord = self.coordinates.iter().min_by(|c1, c2| {
//if we get a None for the compare, then just returns Greater which will ignore it
//for finding the minimum
c1.y.partial_cmp(&c2.y)
.unwrap_or(std::cmp::Ordering::Greater)
});
if let Some(min_coord) = min_coord {
min_coord.y
} else {
0.0
}
}
fn bot_bound(&self) -> f64 {
let max_coord = self.coordinates.iter().max_by(|c1, c2| {
//if we get a None for the compare, then just returns Less which will ignore it
//for finding the maximum
c1.y.partial_cmp(&c2.y).unwrap_or(std::cmp::Ordering::Less)
});
if let Some(max_coord) = max_coord {
max_coord.y
} else {
0.0
}
}
}

@ -0,0 +1,76 @@
use super::{two_dec, FontInfo, ScaleEntity};
use dxf::entities;
use hex_color::HexColor;
use simple_xml_builder::XMLElement;
#[derive(Debug)]
pub struct Text {
rotation: f64,
value: String,
pub x: f64,
pub y: f64,
font: FontInfo,
color: HexColor,
}
impl From<(&entities::Text, HexColor)> for Text {
fn from((txt, color): (&entities::Text, HexColor)) -> Self {
Text {
x: txt.location.x,
y: -txt.location.y,
rotation: if txt.rotation.abs().round() as i64 % 360 != 0 {
txt.rotation - 180.0
} else {
0.0
},
color,
font: if &txt.text_style_name == "STANDARD" {
FontInfo::default()
} else {
//txt.text_style_name.clone()
FontInfo::default()
},
value: txt.value.clone(),
}
}
}
impl From<&Text> for XMLElement {
fn from(txt: &Text) -> Self {
let mut txt_xml: XMLElement = XMLElement::new("text");
txt_xml.add_attribute("x", two_dec(txt.x));
txt_xml.add_attribute("y", two_dec(txt.y));
txt_xml.add_attribute("rotation", two_dec(txt.rotation));
txt_xml.add_attribute("color", txt.color.display_rgb());
txt_xml.add_attribute("font", &txt.font);
txt_xml.add_attribute("text", &txt.value);
txt_xml
}
}
impl ScaleEntity for Text {
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 top_bound(&self) -> f64 {
self.y
}
fn right_bound(&self) -> f64 {
//need to be able to measure text size to get this
todo!()
}
fn bot_bound(&self) -> f64 {
//need to be able to measure text size to get this
todo!()
}
}
Loading…
Cancel
Save