Merge pull request #1811 from ramox81/codex/txt2mtxt

TXT2MTXT: combine text into MText with a settings dialog

# Conflicts:
#	locales/ar-SA/opencadstudio.ftl
#	locales/bg-BG/opencadstudio.ftl
#	locales/cs-CZ/opencadstudio.ftl
#	locales/de-DE/opencadstudio.ftl
#	locales/el-GR/opencadstudio.ftl
#	locales/en-US/opencadstudio.ftl
#	locales/es-ES/opencadstudio.ftl
#	locales/fi-FI/opencadstudio.ftl
#	locales/fr-FR/opencadstudio.ftl
#	locales/hi-IN/opencadstudio.ftl
#	locales/hu-HU/opencadstudio.ftl
#	locales/it-IT/opencadstudio.ftl
#	locales/ja-JP/opencadstudio.ftl
#	locales/ko-KR/opencadstudio.ftl
#	locales/lo-LA/opencadstudio.ftl
#	locales/nl-NL/opencadstudio.ftl
#	locales/pl-PL/opencadstudio.ftl
#	locales/pt-BR/opencadstudio.ftl
#	locales/ru-RU/opencadstudio.ftl
#	locales/tr-TR/opencadstudio.ftl
#	locales/zh-CN/opencadstudio.ftl
#	locales/zh-TW/opencadstudio.ftl
This commit is contained in:
Hakan Seven committed 2026-10-15 16:47:19 +03:00
commit aa618b8327
40 files changed
+1222 -4

No files matched your search

