forked from 1b-agent/opencadstudio-upstream
PDFSHXTEXT: recognise SHX text geometry as MText
Adds PDFSHXTEXT with the PDF Text Recognition Settings dialog, its command-line form, the PDFSHXLAYER / PDFSHXBESTFONT / PDFSHXTEXTSETTINGS variables and the profile settings they are kept in. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
39c3e9af58
commit
907e501765
17 files changed
+2140
No files matched your search
@@ -6596,3 +6596,24 @@ ucs-follow =
|
||||
.prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>:
|
||||
.paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS.
|
||||
.invalid = UCSFOLLOW: only 0 or 1 is allowed.
|
||||
|
||||
pdf-shx =
|
||||
.title = PDF Text Recognition Settings
|
||||
.preview = Preview
|
||||
.sample = Sample text 1234
|
||||
.fonts = SHX fonts to compare
|
||||
.enabled-count = { $on } / { $all } on
|
||||
.fonts-hint = The font at the top is tried first; drag a row or move it with the arrows.
|
||||
.add-font = + Add font...
|
||||
.add-title = Add SHX Font
|
||||
.shx-files = SHX fonts
|
||||
.layer = Layer of the created text
|
||||
.current-layer = Current layer
|
||||
.geometry-layer = Layer of the geometry
|
||||
.recognition = Recognition
|
||||
.threshold = Recognition threshold
|
||||
.best-font = Use best matching font
|
||||
.hint = Turn on only the fonts that look like the geometry; groups below the threshold are not converted.
|
||||
.defaults = Restore defaults
|
||||
.apply = Apply
|
||||
.help = PDFSHXTEXT compares the selected geometry with the fonts that are on, top first, and converts a group with the first font whose recognised share reaches the threshold (with "Use best matching font", with the best one).
|
||||
@@ -6518,3 +6518,24 @@ ucs-follow =
|
||||
.prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>:
|
||||
.paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS.
|
||||
.invalid = UCSFOLLOW: only 0 or 1 is allowed.
|
||||
|
||||
pdf-shx =
|
||||
.title = PDF Metin Tanıma Ayarları
|
||||
.preview = Önizleme
|
||||
.sample = Örnek metin 1234
|
||||
.fonts = Karşılaştırılacak SHX yazı tipleri
|
||||
.enabled-count = { $on } / { $all } etkin
|
||||
.fonts-hint = Üstteki yazı tipi önce denenir; sürükleyin veya oklarla taşıyın.
|
||||
.add-font = + Yazı tipi ekle...
|
||||
.add-title = SHX Yazı Tipi Ekle
|
||||
.shx-files = SHX yazı tipleri
|
||||
.layer = Oluşan metnin katmanı
|
||||
.current-layer = Geçerli katman
|
||||
.geometry-layer = Geometrinin katmanı
|
||||
.recognition = Tanıma
|
||||
.threshold = Başarı eşiği
|
||||
.best-font = En iyi eşleşen yazı tipini kullan
|
||||
.hint = Yalnız geometriye benzeyen yazı tiplerini açık bırakın; eşiğin altında kalan gruplar dönüştürülmez.
|
||||
.defaults = Varsayılana dön
|
||||
.apply = Uygula
|
||||
.help = PDFSHXTEXT seçilen geometriyi açık yazı tipleriyle, en üsttekinden başlayarak karşılaştırır; bir grubu, tanınan oranı eşiğe ulaşan ilk yazı tipiyle ("En iyi eşleşen yazı tipini kullan" açıkken en iyisiyle) dönüştürür.
|
||||
@@ -690,6 +690,7 @@ impl OpenCADStudio {
|
||||
self.open_blocks_palette(Some(tab));
|
||||
}
|
||||
|
||||
cmd if self.dispatch_pdf_shx_text(cmd, i) => {}
|
||||
"PDFIMPORT" => {
|
||||
use crate::command::CadCommand;
|
||||
let command = crate::modules::insert::pdf_import::PdfImportCommand::new();
|
||||
|
||||
@@ -22,6 +22,7 @@ mod pc_colormap;
|
||||
mod pc_extract;
|
||||
mod pdf_dialogs;
|
||||
pub(crate) mod pdf_import;
|
||||
mod pdf_shx_text;
|
||||
mod pdf_underlay;
|
||||
mod plotvars;
|
||||
mod point_cloud;
|
||||
@@ -775,6 +776,9 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
"PDFIMPORTFILTER",
|
||||
"PDFIMPORTLAYERS",
|
||||
"PDFIMPORTIMAGEPATH",
|
||||
"PDFSHXLAYER",
|
||||
"PDFSHXBESTFONT",
|
||||
"PDFSHXTEXTSETTINGS",
|
||||
"XDWGFADECTL",
|
||||
"POINTCLOUDCLIPFRAME",
|
||||
"POINTCLOUDDENSITY",
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
//! PDFSHXTEXT: the selection, the settings (dialog or command line) and the
|
||||
//! conversion of the recognised lines to MTEXT as one undo step.
|
||||
|
||||
use super::*;
|
||||
use crate::modules::insert::pdf_shx_text::{
|
||||
self as shx, ShxSettingsCommand, Stroke, APPLY, OPEN_SETTINGS,
|
||||
};
|
||||
use crate::ui::window::pdf_shx_dialog::{ShxDialogMsg, ShxSettingsState};
|
||||
|
||||
const INTRO: &str = "Select geometry to convert to text...";
|
||||
|
||||
impl OpenCADStudio {
|
||||
fn drawing_path(&self, i: usize) -> Option<String> {
|
||||
self.tabs[i]
|
||||
.current_path
|
||||
.as_ref()
|
||||
.map(|p| p.to_string_lossy().into_owned())
|
||||
.or_else(|| self.tabs[i].scene.document.source_path.clone())
|
||||
}
|
||||
|
||||
/// PDFSHXTEXT: a pre-selection converts at once; otherwise the objects are
|
||||
/// asked for. PDFSHXTEXTSETTINGS 1 shows the settings first (once).
|
||||
pub(in crate::app) fn start_pdf_shx_text(&mut self, i: usize) {
|
||||
let mut settings = shx::settings();
|
||||
self.command_line.push_info(INTRO);
|
||||
if settings.show_settings {
|
||||
settings.show_settings = false;
|
||||
shx::set_settings(settings);
|
||||
self.save_config();
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.open_pdf_shx_settings(i);
|
||||
return;
|
||||
}
|
||||
let preselected = self.tabs[i].scene.selected_handles_in_order();
|
||||
if !preselected.is_empty() {
|
||||
self.command_line
|
||||
.push_output(&format!("{} found", preselected.len()));
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.convert_pdf_shx_text(i, preselected);
|
||||
return;
|
||||
}
|
||||
self.start_pdf_shx_gather(i);
|
||||
}
|
||||
|
||||
/// The selection prompt (also where the settings return to).
|
||||
pub(in crate::app) fn start_pdf_shx_gather(&mut self, i: usize) {
|
||||
use crate::command::CadCommand;
|
||||
use crate::modules::draw::select::SelectObjectsCommand;
|
||||
self.tabs[i].scene.deselect_all();
|
||||
let command = SelectObjectsCommand::with_settings(
|
||||
"PDFSHXTEXT",
|
||||
APPLY,
|
||||
"Select objects or [SEttings]:",
|
||||
OPEN_SETTINGS,
|
||||
);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
|
||||
/// The SEttings option: the dialog, or its command-line form with FILEDIA 0.
|
||||
pub(in crate::app) fn open_pdf_shx_settings(&mut self, i: usize) {
|
||||
if crate::sys::file_dia() {
|
||||
let sample = crate::tr!("pdf-shx", "sample");
|
||||
self.pdf_shx_dialog = Some(ShxSettingsState::new(
|
||||
shx::settings(),
|
||||
self.drawing_path(i),
|
||||
sample,
|
||||
));
|
||||
self.active_modal = Some(crate::app::ModalKind::PdfShxSettings);
|
||||
} else {
|
||||
self.start_pdf_shx_settings_prompt(i);
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::app) fn start_pdf_shx_settings_prompt(&mut self, i: usize) {
|
||||
use crate::command::CadCommand;
|
||||
let command = ShxSettingsCommand::new();
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
|
||||
pub(in crate::app) fn update_pdf_shx_dialog(&mut self, message: ShxDialogMsg) -> Task<Message> {
|
||||
let Some(state) = self.pdf_shx_dialog.as_mut() else {
|
||||
return Task::none();
|
||||
};
|
||||
if !matches!(message, ShxDialogMsg::DragDrop(_) | ShxDialogMsg::DragStart(_)) {
|
||||
state.dragging = None;
|
||||
}
|
||||
let count = state.settings.fonts.len();
|
||||
match message {
|
||||
ShxDialogMsg::Select(index) => {
|
||||
state.selected = index.min(count.saturating_sub(1));
|
||||
state.refresh_preview();
|
||||
}
|
||||
ShxDialogMsg::Toggle(index, on) => {
|
||||
if let Some(font) = state.settings.fonts.get_mut(index) {
|
||||
font.enabled = on;
|
||||
}
|
||||
state.selected = index.min(count.saturating_sub(1));
|
||||
state.refresh_preview();
|
||||
}
|
||||
ShxDialogMsg::Up => {
|
||||
let from = state.selected;
|
||||
state.move_font(from, from.saturating_sub(1));
|
||||
}
|
||||
ShxDialogMsg::Down => {
|
||||
let from = state.selected;
|
||||
state.move_font(from, (from + 1).min(count.saturating_sub(1)));
|
||||
}
|
||||
ShxDialogMsg::Remove => {
|
||||
if count > 1 && state.selected < count {
|
||||
state.settings.fonts.remove(state.selected);
|
||||
state.selected = state.selected.min(count - 2);
|
||||
state.refresh_preview();
|
||||
}
|
||||
}
|
||||
ShxDialogMsg::Add => {
|
||||
return Task::perform(
|
||||
async {
|
||||
crate::sys::file_dialog()
|
||||
.set_title(crate::tr!("pdf-shx", "add-title"))
|
||||
.add_filter(crate::tr!("pdf-shx", "shx-files"), &["shx", "SHX"])
|
||||
.pick_file()
|
||||
.await
|
||||
.map(|h| crate::sys::handle_path(&h))
|
||||
},
|
||||
|path| Message::PdfShxDialog(ShxDialogMsg::Added(path)),
|
||||
);
|
||||
}
|
||||
ShxDialogMsg::Added(path) => {
|
||||
let Some(path) = path else {
|
||||
return Task::none();
|
||||
};
|
||||
let full = path.to_string_lossy().into_owned();
|
||||
let label = shx::font_label(&full);
|
||||
match state
|
||||
.settings
|
||||
.fonts
|
||||
.iter()
|
||||
.position(|f| f.label().eq_ignore_ascii_case(&label))
|
||||
{
|
||||
// A listed font is turned on and chosen rather than added twice.
|
||||
Some(index) => {
|
||||
state.settings.fonts[index].enabled = true;
|
||||
state.selected = index;
|
||||
}
|
||||
None => {
|
||||
let builtin = shx::FONT_NAMES.iter().any(|n| n.eq_ignore_ascii_case(&label));
|
||||
state.settings.fonts.push(shx::ShxFontEntry {
|
||||
name: if builtin { label } else { full },
|
||||
enabled: true,
|
||||
});
|
||||
state.selected = state.settings.fonts.len() - 1;
|
||||
}
|
||||
}
|
||||
state.refresh_preview();
|
||||
}
|
||||
ShxDialogMsg::DragStart(index) => state.dragging = Some(index),
|
||||
ShxDialogMsg::DragDrop(index) => {
|
||||
if let Some(from) = state.dragging.take() {
|
||||
state.move_font(from, index);
|
||||
state.refresh_preview();
|
||||
}
|
||||
}
|
||||
ShxDialogMsg::Threshold(value) => state.set_threshold(value),
|
||||
ShxDialogMsg::ThresholdText(text) => {
|
||||
// A value outside 1-100 or not a number is ignored, the box
|
||||
// keeps what was typed until it is fixed.
|
||||
if let Some(v) = text.trim().parse::<u8>().ok().filter(|v| (1..=100).contains(v)) {
|
||||
state.settings.threshold = v;
|
||||
}
|
||||
state.threshold_text = text;
|
||||
}
|
||||
ShxDialogMsg::Step(dir) => {
|
||||
let value = (i16::from(state.settings.threshold) + i16::from(dir)).clamp(1, 100);
|
||||
state.set_threshold(value as u8);
|
||||
}
|
||||
ShxDialogMsg::CurrentLayer(on) => state.settings.current_layer = on,
|
||||
ShxDialogMsg::BestFont(on) => state.settings.best_font = on,
|
||||
ShxDialogMsg::Sample(text) => {
|
||||
state.sample = text;
|
||||
state.refresh_preview();
|
||||
}
|
||||
ShxDialogMsg::Defaults => {
|
||||
let show = state.settings.show_settings;
|
||||
state.settings = shx::ShxTextSettings {
|
||||
show_settings: show,
|
||||
..Default::default()
|
||||
};
|
||||
state.selected = 0;
|
||||
state.threshold_text = state.settings.threshold.to_string();
|
||||
state.refresh_preview();
|
||||
}
|
||||
ShxDialogMsg::Help => {
|
||||
self.command_line
|
||||
.push_info(&crate::tr!("pdf-shx", "help"));
|
||||
}
|
||||
ShxDialogMsg::Apply => {
|
||||
let Some(state) = self.pdf_shx_dialog.take() else {
|
||||
return Task::none();
|
||||
};
|
||||
shx::set_settings(state.settings);
|
||||
self.save_config();
|
||||
self.close_active_modal();
|
||||
let i = self.active_tab;
|
||||
self.start_pdf_shx_gather(i);
|
||||
return self.focus_cmd_input();
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Recognise the text in `handles` and replace it with MTEXT.
|
||||
pub(in crate::app) fn convert_pdf_shx_text(&mut self, i: usize, handles: Vec<codec::Handle>) {
|
||||
let document = &self.tabs[i].scene.document;
|
||||
let mut strokes = Vec::new();
|
||||
for handle in handles {
|
||||
let Some(entity) = document.get_entity(handle) else {
|
||||
continue;
|
||||
};
|
||||
let layer = entity.common().layer.clone();
|
||||
for points in shx::entity_strokes(entity) {
|
||||
if !points.is_empty() {
|
||||
strokes.push(Stroke {
|
||||
handle,
|
||||
layer: layer.clone(),
|
||||
points,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
let settings = shx::settings();
|
||||
let drawing = self.drawing_path(i);
|
||||
self.command_line.push_output("Converting geometry to text...");
|
||||
let result = shx::recognize(&strokes, &settings, |name| {
|
||||
shx::resolve_font(name, drawing.as_deref())
|
||||
});
|
||||
for (n, group) in result.groups.iter().enumerate() {
|
||||
self.command_line.push_output(&if group.converted {
|
||||
format!("Group {}: Succeeded - {} {}%", n + 1, group.font, group.percent)
|
||||
} else {
|
||||
format!(
|
||||
"Group {}: Failed to meet success threshold - {} {}%",
|
||||
n + 1,
|
||||
group.font,
|
||||
group.percent
|
||||
)
|
||||
});
|
||||
}
|
||||
let converted = result.groups.iter().filter(|g| g.converted).count();
|
||||
self.command_line.push_output(&format!(
|
||||
"{converted} of {} groups converted to text",
|
||||
result.groups.len()
|
||||
));
|
||||
self.command_line
|
||||
.push_output(&format!("{} text objects created", result.lines.len()));
|
||||
if result.lines.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.push_undo_snapshot(i, "PDFSHXTEXT");
|
||||
let current_layer = {
|
||||
let header = &self.tabs[i].scene.document.header;
|
||||
if header.current_layer_name.is_empty() {
|
||||
self.tabs[i].active_layer.clone()
|
||||
} else {
|
||||
header.current_layer_name.clone()
|
||||
}
|
||||
};
|
||||
let scene = &mut self.tabs[i].scene;
|
||||
let mut erase: Vec<codec::Handle> = Vec::new();
|
||||
for line in result.lines {
|
||||
// A style named after the font, made when the drawing lacks it.
|
||||
if !scene.document.text_styles.contains(&line.font) {
|
||||
let mut style = codec::tables::TextStyle::new(line.font.as_str());
|
||||
style.handle = scene.document.allocate_handle();
|
||||
style.font_file = if line.font_spec.to_ascii_lowercase().ends_with(".shx") {
|
||||
line.font_spec.clone()
|
||||
} else {
|
||||
format!("{}.shx", line.font)
|
||||
};
|
||||
let _ = scene.document.text_styles.add(style);
|
||||
}
|
||||
let mut mtext = codec::entities::MText::new();
|
||||
mtext.value = line.value;
|
||||
mtext.insertion_point =
|
||||
codec::types::Vector3::new(line.insertion[0], line.insertion[1], 0.0);
|
||||
mtext.height = line.height;
|
||||
mtext.rectangle_width = 0.0;
|
||||
mtext.attachment_point = codec::entities::AttachmentPoint::BottomLeft;
|
||||
mtext.style = line.font;
|
||||
mtext.common.layer = if settings.current_layer {
|
||||
current_layer.clone()
|
||||
} else {
|
||||
line.layer
|
||||
};
|
||||
scene.add_entity(codec::EntityType::MText(mtext));
|
||||
erase.extend(line.handles);
|
||||
}
|
||||
erase.sort();
|
||||
erase.dedup();
|
||||
scene.erase_entities(&erase);
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
}
|
||||
|
||||
/// The private commands PDFSHXTEXT runs between its prompts.
|
||||
/// Whether `cmd` was one of them.
|
||||
pub(in crate::app) fn dispatch_pdf_shx_text(&mut self, cmd: &str, i: usize) -> bool {
|
||||
match cmd {
|
||||
"PDFSHXTEXT" => self.start_pdf_shx_text(i),
|
||||
APPLY => {
|
||||
let handles = self.tabs[i].scene.selected_handles_in_order();
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.convert_pdf_shx_text(i, handles);
|
||||
}
|
||||
OPEN_SETTINGS => self.open_pdf_shx_settings(i),
|
||||
shx::RESUME => self.start_pdf_shx_gather(i),
|
||||
shx::SAVE_SETTINGS => {
|
||||
self.save_config();
|
||||
self.start_pdf_shx_settings_prompt(i);
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -1224,6 +1224,9 @@ impl OpenCADStudio {
|
||||
| "PDFIMPORTFILTER"
|
||||
| "PDFIMPORTLAYERS"
|
||||
| "PDFIMPORTIMAGEPATH"
|
||||
| "PDFSHXLAYER"
|
||||
| "PDFSHXBESTFONT"
|
||||
| "PDFSHXTEXTSETTINGS"
|
||||
| "XDWGFADECTL"
|
||||
| "POINTCLOUDCLIPFRAME"
|
||||
| "POINTCLOUDDENSITY"
|
||||
@@ -1465,6 +1468,68 @@ impl OpenCADStudio {
|
||||
}
|
||||
return Some(self.finish_dispatch(cmd));
|
||||
}
|
||||
// PDFSHXTEXT's profile switches, taken as 1 / 0 or ON / OFF;
|
||||
// PDFSHXTEXTSETTINGS shows its value as a number.
|
||||
if matches!(
|
||||
name.as_str(),
|
||||
"PDFSHXLAYER" | "PDFSHXBESTFONT" | "PDFSHXTEXTSETTINGS"
|
||||
) {
|
||||
use crate::modules::insert::pdf_shx_text::{set_settings, settings};
|
||||
let mut s = settings();
|
||||
let on = match name.as_str() {
|
||||
"PDFSHXLAYER" => s.current_layer,
|
||||
"PDFSHXBESTFONT" => s.best_font,
|
||||
_ => s.show_settings,
|
||||
};
|
||||
let current = if name == "PDFSHXTEXTSETTINGS" {
|
||||
i16::from(on).to_string()
|
||||
} else if on {
|
||||
"ON".to_string()
|
||||
} else {
|
||||
"OFF".to_string()
|
||||
};
|
||||
let parsed = value.as_deref().map(|v| {
|
||||
match v
|
||||
.trim()
|
||||
.trim_start_matches('_')
|
||||
.to_ascii_uppercase()
|
||||
.as_str()
|
||||
{
|
||||
"1" | "ON" => Some(true),
|
||||
"0" | "OFF" => Some(false),
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
match parsed {
|
||||
Some(Some(v)) => {
|
||||
if v != on {
|
||||
match name.as_str() {
|
||||
"PDFSHXLAYER" => s.current_layer = v,
|
||||
"PDFSHXBESTFONT" => s.best_font = v,
|
||||
_ => s.show_settings = v,
|
||||
}
|
||||
set_settings(s);
|
||||
self.save_config();
|
||||
}
|
||||
}
|
||||
Some(None) => {
|
||||
self.command_line.push_error(
|
||||
crate::t!("Requires 0 or 1 only, or option keyword.").as_ref(),
|
||||
);
|
||||
self.command_line.push_output(&format!(
|
||||
"Enter new value for {name} <{current}>:"
|
||||
));
|
||||
self.pending_setvar = Some(name.clone());
|
||||
}
|
||||
None => {
|
||||
self.command_line.push_output(&format!(
|
||||
"Enter new value for {name} <{current}>:"
|
||||
));
|
||||
self.pending_setvar = Some(name.clone());
|
||||
}
|
||||
}
|
||||
return Some(self.finish_dispatch(cmd));
|
||||
}
|
||||
if name == "LAYOUTCREATEVIEWPORT" {
|
||||
// A profile switch shown and taken as ON / OFF or 1 / 0.
|
||||
let current = if crate::scene::layout_create_viewport() {
|
||||
|
||||
@@ -106,6 +106,7 @@ pub(super) const NAMES: &[&str] = &[
|
||||
"view_background",
|
||||
"image_adjust",
|
||||
"refedit_dialog",
|
||||
"pdf_shx_settings",
|
||||
"pdf_layer_toggle",
|
||||
"pc_manager_toggle",
|
||||
"pc_manager",
|
||||
@@ -2088,6 +2089,72 @@ impl OpenCADStudio {
|
||||
_ => return Err(bad()),
|
||||
}
|
||||
}
|
||||
// The PDF Text Recognition Settings dialog, one edit per call:
|
||||
// "select=<n>", "toggle=<n>,0|1", "up", "down", "remove",
|
||||
// "add=<path to .shx>", "move=<from>,<to>" (dragging a row),
|
||||
// "threshold=<1-100>", "threshold_text=<typed>", "step=+1|-1",
|
||||
// "layer=current|geometry", "best=0|1", "sample=<text>",
|
||||
// "defaults", "apply" or "cancel" (rows count from 0).
|
||||
"pdf_shx_settings" => {
|
||||
use crate::ui::window::pdf_shx_dialog::ShxDialogMsg as S;
|
||||
if self.active_modal != Some(crate::app::ModalKind::PdfShxSettings) {
|
||||
return Err(failure(
|
||||
"no_dialog",
|
||||
"The PDF Text Recognition Settings dialog is not open",
|
||||
));
|
||||
}
|
||||
let spec = string(req, "value")?;
|
||||
let (key, value) = spec.split_once('=').unwrap_or((spec, ""));
|
||||
let bad = || failure("invalid_value", spec);
|
||||
let index = |v: &str| v.trim().parse::<usize>().map_err(|_| bad());
|
||||
let flag = |v: &str| match v.trim() {
|
||||
"1" => Ok(true),
|
||||
"0" => Ok(false),
|
||||
_ => Err(bad()),
|
||||
};
|
||||
match key {
|
||||
"cancel" => Message::CloseModal,
|
||||
"move" => {
|
||||
let (from, to) = value.split_once(',').ok_or_else(bad)?;
|
||||
let (from, to) = (index(from)?, index(to)?);
|
||||
return Ok(Task::batch([
|
||||
self.update(Message::PdfShxDialog(S::DragStart(from))),
|
||||
self.update(Message::PdfShxDialog(S::DragDrop(to))),
|
||||
]));
|
||||
}
|
||||
_ => Message::PdfShxDialog(match key {
|
||||
"apply" => S::Apply,
|
||||
"defaults" => S::Defaults,
|
||||
"up" => S::Up,
|
||||
"down" => S::Down,
|
||||
"remove" => S::Remove,
|
||||
"select" => S::Select(index(value)?),
|
||||
"toggle" => {
|
||||
let (row, on) = value.split_once(',').ok_or_else(bad)?;
|
||||
S::Toggle(index(row)?, flag(on)?)
|
||||
}
|
||||
"add" => S::Added(Some(std::path::PathBuf::from(value.trim()))),
|
||||
"threshold" => S::Threshold(
|
||||
value
|
||||
.trim()
|
||||
.parse::<u8>()
|
||||
.ok()
|
||||
.filter(|v| (1..=100).contains(v))
|
||||
.ok_or_else(bad)?,
|
||||
),
|
||||
"threshold_text" => S::ThresholdText(value.to_string()),
|
||||
"step" => S::Step(value.trim().parse::<i8>().map_err(|_| bad())?),
|
||||
"layer" => match value.trim() {
|
||||
"current" => S::CurrentLayer(true),
|
||||
"geometry" => S::CurrentLayer(false),
|
||||
_ => return Err(bad()),
|
||||
},
|
||||
"best" => S::BestFont(flag(value)?),
|
||||
"sample" => S::Sample(value.to_string()),
|
||||
_ => return Err(bad()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
"pdf_dialog_ok" => {
|
||||
use crate::ui::window::pdf_dialogs::PdfDialogMsg;
|
||||
Message::PdfDialog(match self.active_modal {
|
||||
|
||||
@@ -595,6 +595,7 @@ pub(super) struct OpenCADStudio {
|
||||
image_adjust: Option<crate::ui::window::pdf_dialogs::ImageAdjustState>,
|
||||
/// The Edit Reference dialog (REFEDIT).
|
||||
refedit_dialog: Option<crate::ui::window::refedit_dialog::RefEditDialogState>,
|
||||
pdf_shx_dialog: Option<crate::ui::window::pdf_shx_dialog::ShxSettingsState>,
|
||||
underlay_layers: Option<crate::ui::window::pdf_dialogs::UnderlayLayersState>,
|
||||
pdf_import_settings: Option<crate::modules::insert::pdf_import::PdfImportSettings>,
|
||||
pdf_import_file: Option<crate::ui::window::pdf_dialogs::PdfImportFileState>,
|
||||
@@ -2176,6 +2177,7 @@ pub enum ModalKind {
|
||||
ViewBackground,
|
||||
ImageAdjust,
|
||||
RefEdit,
|
||||
PdfShxSettings,
|
||||
UnderlayLayers,
|
||||
PdfImportSettings,
|
||||
PdfImportFile,
|
||||
@@ -4398,6 +4400,7 @@ pub enum Message {
|
||||
FieldDialog(crate::ui::window::field_dialog::FieldDialogMsg),
|
||||
/// An edit in the Edit Reference dialog.
|
||||
RefEditDialog(crate::ui::window::refedit_dialog::RefEditDialogMsg),
|
||||
PdfShxDialog(crate::ui::window::pdf_shx_dialog::ShxDialogMsg),
|
||||
/// A click or search in the Point Cloud Manager.
|
||||
PcManager(crate::ui::window::pc_manager::PcManagerMsg),
|
||||
Count(crate::ui::window::count_palette::CountMsg),
|
||||
@@ -4614,6 +4617,7 @@ impl OpenCADStudio {
|
||||
view_background: None,
|
||||
image_adjust: None,
|
||||
refedit_dialog: None,
|
||||
pdf_shx_dialog: None,
|
||||
underlay_layers: None,
|
||||
pdf_import_settings: None,
|
||||
pdf_import_file: None,
|
||||
|
||||
@@ -457,6 +457,9 @@ pub struct UserSettings {
|
||||
/// LAYOUTCREATEVIEWPORT: a layout opened for the first time gets a
|
||||
/// viewport.
|
||||
pub layout_create_viewport: bool,
|
||||
/// PDFSHXTEXT's recognition settings (fonts, threshold, PDFSHXLAYER,
|
||||
/// PDFSHXBESTFONT, PDFSHXTEXTSETTINGS).
|
||||
pub pdf_shx_text: crate::modules::insert::pdf_shx_text::ShxTextSettings,
|
||||
/// Nested-copy symbol handling: false inserts, true binds.
|
||||
pub ncopy_bind: bool,
|
||||
/// Last Options page; unknown saved names fall back without rejecting the config.
|
||||
@@ -928,6 +931,7 @@ impl Default for UserSettings {
|
||||
ucs_ortho: true,
|
||||
ucs_view: true,
|
||||
layout_create_viewport: true,
|
||||
pdf_shx_text: Default::default(),
|
||||
ncopy_bind: false,
|
||||
cursor_type: CursorType::Crosshair,
|
||||
crosshair_color: None,
|
||||
|
||||
@@ -825,6 +825,7 @@ impl OpenCADStudio {
|
||||
ucs_ortho: crate::modules::view::view_cmd::ucs_ortho(),
|
||||
ucs_view: crate::modules::view::view_cmd::ucs_view(),
|
||||
layout_create_viewport: crate::scene::layout_create_viewport(),
|
||||
pdf_shx_text: crate::modules::insert::pdf_shx_text::settings(),
|
||||
ncopy_bind: self.ncopy_bind,
|
||||
cursor_type: self.cursor_type,
|
||||
crosshair_color: self.crosshair_color,
|
||||
@@ -969,6 +970,7 @@ impl OpenCADStudio {
|
||||
crate::modules::view::view_cmd::set_ucs_ortho(s.ucs_ortho);
|
||||
crate::modules::view::view_cmd::set_ucs_view(s.ucs_view);
|
||||
crate::scene::set_layout_create_viewport(s.layout_create_viewport);
|
||||
crate::modules::insert::pdf_shx_text::set_settings(s.pdf_shx_text.clone());
|
||||
self.ncopy_bind = s.ncopy_bind;
|
||||
self.cursor_type = s.cursor_type;
|
||||
self.crosshair_color = s.crosshair_color;
|
||||
|
||||
@@ -2409,6 +2409,7 @@ impl OpenCADStudio {
|
||||
Message::AttdefDialog(message) => self.on_attdef_dialog(message),
|
||||
Message::FieldDialog(message) => self.on_field_dialog(message),
|
||||
Message::RefEditDialog(message) => self.update_refedit_dialog(message),
|
||||
Message::PdfShxDialog(message) => self.update_pdf_shx_dialog(message),
|
||||
Message::PcManager(message) => self.update_pc_manager(message),
|
||||
Message::Count(message) => self.on_count(message),
|
||||
Message::SheetSet(message) => self.on_sheet_set(message),
|
||||
@@ -9587,6 +9588,14 @@ impl OpenCADStudio {
|
||||
if self.active_modal == Some(super::ModalKind::RefEdit) {
|
||||
self.refedit_dialog = None;
|
||||
}
|
||||
// Cancel returns to PDFSHXTEXT's selection prompt.
|
||||
if self.active_modal == Some(super::ModalKind::PdfShxSettings) {
|
||||
self.pdf_shx_dialog = None;
|
||||
self.close_active_modal();
|
||||
let i = self.active_tab;
|
||||
self.start_pdf_shx_gather(i);
|
||||
return self.focus_cmd_input();
|
||||
}
|
||||
// Cancelling the section settings drops the waiting extraction.
|
||||
if self.active_modal == Some(super::ModalKind::PcSection) {
|
||||
crate::modules::insert::pc_extract::drop_job();
|
||||
|
||||
@@ -44,6 +44,7 @@ impl OpenCADStudio {
|
||||
),
|
||||
Some(K::ImageAdjust) => crate::tr!("image-adjust", "title"),
|
||||
Some(K::RefEdit) => crate::tr!("ref-edit", "dialog-title"),
|
||||
Some(K::PdfShxSettings) => crate::tr!("pdf-shx", "title"),
|
||||
Some(K::UnderlayLayers) => crate::t!("Underlay Layers").into_owned(),
|
||||
Some(K::PdfImportSettings) => crate::t!("PDF Import Settings").into_owned(),
|
||||
Some(K::PdfImportFile) => crate::t!("Import PDF").into_owned(),
|
||||
@@ -557,6 +558,12 @@ impl OpenCADStudio {
|
||||
crate::ui::window::pdf_dialogs::image_adjust(state, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::PdfShxSettings => {
|
||||
let state = self.pdf_shx_dialog.as_ref()?;
|
||||
sized_flow(ex, 780, 600, |flow| {
|
||||
crate::ui::window::pdf_shx_dialog::view(state, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::RefEdit => {
|
||||
let state = self.refedit_dialog.as_ref()?;
|
||||
sized_flow(ex, 560, 470, |flow| {
|
||||
|
||||
@@ -44,6 +44,8 @@ pub struct SelectObjectsCommand {
|
||||
/// The kinds of object the command takes; the host drops the rest of
|
||||
/// each completed selection silently, and Enter on nothing ends quietly.
|
||||
keep: Option<fn(&codec::EntityType) -> bool>,
|
||||
/// A command-specific SEttings option: the private command it runs.
|
||||
settings_cmd: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl SelectObjectsCommand {
|
||||
@@ -61,6 +63,7 @@ impl SelectObjectsCommand {
|
||||
prompt_text: None,
|
||||
associative_dimensions_only: false,
|
||||
keep: None,
|
||||
settings_cmd: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +82,7 @@ impl SelectObjectsCommand {
|
||||
prompt_text: None,
|
||||
associative_dimensions_only: false,
|
||||
keep: None,
|
||||
settings_cmd: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +101,7 @@ impl SelectObjectsCommand {
|
||||
prompt_text: None,
|
||||
associative_dimensions_only: false,
|
||||
keep: None,
|
||||
settings_cmd: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +120,7 @@ impl SelectObjectsCommand {
|
||||
prompt_text: None,
|
||||
associative_dimensions_only: false,
|
||||
keep: None,
|
||||
settings_cmd: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +156,19 @@ impl SelectObjectsCommand {
|
||||
command
|
||||
}
|
||||
|
||||
/// The command's own wording with a SEttings option that runs
|
||||
/// `settings_cmd` (which brings the gather back when it is done).
|
||||
pub fn with_settings(
|
||||
prompt_cmd: &str,
|
||||
pending_cmd: &str,
|
||||
prompt_text: &'static str,
|
||||
settings_cmd: &'static str,
|
||||
) -> Self {
|
||||
let mut command = Self::with_prompt(prompt_cmd, pending_cmd, prompt_text);
|
||||
command.settings_cmd = Some(settings_cmd);
|
||||
command
|
||||
}
|
||||
|
||||
pub fn auto_constrain(pending_cmd: &str) -> Self {
|
||||
let mut command = Self::new(pending_cmd);
|
||||
command.auto_constrain_settings = true;
|
||||
@@ -242,6 +261,9 @@ impl CadCommand for SelectObjectsCommand {
|
||||
// they work typed as well — these buttons only surface them, which is what
|
||||
// the on-screen keyboard-less case needs.
|
||||
fn options(&self) -> Vec<CmdOption> {
|
||||
if self.settings_cmd.is_some() {
|
||||
return vec![CmdOption::new(t!("SEttings").as_ref(), "SE")];
|
||||
}
|
||||
if self.commit_on_enter && self.show_options {
|
||||
if self.auto_constrain_settings {
|
||||
return vec![CmdOption::new(t!("Settings").as_ref(), "S")];
|
||||
@@ -297,6 +319,12 @@ impl CadCommand for SelectObjectsCommand {
|
||||
if !self.commit_on_enter {
|
||||
return None;
|
||||
}
|
||||
let token = text.trim().trim_start_matches('_').to_uppercase();
|
||||
if let Some(cmd) = self.settings_cmd {
|
||||
if token.len() >= 2 && "SETTINGS".starts_with(token.as_str()) {
|
||||
return Some(CmdResult::Dispatch(cmd.to_string()));
|
||||
}
|
||||
}
|
||||
match text.trim().to_uppercase().as_str() {
|
||||
"S" | "SETTINGS" if self.auto_constrain_settings => {
|
||||
Some(CmdResult::OpenAutoConstrainSettings)
|
||||
|
||||
@@ -22,6 +22,7 @@ pub(crate) mod pc_stylize;
|
||||
pub(crate) mod pdf_attach;
|
||||
pub(crate) mod pdf_clip;
|
||||
pub(crate) mod pdf_import;
|
||||
pub(crate) mod pdf_shx_text;
|
||||
pub(crate) mod picker;
|
||||
mod snap_underlays;
|
||||
pub(crate) mod solid3d_cmds;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -27,6 +27,7 @@ pub mod open_progress;
|
||||
pub mod options;
|
||||
pub mod pc_manager;
|
||||
pub mod pdf_dialogs;
|
||||
pub mod pdf_shx_dialog;
|
||||
pub mod plot;
|
||||
pub mod plugin_manager;
|
||||
pub mod print_all;
|
||||
|
||||
@@ -0,0 +1,400 @@
|
||||
//! PDF Text Recognition Settings (PDFSHXTEXT's SEttings): a preview of the
|
||||
//! chosen font with a sample text on top; the SHX fonts to compare as a
|
||||
//! numbered list of switches (dragged or moved with the arrows of the chosen
|
||||
//! row, added from a file, removed); the layer the text goes on; the
|
||||
//! recognition threshold and "use best matching font".
|
||||
|
||||
use iced::widget::{button, column, container, mouse_area, row, scrollable, text, text_input, toggler, Space};
|
||||
use iced::{Background, Border, Element, Fill, Length, Theme};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::modules::insert::pdf_shx_text::ShxTextSettings;
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
use crate::ui::style::common::muted_style;
|
||||
use crate::ui::style::form::{button_style, dialog_button, field_style};
|
||||
use crate::ui::window::pdf_dialogs::{accent_text, card_style, well_style};
|
||||
|
||||
/// One edit in the PDF Text Recognition Settings dialog.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ShxDialogMsg {
|
||||
Select(usize),
|
||||
Toggle(usize, bool),
|
||||
Up,
|
||||
Down,
|
||||
Remove,
|
||||
Add,
|
||||
Added(Option<std::path::PathBuf>),
|
||||
/// The drag handle of a row was pressed.
|
||||
DragStart(usize),
|
||||
/// The mouse was released over a row.
|
||||
DragDrop(usize),
|
||||
Threshold(u8),
|
||||
ThresholdText(String),
|
||||
Step(i8),
|
||||
CurrentLayer(bool),
|
||||
BestFont(bool),
|
||||
Sample(String),
|
||||
Defaults,
|
||||
Help,
|
||||
Apply,
|
||||
}
|
||||
|
||||
fn msg(m: ShxDialogMsg) -> Message {
|
||||
Message::PdfShxDialog(m)
|
||||
}
|
||||
|
||||
pub struct ShxSettingsState {
|
||||
pub settings: ShxTextSettings,
|
||||
pub selected: usize,
|
||||
pub threshold_text: String,
|
||||
pub sample: String,
|
||||
pub dragging: Option<usize>,
|
||||
/// The folder of the drawing, where a font may sit next to it.
|
||||
pub drawing: Option<String>,
|
||||
pub preview: Vec<WireModel>,
|
||||
}
|
||||
|
||||
impl ShxSettingsState {
|
||||
pub fn new(settings: ShxTextSettings, drawing: Option<String>, sample: String) -> Self {
|
||||
let mut state = Self {
|
||||
threshold_text: settings.threshold.to_string(),
|
||||
settings,
|
||||
selected: 0,
|
||||
sample,
|
||||
dragging: None,
|
||||
drawing,
|
||||
preview: Vec::new(),
|
||||
};
|
||||
state.refresh_preview();
|
||||
state
|
||||
}
|
||||
|
||||
/// The sample text in the chosen font.
|
||||
pub fn refresh_preview(&mut self) {
|
||||
self.preview.clear();
|
||||
let Some(font) = self.settings.fonts.get(self.selected) else {
|
||||
return;
|
||||
};
|
||||
let spec = crate::modules::insert::pdf_shx_text::resolve_font(
|
||||
&font.name,
|
||||
self.drawing.as_deref(),
|
||||
);
|
||||
let (strokes, _) = crate::scene::text::lff::tessellate_text_ex(
|
||||
[0.0, 0.0],
|
||||
9.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
&spec,
|
||||
&self.sample,
|
||||
);
|
||||
self.preview = strokes
|
||||
.into_iter()
|
||||
.filter(|s| s.len() >= 2)
|
||||
.map(|s| {
|
||||
WireModel::solid(
|
||||
"pdfshx_preview".into(),
|
||||
s.into_iter().map(|p| [p[0], p[1], 0.0]).collect(),
|
||||
[0.86, 0.86, 0.86, 1.0],
|
||||
false,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
pub fn set_threshold(&mut self, value: u8) {
|
||||
self.settings.threshold = value.clamp(1, 100);
|
||||
self.threshold_text = self.settings.threshold.to_string();
|
||||
}
|
||||
|
||||
/// Moves the font at `from` to `to`, keeping it chosen.
|
||||
pub fn move_font(&mut self, from: usize, to: usize) {
|
||||
let n = self.settings.fonts.len();
|
||||
if from >= n || to >= n || from == to {
|
||||
return;
|
||||
}
|
||||
let font = self.settings.fonts.remove(from);
|
||||
self.settings.fonts.insert(to, font);
|
||||
self.selected = to;
|
||||
}
|
||||
}
|
||||
|
||||
fn section<'a>(title: String, aside: Option<String>, content: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
|
||||
let mut head = row![text(title.to_uppercase()).size(10).style(accent_text)]
|
||||
.align_y(iced::Center);
|
||||
if let Some(aside) = aside {
|
||||
head = head
|
||||
.push(Space::new().width(Fill))
|
||||
.push(text(aside).size(10).style(muted_style));
|
||||
}
|
||||
container(column![head, content.into()].spacing(8))
|
||||
.padding([10, 12])
|
||||
.width(Fill)
|
||||
.style(card_style)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn small_button<'a>(label: &'static str, message: ShxDialogMsg, danger: bool) -> Element<'a, Message> {
|
||||
button(
|
||||
text(label)
|
||||
.size(11)
|
||||
.align_x(iced::Center)
|
||||
.style(move |theme: &Theme| text::Style {
|
||||
color: Some(if danger {
|
||||
iced::Color::from_rgb8(0xff, 0x5a, 0x4f)
|
||||
} else {
|
||||
theme.palette().background.base.text
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.on_press(msg(message))
|
||||
.style(button_style(false))
|
||||
.padding([2, 8])
|
||||
.into()
|
||||
}
|
||||
|
||||
fn font_row<'a>(state: &'a ShxSettingsState, index: usize) -> Element<'a, Message> {
|
||||
let font = &state.settings.fonts[index];
|
||||
let chosen = index == state.selected;
|
||||
let grip = mouse_area(
|
||||
container(text("⠿").size(12).style(muted_style)).padding([0, 2]),
|
||||
)
|
||||
.on_press(msg(ShxDialogMsg::DragStart(index)))
|
||||
.interaction(iced::mouse::Interaction::Grab);
|
||||
let switch = toggler(font.enabled)
|
||||
.size(16.0)
|
||||
.on_toggle(move |on| msg(ShxDialogMsg::Toggle(index, on)));
|
||||
let enabled = font.enabled;
|
||||
let name = text(font.label()).size(12).style(move |theme: &Theme| {
|
||||
if enabled {
|
||||
text::Style::default()
|
||||
} else {
|
||||
muted_style(theme)
|
||||
}
|
||||
});
|
||||
let mut line = row![
|
||||
text(format!("{}", index + 1))
|
||||
.size(10)
|
||||
.style(muted_style)
|
||||
.width(Length::Fixed(16.0))
|
||||
.align_x(iced::alignment::Horizontal::Right),
|
||||
grip,
|
||||
switch,
|
||||
button(name.width(Fill))
|
||||
.on_press(msg(ShxDialogMsg::Select(index)))
|
||||
.style(button::text)
|
||||
.padding([2, 4])
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center);
|
||||
if chosen {
|
||||
line = line
|
||||
.push(small_button("↑", ShxDialogMsg::Up, false))
|
||||
.push(small_button("↓", ShxDialogMsg::Down, false))
|
||||
.push(small_button("✕", ShxDialogMsg::Remove, true));
|
||||
}
|
||||
let body = container(line)
|
||||
.padding([3, 6])
|
||||
.width(Fill)
|
||||
.style(move |theme: &Theme| {
|
||||
if chosen {
|
||||
container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().primary.base.color.scale_alpha(0.13),
|
||||
)),
|
||||
border: Border {
|
||||
width: 1.0,
|
||||
radius: 5.0.into(),
|
||||
color: theme.palette().primary.base.color.scale_alpha(0.45),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
container::Style::default()
|
||||
}
|
||||
});
|
||||
mouse_area(body)
|
||||
.on_release(msg(ShxDialogMsg::DragDrop(index)))
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn view<'a>(
|
||||
state: &'a ShxSettingsState,
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
) -> Element<'a, Message> {
|
||||
let s = &state.settings;
|
||||
let chosen_label = s
|
||||
.fonts
|
||||
.get(state.selected)
|
||||
.map(|f| f.label())
|
||||
.unwrap_or_default();
|
||||
let preview = container(crate::ui::window::block_palette::preview_canvas(
|
||||
&state.preview,
|
||||
86.0,
|
||||
))
|
||||
.padding([6, 10])
|
||||
.width(Fill)
|
||||
.style(well_style);
|
||||
let sample = text_input("", &state.sample)
|
||||
.on_input(|v| msg(ShxDialogMsg::Sample(v)))
|
||||
.size(12)
|
||||
.padding([5, 8])
|
||||
.width(Length::Fixed(270.0))
|
||||
.style(field_style);
|
||||
let preview_card = container(
|
||||
column![
|
||||
row![
|
||||
text(format!("{} · {}", crate::tr!("pdf-shx", "preview"), chosen_label).to_uppercase())
|
||||
.size(10)
|
||||
.style(accent_text),
|
||||
Space::new().width(Fill),
|
||||
sample
|
||||
]
|
||||
.align_y(iced::Center),
|
||||
preview
|
||||
]
|
||||
.spacing(8),
|
||||
)
|
||||
.padding([10, 12])
|
||||
.width(Fill)
|
||||
.style(card_style);
|
||||
|
||||
let enabled = s.fonts.iter().filter(|f| f.enabled).count();
|
||||
let mut list = column![].spacing(2);
|
||||
for index in 0..s.fonts.len() {
|
||||
list = list.push(font_row(state, index));
|
||||
}
|
||||
let add = button(text(crate::tr!("pdf-shx", "add-font")).size(11))
|
||||
.on_press(msg(ShxDialogMsg::Add))
|
||||
.padding([5, 12])
|
||||
.style(|theme: &Theme, status| {
|
||||
let mut style = button_style(false)(theme, status);
|
||||
style.border = Border {
|
||||
width: 1.0,
|
||||
radius: 14.0.into(),
|
||||
color: theme.palette().primary.base.color,
|
||||
};
|
||||
style
|
||||
});
|
||||
let fonts_card = section(
|
||||
crate::tr!("pdf-shx", "fonts"),
|
||||
Some(crate::tr!(
|
||||
"pdf-shx",
|
||||
"enabled-count",
|
||||
on = enabled.to_string(),
|
||||
all = s.fonts.len().to_string()
|
||||
)),
|
||||
column![
|
||||
text(crate::tr!("pdf-shx", "fonts-hint")).size(10).style(muted_style),
|
||||
scrollable(list).height(Length::Fixed(250.0)),
|
||||
add,
|
||||
]
|
||||
.spacing(8),
|
||||
);
|
||||
|
||||
let segment = |current: bool, label: String| {
|
||||
button(text(label).size(12).width(Fill).align_x(iced::Center))
|
||||
.on_press(msg(ShxDialogMsg::CurrentLayer(current)))
|
||||
.style(button_style(s.current_layer == current))
|
||||
.padding([6, 8])
|
||||
.width(Fill)
|
||||
};
|
||||
let layer_card = section(
|
||||
crate::tr!("pdf-shx", "layer"),
|
||||
None,
|
||||
container(
|
||||
row![
|
||||
segment(true, crate::tr!("pdf-shx", "current-layer")),
|
||||
segment(false, crate::tr!("pdf-shx", "geometry-layer")),
|
||||
]
|
||||
.spacing(2),
|
||||
)
|
||||
.padding(2)
|
||||
.width(Fill)
|
||||
.style(well_style),
|
||||
);
|
||||
|
||||
let slide = iced::widget::slider(1..=100u8, s.threshold, |v| msg(ShxDialogMsg::Threshold(v)))
|
||||
.height(16.0)
|
||||
.width(Fill);
|
||||
let step = |dir: i8| {
|
||||
button(text(if dir < 0 { "\u{2212}" } else { "+" }).size(12))
|
||||
.on_press(msg(ShxDialogMsg::Step(dir)))
|
||||
.style(button_style(false))
|
||||
.padding([2, 8])
|
||||
};
|
||||
let value = text_input("", &state.threshold_text)
|
||||
.on_input(|v| msg(ShxDialogMsg::ThresholdText(v)))
|
||||
.size(11)
|
||||
.padding([3, 4])
|
||||
.width(Length::Fixed(36.0))
|
||||
.align_x(iced::alignment::Horizontal::Right)
|
||||
.style(field_style);
|
||||
let stepper = container(
|
||||
row![step(-1), value, text("%").size(11), step(1)]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.padding(2)
|
||||
.style(well_style);
|
||||
let recognition_card = section(
|
||||
crate::tr!("pdf-shx", "recognition"),
|
||||
None,
|
||||
column![
|
||||
text(crate::tr!("pdf-shx", "threshold")).size(11).style(muted_style),
|
||||
row![slide, stepper].spacing(10).align_y(iced::Center),
|
||||
row![
|
||||
toggler(s.best_font)
|
||||
.size(16.0)
|
||||
.on_toggle(|on| msg(ShxDialogMsg::BestFont(on))),
|
||||
text(crate::tr!("pdf-shx", "best-font")).size(12).width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
text(crate::tr!("pdf-shx", "hint")).size(10).style(muted_style),
|
||||
]
|
||||
.spacing(10),
|
||||
);
|
||||
|
||||
let defaults = button(
|
||||
text(crate::tr!("pdf-shx", "defaults"))
|
||||
.size(11)
|
||||
.style(accent_text),
|
||||
)
|
||||
.on_press(msg(ShxDialogMsg::Defaults))
|
||||
.style(button::text)
|
||||
.padding([5, 4]);
|
||||
let footer = row![
|
||||
button(text("?").size(12))
|
||||
.on_press(msg(ShxDialogMsg::Help))
|
||||
.style(button_style(false))
|
||||
.padding([5, 11]),
|
||||
defaults,
|
||||
Space::new().width(Fill),
|
||||
dialog_button(crate::t!("Cancel"), Message::CloseModal, false),
|
||||
dialog_button(
|
||||
std::borrow::Cow::Owned(crate::tr!("pdf-shx", "apply")),
|
||||
msg(ShxDialogMsg::Apply),
|
||||
true
|
||||
),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center);
|
||||
|
||||
column![
|
||||
preview_card,
|
||||
row![
|
||||
container(fonts_card).width(Length::FillPortion(11)),
|
||||
column![layer_card, recognition_card]
|
||||
.spacing(10)
|
||||
.width(Length::FillPortion(7)),
|
||||
]
|
||||
.spacing(10),
|
||||
footer
|
||||
]
|
||||
.spacing(10)
|
||||
.padding([10, 12])
|
||||
.width(sizing.width)
|
||||
.into()
|
||||
}
|
||||
Reference in new issue
Block a user