From f62c1cfafe03eff36be1486754f604d457f6acb3 Mon Sep 17 00:00:00 2001 From: FredApps <153173854+FredApps@users.noreply.github.com> Date: Sun, 1 Nov 2026 12:54:00 +0100 Subject: [PATCH] refactor(text): share source font names and selection defaults Reuse the existing subset-tag parser through the engine facade and share the fallback formatting state. Co-Authored-By: Codex GPT-6.1 --- crates/edit/src/lib.rs | 1 + crates/edit/src/text.rs | 3 +- crates/edit/src/text/fallback.rs | 2 +- crates/engine/src/lib.rs | 1 + crates/ui-egui/src/tools/edit_text_ui.rs | 61 ++++++++++-------------- 5 files changed, 31 insertions(+), 37 deletions(-) diff --git a/crates/edit/src/lib.rs b/crates/edit/src/lib.rs index fb0ea5a7f1..aa11617c14 100644 --- a/crates/edit/src/lib.rs +++ b/crates/edit/src/lib.rs @@ -35,6 +35,7 @@ pub type Rgb = [f64; 3]; pub mod backdrop; pub use backdrop::{Backdrop, backdrops_left_off, page_backdrop, page_backdrops}; +pub use text::strip_subset_tag; mod background; pub use background::page_background; pub mod objects; diff --git a/crates/edit/src/text.rs b/crates/edit/src/text.rs index 98824c7694..0599b8ed38 100644 --- a/crates/edit/src/text.rs +++ b/crates/edit/src/text.rs @@ -24,6 +24,7 @@ use crate::EditError; mod regions; #[path = "text_session.rs"] mod session; +pub use fallback::strip_subset_tag; use regions::{alignment_frame, group_blocks, is_centred, rewrite_styled_block}; pub use session::TextSession; @@ -47,7 +48,7 @@ pub use editor::{TextEditLayout, TextEditorFont, register_text_font, text_edit_l pub(crate) use fallback::SOURCE_SPACE_CALLS; use fallback::{ MAX_TYPE3_GLYPHS, Type3Glyph, fallback_face, needs_type3, no_fallback_font, no_glyph_for, same_base_font, same_font, source_encodes, - source_family, source_program, source_show, source_space, source_width, strip_subset_tag, type3_faces, type3_glyph, write_type3_glyphs, + source_family, source_program, source_show, source_space, source_width, type3_faces, type3_glyph, write_type3_glyphs, }; #[cfg(test)] use fallback::{add_type3_font, is_type3_fallback}; diff --git a/crates/edit/src/text/fallback.rs b/crates/edit/src/text/fallback.rs index b8f45a221f..78f9014a20 100644 --- a/crates/edit/src/text/fallback.rs +++ b/crates/edit/src/text/fallback.rs @@ -163,7 +163,7 @@ pub(crate) fn same_base_font(a: &str, b: &str) -> bool { } /// `ABCDEF+Arial` → `Arial`. A name without a subset tag is returned unchanged. -pub(crate) fn strip_subset_tag(name: &str) -> &str { +pub fn strip_subset_tag(name: &str) -> &str { let bytes = name.as_bytes(); let is_tag = bytes.get(6) == Some(&b'+') && bytes.get(..6).is_some_and(|tag| tag.iter().all(u8::is_ascii_uppercase)); if is_tag { name.get(7..).unwrap_or(name) } else { name } diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index ce8d3c9cec..fe139164be 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -65,6 +65,7 @@ use std::sync::Arc; pub use links::{ MAX_REMOTE_DOCUMENT_BYTES, RemoteDocumentPathError, read_remote_document, remote_document_path_is_absolute, resolve_remote_document_path, }; +pub use pdfcraft_edit::strip_subset_tag; pub use signature_image::{ImageSignaturePreview, SignatureImage}; pub use signature_scan::{PhotoRect, ScanError, SignaturePhoto, Turn}; pub use stamp_preview::{CommentLayer, StampPreview}; diff --git a/crates/ui-egui/src/tools/edit_text_ui.rs b/crates/ui-egui/src/tools/edit_text_ui.rs index c6e6116824..1b589af179 100644 --- a/crates/ui-egui/src/tools/edit_text_ui.rs +++ b/crates/ui-egui/src/tools/edit_text_ui.rs @@ -240,10 +240,7 @@ impl LineEditor { } let base = self.source.as_ref().map(|(block, _)| block.base_font.as_str()).unwrap_or_default(); // "ABCDEF+Calibri" is a subset of Calibri. - let name = match base.split_once('+') { - Some((tag, rest)) if tag.len() == 6 && tag.bytes().all(|b| b.is_ascii_uppercase()) => rest, - _ => base, - }; + let name = pdfcraft_engine::strip_subset_tag(base); if name.is_empty() { tl!("Keep source fonts").to_string() } else { name.to_string() } } @@ -363,16 +360,20 @@ impl LineEditor { self.original = self.text.clone(); } + fn current_formatting(&self, sources: &[(String, String)]) -> SelectionFormatting { + SelectionFormatting { + font_shown: self.font_shown(sources), + size: Some(self.look.size), + family: Some(self.look.family), + bold: Some(self.look.bold), + italic: Some(self.look.italic), + color: Some(self.look.color), + } + } + pub fn selection_formatting(&self, sources: &[(String, String)]) -> SelectionFormatting { let Some((block, lines)) = &self.source else { - return SelectionFormatting { - font_shown: self.font_shown(sources), - size: Some(self.look.size), - family: Some(self.look.family), - bold: Some(self.look.bold), - italic: Some(self.look.italic), - color: Some(self.look.color), - }; + return self.current_formatting(sources); }; let spans = block.edited_spans(&self.text); @@ -383,26 +384,16 @@ impl LineEditor { _ => spans.iter().filter_map(|(_, idx)| lines.get(*idx)).collect(), }; - if matching_lines.is_empty() { - return SelectionFormatting { - font_shown: self.font_shown(sources), - size: Some(self.look.size), - family: Some(self.look.family), - bold: Some(self.look.bold), - italic: Some(self.look.italic), - color: Some(self.look.color), - }; - } + let Some(first) = matching_lines.first().copied() else { + return self.current_formatting(sources); + }; let line_font = |l: &pdfcraft_engine::TextLine| -> String { if let Some((_, face)) = sources.iter().find(|(res, _)| res == &l.font) { return face.clone(); } let base = l.base_font.as_str(); - let name = match base.split_once('+') { - Some((tag, rest)) if tag.len() == 6 && tag.bytes().all(|b| b.is_ascii_uppercase()) => rest, - _ => base, - }; + let name = pdfcraft_engine::strip_subset_tag(base); if let Some((_, face)) = sources.iter().find(|(_, face)| face.as_str() == name) { return face.clone(); } @@ -414,7 +405,7 @@ impl LineEditor { if name.is_empty() { tl!("Keep source fonts").to_string() } else { name.to_string() } }; - let first_font = line_font(matching_lines[0]); + let first_font = line_font(first); let same_font = matching_lines.iter().skip(1).all(|l| line_font(l) == first_font); let font_shown = if same_font { if self.font_resource.is_some() || self.look.family != self.look0.family || self.family_chosen { @@ -426,19 +417,19 @@ impl LineEditor { tl!("Mixed").to_string() }; - let first_size = (matching_lines[0].size * 10.0).round() / 10.0; + let first_size = (first.size * 10.0).round() / 10.0; let same_size = matching_lines.iter().skip(1).all(|l| ((l.size * 10.0).round() / 10.0 - first_size).abs() < 0.05); let size = if same_size { Some(first_size) } else { None }; - let first_look = look_of_line(matching_lines[0]); + let first_look = look_of_line(first); let same_family = matching_lines.iter().skip(1).all(|l| look_of_line(l).family == first_look.family); let family = if same_family { Some(first_look.family) } else { None }; - let same_bold = matching_lines.iter().skip(1).all(|l| l.bold == matching_lines[0].bold); - let bold = if same_bold { Some(matching_lines[0].bold) } else { None }; - let same_italic = matching_lines.iter().skip(1).all(|l| l.italic == matching_lines[0].italic); - let italic = if same_italic { Some(matching_lines[0].italic) } else { None }; - let same_color = matching_lines.iter().skip(1).all(|l| l.color == matching_lines[0].color); - let color = if same_color { Some(matching_lines[0].color) } else { None }; + let same_bold = matching_lines.iter().skip(1).all(|l| l.bold == first.bold); + let bold = if same_bold { Some(first.bold) } else { None }; + let same_italic = matching_lines.iter().skip(1).all(|l| l.italic == first.italic); + let italic = if same_italic { Some(first.italic) } else { None }; + let same_color = matching_lines.iter().skip(1).all(|l| l.color == first.color); + let color = if same_color { Some(first.color) } else { None }; SelectionFormatting { font_shown, size, family, bold, italic, color } }