+73
View File
@@ -1335,6 +1335,30 @@ impl OpenCADStudio {
}
}
// TXT2MTXT — TEXT / MTEXT combined into MTEXT. A preselection is
// used at once; otherwise the selection prompt with SEttings.
"TXT2MTXT" => {
if !self.tabs[i].scene.selected.is_empty() {
return Some(self.dispatch_command("_TXT2MTXTSEL"));
}
use crate::modules::draw::select::SelectObjectsCommand;
self.command_line
.push_output(crate::t!("Select text objects to combine ...").as_ref());
let command = SelectObjectsCommand::with_settings(
"TXT2MTXT",
"_TXT2MTXTSEL",
"Select objects or [SEttings]:",
|| crate::command::CmdResult::OpenTxt2MtxtSettings,
);
self.command_line.push_info(&command.prompt());
self.command_line.set_step_options(command.options());
self.tabs[i].active_cmd = Some(Box::new(command));
}
"_TXT2MTXTSEL" => {
self.apply_txt2mtxt(i);
return Some(self.finish_dispatch("TXT2MTXT"));
}
"OFFSET" => {
use crate::modules::draw::modify::offset::{is_offsettable, OffsetCommand};
let all_entities: Vec<_> = self.tabs[i]
@@ -1546,3 +1570,52 @@ fn title_case(s: &str) -> String {
.collect::<Vec<_>>()
.join(" ")
}
impl OpenCADStudio {
/// TXT2MTXT on the selection (in selection order), as one undo step.
fn apply_txt2mtxt(&mut self, i: usize) {
let scene = &self.tabs[i].scene;
let handles: Vec<codec::Handle> = scene
.selected_handles_in_order()
.into_iter()
.filter(|handle| !scene.is_layer_locked(*handle))
.collect();
let plan = {
let selected: Vec<(codec::Handle, &codec::EntityType)> = handles
.iter()
.filter_map(|h| scene.document.get_entity(*h).map(|e| (*h, e)))
.collect();
crate::modules::annotate::txt2mtxt::plan(
&selected,
&scene.document,
self.txt2mtxt_settings,
)
};
self.tabs[i].scene.deselect_all();
// Enter on an empty selection ends quietly.
if handles.is_empty() {
return;
}
let Some(plan) = plan else {
self.command_line
.push_output(" 0 Text objects removed, 0 MText objects added.");
return;
};
self.push_undo_snapshot(i, "TXT2MTXT");
self.tabs[i].scene.erase_entities(&plan.erase);
let added = plan.add.len();
for entity in plan.add {
self.tabs[i].scene.add_entity(entity);
}
self.tabs[i].dirty = true;
self.refresh_properties();
// Every selected object counts as removed text, the way the
// reference reports it.
let removed = handles.len();
self.command_line.push_output(&if added == 1 {
format!(" {removed} Text objects removed, 1 MText object added.")
} else {
format!(" {removed} Text objects removed, {added} MText objects added.")
});
}
}
+4
View File
@@ -779,6 +779,10 @@ inventory::submit!(crate::command::CommandRegistration {
"UCSDETECT",
"SURFACEASSOCIATIVITY",
"LAYOUTCREATEVIEWPORT",
"TXT2MTXTCOMBINEMTEXT",
"TXT2MTXTTEXTORDER",
"TXT2MTXTWORDWRAP",
"TXT2MTXTLINESPACING",
"FIELDEVAL",
"PDFIMPORTMODE",
"PDFIMPORTFILTER",
+38
View File
@@ -348,6 +348,12 @@ impl OpenCADStudio {
self.active_modal = Some(crate::app::ModalKind::PdfImportSettings);
}
/// TXT2MTXT's SEttings option: the dialog over the running command.
pub(in crate::app) fn open_txt2mtxt_settings(&mut self) {
self.txt2mtxt_dialog = Some(self.txt2mtxt_settings);
self.active_modal = Some(crate::app::ModalKind::Txt2MtxtSettings);
}
/// PDFIMPORT's File option after the file is chosen.
pub(in crate::app) fn open_pdf_import_file(&mut self, path: &str) {
self.pdf_import_file = Some(PdfImportFileState {
@@ -442,6 +448,7 @@ impl OpenCADStudio {
crate::t!("Sets the color schemes and ranges a point cloud is colored by intensity or elevation.")
}
"layers" => crate::t!("Turns the layers of a PDF underlay on or off."),
"txt2mtxt" => std::borrow::Cow::Owned(crate::tr!("txt2mtxt", "help")),
_ => crate::t!("Imports the geometry, fills, raster images and text of a PDF file as drawing objects."),
};
self.command_line.push_info(text.as_ref());
@@ -726,6 +733,37 @@ impl OpenCADStudio {
self.close_active_modal();
}
// ── Text to MText Settings ────────────────────────────────────
PdfDialogMsg::T2mCombine(on) => {
if let Some(s) = self.txt2mtxt_dialog.as_mut() {
s.combine = on;
}
}
PdfDialogMsg::T2mSelectOrder(on) => {
if let Some(s) = self.txt2mtxt_dialog.as_mut() {
s.select_order = on;
}
}
PdfDialogMsg::T2mWrap(on) => {
if let Some(s) = self.txt2mtxt_dialog.as_mut() {
s.word_wrap = on;
}
}
PdfDialogMsg::T2mUniform(on) => {
if let Some(s) = self.txt2mtxt_dialog.as_mut() {
s.uniform_spacing = on;
}
}
PdfDialogMsg::T2mOk => {
if let Some(s) = self.txt2mtxt_dialog {
if s != self.txt2mtxt_settings {
self.txt2mtxt_settings = s;
self.save_config();
}
}
self.close_active_modal();
}
// ── Import PDF ────────────────────────────────────────────────
PdfDialogMsg::ImportBrowse => return Task::done(Message::PdfImportPick),
PdfDialogMsg::ImportPage(page) => {
+48
View File
@@ -1282,6 +1282,10 @@ impl OpenCADStudio {
| "DYNCONSTRAINTDISPLAY"
| "CCONSTRAINTFORM"
| "DYNMODE"
| "TXT2MTXTCOMBINEMTEXT"
| "TXT2MTXTTEXTORDER"
| "TXT2MTXTWORDWRAP"
| "TXT2MTXTLINESPACING"
) =>
{
return self.dispatch_styleprops(&format!("SETVAR {cmd}"), i);
@@ -1578,6 +1582,50 @@ impl OpenCADStudio {
}
return Some(self.finish_dispatch(cmd));
}
if let Some(field) = match name.as_str() {
"TXT2MTXTCOMBINEMTEXT" => Some(0),
"TXT2MTXTTEXTORDER" => Some(1),
"TXT2MTXTWORDWRAP" => Some(2),
"TXT2MTXTLINESPACING" => Some(3),
_ => None,
} {
// TXT2MTXT's profile settings: 0 / 1 (TEXTORDER 1 =
// selection order).
let s = &mut self.txt2mtxt_settings;
let slot = match field {
0 => &mut s.combine,
1 => &mut s.select_order,
2 => &mut s.word_wrap,
_ => &mut s.uniform_spacing,
};
let current = i16::from(*slot);
match value
.as_deref()
.map(|v| v.trim().parse::<i16>().ok().filter(|v| (0..=1).contains(v)))
{
Some(Some(mode)) => {
if current != mode {
*slot = mode == 1;
self.save_config();
}
}
Some(None) => {
self.command_line
.push_error(crate::t!("Requires 0 or 1 only.").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 == "FIELDDISPLAY" {
let current = i16::from(crate::entities::field::display());
match value