From c72c0eebfb445abddc35e159efbbd153d851c608 Mon Sep 17 00:00:00 2001 From: Andrea Ficarra Date: Sun, 1 Nov 2026 20:38:04 +0100 Subject: [PATCH] SAVEAS portableFonts: Windows fonts for Linux text style fonts (#2576) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SAVEAS portableFonts: Windows fonts for Linux text style fonts A text style using a Linux TrueType font (Liberation, DejaVu, Open Sans) shows in AutoCAD on Windows with a substitute it picks. A save now names those styles on the command line (and as `nonPortableFonts` in the result), and SAVEAS {"portableFonts": true} writes the Windows equivalent in the file (Liberation -> Arial, Times New Roman, Courier New; DejaVu Sans -> Verdana; Open Sans -> Arial) while the drawing keeps its own fonts. Narrow and condensed families have no Windows stand-in (Arial Narrow comes with Office) and are kept and named. QSAVE takes no SAVEAS options. Tests: stand_in_tests in cmd/file.rs, tests/save_portable_fonts.rs. Co-Authored-By: Claude Opus 5.5 * Portable fonts kept in the file, applied by the writer Reworked after review. SAVEAS `portableFonts` writes a copy of the drawing with `Drawing::portable_fonts` set, and the DXF/DWG writer turns each style's Linux font into its Windows font, keeping the style's own font in the CADCraft FONT xdata already used for the built-in font. Reading the file gives the Linux fonts back (unless the font was changed since, in AutoCAD); the drawing itself is never changed. It applies to that save: a later QSAVE writes the drawing's fonts and says so, naming the styles and SAVEAS portableFonts. The family table moves to `cadcraft_fonts::windows`, used by the writer and the note. Styles from an xref aren't saved and aren't named. The parity docs are left to the maintainers (CLAUDE.md). Tests: crates/io/tests/it/portable_fonts.rs (DXF, DWG, a font changed in AutoCAD), crates/engine/tests/save_portable_fonts.rs, windows.rs tests. Co-Authored-By: Claude Opus 5.5 * Portable fonts: match the Windows font by file name A STYLE whose Windows font carries a path (C:\Windows\Fonts\…) still gets its own font back. Co-Authored-By: Claude Opus 5.5 * Portable fonts: say the font list again only when it changes A save still returns nonPortableFonts, but the command line note isn't repeated on every QSAVE of the same drawing. Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Opus 5.5 --- crates/doc/src/lib.rs | 5 ++ crates/engine/src/cmd/file.rs | 59 ++++++++++++++-- crates/engine/src/doc_state.rs | 4 ++ crates/engine/tests/save_portable_fonts.rs | 54 ++++++++++++++ crates/fonts/src/lib.rs | 1 + crates/fonts/src/windows.rs | 82 ++++++++++++++++++++++ crates/io/src/dxf_read/tables.rs | 10 +-- crates/io/src/dxf_write/sections.rs | 7 +- crates/io/tests/it/main.rs | 1 + crates/io/tests/it/portable_fonts.rs | 59 ++++++++++++++++ 10 files changed, 268 insertions(+), 14 deletions(-) create mode 100644 crates/engine/tests/save_portable_fonts.rs create mode 100644 crates/fonts/src/windows.rs create mode 100644 crates/io/tests/it/portable_fonts.rs diff --git a/crates/doc/src/lib.rs b/crates/doc/src/lib.rs index e07a08609..8f2a1e2e4 100644 --- a/crates/doc/src/lib.rs +++ b/crates/doc/src/lib.rs @@ -132,6 +132,10 @@ pub struct Drawing { /// Records of the file this drawing was read from that the reader could not place (a damaged /// file), counted by the section they came from. Never written to a file. pub skipped_records: BTreeMap, + /// A save option, set on the copy SAVEAS `portableFonts` writes: the file gets Windows fonts + /// for the Linux fonts of text styles, and each style keeps its own in CADCraft xdata, so the + /// drawing reopens with it. Never set on an open drawing. + pub portable_fonts: bool, } impl Default for Drawing { @@ -191,6 +195,7 @@ impl Drawing { read_notes: Vec::new(), refedit: None, skipped_records: BTreeMap::new(), + portable_fonts: false, } } diff --git a/crates/engine/src/cmd/file.rs b/crates/engine/src/cmd/file.rs index 1c3fbd5c8..234769ad4 100644 --- a/crates/engine/src/cmd/file.rs +++ b/crates/engine/src/cmd/file.rs @@ -85,12 +85,13 @@ pub fn deliver() -> Option { DELIVER.get().copied() } -/// How a drawing file is written: its version (`None`: the drawing's own, `$ACADVER`) and -/// binary instead of ASCII DXF. +/// How a drawing file is written: its version (`None`: the drawing's own, `$ACADVER`), binary +/// instead of ASCII DXF, and Windows fonts for Linux ones ([`cadcraft_doc::Drawing::portable_fonts`]). #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct SaveAs { pub version: Option, pub binary: bool, + pub portable_fonts: bool, } /// Write a drawing with [`SaveAs`] options (installed by hosts whose writer supports them). @@ -131,7 +132,12 @@ fn save_options(cmd: &str, p: &Value) -> Result { Some(Value::Bool(b)) => *b, Some(_) => return Err(bad(cmd, "`binary` must be true or false")), }; - Ok(SaveAs { version, binary }) + let portable_fonts = match p.get("portableFonts") { + None | Some(Value::Null) => false, + Some(Value::Bool(b)) => *b, + Some(_) => return Err(bad(cmd, "`portableFonts` must be true or false")), + }; + Ok(SaveAs { version, binary, portable_fonts }) } /// Whether `path` names a DXF file (or has no extension, which saves as DXF). @@ -148,7 +154,9 @@ fn write_bytes(s: &Session, cmd: &str, path: &str, opts: &SaveAs) -> Result Vec { CommandSpec::new("saveas", "Save As...", run_saveas_reporting) .menu(&["File", "Save As..."]) .key("Cmd+Shift+S") - .params("{path, version?: 2000|2004|2007|2010|2013|2018, binary?: bool}") + .params( + "{path, version?: 2000|2004|2007|2010|2013|2018, binary?: bool, portableFonts?: bool (Windows fonts for Linux ones, in this file)}", + ) .noundo(), CommandSpec::new("document.switch", "Switch Drawing", run_switch).params("{index}").enabled(always).noundo(), CommandSpec::new("document.bytes", "Drawing as bytes", run_bytes) - .params("{format?: dxf, version?: 2000…2018, binary?: bool} → {data: base64}") + .params("{format?: dxf, version?: 2000…2018, binary?: bool, portableFonts?: bool} → {data: base64}") .noundo(), CommandSpec::new("script", "Run Script...", run_script) .menu(&["Tools", "Run Script..."]) @@ -395,7 +405,7 @@ fn run_closeall(s: &mut Session, _p: &Value) -> Result { } pub(crate) fn save_to(s: &mut Session, path: &str) -> Result { - let opts = SaveAs { version: None, binary: s.state()?.binary_dxf && is_dxf(path) }; + let opts = SaveAs { binary: s.state()?.binary_dxf && is_dxf(path), ..SaveAs::default() }; save_as(s, "save", path, &opts) } @@ -677,6 +687,7 @@ fn run_saveas(s: &mut Session, p: &Value) -> Result { if is_drawing_file(&path) { r["version"] = json!(opts.version.unwrap_or_else(|| drawing_version(s, &path)).year()); r["binary"] = json!(opts.binary); + r["portableFonts"] = json!(opts.portable_fonts); } Ok(r) } @@ -799,9 +810,43 @@ fn save_reporting(s: &mut Session, p: &Value, run: fn(&mut Session, &Value) -> R if r.get("path").and_then(Value::as_str).is_some_and(drops_unknown) { report_not_saved(s, &mut r, &lost); } + if r.get("path").and_then(Value::as_str).is_some_and(is_drawing_file) { + report_fonts(s, &mut r); + } Ok(r) } +/// Name the text styles whose font in the saved file AutoCAD on Windows doesn't have: in the +/// result, and at the command line when the list changed since the last save. Styles from an xref +/// (`XREF|name`) aren't saved: AutoCAD takes them from the xref. +fn report_fonts(s: &mut Session, r: &mut Value) { + use cadcraft_fonts::windows::{missing_on_windows, windows_font, written_font}; + let portable = r.get("portableFonts").and_then(Value::as_bool).unwrap_or(false); + let Ok(d) = s.state().map(|st| st.drawing()) else { return }; + let fonts: Vec<(String, String)> = d + .text_styles + .iter() + .filter(|t| !t.name.contains('|') && missing_on_windows(written_font(&t.font, portable))) + .map(|t| (t.name.clone(), t.font.clone())) + .collect(); + let list = fonts.iter().map(|(style, font)| format!("{style} ({font})")).collect::>().join(", "); + let changed = s.state_mut().map(|st| std::mem::replace(&mut st.fonts_noted, list.clone()) != list).unwrap_or(true); + if fonts.is_empty() { + return; + } + if changed { + let hint = if fonts.iter().any(|(_, font)| windows_font(font).is_some()) { + " SAVEAS with portableFonts writes Windows fonts for those that have one." + } else { + "" + }; + s.echo(format!("Fonts AutoCAD on Windows doesn't have: {list}.{hint}")); + } + if let Some(o) = r.as_object_mut() { + o.insert("nonPortableFonts".into(), json!(fonts.iter().map(|(style, font)| json!({ "style": style, "font": font })).collect::>())); + } +} + fn run_qsave_reporting(s: &mut Session, p: &Value) -> Result { save_reporting(s, p, run_save) } diff --git a/crates/engine/src/doc_state.rs b/crates/engine/src/doc_state.rs index c70989a58..d11c6f0eb 100644 --- a/crates/engine/src/doc_state.rs +++ b/crates/engine/src/doc_state.rs @@ -35,6 +35,9 @@ pub struct DocState { /// The Block Editor (BEDIT), when it is open: `doc` is then the editor's drawing, and the /// drawing itself waits in it ([`DocState::drawing`]). pub block_edit: Option, + /// The fonts missing on Windows that a save last named: the next save names them again only + /// when they change. + pub(crate) fonts_noted: String, } static NEXT_UID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1); @@ -61,6 +64,7 @@ impl DocState { uid: NEXT_UID.fetch_add(1, std::sync::atomic::Ordering::Relaxed), binary_dxf: false, block_edit: None, + fonts_noted: String::new(), } } /// The drawing has changes not saved to its file, or the Block Editor has changes not yet diff --git a/crates/engine/tests/save_portable_fonts.rs b/crates/engine/tests/save_portable_fonts.rs new file mode 100644 index 000000000..a63016d60 --- /dev/null +++ b/crates/engine/tests/save_portable_fonts.rs @@ -0,0 +1,54 @@ +//! Text styles with Linux fonts AutoCAD on Windows doesn't have: a save names them, and SAVEAS +//! with portableFonts writes Windows fonts in that file while the drawing keeps its own. + +use cadcraft_doc::{Drawing, TextStyle}; +use cadcraft_engine::Session; +use cadcraft_engine::cmd::file::{IoHooks, set_io}; +use serde_json::json; + +fn dxf(d: &Drawing, _: &str) -> Result, String> { + Ok(cadcraft_io::write_dxf(d).into_bytes()) +} + +fn session() -> Session { + set_io(IoHooks { read: |_, _| Ok(Drawing::new_metric()), write: dxf, plot: None }); + let mut d = Drawing::new_metric(); + for (name, font) in [("Notes", "DejaVuSans.ttf"), ("Plain", "arial.ttf"), ("Tight", "Liberation Sans Narrow")] { + d.text_styles.push(TextStyle { name: name.into(), font: font.into(), ..TextStyle::default() }); + } + let mut s = Session::empty(); + s.open_drawing(d, "fonts.dxf", None); + s +} + +#[test] +fn portable_fonts_writes_windows_fonts_in_that_file() { + let mut s = session(); + let dir = std::env::temp_dir().join(format!("cadcraft-portable-fonts-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let path = |name: &str| dir.join(name).to_string_lossy().to_string(); + let file = |name: &str| std::fs::read_to_string(dir.join(name)).unwrap(); + let both = json!([{ "style": "Notes", "font": "DejaVuSans.ttf" }, { "style": "Tight", "font": "Liberation Sans Narrow" }]); + + // portableFonts writes Verdana for DejaVu Sans; the narrow style has no Windows font. + let r = s.execute("saveas", &json!({ "path": path("portable.dxf"), "portableFonts": true })).unwrap(); + assert_eq!(r["nonPortableFonts"], json!([{ "style": "Tight", "font": "Liberation Sans Narrow" }])); + assert_eq!(s.log.last().map(String::as_str), Some("Fonts AutoCAD on Windows doesn't have: Tight (Liberation Sans Narrow).")); + assert!(file("portable.dxf").contains("verdana.ttf")); + assert!(s.state().unwrap().drawing().text_styles.iter().any(|t| t.font == "DejaVuSans.ttf"), "the drawing keeps its fonts"); + + // A later save without it writes the drawing's fonts again, and says so. + let r = s.execute("qsave", &json!({})).unwrap(); + assert_eq!(r["nonPortableFonts"], both); + assert!(s.log.last().is_some_and(|l| l.ends_with("SAVEAS with portableFonts writes Windows fonts for those that have one.")), "{:?}", s.log); + assert!(!file("portable.dxf").contains("verdana.ttf")); + + // The same list isn't said again on every save. + let lines = s.log.len(); + let r = s.execute("qsave", &json!({})).unwrap(); + assert_eq!(r["nonPortableFonts"], both); + assert!(s.log.iter().skip(lines).all(|l| !l.starts_with("Fonts AutoCAD")), "{:?}", s.log); + + assert!(s.execute("saveas", &json!({ "path": path("bad.dxf"), "portableFonts": "yes" })).is_err()); + let _ = std::fs::remove_dir_all(&dir); +} diff --git a/crates/fonts/src/lib.rs b/crates/fonts/src/lib.rs index dd5c37597..beb8996ff 100644 --- a/crates/fonts/src/lib.rs +++ b/crates/fonts/src/lib.rs @@ -21,6 +21,7 @@ mod mtext; mod stroke; pub mod subset; pub mod ttf; +pub mod windows; use cadcraft_geom::{Bounds2, Vec2}; diff --git a/crates/fonts/src/windows.rs b/crates/fonts/src/windows.rs new file mode 100644 index 000000000..729a2eea0 --- /dev/null +++ b/crates/fonts/src/windows.rs @@ -0,0 +1,82 @@ +//! Linux font families that AutoCAD on Windows doesn't have, and the Windows fonts a drawing +//! saved with portable fonts gets instead. + +/// Family key (lower case, without spaces, hyphens or underscores; a longer key before a shorter +/// one it starts with) and the Windows fonts for its regular, bold, italic and bold italic styles. +const FAMILIES: [(&str, [&str; 4]); 7] = [ + ("liberationsans", ["arial.ttf", "arialbd.ttf", "ariali.ttf", "arialbi.ttf"]), + ("liberationserif", ["times.ttf", "timesbd.ttf", "timesi.ttf", "timesbi.ttf"]), + ("liberationmono", ["cour.ttf", "courbd.ttf", "couri.ttf", "courbi.ttf"]), + ("opensans", ["arial.ttf", "arialbd.ttf", "ariali.ttf", "arialbi.ttf"]), + ("dejavusansmono", ["cour.ttf", "courbd.ttf", "couri.ttf", "courbi.ttf"]), + ("dejavusans", ["verdana.ttf", "verdanab.ttf", "verdanai.ttf", "verdanaz.ttf"]), + ("dejavuserif", ["times.ttf", "timesbd.ttf", "timesi.ttf", "timesbi.ttf"]), +]; + +/// The family of a TrueType `font` (file or family name) from [`FAMILIES`], with the rest of its +/// key (the style: `bold`, `italic`, …). +fn family(font: &str) -> Option<(&'static [&'static str; 4], String)> { + let name = font.trim().rsplit(['/', '\\']).next().unwrap_or_default().to_lowercase(); + let stem = name.strip_suffix(".ttf").or_else(|| name.strip_suffix(".otf")).unwrap_or(&name); + if stem.contains('.') { + return None; // another kind of file, such as an SHX font + } + let key: String = stem.chars().filter(|c| !matches!(c, ' ' | '-' | '_')).collect(); + let (family, fonts) = FAMILIES.iter().find(|(f, _)| key.starts_with(f))?; + Some((fonts, key.get(family.len()..).unwrap_or_default().to_string())) +} + +/// Whether `font` is a Linux font AutoCAD on Windows doesn't have. +pub fn missing_on_windows(font: &str) -> bool { + family(font).is_some() +} + +/// The Windows font for a Linux `font`. Narrow and condensed styles have none (Arial Narrow comes +/// with Office, not with Windows). +pub fn windows_font(font: &str) -> Option<&'static str> { + let (fonts, style) = family(font)?; + if style.contains("narrow") || style.contains("condensed") { + return None; + } + let bold = style.contains("bold"); + let italic = style.contains("italic") || style.contains("oblique"); + fonts.get(usize::from(bold) + 2 * usize::from(italic)).copied() +} + +/// The font a drawing file gets for a text style's `font`: with `portable`, the Windows font for +/// a Linux one. +pub fn written_font(font: &str, portable: bool) -> &str { + if portable { windows_font(font).unwrap_or(font) } else { font } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn windows_fonts_by_file_or_family_name() { + assert_eq!(windows_font("DejaVuSans.ttf"), Some("verdana.ttf")); + assert_eq!(windows_font("/usr/share/fonts/truetype/LiberationSerif-Bold.ttf"), Some("timesbd.ttf")); + assert_eq!(windows_font("Liberation Mono"), Some("cour.ttf")); + assert_eq!(windows_font("LiberationMono-BoldItalic.ttf"), Some("courbi.ttf")); + assert_eq!(windows_font("OpenSans-SemiBold.ttf"), Some("arialbd.ttf")); + assert_eq!(windows_font("Open Sans"), Some("arial.ttf")); + assert_eq!(windows_font("DejaVuSansMono-BoldOblique.ttf"), Some("courbi.ttf")); + assert_eq!(windows_font("DejaVuSerif-Italic.ttf"), Some("timesi.ttf")); + // Narrow and condensed styles are Linux fonts with no Windows one. + for font in ["Liberation Sans Narrow", "LiberationSansNarrow-Bold.ttf", "OpenSans-CondensedBold.ttf", "DejaVuSansCondensed.ttf"] { + assert!(missing_on_windows(font) && windows_font(font).is_none(), "{font}"); + } + // Fonts AutoCAD has, other kinds of file, or names that aren't fonts. + for font in ["arial.ttf", "txt", "dejavusans.shx", "", "/", "Liberation"] { + assert!(!missing_on_windows(font) && windows_font(font).is_none(), "{font}"); + } + } + + #[test] + fn written_font_only_changes_with_portable_fonts() { + assert_eq!(written_font("DejaVuSans.ttf", true), "verdana.ttf"); + assert_eq!(written_font("DejaVuSans.ttf", false), "DejaVuSans.ttf"); + assert_eq!(written_font("Liberation Sans Narrow", true), "Liberation Sans Narrow"); + } +} diff --git a/crates/io/src/dxf_read/tables.rs b/crates/io/src/dxf_read/tables.rs index 8a57f7f9e..ee42b8a58 100644 --- a/crates/io/src/dxf_read/tables.rs +++ b/crates/io/src/dxf_read/tables.rs @@ -4,6 +4,7 @@ use super::{ Color, DashElement, DimStyle, Drawing, HVal, HashMap, Layer, Linetype, Lineweight, NamedView, Rgb, Rx, Tag, TextStyle, Ucs, Vec2, Vec3, VportConfig, VportTile, records, }; +use cadcraft_fonts::windows::windows_font; pub(super) fn header(tags: &[Tag], d: &mut Drawing) { let mut i = 0; @@ -66,9 +67,9 @@ fn annotative(tags: &[Tag]) -> bool { } /// A text style's font: CADCraft's own font name from `CADCRAFT` xdata (`1000 FONT`, name) -/// while the font file is still the `txt` written for other readers; the TrueType family of the -/// style's `ACAD` xdata (`1000` typeface) when it has no font file (how AutoCAD stores TrueType -/// styles); otherwise the file. +/// while the font file is still the `txt` or Windows font written for other readers; the TrueType +/// family of the style's `ACAD` xdata (`1000` typeface) when it has no font file (how AutoCAD +/// stores TrueType styles); otherwise the file. fn style_font(tags: &[Tag], file: String) -> String { if file.trim().is_empty() && let Some(face) = @@ -82,8 +83,9 @@ fn style_font(tags: &[Tag], file: String) -> String { _ => None, }); let stem = file.trim().to_ascii_lowercase(); + let base = stem.rsplit(['/', '\\']).next().unwrap_or_default(); match own { - Some(name) if !name.trim().is_empty() && (stem == "txt" || stem == "txt.shx") => name, + Some(name) if !name.trim().is_empty() && (stem == "txt" || stem == "txt.shx" || windows_font(&name) == Some(base)) => name, _ => file, } } diff --git a/crates/io/src/dxf_write/sections.rs b/crates/io/src/dxf_write/sections.rs index 9a21ef7a9..2d45a532f 100644 --- a/crates/io/src/dxf_write/sections.rs +++ b/crates/io/src/dxf_write/sections.rs @@ -90,9 +90,10 @@ fn write_tables(w: &mut W, plan: TableWritePlan<'_>) -> HashMap w.f(50, s.oblique.to_degrees()); w.i(71, if s.backwards { 2 } else { 0 } | if s.upside_down { 4 } else { 0 }); w.f(42, 0.2); - // The built-in font goes out as `txt`, which every reader has; CADCraft's own name - // travels in `CADCRAFT` xdata (`1000 FONT`, name) so the style reopens with it. - let file = if s.font == cadcraft_fonts_name() { "txt" } else { s.font.as_str() }; + // The built-in font goes out as `txt`, which every reader has, and a Linux font as its + // Windows one with portable fonts; CADCraft's own name travels in `CADCRAFT` xdata + // (`1000 FONT`, name) so the style reopens with it. + let file = if s.font == cadcraft_fonts_name() { "txt" } else { cadcraft_fonts::windows::written_font(&s.font, d.portable_fonts) }; w.s(3, file); w.s(4, &s.big_font); if s.annotative { diff --git a/crates/io/tests/it/main.rs b/crates/io/tests/it/main.rs index 52f37068a..e0ff04f97 100644 --- a/crates/io/tests/it/main.rs +++ b/crates/io/tests/it/main.rs @@ -54,6 +54,7 @@ mod pdf_read; mod plot_layout_scale; mod plot_model_png_scale; mod plot_styles; +mod portable_fonts; mod region_roundtrip; mod roundtrip; mod roundtrip_audit; diff --git a/crates/io/tests/it/portable_fonts.rs b/crates/io/tests/it/portable_fonts.rs new file mode 100644 index 000000000..070c376e8 --- /dev/null +++ b/crates/io/tests/it/portable_fonts.rs @@ -0,0 +1,59 @@ +//! Portable fonts: a drawing saved with them gets the Windows font for each Linux font in its +//! STYLE table, keeps its own in CADCraft xdata, and reopens with it, from DXF and DWG. + +use cadcraft_doc::{Drawing, TextStyle}; +use cadcraft_io::{read, read_dxf, write, write_dxf}; + +fn drawing(portable_fonts: bool) -> Drawing { + let mut d = Drawing { portable_fonts, ..Drawing::new_metric() }; + // A Linux font, and one with no Windows font. + for (name, font) in [("Notes", "DejaVuSans-Bold.ttf"), ("Tight", "Liberation Sans Narrow")] { + d.text_styles.push(TextStyle { name: name.into(), font: font.into(), ..TextStyle::default() }); + } + d +} + +/// (style, font) of the drawing's text styles. +fn fonts(d: &Drawing) -> Vec<(String, String)> { + d.text_styles.iter().map(|t| (t.name.clone(), t.font.clone())).collect() +} + +/// (style, font file) of the STYLE records of DXF text. +fn style_files(dxf: &str) -> Vec<(String, String)> { + let mut out: Vec<(String, String)> = Vec::new(); + let mut style = false; + for t in cadcraft_dxf::parse(dxf.as_bytes()).unwrap() { + match t.code { + 0 => style = t.str() == "STYLE", + 2 if style => out.push((t.str(), String::new())), + 3 if style => out.last_mut().unwrap().1 = t.str(), + _ => {} + } + } + out +} + +#[test] +fn styles_get_windows_fonts_and_reopen_with_their_own() { + let d = drawing(true); + let dxf = write_dxf(&d); + let files = style_files(&dxf); + for (style, file) in [("Notes", "verdanab.ttf"), ("Tight", "Liberation Sans Narrow")] { + assert!(files.contains(&(style.into(), file.into())), "{style}: {files:?}"); + } + for back in [read_dxf(dxf.as_bytes()).unwrap(), read(&write(&d, "t.dwg").unwrap(), "t.dwg").unwrap()] { + assert_eq!(fonts(&back), fonts(&drawing(false))); + } + // The Windows font with a path is still the one written for the style's own. + let path = read_dxf(dxf.replace("verdanab.ttf", "C:\\Windows\\Fonts\\verdanab.ttf").as_bytes()).unwrap(); + assert_eq!(fonts(&path), fonts(&drawing(false))); + // A font changed in AutoCAD since (the xdata left as it was) is the font read. + let edited = read_dxf(dxf.replace("verdanab.ttf", "times.ttf").as_bytes()).unwrap(); + assert!(fonts(&edited).contains(&("Notes".into(), "times.ttf".into()))); +} + +#[test] +fn without_portable_fonts_the_file_has_the_drawings_fonts() { + let files = style_files(&write_dxf(&drawing(false))); + assert!(files.contains(&("Notes".into(), "DejaVuSans-Bold.ttf".into())), "{files:?}"); +}