forked from 1b-agent/opencadstudio-upstream
TXT2MTXT: combine text into MText, with its settings dialog
TEXT and MTEXT objects are merged into one MTEXT (top-down or in selection order, word-wrapped or one paragraph per object, with the original pitch kept as line spacing) or each TEXT and MTEXT line becomes an MTEXT of its own. The SEttings option opens the Combine Text settings dialog; the settings persist as profile values and are exposed as the TXT2MTXT* variables and the txt2mtxt_settings automation action. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
39c3e9af58
commit
4e4dc86908
40 files changed
+1244
-4
No files matched your search
@@ -6609,3 +6609,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6597,3 +6597,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6594,3 +6594,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6595,3 +6595,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6597,3 +6597,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6596,3 +6596,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6597,3 +6597,21 @@ ucs-follow =
|
||||
.prompt = UCSFOLLOW: introduce 0 (conservar vista) o 1 (seguir SCP) <__ocs_fmt_0__>:
|
||||
.paper-space = UCSFOLLOW: activa una ventana de modelo; no se aplica al espacio papel.
|
||||
.invalid = UCSFOLLOW: solo se permite 0 o 1.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6594,3 +6594,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6597,3 +6597,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6585,3 +6585,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6581,3 +6581,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6596,3 +6596,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6585,3 +6585,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6596,3 +6596,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6597,3 +6597,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6595,3 +6595,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6596,3 +6596,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6597,3 +6597,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6598,3 +6598,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6518,3 +6518,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Metni Birleştir — Ayarlar
|
||||
.result = Sonuç
|
||||
.combine = Tek bir MText nesnesinde birleştir
|
||||
.order = Satır sırası
|
||||
.top-down = Üstten aşağıya
|
||||
.selection-order = Seçim sırası
|
||||
.combine-note = Kapalıyken her metin ayrı MText olur; sıra seçici pasifleşir.
|
||||
.lines = Satırlar
|
||||
.wrap = Sözcük kaydırma
|
||||
.wrap-hint = — satırlar boşlukla birleşir, genişlik hesaplanır
|
||||
.uniform = Satır aralığını eşitle
|
||||
.uniform-hint = (sözcük kaydırma açıkken her zaman eşit)
|
||||
.selected = Seçilen
|
||||
.result-count = Sonuç · { $count } MText
|
||||
.apply = Uygula
|
||||
.help = TXT2MTXT komutunun metin nesnelerini MText olarak nasıl birleştireceğini ayarlar.
|
||||
@@ -6580,3 +6580,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -6592,3 +6592,21 @@ 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.
|
||||
|
||||
txt2mtxt =
|
||||
.title = Combine Text — Settings
|
||||
.result = Result
|
||||
.combine = Combine into a single MText object
|
||||
.order = Line order
|
||||
.top-down = Top-down
|
||||
.selection-order = Selection order
|
||||
.combine-note = When off, each text becomes its own MText; the order choice is inactive.
|
||||
.lines = Lines
|
||||
.wrap = Word-wrap
|
||||
.wrap-hint = — lines join with spaces, the width is computed
|
||||
.uniform = Force uniform line spacing
|
||||
.uniform-hint = (always uniform while word-wrap is on)
|
||||
.selected = Selected
|
||||
.result-count = Result · { $count } MText
|
||||
.apply = Apply
|
||||
.help = Sets how TXT2MTXT combines text objects into MText.
|
||||
@@ -445,6 +445,7 @@ impl OpenCADStudio {
|
||||
} => {
|
||||
self.finish_qselect_sample(*state, tab_id, handle);
|
||||
}
|
||||
CmdResult::OpenTxt2MtxtSettings => self.open_txt2mtxt_settings(),
|
||||
CmdResult::OpenAutoConstrainSettings => {
|
||||
self.handle_open_auto_constrain_settings();
|
||||
}
|
||||
|
||||
@@ -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.")
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -770,6 +770,10 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
"SELECTIONCYCLING",
|
||||
"SURFACEASSOCIATIVITY",
|
||||
"LAYOUTCREATEVIEWPORT",
|
||||
"TXT2MTXTCOMBINEMTEXT",
|
||||
"TXT2MTXTTEXTORDER",
|
||||
"TXT2MTXTWORDWRAP",
|
||||
"TXT2MTXTLINESPACING",
|
||||
"FIELDEVAL",
|
||||
"PDFIMPORTMODE",
|
||||
"PDFIMPORTFILTER",
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -1281,6 +1281,10 @@ impl OpenCADStudio {
|
||||
| "DYNCONSTRAINTDISPLAY"
|
||||
| "CCONSTRAINTFORM"
|
||||
| "DYNMODE"
|
||||
| "TXT2MTXTCOMBINEMTEXT"
|
||||
| "TXT2MTXTTEXTORDER"
|
||||
| "TXT2MTXTWORDWRAP"
|
||||
| "TXT2MTXTLINESPACING"
|
||||
) =>
|
||||
{
|
||||
return self.dispatch_styleprops(&format!("SETVAR {cmd}"), i);
|
||||
@@ -1577,6 +1581,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
|
||||
|
||||
@@ -103,6 +103,7 @@ pub(super) const NAMES: &[&str] = &[
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
"external_change_cancel",
|
||||
"pdf_dialog_ok",
|
||||
"txt2mtxt_settings",
|
||||
"view_background",
|
||||
"image_adjust",
|
||||
"refedit_dialog",
|
||||
@@ -2088,6 +2089,27 @@ impl OpenCADStudio {
|
||||
_ => return Err(bad()),
|
||||
}
|
||||
}
|
||||
// Text to MText Settings (TXT2MTXT SEttings): "combine=0",
|
||||
// "order=select" / "order=top", "wrap=0", "uniform=1", "ok",
|
||||
// "cancel".
|
||||
"txt2mtxt_settings" => {
|
||||
use crate::ui::window::pdf_dialogs::PdfDialogMsg as M;
|
||||
if self.active_modal != Some(crate::app::ModalKind::Txt2MtxtSettings) {
|
||||
return Err(failure("no_dialog", "Text to MText Settings is not open"));
|
||||
}
|
||||
let value = string(req, "value")?;
|
||||
let (key, arg) = value.split_once('=').unwrap_or((value, ""));
|
||||
let on = arg != "0";
|
||||
match key {
|
||||
"combine" => Message::PdfDialog(M::T2mCombine(on)),
|
||||
"order" => Message::PdfDialog(M::T2mSelectOrder(arg == "select")),
|
||||
"wrap" => Message::PdfDialog(M::T2mWrap(on)),
|
||||
"uniform" => Message::PdfDialog(M::T2mUniform(on)),
|
||||
"ok" => Message::PdfDialog(M::T2mOk),
|
||||
"cancel" => Message::CloseModal,
|
||||
_ => return Err(failure("invalid_value", "Unknown setting")),
|
||||
}
|
||||
}
|
||||
"pdf_dialog_ok" => {
|
||||
use crate::ui::window::pdf_dialogs::PdfDialogMsg;
|
||||
Message::PdfDialog(match self.active_modal {
|
||||
|
||||
@@ -381,6 +381,7 @@ fn command_selection_policy(name: &str) -> &'static str {
|
||||
"ERASE",
|
||||
"EXPLODE",
|
||||
"TXTEXP",
|
||||
"TXT2MTXT",
|
||||
"GROUP",
|
||||
"LAYFRZ",
|
||||
"LAYLCK",
|
||||
|
||||
@@ -764,6 +764,10 @@ pub(super) struct OpenCADStudio {
|
||||
/// pill shows only the glyph.
|
||||
pub show_constraint_values: bool,
|
||||
pub auto_constrain_settings: settings::AutoConstrainSettings,
|
||||
/// TXT2MTXT settings (profile).
|
||||
pub txt2mtxt_settings: settings::Txt2MtxtSettings,
|
||||
/// The Text to MText Settings dialog's working copy.
|
||||
txt2mtxt_dialog: Option<settings::Txt2MtxtSettings>,
|
||||
auto_constrain_saved: Option<settings::AutoConstrainSettings>,
|
||||
auto_constrain_selected_row: usize,
|
||||
auto_constrain_distance_input: String,
|
||||
@@ -2178,6 +2182,8 @@ pub enum ModalKind {
|
||||
RefEdit,
|
||||
UnderlayLayers,
|
||||
PdfImportSettings,
|
||||
/// TXT2MTXT's Text to MText Settings, over the running command.
|
||||
Txt2MtxtSettings,
|
||||
PdfImportFile,
|
||||
XrefAttach,
|
||||
WriteBlock,
|
||||
@@ -4693,6 +4699,8 @@ impl OpenCADStudio {
|
||||
file_assoc_enabled: true,
|
||||
show_constraint_values: true,
|
||||
auto_constrain_settings: settings::AutoConstrainSettings::default(),
|
||||
txt2mtxt_settings: settings::Txt2MtxtSettings::default(),
|
||||
txt2mtxt_dialog: None,
|
||||
auto_constrain_saved: None,
|
||||
auto_constrain_selected_row: 0,
|
||||
auto_constrain_distance_input: "0.05".to_string(),
|
||||
|
||||
@@ -108,6 +108,34 @@ impl AutoConstrainSettings {
|
||||
}
|
||||
}
|
||||
|
||||
/// TXT2MTXT (Combine Text) settings, the profile values
|
||||
/// TXT2MTXTCOMBINEMTEXT, TXT2MTXTTEXTORDER, TXT2MTXTWORDWRAP and
|
||||
/// TXT2MTXTLINESPACING.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Txt2MtxtSettings {
|
||||
/// Combine everything into one MTEXT (else each TEXT becomes its own).
|
||||
pub combine: bool,
|
||||
/// Keep the selection order (else sort top-down).
|
||||
pub select_order: bool,
|
||||
/// Run the lines on separated by spaces in a computed width (else one
|
||||
/// paragraph per object).
|
||||
pub word_wrap: bool,
|
||||
/// Force uniform line spacing (always on while word-wrap is).
|
||||
pub uniform_spacing: bool,
|
||||
}
|
||||
|
||||
impl Default for Txt2MtxtSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
combine: true,
|
||||
select_order: false,
|
||||
word_wrap: true,
|
||||
uniform_spacing: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cursor shown over the drawing viewport.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CursorType {
|
||||
@@ -568,6 +596,9 @@ pub struct UserSettings {
|
||||
/// the Auto Constrain command.
|
||||
#[serde(default)]
|
||||
pub auto_constrain: AutoConstrainSettings,
|
||||
/// TXT2MTXT settings (Text to MText Settings dialog).
|
||||
#[serde(default)]
|
||||
pub txt2mtxt: Txt2MtxtSettings,
|
||||
/// Keep existing geometry size while solving after a constraint edit.
|
||||
#[serde(default = "default_constraint_solve_mode")]
|
||||
pub constraint_solve_mode: bool,
|
||||
@@ -962,6 +993,7 @@ impl Default for UserSettings {
|
||||
file_assoc_enabled: true,
|
||||
show_constraint_values: true,
|
||||
auto_constrain: AutoConstrainSettings::default(),
|
||||
txt2mtxt: Txt2MtxtSettings::default(),
|
||||
constraint_solve_mode: true,
|
||||
constraint_infer: false,
|
||||
constraint_bar_display: 3,
|
||||
|
||||
@@ -862,6 +862,7 @@ impl OpenCADStudio {
|
||||
file_assoc_enabled: self.file_assoc_enabled,
|
||||
show_constraint_values: self.show_constraint_values,
|
||||
auto_constrain: self.auto_constrain_settings.clone(),
|
||||
txt2mtxt: self.txt2mtxt_settings,
|
||||
constraint_solve_mode: self.constraint_solve_mode,
|
||||
constraint_infer: self.constraint_infer,
|
||||
constraint_bar_display: self.constraint_bar_display,
|
||||
@@ -1031,6 +1032,7 @@ impl OpenCADStudio {
|
||||
self.file_assoc_enabled = s.file_assoc_enabled;
|
||||
self.show_constraint_values = s.show_constraint_values;
|
||||
self.auto_constrain_settings = s.auto_constrain.clone();
|
||||
self.txt2mtxt_settings = s.txt2mtxt;
|
||||
self.auto_constrain_settings.sanitize();
|
||||
self.auto_constrain_distance_input =
|
||||
format!("{}", self.auto_constrain_settings.distance_tolerance);
|
||||
|
||||
@@ -237,6 +237,17 @@ impl OpenCADStudio {
|
||||
Some(PdfAttach) => self.pdf_attach = None,
|
||||
Some(UnderlayLayers) => self.underlay_layers = None,
|
||||
Some(PdfImportSettings) => self.pdf_import_settings = None,
|
||||
// OK or Cancel: TXT2MTXT asks for its selection again.
|
||||
Some(Txt2MtxtSettings) => {
|
||||
self.txt2mtxt_dialog = None;
|
||||
if let Some(prompt) = self.tabs[self.active_tab]
|
||||
.active_cmd
|
||||
.as_ref()
|
||||
.map(|command| command.prompt())
|
||||
{
|
||||
self.command_line.push_info(&prompt);
|
||||
}
|
||||
}
|
||||
Some(PdfImportFile) => self.pdf_import_file = None,
|
||||
Some(XrefAttach) => self.xref_attach = None,
|
||||
Some(WriteBlock) => self.wblock = None,
|
||||
|
||||
@@ -46,6 +46,7 @@ impl OpenCADStudio {
|
||||
Some(K::RefEdit) => crate::tr!("ref-edit", "dialog-title"),
|
||||
Some(K::UnderlayLayers) => crate::t!("Underlay Layers").into_owned(),
|
||||
Some(K::PdfImportSettings) => crate::t!("PDF Import Settings").into_owned(),
|
||||
Some(K::Txt2MtxtSettings) => crate::tr!("txt2mtxt", "title"),
|
||||
Some(K::PdfImportFile) => crate::t!("Import PDF").into_owned(),
|
||||
Some(K::XrefAttach) => crate::t!("Attach External Reference").into_owned(),
|
||||
Some(K::WriteBlock) => crate::t!("Write Block").into_owned(),
|
||||
@@ -575,6 +576,12 @@ impl OpenCADStudio {
|
||||
crate::ui::window::pdf_dialogs::view_import_settings(settings, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::Txt2MtxtSettings => {
|
||||
let settings = self.txt2mtxt_dialog.as_ref()?;
|
||||
sized_flow(ex, 600, 470, |flow| {
|
||||
crate::ui::window::pdf_dialogs::view_txt2mtxt_settings(settings, flow)
|
||||
})
|
||||
}
|
||||
super::super::ModalKind::PdfImportFile => {
|
||||
let state = self.pdf_import_file.as_ref()?;
|
||||
sized_flow(ex, 960, 560, |flow| {
|
||||
|
||||
@@ -1812,6 +1812,9 @@ pub enum CmdResult {
|
||||
second: crate::scene::parametric_constraints::ParametricRef,
|
||||
label: &'static str,
|
||||
},
|
||||
/// Opens the Text to MText Settings dialog from TXT2MTXT's selection
|
||||
/// prompt; the command keeps its prompt.
|
||||
OpenTxt2MtxtSettings,
|
||||
/// Opens the Auto Constrain settings dialog from the selection prompt.
|
||||
OpenAutoConstrainSettings,
|
||||
/// Adds an ordered Coincident relation. Point/point selections create a
|
||||
|
||||
+13
-4
@@ -114,6 +114,16 @@ fn display_text(t: &MText, document: &codec::CadDocument) -> String {
|
||||
/// click-to-select preview. Uses the exact same layout opts as `to_render`
|
||||
/// so the boxes line up with the rendered glyphs.
|
||||
pub fn glyph_boxes(t: &MText, document: &codec::CadDocument) -> Vec<GlyphBox> {
|
||||
layout_of(t, document, true).glyph_boxes
|
||||
}
|
||||
|
||||
/// The MTEXT laid out as it is drawn: its sub-lines, their widths and the
|
||||
/// first baseline, in the entity's own (pre-rotation) frame.
|
||||
pub fn layout_of(
|
||||
t: &MText,
|
||||
document: &codec::CadDocument,
|
||||
want_glyph_boxes: bool,
|
||||
) -> crate::entities::text_support::MTextLayout {
|
||||
let resolved_style = resolve_text_style(&t.style, document);
|
||||
let attach_h_anchor: f32 = match t.attachment_point {
|
||||
AttachmentPoint::TopCenter
|
||||
@@ -143,7 +153,7 @@ pub fn glyph_boxes(t: &MText, document: &codec::CadDocument) -> Vec<GlyphBox> {
|
||||
// A field shows its stored value: fields update on their evaluation
|
||||
// events (open, save, plot, regen, UPDATEFIELD), never on a redraw.
|
||||
let display = display_text(t, document);
|
||||
let layout = layout_mtext(&MTextRenderOpts {
|
||||
layout_mtext(&MTextRenderOpts {
|
||||
value: &display,
|
||||
insertion: [
|
||||
t.insertion_point.x,
|
||||
@@ -165,10 +175,9 @@ pub fn glyph_boxes(t: &MText, document: &codec::CadDocument) -> Vec<GlyphBox> {
|
||||
vertical_text: matches!(t.drawing_direction, DrawingDirection::TopToBottom)
|
||||
|| matches!(t.drawing_direction, DrawingDirection::ByStyle)
|
||||
&& resolved_style.is_vertical,
|
||||
want_glyph_boxes: true,
|
||||
want_glyph_boxes,
|
||||
columns: columns_of(t),
|
||||
});
|
||||
layout.glyph_boxes
|
||||
})
|
||||
}
|
||||
|
||||
fn to_render(t: &MText, document: &codec::CadDocument) -> RenderEntity {
|
||||
|
||||
@@ -29,6 +29,7 @@ pub mod radius_dim;
|
||||
pub mod table_cmd;
|
||||
pub mod text;
|
||||
pub mod textedit;
|
||||
pub mod txt2mtxt;
|
||||
pub mod tolerance_cmd;
|
||||
|
||||
use crate::modules::{CadModule, RibbonGroup, RibbonItem, StyleKey};
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
//! TXT2MTXT (Combine Text): TEXT and MTEXT objects merged into one MTEXT, or
|
||||
//! each TEXT (and each line of an MTEXT) turned into an MTEXT of its own.
|
||||
//!
|
||||
//! The combined MTEXT sits at the top-left corner of the first object (the
|
||||
//! topmost one unless the selection order is kept) and takes its layer, style,
|
||||
//! height and rotation. With word-wrap the objects run on separated by spaces
|
||||
//! in a box as wide as their bounding box (plus half a percent), and an even
|
||||
//! pitch between them becomes the line spacing factor. Without it each object
|
||||
//! starts a paragraph; unless line spacing is forced uniform, each paragraph
|
||||
//! keeps its own pitch (`\pxsm` / `\psm`). A height change starts `\H<h>;`.
|
||||
|
||||
use codec::entities::{
|
||||
AttachmentPoint, DrawingDirection, LineSpacingStyle, MText, TextHorizontalAlignment as HA,
|
||||
};
|
||||
use codec::types::Vector3;
|
||||
use codec::{CadDocument, EntityType, Handle};
|
||||
|
||||
use crate::app::settings::Txt2MtxtSettings;
|
||||
use crate::entities::text_support::{resolve_dxf_special_chars, text_local_bounds};
|
||||
|
||||
/// What the command does to the drawing.
|
||||
pub struct Txt2MtxtPlan {
|
||||
pub erase: Vec<Handle>,
|
||||
pub add: Vec<EntityType>,
|
||||
}
|
||||
|
||||
/// One TEXT or MTEXT of the selection, in its own frame.
|
||||
struct Piece<'a> {
|
||||
handle: Handle,
|
||||
entity: &'a EntityType,
|
||||
/// MTEXT-encoded value.
|
||||
content: String,
|
||||
height: f64,
|
||||
/// The frame's origin (WCS) and direction.
|
||||
origin: [f64; 2],
|
||||
rotation: f64,
|
||||
/// Box in the frame: left, right, last baseline, top.
|
||||
rect: [f64; 4],
|
||||
}
|
||||
|
||||
impl Piece<'_> {
|
||||
fn point(&self, x: f64, y: f64) -> [f64; 2] {
|
||||
let (s, c) = self.rotation.sin_cos();
|
||||
[
|
||||
self.origin[0] + x * c - y * s,
|
||||
self.origin[1] + x * s + y * c,
|
||||
]
|
||||
}
|
||||
|
||||
fn top_left(&self) -> [f64; 2] {
|
||||
self.point(self.rect[0], self.rect[3])
|
||||
}
|
||||
|
||||
fn corners(&self) -> [[f64; 2]; 4] {
|
||||
let [l, r, b, t] = self.rect;
|
||||
[
|
||||
self.point(l, b),
|
||||
self.point(r, b),
|
||||
self.point(r, t),
|
||||
self.point(l, t),
|
||||
]
|
||||
}
|
||||
|
||||
/// First and last baselines, as heights in a frame turned by `angle`.
|
||||
fn baselines(&self, angle: f64) -> (f64, f64) {
|
||||
let [l, _, b, t] = self.rect;
|
||||
(
|
||||
up(self.point(l, t - self.height), angle),
|
||||
up(self.point(l, b), angle),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Height of a WCS point in a frame turned by `angle`.
|
||||
fn up(p: [f64; 2], angle: f64) -> f64 {
|
||||
let (s, c) = angle.sin_cos();
|
||||
-p[0] * s + p[1] * c
|
||||
}
|
||||
|
||||
/// A TEXT value as MTEXT content: `%%u` / `%%o` become underline / overline
|
||||
/// switches, braces are escaped, other `%%` codes stay, and a backslash is
|
||||
/// dropped unless it starts a `\U+` / `\M+` character code.
|
||||
fn text_content(value: &str) -> String {
|
||||
let chars: Vec<char> = value.chars().collect();
|
||||
let (mut under, mut over) = (false, false);
|
||||
let mut out = String::new();
|
||||
let mut i = 0;
|
||||
while i < chars.len() {
|
||||
let ch = chars[i];
|
||||
let next = chars.get(i + 1).copied();
|
||||
if ch == '%' && next == Some('%') {
|
||||
match chars.get(i + 2).map(|c| c.to_ascii_lowercase()) {
|
||||
Some('u') => {
|
||||
out.push_str(if under { "\\l" } else { "\\L" });
|
||||
under = !under;
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
Some('o') => {
|
||||
out.push_str(if over { "\\o" } else { "\\O" });
|
||||
over = !over;
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
match ch {
|
||||
'{' | '}' => {
|
||||
out.push('\\');
|
||||
out.push(ch);
|
||||
}
|
||||
'\\' if matches!(next, Some('U' | 'M')) && chars.get(i + 2) == Some(&'+') => {
|
||||
out.push(ch)
|
||||
}
|
||||
'\\' => {}
|
||||
_ => out.push(ch),
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn piece<'a>(handle: Handle, entity: &'a EntityType, doc: &CadDocument) -> Option<Piece<'a>> {
|
||||
match entity {
|
||||
EntityType::Text(t) => {
|
||||
let p = crate::entities::text::text_run_placement_at_scale(t, doc, 1.0);
|
||||
let advance = text_local_bounds(
|
||||
&p.font,
|
||||
&resolve_dxf_special_chars(&t.value),
|
||||
p.height,
|
||||
p.width_factor,
|
||||
p.oblique_angle,
|
||||
)
|
||||
.map_or(0.0, |b| b.advance.abs() as f64);
|
||||
Some(Piece {
|
||||
handle,
|
||||
entity,
|
||||
content: text_content(&t.value),
|
||||
height: p.height as f64,
|
||||
origin: p.origin,
|
||||
rotation: p.rotation as f64,
|
||||
rect: [0.0, advance, 0.0, p.height as f64],
|
||||
})
|
||||
}
|
||||
EntityType::MText(m) => {
|
||||
let layout = crate::entities::mtext::layout_of(m, doc, false);
|
||||
let width = layout.line_widths.iter().fold(0.0_f32, |a, w| a.max(*w)) as f64;
|
||||
let anchor = match m.attachment_point {
|
||||
AttachmentPoint::TopCenter
|
||||
| AttachmentPoint::MiddleCenter
|
||||
| AttachmentPoint::BottomCenter => 0.5,
|
||||
AttachmentPoint::TopRight
|
||||
| AttachmentPoint::MiddleRight
|
||||
| AttachmentPoint::BottomRight => 1.0,
|
||||
_ => 0.0,
|
||||
};
|
||||
// The block runs from the first line's cap height down to the
|
||||
// last baseline, hung from the attachment point.
|
||||
let lines = layout.line_count.max(1);
|
||||
let block = m.height + (lines - 1) as f64 * layout.line_height as f64;
|
||||
let top = match m.attachment_point {
|
||||
AttachmentPoint::MiddleLeft
|
||||
| AttachmentPoint::MiddleCenter
|
||||
| AttachmentPoint::MiddleRight => block / 2.0,
|
||||
AttachmentPoint::BottomLeft
|
||||
| AttachmentPoint::BottomCenter
|
||||
| AttachmentPoint::BottomRight => block,
|
||||
_ => 0.0,
|
||||
};
|
||||
let left = -anchor * width;
|
||||
Some(Piece {
|
||||
handle,
|
||||
entity,
|
||||
content: m.value.clone(),
|
||||
height: m.height,
|
||||
origin: [m.insertion_point.x, m.insertion_point.y],
|
||||
rotation: m.rotation,
|
||||
rect: [left, left + width, top - block, top],
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// A new MTEXT with `source`'s layer, colour and style.
|
||||
fn mtext_from(source: &EntityType, value: String, at: [f64; 2], height: f64, rotation: f64) -> MText {
|
||||
let mut m = MText::new();
|
||||
let (common, style, z, normal) = match source {
|
||||
EntityType::Text(t) => (&t.common, &t.style, t.insertion_point.z, t.normal),
|
||||
EntityType::MText(t) => (&t.common, &t.style, t.insertion_point.z, t.normal),
|
||||
_ => unreachable!("pieces are TEXT or MTEXT"),
|
||||
};
|
||||
m.common = common.clone();
|
||||
m.common.handle = Handle::NULL;
|
||||
m.common.reactors.clear();
|
||||
m.common.xdictionary_handle = None;
|
||||
m.style = style.clone();
|
||||
m.normal = normal;
|
||||
m.value = value;
|
||||
m.insertion_point = Vector3::new(at[0], at[1], z);
|
||||
m.height = height;
|
||||
m.rotation = rotation;
|
||||
m.rectangle_width = 0.0;
|
||||
m.attachment_point = AttachmentPoint::TopLeft;
|
||||
m.drawing_direction = DrawingDirection::ByStyle;
|
||||
m.line_spacing_style = LineSpacingStyle::AtLeast;
|
||||
m.line_spacing_factor = 1.0;
|
||||
m
|
||||
}
|
||||
|
||||
/// Each TEXT and each line of an MTEXT as an MTEXT of its own. A TEXT keeps
|
||||
/// its horizontal justification as a top attachment.
|
||||
fn separate(p: &Piece, doc: &CadDocument) -> Vec<EntityType> {
|
||||
match p.entity {
|
||||
EntityType::Text(t) => {
|
||||
let (x, attachment) = match t.horizontal_alignment {
|
||||
HA::Center | HA::Middle => (0.5, AttachmentPoint::TopCenter),
|
||||
HA::Right => (1.0, AttachmentPoint::TopRight),
|
||||
_ => (0.0, AttachmentPoint::TopLeft),
|
||||
};
|
||||
let at = p.point(p.rect[0] + x * (p.rect[1] - p.rect[0]), p.rect[3]);
|
||||
let mut m = mtext_from(p.entity, p.content.clone(), at, p.height, p.rotation);
|
||||
m.attachment_point = attachment;
|
||||
vec![EntityType::MText(m)]
|
||||
}
|
||||
EntityType::MText(m) => {
|
||||
// The drawn words, grouped into lines by their baseline.
|
||||
// ponytail: a line keeps only its words, not inline formatting.
|
||||
let words = crate::entities::mtext::explode_mtext(m, doc);
|
||||
let mut lines: Vec<(f64, Vec<(f64, &codec::entities::Text)>)> = Vec::new();
|
||||
for word in &words {
|
||||
let EntityType::Text(t) = word else { continue };
|
||||
let at = [t.insertion_point.x, t.insertion_point.y];
|
||||
let (s, c) = m.rotation.sin_cos();
|
||||
let along = at[0] * c + at[1] * s;
|
||||
let v = up(at, m.rotation);
|
||||
match lines
|
||||
.iter_mut()
|
||||
.find(|(base, _)| (base - v).abs() < m.height * 0.25)
|
||||
{
|
||||
Some((_, line)) => line.push((along, t)),
|
||||
None => lines.push((v, vec![(along, t)])),
|
||||
}
|
||||
}
|
||||
lines.sort_by(|a, b| b.0.total_cmp(&a.0));
|
||||
// Line k hangs one line step below line k-1 from the block's top.
|
||||
let step = (p.rect[3] - p.rect[2] - p.height) / (lines.len().max(2) - 1) as f64;
|
||||
let origin_along = {
|
||||
let (s, c) = m.rotation.sin_cos();
|
||||
p.origin[0] * c + p.origin[1] * s
|
||||
};
|
||||
lines
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(k, (_, mut line))| {
|
||||
line.sort_by(|a, b| a.0.total_cmp(&b.0));
|
||||
let value = line
|
||||
.iter()
|
||||
.map(|(_, t)| t.value.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
let at = p.point(line[0].0 - origin_along, p.rect[3] - k as f64 * step);
|
||||
EntityType::MText(mtext_from(p.entity, value, at, line[0].1.height, m.rotation))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The edit for `selected` (in selection order); `None` when nothing in it is
|
||||
/// text.
|
||||
pub fn plan(
|
||||
selected: &[(Handle, &EntityType)],
|
||||
doc: &CadDocument,
|
||||
settings: Txt2MtxtSettings,
|
||||
) -> Option<Txt2MtxtPlan> {
|
||||
let mut pieces: Vec<Piece> = selected
|
||||
.iter()
|
||||
.filter_map(|(h, e)| piece(*h, e, doc))
|
||||
.collect();
|
||||
let frame = pieces.first()?.rotation;
|
||||
if !settings.combine {
|
||||
return Some(Txt2MtxtPlan {
|
||||
erase: pieces.iter().map(|p| p.handle).collect(),
|
||||
add: pieces.iter().flat_map(|p| separate(p, doc)).collect(),
|
||||
});
|
||||
}
|
||||
if !settings.select_order {
|
||||
// Top-down; a tie keeps the selection order.
|
||||
let top = |p: &Piece| p.corners().map(|c| up(c, frame)).into_iter().fold(f64::MIN, f64::max);
|
||||
pieces.sort_by(|a, b| top(b).total_cmp(&top(a)));
|
||||
}
|
||||
let first = &pieces[0];
|
||||
let frame = first.rotation;
|
||||
// Each paragraph's pitch to the next one, as a factor of the default
|
||||
// step (5/3 of its text height).
|
||||
let pitches: Vec<f64> = pieces
|
||||
.windows(2)
|
||||
.map(|w| {
|
||||
let gap = w[0].baselines(frame).1 - w[1].baselines(frame).0;
|
||||
(gap / (w[0].height * 5.0 / 3.0)).clamp(0.25, 4.0)
|
||||
})
|
||||
.collect();
|
||||
let per_paragraph = !settings.word_wrap && !settings.uniform_spacing;
|
||||
let mut value = String::new();
|
||||
let mut current = first.height;
|
||||
for (i, p) in pieces.iter().enumerate() {
|
||||
if i > 0 {
|
||||
value.push_str(if settings.word_wrap { " " } else { "\\P" });
|
||||
}
|
||||
if let (true, Some(pitch)) = (per_paragraph, pitches.get(i)) {
|
||||
let x = if i == 0 { "x" } else { "" };
|
||||
value.push_str(&format!("\\p{x}sm{pitch:.2};"));
|
||||
}
|
||||
if (p.height - current).abs() > 1e-9 {
|
||||
value.push_str(&format!("\\H{:.4};", p.height));
|
||||
current = p.height;
|
||||
}
|
||||
value.push_str(&p.content);
|
||||
}
|
||||
let mut m = mtext_from(first.entity, value, first.top_left(), first.height, frame);
|
||||
if settings.word_wrap {
|
||||
// The objects' WCS bounding box, measured along the first one.
|
||||
let mut bounds = [f64::MAX, f64::MAX, f64::MIN, f64::MIN];
|
||||
for c in pieces.iter().flat_map(|p| p.corners()) {
|
||||
bounds = [
|
||||
bounds[0].min(c[0]),
|
||||
bounds[1].min(c[1]),
|
||||
bounds[2].max(c[0]),
|
||||
bounds[3].max(c[1]),
|
||||
];
|
||||
}
|
||||
let (s, c) = frame.sin_cos();
|
||||
let along = [
|
||||
[bounds[0], bounds[1]],
|
||||
[bounds[2], bounds[1]],
|
||||
[bounds[2], bounds[3]],
|
||||
[bounds[0], bounds[3]],
|
||||
]
|
||||
.map(|p| p[0] * c + p[1] * s);
|
||||
let span = along.iter().fold(f64::MIN, |a, v| a.max(*v))
|
||||
- along.iter().fold(f64::MAX, |a, v| a.min(*v));
|
||||
m.rectangle_width = span * 1.005;
|
||||
// An even pitch carries over; an uneven one falls back to single.
|
||||
if let Some(&pitch) = pitches.first() {
|
||||
if pitches.iter().all(|p| (p - pitch).abs() < 1e-4) {
|
||||
m.line_spacing_factor = pitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Txt2MtxtPlan {
|
||||
erase: pieces.iter().map(|p| p.handle).collect(),
|
||||
add: vec![EntityType::MText(m)],
|
||||
})
|
||||
}
|
||||
|
||||
inventory::submit!(crate::command::CommandRegistration {
|
||||
names: &["TXT2MTXT"]
|
||||
});
|
||||
@@ -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 SEttings keyword at the prompt, and what it does.
|
||||
settings: Option<fn() -> CmdResult>,
|
||||
}
|
||||
|
||||
impl SelectObjectsCommand {
|
||||
@@ -61,6 +63,7 @@ impl SelectObjectsCommand {
|
||||
prompt_text: None,
|
||||
associative_dimensions_only: false,
|
||||
keep: None,
|
||||
settings: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +82,7 @@ impl SelectObjectsCommand {
|
||||
prompt_text: None,
|
||||
associative_dimensions_only: false,
|
||||
keep: None,
|
||||
settings: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +101,7 @@ impl SelectObjectsCommand {
|
||||
prompt_text: None,
|
||||
associative_dimensions_only: false,
|
||||
keep: None,
|
||||
settings: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +120,7 @@ impl SelectObjectsCommand {
|
||||
prompt_text: None,
|
||||
associative_dimensions_only: false,
|
||||
keep: None,
|
||||
settings: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +156,19 @@ impl SelectObjectsCommand {
|
||||
command
|
||||
}
|
||||
|
||||
/// The command's own wording with a SEttings keyword; Enter on nothing
|
||||
/// ends quietly.
|
||||
pub fn with_settings(
|
||||
prompt_cmd: &str,
|
||||
pending_cmd: &str,
|
||||
prompt_text: &'static str,
|
||||
settings: fn() -> CmdResult,
|
||||
) -> Self {
|
||||
let mut command = Self::filtered(prompt_cmd, pending_cmd, prompt_text, |_| true);
|
||||
command.settings = Some(settings);
|
||||
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.is_some() && self.pick.is_none() {
|
||||
return vec![CmdOption::new("SEttings", "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 word = text.trim().trim_start_matches('_').to_uppercase();
|
||||
if let Some(settings) = self.settings {
|
||||
if word.len() >= 2 && "SETTINGS".starts_with(word.as_str()) {
|
||||
return Some(settings());
|
||||
}
|
||||
}
|
||||
match text.trim().to_uppercase().as_str() {
|
||||
"S" | "SETTINGS" if self.auto_constrain_settings => {
|
||||
Some(CmdResult::OpenAutoConstrainSettings)
|
||||
|
||||
@@ -101,6 +101,13 @@ pub enum PdfDialogMsg {
|
||||
Lineweights(bool),
|
||||
Linetypes(bool),
|
||||
SettingsOk,
|
||||
// Text to MText Settings (TXT2MTXT)
|
||||
T2mCombine(bool),
|
||||
/// Keep the selection order (else top-down).
|
||||
T2mSelectOrder(bool),
|
||||
T2mWrap(bool),
|
||||
T2mUniform(bool),
|
||||
T2mOk,
|
||||
// Import PDF
|
||||
ImportBrowse,
|
||||
ImportPage(usize),
|
||||
@@ -1261,6 +1268,194 @@ pub fn view_import_settings<'a>(
|
||||
.into()
|
||||
}
|
||||
|
||||
// ── Text to MText Settings (TXT2MTXT) ──────────────────────────────────────
|
||||
|
||||
/// A labelled switch; inactive (dimmed) while `on` is None.
|
||||
fn t2m_switch<'a>(
|
||||
label: String,
|
||||
hint: String,
|
||||
value: bool,
|
||||
on: Option<fn(bool) -> PdfDialogMsg>,
|
||||
) -> Element<'a, Message> {
|
||||
let enabled = on.is_some();
|
||||
let mut switch = iced::widget::toggler(value).size(18.0);
|
||||
if let Some(on) = on {
|
||||
switch = switch.on_toggle(move |v| msg(on(v)));
|
||||
}
|
||||
let dim = move |theme: &Theme| text::Style {
|
||||
color: Some(theme.palette().background.strong.color),
|
||||
};
|
||||
let name = text(label).size(12);
|
||||
let name = if enabled { name } else { name.style(dim) };
|
||||
row![
|
||||
switch,
|
||||
name,
|
||||
text(hint).size(11).style(if enabled { muted_style } else { dim }),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Top-down or selection order, as joined buttons; inactive without `enabled`.
|
||||
fn t2m_order<'a>(select_order: bool, enabled: bool) -> Element<'a, Message> {
|
||||
let choice = |value: bool, label: String| -> Element<'a, Message> {
|
||||
let chosen = enabled && value == select_order;
|
||||
button(text(label).size(11).width(Fill).align_x(iced::Center))
|
||||
.on_press_maybe(enabled.then(|| msg(PdfDialogMsg::T2mSelectOrder(value))))
|
||||
.style(move |theme: &Theme, status| {
|
||||
let mut style = button_style(chosen)(theme, status);
|
||||
if !enabled {
|
||||
style.text_color = theme.palette().background.strong.color;
|
||||
}
|
||||
style
|
||||
})
|
||||
.padding([5, 6])
|
||||
.width(Fill)
|
||||
.into()
|
||||
};
|
||||
container(
|
||||
row![
|
||||
choice(false, crate::tr!("txt2mtxt", "top-down")),
|
||||
choice(true, crate::tr!("txt2mtxt", "selection-order"))
|
||||
]
|
||||
.spacing(2),
|
||||
)
|
||||
.padding(2)
|
||||
.width(Length::Fixed(300.0))
|
||||
.style(well_style)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// A text sample in a well, one line per entry.
|
||||
fn t2m_sample<'a>(lines: Vec<String>, framed: bool, width: f32) -> Element<'a, Message> {
|
||||
let body = iced::widget::Column::with_children(
|
||||
lines
|
||||
.into_iter()
|
||||
.map(|l| text(l).size(12).font(iced::Font::MONOSPACE).into())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.spacing(6);
|
||||
let inner = container(body).padding([6, 8]).width(Fill);
|
||||
let inner: Element<'a, Message> = if framed {
|
||||
inner
|
||||
.style(|theme: &Theme| container::Style {
|
||||
border: Border {
|
||||
width: 1.0,
|
||||
radius: 2.0.into(),
|
||||
color: theme.palette().primary.base.color,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
inner.into()
|
||||
};
|
||||
container(inner)
|
||||
.padding(6)
|
||||
.width(Length::Fixed(width))
|
||||
.height(Length::Fixed(86.0))
|
||||
.style(well_style)
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn view_txt2mtxt_settings<'a>(
|
||||
s: &'a crate::app::settings::Txt2MtxtSettings,
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
) -> Element<'a, Message> {
|
||||
let result = card(
|
||||
crate::tr!("txt2mtxt", "result"),
|
||||
column![
|
||||
t2m_switch(
|
||||
crate::tr!("txt2mtxt", "combine"),
|
||||
String::new(),
|
||||
s.combine,
|
||||
Some(PdfDialogMsg::T2mCombine),
|
||||
),
|
||||
column![
|
||||
text(crate::tr!("txt2mtxt", "order")).size(11).style(muted_style),
|
||||
t2m_order(s.select_order, s.combine),
|
||||
]
|
||||
.spacing(6)
|
||||
.padding(iced::Padding::ZERO.left(44)),
|
||||
text(crate::tr!("txt2mtxt", "combine-note"))
|
||||
.size(11)
|
||||
.style(muted_style),
|
||||
]
|
||||
.spacing(8),
|
||||
);
|
||||
let lines = card(
|
||||
crate::tr!("txt2mtxt", "lines"),
|
||||
column![
|
||||
t2m_switch(
|
||||
crate::tr!("txt2mtxt", "wrap"),
|
||||
crate::tr!("txt2mtxt", "wrap-hint"),
|
||||
s.word_wrap,
|
||||
s.combine.then_some(PdfDialogMsg::T2mWrap as fn(bool) -> PdfDialogMsg),
|
||||
),
|
||||
t2m_switch(
|
||||
crate::tr!("txt2mtxt", "uniform"),
|
||||
crate::tr!("txt2mtxt", "uniform-hint"),
|
||||
s.uniform_spacing || s.word_wrap,
|
||||
(s.combine && !s.word_wrap)
|
||||
.then_some(PdfDialogMsg::T2mUniform as fn(bool) -> PdfDialogMsg),
|
||||
),
|
||||
]
|
||||
.spacing(10),
|
||||
);
|
||||
let sample: Vec<String> = ["First line", "Second line", "Third line"]
|
||||
.into_iter()
|
||||
.map(String::from)
|
||||
.collect();
|
||||
let (count, out) = if !s.combine {
|
||||
(3, sample.clone())
|
||||
} else if s.word_wrap {
|
||||
(
|
||||
1,
|
||||
vec!["First line Second line Third".into(), "line".into()],
|
||||
)
|
||||
} else {
|
||||
(1, sample.clone())
|
||||
};
|
||||
let preview = container(
|
||||
row![
|
||||
column![
|
||||
text(crate::tr!("txt2mtxt", "selected")).size(11).style(muted_style),
|
||||
t2m_sample(sample, false, 190.0)
|
||||
]
|
||||
.spacing(4),
|
||||
text("→").size(20).style(accent_text),
|
||||
column![
|
||||
text(crate::tr!("txt2mtxt", "result-count", count = count))
|
||||
.size(11)
|
||||
.style(muted_style),
|
||||
t2m_sample(out, true, 262.0)
|
||||
]
|
||||
.spacing(4),
|
||||
]
|
||||
.spacing(14)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.padding([10, 12])
|
||||
.width(Fill)
|
||||
.style(card_style);
|
||||
column![
|
||||
result,
|
||||
lines,
|
||||
preview,
|
||||
footer(
|
||||
"txt2mtxt",
|
||||
None,
|
||||
crate::tr!("txt2mtxt", "apply").into(),
|
||||
PdfDialogMsg::T2mOk,
|
||||
),
|
||||
]
|
||||
.spacing(10)
|
||||
.padding([10, 12])
|
||||
.width(sizing.width)
|
||||
.into()
|
||||
}
|
||||
|
||||
// ── Import PDF (File) ──────────────────────────────────────────────────────
|
||||
|
||||
pub struct PdfImportFileState {
|
||||
|
||||
Reference in new issue
Block a user