forked from 1b-agent/opencadstudio-upstream
fix(plot-style): load plot style tables written by other applications
Tables copied from other applications into the plot styles folder were listed but refused on selection, showing as "missing" with the reason lost on a console: the loader failed hard on a checksum or a text length that did not add up and on any non-UTF-8 byte — and those files carry their descriptions in Windows-1252. - decompress_ctb reads the file as it is: a checksum or length mismatch is reported and read past (a genuinely cut-off file is still an error), a zlib stream with a bad trailer is retried as raw deflate, and text that is not UTF-8 is read as Windows-1252 (pens and colours are numbers and unaffected; other code pages only show wrong characters in descriptions). - PlotStyleTable carries load_warnings; every load site reports them on the command line, so a table that needed tolerance says so. - load_named explains itself: a .stb name says named tables are not supported yet, a .ctb missing from the folder says which folder to copy it into (instead of "Unknown built-in plot style"). - The Plot dialog shows the reason under the Table row (style_error) — from a selection, from a page setup that names a table, and from the Load… picker, whose error used to be swallowed. - A table picked with Load… from outside the plot styles folder is copied into it (unless a file of that name is already there), so it is listed from then on and page setups naming it resolve after a restart. Tests: flipped checksum, text length off by one, a short payload field, Windows-1252 descriptions, a truncated stream, .stb and missing names. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2b38f798c0
commit
7a75d430cd
28 files changed
+339
-35
No files matched your search
@@ -5100,6 +5100,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = جدول نمط الطباعة (تعيينات الأقلام)
|
.plot-style-table-pen-assignments = جدول نمط الطباعة (تعيينات الأقلام)
|
||||||
.plot-transparency = شفافية الطباعة
|
.plot-transparency = شفافية الطباعة
|
||||||
.plot-upside-down = الطباعة مقلوبة
|
.plot-upside-down = الطباعة مقلوبة
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = جداول أنماط الطباعة المسماة (.stb) غير مدعومة بعد: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = لم يتم العثور على __ocs_fmt_0__ في مجلد أنماط الطباعة (__ocs_fmt_1__).
|
||||||
.preview = معاينة
|
.preview = معاينة
|
||||||
.printer-plotter = الطابعة / الراسم
|
.printer-plotter = الطابعة / الراسم
|
||||||
.properties = الخصائص…
|
.properties = الخصائص…
|
||||||
|
|||||||
@@ -5088,6 +5088,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = Таблица със стилове за плотиране (присвоявания на пера)
|
.plot-style-table-pen-assignments = Таблица със стилове за плотиране (присвоявания на пера)
|
||||||
.plot-transparency = Прозрачност при плотиране
|
.plot-transparency = Прозрачност при плотиране
|
||||||
.plot-upside-down = Плотирай обърнато наопаки
|
.plot-upside-down = Плотирай обърнато наопаки
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = Именуваните таблици със стилове на печат (.stb) все още не се поддържат: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = __ocs_fmt_0__ не беше намерен в папката със стилове на печат (__ocs_fmt_1__).
|
||||||
.preview = Преглед
|
.preview = Преглед
|
||||||
.printer-plotter = Принтер / плотер
|
.printer-plotter = Принтер / плотер
|
||||||
.properties = Свойства…
|
.properties = Свойства…
|
||||||
|
|||||||
@@ -5085,6 +5085,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = Tabulka stylu vykreslování (přiřazení pera)
|
.plot-style-table-pen-assignments = Tabulka stylu vykreslování (přiřazení pera)
|
||||||
.plot-transparency =Průhlednost plotru
|
.plot-transparency =Průhlednost plotru
|
||||||
.plot-upside-down = Zápletka vzhůru nohama
|
.plot-upside-down = Zápletka vzhůru nohama
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = Pojmenované tabulky stylů vykreslování (.stb) zatím nejsou podporovány: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = __ocs_fmt_0__ nebyl nalezen ve složce stylů vykreslování (__ocs_fmt_1__).
|
||||||
.preview = Náhled
|
.preview = Náhled
|
||||||
.printer-plotter = Tiskárna / plotr
|
.printer-plotter = Tiskárna / plotr
|
||||||
.properties = Vlastnosti…
|
.properties = Vlastnosti…
|
||||||
|
|||||||
@@ -5086,6 +5086,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = Plot-Tabelle (Stiftzuweisungen)
|
.plot-style-table-pen-assignments = Plot-Tabelle (Stiftzuweisungen)
|
||||||
.plot-transparency = Transparenz der Parzellen
|
.plot-transparency = Transparenz der Parzellen
|
||||||
.plot-upside-down = Plot auf dem Kopf
|
.plot-upside-down = Plot auf dem Kopf
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = Benannte Plotstiltabellen (.stb) werden noch nicht unterstützt: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = __ocs_fmt_0__ wurde im Plotstil-Ordner (__ocs_fmt_1__) nicht gefunden.
|
||||||
.preview = Vorschau
|
.preview = Vorschau
|
||||||
.printer-plotter = Drucker/Plotter
|
.printer-plotter = Drucker/Plotter
|
||||||
.properties = Eigenschaften...
|
.properties = Eigenschaften...
|
||||||
|
|||||||
@@ -5088,6 +5088,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = Πίνακας στυλ εκτύπωσης (αντιστοιχίσεις πενών)
|
.plot-style-table-pen-assignments = Πίνακας στυλ εκτύπωσης (αντιστοιχίσεις πενών)
|
||||||
.plot-transparency = Εκτύπωση διαφάνειας
|
.plot-transparency = Εκτύπωση διαφάνειας
|
||||||
.plot-upside-down = Εκτύπωση ανάποδα
|
.plot-upside-down = Εκτύπωση ανάποδα
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = Οι επώνυμοι πίνακες στυλ εκτύπωσης (.stb) δεν υποστηρίζονται ακόμα: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = Το __ocs_fmt_0__ δεν βρέθηκε στον φάκελο των στυλ εκτύπωσης (__ocs_fmt_1__).
|
||||||
.preview = Προεπισκόπηση
|
.preview = Προεπισκόπηση
|
||||||
.printer-plotter = Εκτυπωτής / σχεδιογράφος
|
.printer-plotter = Εκτυπωτής / σχεδιογράφος
|
||||||
.properties = Ιδιότητες…
|
.properties = Ιδιότητες…
|
||||||
|
|||||||
@@ -5089,6 +5089,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = Plot style table (pen assignments)
|
.plot-style-table-pen-assignments = Plot style table (pen assignments)
|
||||||
.plot-transparency = Plot transparency
|
.plot-transparency = Plot transparency
|
||||||
.plot-upside-down = Plot upside-down
|
.plot-upside-down = Plot upside-down
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = Named plot style tables (.stb) are not supported yet: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = __ocs_fmt_0__ was not found in the plot styles folder (__ocs_fmt_1__).
|
||||||
.preview = Preview
|
.preview = Preview
|
||||||
.printer-plotter = Printer / plotter
|
.printer-plotter = Printer / plotter
|
||||||
.properties = Properties…
|
.properties = Properties…
|
||||||
|
|||||||
@@ -5088,6 +5088,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = Tabla de estilos de trazado (asignaciones de plumillas)
|
.plot-style-table-pen-assignments = Tabla de estilos de trazado (asignaciones de plumillas)
|
||||||
.plot-transparency = Transparencia de trazado
|
.plot-transparency = Transparencia de trazado
|
||||||
.plot-upside-down = Trazar cabeza abajo
|
.plot-upside-down = Trazar cabeza abajo
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = Las tablas de estilos de trazado con nombre (.stb) aún no se admiten: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = No se encontró __ocs_fmt_0__ en la carpeta de estilos de trazado (__ocs_fmt_1__).
|
||||||
.preview = Vista preliminar
|
.preview = Vista preliminar
|
||||||
.printer-plotter = Impresora / trazador
|
.printer-plotter = Impresora / trazador
|
||||||
.properties = Propiedades…
|
.properties = Propiedades…
|
||||||
|
|||||||
@@ -5087,6 +5087,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = Piirustyylitaulukko (kynätehtävät)
|
.plot-style-table-pen-assignments = Piirustyylitaulukko (kynätehtävät)
|
||||||
.plot-transparency =Juonen läpinäkyvyys
|
.plot-transparency =Juonen läpinäkyvyys
|
||||||
.plot-upside-down = Juoni ylösalaisin
|
.plot-upside-down = Juoni ylösalaisin
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = Nimettyjä tulostustyylitaulukoita (.stb) ei vielä tueta: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = Tiedostoa __ocs_fmt_0__ ei löytynyt tulostustyylien kansiosta (__ocs_fmt_1__).
|
||||||
.preview = Esikatselu
|
.preview = Esikatselu
|
||||||
.printer-plotter = Tulostin/plotteri
|
.printer-plotter = Tulostin/plotteri
|
||||||
.properties = Ominaisuudet…
|
.properties = Ominaisuudet…
|
||||||
|
|||||||
@@ -5088,6 +5088,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = Table des styles de tracé (affectation des plumes)
|
.plot-style-table-pen-assignments = Table des styles de tracé (affectation des plumes)
|
||||||
.plot-transparency = Transparence du lot
|
.plot-transparency = Transparence du lot
|
||||||
.plot-upside-down = Placer vers l'envers
|
.plot-upside-down = Placer vers l'envers
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = Les tables de styles de tracé nommés (.stb) ne sont pas encore prises en charge : __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = __ocs_fmt_0__ est introuvable dans le dossier des styles de tracé (__ocs_fmt_1__).
|
||||||
.preview = Prévisualisation
|
.preview = Prévisualisation
|
||||||
.printer-plotter = Imprimante / traceur
|
.printer-plotter = Imprimante / traceur
|
||||||
.properties = Propriétés...
|
.properties = Propriétés...
|
||||||
|
|||||||
@@ -5076,6 +5076,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = प्लॉट शैली तालिका (पेन असाइनमेंट)
|
.plot-style-table-pen-assignments = प्लॉट शैली तालिका (पेन असाइनमेंट)
|
||||||
.plot-transparency = प्लॉट पारदर्शिता
|
.plot-transparency = प्लॉट पारदर्शिता
|
||||||
.plot-upside-down = ऊपर की ओर नीचे प्लॉट
|
.plot-upside-down = ऊपर की ओर नीचे प्लॉट
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = नामित प्लॉट स्टाइल तालिकाएँ (.stb) अभी समर्थित नहीं हैं: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = __ocs_fmt_0__ प्लॉट स्टाइल फ़ोल्डर (__ocs_fmt_1__) में नहीं मिला।
|
||||||
.preview = पूर्वावलोकन
|
.preview = पूर्वावलोकन
|
||||||
.printer-plotter = प्रिंटर / प्लॉटर
|
.printer-plotter = प्रिंटर / प्लॉटर
|
||||||
.properties = गुण...
|
.properties = गुण...
|
||||||
|
|||||||
@@ -5087,6 +5087,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = Nyomtatási stílus táblázat (toll-hozzárendelések)
|
.plot-style-table-pen-assignments = Nyomtatási stílus táblázat (toll-hozzárendelések)
|
||||||
.plot-transparency =A telek átláthatósága
|
.plot-transparency =A telek átláthatósága
|
||||||
.plot-upside-down = Telek fejjel lefelé
|
.plot-upside-down = Telek fejjel lefelé
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = A nevesített nyomtatási stílustáblák (.stb) még nem támogatottak: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = A(z) __ocs_fmt_0__ nem található a nyomtatási stílusok mappájában (__ocs_fmt_1__).
|
||||||
.preview = Előnézet
|
.preview = Előnézet
|
||||||
.printer-plotter = Nyomtató / plotter
|
.printer-plotter = Nyomtató / plotter
|
||||||
.properties = Tulajdonságok…
|
.properties = Tulajdonságok…
|
||||||
|
|||||||
@@ -5087,6 +5087,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = Tabella stili di stampa (assegnazioni penne)
|
.plot-style-table-pen-assignments = Tabella stili di stampa (assegnazioni penne)
|
||||||
.plot-transparency =Trasparenza della trama
|
.plot-transparency =Trasparenza della trama
|
||||||
.plot-upside-down = Trama sottosopra
|
.plot-upside-down = Trama sottosopra
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = Le tabelle di stili di stampa con nome (.stb) non sono ancora supportate: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = __ocs_fmt_0__ non è stato trovato nella cartella degli stili di stampa (__ocs_fmt_1__).
|
||||||
.preview = Anteprima
|
.preview = Anteprima
|
||||||
.printer-plotter = Stampante/plotter
|
.printer-plotter = Stampante/plotter
|
||||||
.properties = Proprietà…
|
.properties = Proprietà…
|
||||||
|
|||||||
@@ -5076,6 +5076,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = 印刷スタイルテーブル(ペン割当)
|
.plot-style-table-pen-assignments = 印刷スタイルテーブル(ペン割当)
|
||||||
.plot-transparency = 透明度を印刷
|
.plot-transparency = 透明度を印刷
|
||||||
.plot-upside-down = 上下反転印刷
|
.plot-upside-down = 上下反転印刷
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = 名前付き印刷スタイルテーブル (.stb) はまだサポートされていません: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = __ocs_fmt_0__ は印刷スタイルフォルダー (__ocs_fmt_1__) に見つかりませんでした。
|
||||||
.preview = プレビュー
|
.preview = プレビュー
|
||||||
.printer-plotter = プリンター / プロッター
|
.printer-plotter = プリンター / プロッター
|
||||||
.properties = プロパティ…
|
.properties = プロパティ…
|
||||||
|
|||||||
@@ -5087,6 +5087,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = 플롯 스타일 테이블(펜 지정)
|
.plot-style-table-pen-assignments = 플롯 스타일 테이블(펜 지정)
|
||||||
.plot-transparency =플롯 투명도
|
.plot-transparency =플롯 투명도
|
||||||
.plot-upside-down = 거꾸로 플롯
|
.plot-upside-down = 거꾸로 플롯
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = 명명된 플롯 스타일 테이블(.stb)은 아직 지원되지 않습니다: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = __ocs_fmt_0__을(를) 플롯 스타일 폴더(__ocs_fmt_1__)에서 찾을 수 없습니다.
|
||||||
.preview = 미리보기
|
.preview = 미리보기
|
||||||
.printer-plotter = 프린터/플로터
|
.printer-plotter = 프린터/플로터
|
||||||
.properties = 속성…
|
.properties = 속성…
|
||||||
|
|||||||
@@ -5086,6 +5086,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = Plotstijltabel (pentoewijzingen)
|
.plot-style-table-pen-assignments = Plotstijltabel (pentoewijzingen)
|
||||||
.plot-transparency = Doorzichtigheid van de plaats van uitvoering
|
.plot-transparency = Doorzichtigheid van de plaats van uitvoering
|
||||||
.plot-upside-down = Ondersteboven plotten
|
.plot-upside-down = Ondersteboven plotten
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = Benoemde plotstijltabellen (.stb) worden nog niet ondersteund: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = __ocs_fmt_0__ is niet gevonden in de map met plotstijlen (__ocs_fmt_1__).
|
||||||
.preview = Voorbeeld
|
.preview = Voorbeeld
|
||||||
.printer-plotter = Printer/plotter
|
.printer-plotter = Printer/plotter
|
||||||
.properties = Eigenschappen...
|
.properties = Eigenschappen...
|
||||||
|
|||||||
@@ -5087,6 +5087,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = Tabela stylów wydruku (przypisania pisaków)
|
.plot-style-table-pen-assignments = Tabela stylów wydruku (przypisania pisaków)
|
||||||
.plot-transparency =Przejrzystość fabuły
|
.plot-transparency =Przejrzystość fabuły
|
||||||
.plot-upside-down = Fabuła do góry nogami
|
.plot-upside-down = Fabuła do góry nogami
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = Nazwane tabele stylów wydruku (.stb) nie są jeszcze obsługiwane: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = Nie znaleziono __ocs_fmt_0__ w folderze stylów wydruku (__ocs_fmt_1__).
|
||||||
.preview = Podgląd
|
.preview = Podgląd
|
||||||
.printer-plotter = Drukarka / ploter
|
.printer-plotter = Drukarka / ploter
|
||||||
.properties = Właściwości…
|
.properties = Właściwości…
|
||||||
|
|||||||
@@ -5088,6 +5088,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = Tabela de estilos de plotagem (atribuições de penas)
|
.plot-style-table-pen-assignments = Tabela de estilos de plotagem (atribuições de penas)
|
||||||
.plot-transparency = Transparência de plotagem
|
.plot-transparency = Transparência de plotagem
|
||||||
.plot-upside-down = Plotar de cabeça para baixo
|
.plot-upside-down = Plotar de cabeça para baixo
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = Tabelas de estilos de plotagem nomeados (.stb) ainda não são suportadas: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = __ocs_fmt_0__ não foi encontrado na pasta de estilos de plotagem (__ocs_fmt_1__).
|
||||||
.preview = Visualização
|
.preview = Visualização
|
||||||
.printer-plotter = Impressora / plotadora
|
.printer-plotter = Impressora / plotadora
|
||||||
.properties = Propriedades…
|
.properties = Propriedades…
|
||||||
|
|||||||
@@ -5089,6 +5089,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = Стол стиля сюжета (пен-задания)
|
.plot-style-table-pen-assignments = Стол стиля сюжета (пен-задания)
|
||||||
.plot-transparency = Прозрачность сюжета
|
.plot-transparency = Прозрачность сюжета
|
||||||
.plot-upside-down = Заговор вверх ногами
|
.plot-upside-down = Заговор вверх ногами
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = Именованные таблицы стилей печати (.stb) пока не поддерживаются: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = __ocs_fmt_0__ не найден в папке стилей печати (__ocs_fmt_1__).
|
||||||
.preview = Предпросмотр
|
.preview = Предпросмотр
|
||||||
.printer-plotter = Принтер / Плоттер
|
.printer-plotter = Принтер / Плоттер
|
||||||
.properties = Свойства...
|
.properties = Свойства...
|
||||||
|
|||||||
@@ -5028,6 +5028,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = Çizim stili tablosu (kalem atamaları)
|
.plot-style-table-pen-assignments = Çizim stili tablosu (kalem atamaları)
|
||||||
.plot-transparency = Saydamlığı yazdır
|
.plot-transparency = Saydamlığı yazdır
|
||||||
.plot-upside-down = Ters yazdır
|
.plot-upside-down = Ters yazdır
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = Adlandırılmış çizim stili tabloları (.stb) henüz desteklenmiyor: __ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = __ocs_fmt_0__, çizim stilleri klasöründe (__ocs_fmt_1__) bulunamadı.
|
||||||
.preview = Önizleme
|
.preview = Önizleme
|
||||||
.printer-plotter = Yazıcı / çizici
|
.printer-plotter = Yazıcı / çizici
|
||||||
.properties = Özellikler…
|
.properties = Özellikler…
|
||||||
|
|||||||
@@ -5071,6 +5071,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = 打印样式表(笔指定)
|
.plot-style-table-pen-assignments = 打印样式表(笔指定)
|
||||||
.plot-transparency = 打印透明度
|
.plot-transparency = 打印透明度
|
||||||
.plot-upside-down = 上下颠倒打印
|
.plot-upside-down = 上下颠倒打印
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = 尚不支持命名打印样式表 (.stb):__ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = 在打印样式文件夹 (__ocs_fmt_1__) 中找不到 __ocs_fmt_0__。
|
||||||
.preview = 预览
|
.preview = 预览
|
||||||
.printer-plotter = 打印机/绘图仪
|
.printer-plotter = 打印机/绘图仪
|
||||||
.properties = 特性…
|
.properties = 特性…
|
||||||
|
|||||||
@@ -5083,6 +5083,8 @@ plot =
|
|||||||
.plot-style-table-pen-assignments = 列印樣式表(筆指定)
|
.plot-style-table-pen-assignments = 列印樣式表(筆指定)
|
||||||
.plot-transparency = 列印透明度
|
.plot-transparency = 列印透明度
|
||||||
.plot-upside-down = 上下顛倒列印
|
.plot-upside-down = 上下顛倒列印
|
||||||
|
.named-plot-style-tables-stb-are-not-supported-yet-name = 尚不支援具名出圖型式表 (.stb):__ocs_fmt_0__
|
||||||
|
.name-was-not-found-in-the-plot-styles-folder-folder = 在出圖型式資料夾 (__ocs_fmt_1__) 中找不到 __ocs_fmt_0__。
|
||||||
.preview = 預覽
|
.preview = 預覽
|
||||||
.printer-plotter = 印表機/繪圖儀
|
.printer-plotter = 印表機/繪圖儀
|
||||||
.properties = 特性…
|
.properties = 特性…
|
||||||
|
|||||||
+3
-1
@@ -3422,7 +3422,9 @@ pub enum Message {
|
|||||||
/// Open file dialog to load a CTB/STB plot style table.
|
/// Open file dialog to load a CTB/STB plot style table.
|
||||||
PlotStyleLoad,
|
PlotStyleLoad,
|
||||||
/// Callback when the user picks (or cancels) a CTB/STB file.
|
/// Callback when the user picks (or cancels) a CTB/STB file.
|
||||||
PlotStyleLoaded(Option<crate::io::plot_style::PlotStyleTable>),
|
/// The Load… picker finished: a table, nothing (cancelled), or why the
|
||||||
|
/// file could not be read.
|
||||||
|
PlotStyleLoaded(Result<Option<crate::io::plot_style::PlotStyleTable>, String>),
|
||||||
/// Clear the active plot style table.
|
/// Clear the active plot style table.
|
||||||
PlotStyleClear,
|
PlotStyleClear,
|
||||||
/// Open/close the Plot Style panel.
|
/// Open/close the Plot Style panel.
|
||||||
|
|||||||
+23
-4
@@ -4468,18 +4468,22 @@ impl OpenCADStudio {
|
|||||||
self.plot_dialog.apply_plot_styles = false;
|
self.plot_dialog.apply_plot_styles = false;
|
||||||
self.plot_dialog.show_plot_styles = false;
|
self.plot_dialog.show_plot_styles = false;
|
||||||
self.plot_dialog.style_missing = false;
|
self.plot_dialog.style_missing = false;
|
||||||
|
self.plot_dialog.style_error = None;
|
||||||
} else {
|
} else {
|
||||||
match crate::io::plot_style::PlotStyleTable::load_named(&name) {
|
match crate::io::plot_style::PlotStyleTable::load_named(&name) {
|
||||||
Ok(table) => {
|
Ok(table) => {
|
||||||
self.plot_dialog.style_name = table.name.clone();
|
self.plot_dialog.style_name = table.name.clone();
|
||||||
self.plot_dialog.apply_plot_styles = true;
|
self.plot_dialog.apply_plot_styles = true;
|
||||||
self.plot_dialog.style_missing = false;
|
self.plot_dialog.style_missing = false;
|
||||||
|
self.plot_dialog.style_error = None;
|
||||||
|
self.report_plot_style_warnings(&table);
|
||||||
self.active_plot_style = Some(table);
|
self.active_plot_style = Some(table);
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
self.plot_dialog.style_name = name;
|
self.plot_dialog.style_name = name;
|
||||||
self.plot_dialog.style_missing = true;
|
self.plot_dialog.style_missing = true;
|
||||||
self.command_line.push_error(&error);
|
self.command_line.push_error(&error);
|
||||||
|
self.plot_dialog.style_error = Some(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4744,16 +4748,19 @@ impl OpenCADStudio {
|
|||||||
));
|
));
|
||||||
self.plot_dialog.window = self.plot_window;
|
self.plot_dialog.window = self.plot_window;
|
||||||
}
|
}
|
||||||
|
let mut style_error = None;
|
||||||
if !ps.current_style_sheet.is_empty()
|
if !ps.current_style_sheet.is_empty()
|
||||||
&& self
|
&& self
|
||||||
.active_plot_style
|
.active_plot_style
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_none_or(|table| !table.name.eq_ignore_ascii_case(&ps.current_style_sheet))
|
.is_none_or(|table| !table.name.eq_ignore_ascii_case(&ps.current_style_sheet))
|
||||||
{
|
{
|
||||||
if let Ok(table) =
|
match crate::io::plot_style::PlotStyleTable::load_named(&ps.current_style_sheet) {
|
||||||
crate::io::plot_style::PlotStyleTable::load_named(&ps.current_style_sheet)
|
Ok(table) => {
|
||||||
{
|
self.report_plot_style_warnings(&table);
|
||||||
self.active_plot_style = Some(table);
|
self.active_plot_style = Some(table);
|
||||||
|
}
|
||||||
|
Err(error) => style_error = Some(error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let active_style_name = self
|
let active_style_name = self
|
||||||
@@ -4901,6 +4908,18 @@ impl OpenCADStudio {
|
|||||||
d.apply_plot_styles = ps.flags.plot_plot_styles;
|
d.apply_plot_styles = ps.flags.plot_plot_styles;
|
||||||
d.show_plot_styles = ps.flags.show_plot_styles;
|
d.show_plot_styles = ps.flags.show_plot_styles;
|
||||||
d.style_missing = !d.style_name.is_empty() && !style_loaded;
|
d.style_missing = !d.style_name.is_empty() && !style_loaded;
|
||||||
|
d.style_error = if d.style_missing { style_error } else { None };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tell the user what a plot style table's loader had to tolerate.
|
||||||
|
pub(in crate::app) fn report_plot_style_warnings(
|
||||||
|
&mut self,
|
||||||
|
table: &crate::io::plot_style::PlotStyleTable,
|
||||||
|
) {
|
||||||
|
for warning in &table.load_warnings {
|
||||||
|
let name = &table.name;
|
||||||
|
self.command_line.push_warning(&format!("{name}: {warning}"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copy transient paper/scale choices out of the dialog without changing
|
/// Copy transient paper/scale choices out of the dialog without changing
|
||||||
|
|||||||
+15
-2
@@ -8762,7 +8762,7 @@ impl OpenCADStudio {
|
|||||||
Message::PlotStyleLoad => {
|
Message::PlotStyleLoad => {
|
||||||
Task::perform(crate::io::pick_plot_style(), Message::PlotStyleLoaded)
|
Task::perform(crate::io::pick_plot_style(), Message::PlotStyleLoaded)
|
||||||
}
|
}
|
||||||
Message::PlotStyleLoaded(Some(table)) => {
|
Message::PlotStyleLoaded(Ok(Some(table))) => {
|
||||||
if table.is_stb {
|
if table.is_stb {
|
||||||
self.command_line.push_error(
|
self.command_line.push_error(
|
||||||
crate::t!(
|
crate::t!(
|
||||||
@@ -8774,6 +8774,8 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
self.plot_dialog.style_name = table.name.clone();
|
self.plot_dialog.style_name = table.name.clone();
|
||||||
self.plot_dialog.style_missing = false;
|
self.plot_dialog.style_missing = false;
|
||||||
|
self.plot_dialog.style_error = None;
|
||||||
|
self.report_plot_style_warnings(&table);
|
||||||
self.command_line.push_output(
|
self.command_line.push_output(
|
||||||
crate::tf!(
|
crate::tf!(
|
||||||
"Plot style '{}' loaded ({} color entries).",
|
"Plot style '{}' loaded ({} color entries).",
|
||||||
@@ -8790,7 +8792,15 @@ impl OpenCADStudio {
|
|||||||
self.plot_dialog.plot_styles = crate::io::plot_style::available_ctb_names();
|
self.plot_dialog.plot_styles = crate::io::plot_style::available_ctb_names();
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::PlotStyleLoaded(None) => Task::none(),
|
Message::PlotStyleLoaded(Ok(None)) => Task::none(),
|
||||||
|
Message::PlotStyleLoaded(Err(error)) => {
|
||||||
|
// The file the user pointed at could not be read: say why,
|
||||||
|
// in the dialog as well as on the command line.
|
||||||
|
self.command_line.push_error(&error);
|
||||||
|
self.plot_dialog.style_missing = true;
|
||||||
|
self.plot_dialog.style_error = Some(error);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
Message::PlotStyleClear => {
|
Message::PlotStyleClear => {
|
||||||
self.active_plot_style = None;
|
self.active_plot_style = None;
|
||||||
self.plot_dialog.style_name.clear();
|
self.plot_dialog.style_name.clear();
|
||||||
@@ -8817,12 +8827,15 @@ impl OpenCADStudio {
|
|||||||
if needs_load {
|
if needs_load {
|
||||||
match crate::io::plot_style::PlotStyleTable::load_named(&selected_style) {
|
match crate::io::plot_style::PlotStyleTable::load_named(&selected_style) {
|
||||||
Ok(table) => {
|
Ok(table) => {
|
||||||
|
self.report_plot_style_warnings(&table);
|
||||||
self.active_plot_style = Some(table);
|
self.active_plot_style = Some(table);
|
||||||
self.plot_dialog.style_missing = false;
|
self.plot_dialog.style_missing = false;
|
||||||
|
self.plot_dialog.style_error = None;
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
self.plot_dialog.style_missing = true;
|
self.plot_dialog.style_missing = true;
|
||||||
self.command_line.push_error(&error);
|
self.command_line.push_error(&error);
|
||||||
|
self.plot_dialog.style_error = Some(error);
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-3
@@ -1522,7 +1522,12 @@ mod save_failure_tests {
|
|||||||
// ── Plot Style Table ──────────────────────────────────────────────────────
|
// ── Plot Style Table ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Show a file-open dialog and load the selected CTB or STB file.
|
/// Show a file-open dialog and load the selected CTB or STB file.
|
||||||
pub async fn pick_plot_style() -> Option<plot_style::PlotStyleTable> {
|
/// Pick a plot style table file and load it. `Ok(None)` when the picker was
|
||||||
|
/// cancelled; `Err` says why the chosen file could not be read. A table
|
||||||
|
/// picked from outside the plot styles folder is copied into it (unless a
|
||||||
|
/// file of that name is already there), so the dialog lists it from now on
|
||||||
|
/// and page setups naming it resolve after a restart.
|
||||||
|
pub async fn pick_plot_style() -> Result<Option<plot_style::PlotStyleTable>, String> {
|
||||||
let dialog = crate::sys::file_dialog()
|
let dialog = crate::sys::file_dialog()
|
||||||
.set_title(crate::t!("Load Plot Style Table").as_ref())
|
.set_title(crate::t!("Load Plot Style Table").as_ref())
|
||||||
.add_filter(crate::t!("Plot Style Tables").as_ref(), &["ctb", "CTB"])
|
.add_filter(crate::t!("Plot Style Tables").as_ref(), &["ctb", "CTB"])
|
||||||
@@ -1533,8 +1538,26 @@ pub async fn pick_plot_style() -> Option<plot_style::PlotStyleTable> {
|
|||||||
Ok(dir) => dialog.set_directory(dir),
|
Ok(dir) => dialog.set_directory(dir),
|
||||||
Err(_) => dialog,
|
Err(_) => dialog,
|
||||||
};
|
};
|
||||||
let handle = dialog.pick_file().await?;
|
let Some(handle) = dialog.pick_file().await else {
|
||||||
plot_style::PlotStyleTable::load(&crate::sys::handle_path(&handle)).ok()
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let path = crate::sys::handle_path(&handle);
|
||||||
|
let table = plot_style::PlotStyleTable::load(&path)?;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
if let (Ok(dir), Some(file_name)) = (plot_style::ensure_plot_styles_dir(), path.file_name()) {
|
||||||
|
let destination = dir.join(file_name);
|
||||||
|
let same_place = path.parent().is_some_and(|parent| {
|
||||||
|
parent
|
||||||
|
.canonicalize()
|
||||||
|
.ok()
|
||||||
|
.zip(dir.canonicalize().ok())
|
||||||
|
.is_some_and(|(a, b)| a == b)
|
||||||
|
});
|
||||||
|
if !same_place && !destination.exists() {
|
||||||
|
let _ = std::fs::copy(&path, &destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(table))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Image file picker ─────────────────────────────────────────────────────
|
// ── Image file picker ─────────────────────────────────────────────────────
|
||||||
|
|||||||
+212
-25
@@ -175,6 +175,10 @@ pub struct PlotStyleTable {
|
|||||||
pub aci_entries: Vec<PlotStyleEntry>, // 256 entries, index = ACI
|
pub aci_entries: Vec<PlotStyleEntry>, // 256 entries, index = ACI
|
||||||
/// For STB: named style entries.
|
/// For STB: named style entries.
|
||||||
pub named_entries: HashMap<String, PlotStyleEntry>,
|
pub named_entries: HashMap<String, PlotStyleEntry>,
|
||||||
|
/// What had to be tolerated to read the file (a bad checksum, a length
|
||||||
|
/// that did not add up, descriptions in a Windows code page): the file
|
||||||
|
/// loaded, but the user deserves to know it was not quite as written.
|
||||||
|
pub load_warnings: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotStyleTable {
|
impl PlotStyleTable {
|
||||||
@@ -190,6 +194,7 @@ impl PlotStyleTable {
|
|||||||
lineweights: LW_TABLE.to_vec(),
|
lineweights: LW_TABLE.to_vec(),
|
||||||
aci_entries: (0..=255).map(|_| PlotStyleEntry::default()).collect(),
|
aci_entries: (0..=255).map(|_| PlotStyleEntry::default()).collect(),
|
||||||
named_entries: HashMap::default(),
|
named_entries: HashMap::default(),
|
||||||
|
load_warnings: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,8 +212,10 @@ impl PlotStyleTable {
|
|||||||
pub fn from_bytes(name: impl Into<String>, raw: &[u8]) -> Result<Self, String> {
|
pub fn from_bytes(name: impl Into<String>, raw: &[u8]) -> Result<Self, String> {
|
||||||
let name = name.into();
|
let name = name.into();
|
||||||
let is_stb = name.to_ascii_lowercase().ends_with(".stb");
|
let is_stb = name.to_ascii_lowercase().ends_with(".stb");
|
||||||
let text = decompress_ctb(raw)?;
|
let (text, warnings) = decompress_ctb(raw)?;
|
||||||
parse_plot_style_text(&text, name, is_stb)
|
let mut table = parse_plot_style_text(&text, name, is_stb)?;
|
||||||
|
table.load_warnings = warnings;
|
||||||
|
Ok(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn builtin(name: &str) -> Result<Self, String> {
|
pub fn builtin(name: &str) -> Result<Self, String> {
|
||||||
@@ -264,12 +271,18 @@ impl PlotStyleTable {
|
|||||||
let path = Path::new(name);
|
let path = Path::new(name);
|
||||||
if path.components().count() != 1
|
if path.components().count() != 1
|
||||||
|| !matches!(path.components().next(), Some(Component::Normal(_)))
|
|| !matches!(path.components().next(), Some(Component::Normal(_)))
|
||||||
|| !path
|
|
||||||
.extension()
|
|
||||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("ctb"))
|
|
||||||
{
|
{
|
||||||
return Err(format!("Invalid plot style name: {name}"));
|
return Err(format!("Invalid plot style name: {name}"));
|
||||||
}
|
}
|
||||||
|
if path.extension().is_some_and(|ext| ext.eq_ignore_ascii_case("stb")) {
|
||||||
|
return Err(crate::tf!(
|
||||||
|
"Named plot style tables (.stb) are not supported yet: {name}"
|
||||||
|
)
|
||||||
|
.into_owned());
|
||||||
|
}
|
||||||
|
if !path.extension().is_some_and(|ext| ext.eq_ignore_ascii_case("ctb")) {
|
||||||
|
return Err(format!("Invalid plot style name: {name}"));
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
{
|
{
|
||||||
@@ -286,7 +299,12 @@ impl PlotStyleTable {
|
|||||||
.map(|entry| entry.path());
|
.map(|entry| entry.path());
|
||||||
return match matched {
|
return match matched {
|
||||||
Some(path) => Self::load(&path),
|
Some(path) => Self::load(&path),
|
||||||
None => Self::builtin(name),
|
None => Self::builtin(name).map_err(|_| {
|
||||||
|
// Not a built-in either: say where a copy would be found.
|
||||||
|
let folder = dir.display();
|
||||||
|
crate::tf!("{name} was not found in the plot styles folder ({folder}).")
|
||||||
|
.into_owned()
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,10 +442,18 @@ impl PlotStyleTable {
|
|||||||
|
|
||||||
// ── Deflate helpers ───────────────────────────────────────────────────────────
|
// ── Deflate helpers ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Decompress a CTB/STB file's raw bytes into the text content.
|
/// Decompress a CTB/STB file's raw bytes into the text content, with the
|
||||||
|
/// things that had to be tolerated on the way.
|
||||||
///
|
///
|
||||||
fn decompress_ctb(data: &[u8]) -> Result<String, String> {
|
/// Files written by other applications are read as they are, not as the
|
||||||
|
/// container says they should be: a checksum or a length that does not add
|
||||||
|
/// up is reported and read past (the text is either there or the zlib
|
||||||
|
/// stream fails on its own), a zlib stream with a bad trailer is retried as
|
||||||
|
/// raw deflate, and text that is not UTF-8 is read as Windows-1252 — the
|
||||||
|
/// code page those applications write descriptions in.
|
||||||
|
fn decompress_ctb(data: &[u8]) -> Result<(String, Vec<String>), String> {
|
||||||
const PREFIX: &[u8] = b"PIAFILEVERSION_2.0,CTBVER1,compress\r\npmzlibcodec";
|
const PREFIX: &[u8] = b"PIAFILEVERSION_2.0,CTBVER1,compress\r\npmzlibcodec";
|
||||||
|
let mut warnings = Vec::new();
|
||||||
let mut decoded = Vec::new();
|
let mut decoded = Vec::new();
|
||||||
if data.starts_with(PREFIX) {
|
if data.starts_with(PREFIX) {
|
||||||
if data.len() < 60 {
|
if data.len() < 60 {
|
||||||
@@ -436,20 +462,34 @@ fn decompress_ctb(data: &[u8]) -> Result<String, String> {
|
|||||||
let checksum = u32::from_le_bytes(data[48..52].try_into().unwrap());
|
let checksum = u32::from_le_bytes(data[48..52].try_into().unwrap());
|
||||||
let text_len = u32::from_le_bytes(data[52..56].try_into().unwrap()) as usize;
|
let text_len = u32::from_le_bytes(data[52..56].try_into().unwrap()) as usize;
|
||||||
let compressed_len = u32::from_le_bytes(data[56..60].try_into().unwrap()) as usize;
|
let compressed_len = u32::from_le_bytes(data[56..60].try_into().unwrap()) as usize;
|
||||||
if compressed_len > data.len() - 60 {
|
let available = data.len() - 60;
|
||||||
return Err("CTB compressed payload is truncated".into());
|
let payload = if compressed_len > available {
|
||||||
}
|
warnings.push(format!(
|
||||||
let payload = &data[60..60 + compressed_len];
|
"the header announces {compressed_len} compressed bytes \
|
||||||
|
but the file holds {available}"
|
||||||
|
));
|
||||||
|
&data[60..]
|
||||||
|
} else {
|
||||||
|
&data[60..60 + compressed_len]
|
||||||
|
};
|
||||||
if adler32(payload) != checksum {
|
if adler32(payload) != checksum {
|
||||||
return Err("CTB compressed payload checksum mismatch".into());
|
warnings.push("the compressed payload's checksum does not match".into());
|
||||||
}
|
}
|
||||||
use flate2::read::ZlibDecoder;
|
decoded = inflate_lenient(payload, &mut warnings)?;
|
||||||
ZlibDecoder::new(payload)
|
// A byte or a few short is a header that counted its trailing NUL
|
||||||
.read_to_end(&mut decoded)
|
// differently; far short of the announced text is a cut-off file,
|
||||||
.map_err(|e| format!("CTB zlib decompress: {e}"))?;
|
// which the inflater does not always notice on its own.
|
||||||
if decoded.len() != text_len {
|
const TOLERATED_SHORTFALL: usize = 16;
|
||||||
|
if decoded.len() + TOLERATED_SHORTFALL < text_len {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"CTB content length mismatch: expected {text_len}, got {}",
|
"CTB text is incomplete: the header announces {text_len} bytes \
|
||||||
|
but only {} could be read",
|
||||||
|
decoded.len()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if decoded.len() != text_len {
|
||||||
|
warnings.push(format!(
|
||||||
|
"the header announces {text_len} bytes of text but {} were read",
|
||||||
decoded.len()
|
decoded.len()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -461,10 +501,7 @@ fn decompress_ctb(data: &[u8]) -> Result<String, String> {
|
|||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
let payload = &data[split_at..];
|
let payload = &data[split_at..];
|
||||||
if payload.starts_with(&[0x78]) {
|
if payload.starts_with(&[0x78]) {
|
||||||
use flate2::read::ZlibDecoder;
|
decoded = inflate_lenient(payload, &mut warnings)?;
|
||||||
ZlibDecoder::new(payload)
|
|
||||||
.read_to_end(&mut decoded)
|
|
||||||
.map_err(|e| format!("legacy CTB zlib decompress: {e}"))?;
|
|
||||||
} else {
|
} else {
|
||||||
use flate2::read::DeflateDecoder;
|
use flate2::read::DeflateDecoder;
|
||||||
DeflateDecoder::new(payload)
|
DeflateDecoder::new(payload)
|
||||||
@@ -472,10 +509,72 @@ fn decompress_ctb(data: &[u8]) -> Result<String, String> {
|
|||||||
.map_err(|e| format!("legacy CTB deflate decompress: {e}"))?;
|
.map_err(|e| format!("legacy CTB deflate decompress: {e}"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if decoded.last() == Some(&0) {
|
while decoded.last() == Some(&0) {
|
||||||
decoded.pop();
|
decoded.pop();
|
||||||
}
|
}
|
||||||
String::from_utf8(decoded).map_err(|e| format!("CTB text is not UTF-8: {e}"))
|
let (text, code_page) = decode_plot_style_text(decoded);
|
||||||
|
if let Some(note) = code_page {
|
||||||
|
warnings.push(note);
|
||||||
|
}
|
||||||
|
Ok((text, warnings))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inflate a zlib stream, falling back to the raw deflate data behind its
|
||||||
|
/// two-byte header when the stream's own trailer is wrong.
|
||||||
|
fn inflate_lenient(payload: &[u8], warnings: &mut Vec<String>) -> Result<Vec<u8>, String> {
|
||||||
|
use flate2::read::{DeflateDecoder, ZlibDecoder};
|
||||||
|
let mut decoded = Vec::new();
|
||||||
|
match ZlibDecoder::new(payload).read_to_end(&mut decoded) {
|
||||||
|
Ok(_) => Ok(decoded),
|
||||||
|
Err(zlib_error) if payload.len() > 2 => {
|
||||||
|
let mut raw = Vec::new();
|
||||||
|
DeflateDecoder::new(&payload[2..])
|
||||||
|
.read_to_end(&mut raw)
|
||||||
|
.map_err(|_| format!("CTB zlib decompress: {zlib_error}"))?;
|
||||||
|
warnings.push("the zlib stream is damaged; its deflate data was read directly".into());
|
||||||
|
Ok(raw)
|
||||||
|
}
|
||||||
|
Err(zlib_error) => Err(format!("CTB zlib decompress: {zlib_error}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The table's text out of its bytes: UTF-8 when it is, else Windows-1252
|
||||||
|
/// (Latin-1 with the 0x80–0x9F block the code page defines), which is what
|
||||||
|
/// applications on Western systems write into descriptions. Other code
|
||||||
|
/// pages come out as readable but wrong characters in descriptions only —
|
||||||
|
/// pens and colours are numbers and unaffected. The second value says when
|
||||||
|
/// the fallback was taken.
|
||||||
|
pub fn decode_plot_style_text(bytes: Vec<u8>) -> (String, Option<String>) {
|
||||||
|
match String::from_utf8(bytes) {
|
||||||
|
Ok(text) => (text, None),
|
||||||
|
Err(error) => {
|
||||||
|
let text = error
|
||||||
|
.into_bytes()
|
||||||
|
.into_iter()
|
||||||
|
.map(windows_1252_char)
|
||||||
|
.collect();
|
||||||
|
(
|
||||||
|
text,
|
||||||
|
Some("descriptions were read as Windows-1252 text (the file is not UTF-8)".into()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One Windows-1252 byte as a character: Latin-1 except for the 0x80–0x9F
|
||||||
|
/// block, which the code page fills with punctuation and accented letters.
|
||||||
|
fn windows_1252_char(byte: u8) -> char {
|
||||||
|
const HIGH: [char; 32] = [
|
||||||
|
'\u{20AC}', '\u{FFFD}', '\u{201A}', '\u{0192}', '\u{201E}', '\u{2026}', '\u{2020}',
|
||||||
|
'\u{2021}', '\u{02C6}', '\u{2030}', '\u{0160}', '\u{2039}', '\u{0152}', '\u{FFFD}',
|
||||||
|
'\u{017D}', '\u{FFFD}', '\u{FFFD}', '\u{2018}', '\u{2019}', '\u{201C}', '\u{201D}',
|
||||||
|
'\u{2022}', '\u{2013}', '\u{2014}', '\u{02DC}', '\u{2122}', '\u{0161}', '\u{203A}',
|
||||||
|
'\u{0153}', '\u{FFFD}', '\u{017E}', '\u{0178}',
|
||||||
|
];
|
||||||
|
match byte {
|
||||||
|
0x80..=0x9F => HIGH[usize::from(byte - 0x80)],
|
||||||
|
_ => char::from(byte),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compress plot-style text content as a CTB/STB file.
|
/// Compress plot-style text content as a CTB/STB file.
|
||||||
@@ -686,6 +785,7 @@ fn parse_plot_style_text(text: &str, name: String, is_stb: bool) -> Result<PlotS
|
|||||||
lineweights,
|
lineweights,
|
||||||
aci_entries,
|
aci_entries,
|
||||||
named_entries,
|
named_entries,
|
||||||
|
load_warnings: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -797,3 +897,90 @@ mod round_trip_tests {
|
|||||||
assert_eq!(reloaded.resolve_lineweight(1), Some(0.035));
|
assert_eq!(reloaded.resolve_lineweight(1), Some(0.035));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod lenient_loading_tests {
|
||||||
|
use super::{compress_ctb, decode_plot_style_text, PlotStyleTable};
|
||||||
|
|
||||||
|
fn table_with_description(description: &str) -> PlotStyleTable {
|
||||||
|
let mut table = PlotStyleTable::identity("foreign.ctb");
|
||||||
|
table.description = description.to_string();
|
||||||
|
table.aci_entries[1].lineweight = 13;
|
||||||
|
table
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compressed(table: &PlotStyleTable) -> Vec<u8> {
|
||||||
|
compress_ctb(table.to_text().as_bytes()).expect("compress")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The pen data must survive every tolerated defect.
|
||||||
|
fn assert_pens_kept(table: &PlotStyleTable) {
|
||||||
|
assert_eq!(table.aci_entries[1].lineweight, 13);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_wrong_checksum_is_reported_not_fatal() {
|
||||||
|
let mut bytes = compressed(&table_with_description("ok"));
|
||||||
|
bytes[48] ^= 0xFF;
|
||||||
|
let table = PlotStyleTable::from_bytes("foreign.ctb", &bytes).expect("loads");
|
||||||
|
assert_pens_kept(&table);
|
||||||
|
assert_eq!(table.load_warnings.len(), 1, "{:?}", table.load_warnings);
|
||||||
|
assert!(table.load_warnings[0].contains("checksum"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_text_length_off_by_one_and_a_short_payload_field_are_tolerated() {
|
||||||
|
let mut bytes = compressed(&table_with_description("ok"));
|
||||||
|
let text_len = u32::from_le_bytes(bytes[52..56].try_into().unwrap());
|
||||||
|
bytes[52..56].copy_from_slice(&(text_len - 1).to_le_bytes());
|
||||||
|
let table = PlotStyleTable::from_bytes("foreign.ctb", &bytes).expect("loads");
|
||||||
|
assert_pens_kept(&table);
|
||||||
|
assert!(table.load_warnings.iter().any(|w| w.contains("bytes of text")));
|
||||||
|
|
||||||
|
let mut bytes = compressed(&table_with_description("ok"));
|
||||||
|
let compressed_len = u32::from_le_bytes(bytes[56..60].try_into().unwrap());
|
||||||
|
bytes[56..60].copy_from_slice(&(compressed_len + 100).to_le_bytes());
|
||||||
|
let table = PlotStyleTable::from_bytes("foreign.ctb", &bytes).expect("loads");
|
||||||
|
assert_pens_kept(&table);
|
||||||
|
assert!(table.load_warnings.iter().any(|w| w.contains("compressed bytes")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn windows_1252_descriptions_are_read_with_a_note() {
|
||||||
|
// "Descripción" and "Ελληνικά" are not what a Western file holds; a
|
||||||
|
// Western one holds e.g. "Plumas – señal" in Windows-1252.
|
||||||
|
let text = table_with_description("marker").to_text();
|
||||||
|
let mut bytes = text.into_bytes();
|
||||||
|
let marker = bytes.windows(6).position(|w| w == b"marker").unwrap();
|
||||||
|
let cp1252 = [
|
||||||
|
0x50, 0x6C, 0x75, 0x6D, 0x61, 0x73, 0x20, 0x96, 0x20, 0x73, 0x65, 0xF1, 0x61, 0x6C,
|
||||||
|
];
|
||||||
|
bytes.splice(marker..marker + 6, cp1252);
|
||||||
|
let compressed = compress_ctb(&bytes).unwrap();
|
||||||
|
let table = PlotStyleTable::from_bytes("foreign.ctb", &compressed).expect("loads");
|
||||||
|
assert_pens_kept(&table);
|
||||||
|
assert_eq!(table.description, "Plumas – señal");
|
||||||
|
assert!(table.load_warnings.iter().any(|w| w.contains("Windows-1252")));
|
||||||
|
// Clean UTF-8 gets no note.
|
||||||
|
let (text, note) = decode_plot_style_text("Ελληνικά".as_bytes().to_vec());
|
||||||
|
assert_eq!(text, "Ελληνικά");
|
||||||
|
assert!(note.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_truncated_stream_is_still_an_error() {
|
||||||
|
let bytes = compressed(&table_with_description("ok"));
|
||||||
|
let cut = &bytes[..bytes.len() / 2];
|
||||||
|
let error = PlotStyleTable::from_bytes("foreign.ctb", cut).unwrap_err();
|
||||||
|
assert!(error.contains("incomplete"), "{error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn named_tables_and_missing_files_get_a_reason() {
|
||||||
|
let error = PlotStyleTable::load_named("styles.stb").unwrap_err();
|
||||||
|
assert!(error.contains(".stb"), "{error}");
|
||||||
|
let error = PlotStyleTable::load_named("nowhere-to-be-found.ctb").unwrap_err();
|
||||||
|
assert!(error.contains("nowhere-to-be-found.ctb"), "{error}");
|
||||||
|
assert!(PlotStyleTable::load_named("../escape.ctb").is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2059,6 +2059,8 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
|||||||
"Plot table type" => Some(("properties", "plot-table-type")),
|
"Plot table type" => Some(("properties", "plot-table-type")),
|
||||||
"Plot transparency" => Some(("plot", "plot-transparency")),
|
"Plot transparency" => Some(("plot", "plot-transparency")),
|
||||||
"Plot upside-down" => Some(("plot", "plot-upside-down")),
|
"Plot upside-down" => Some(("plot", "plot-upside-down")),
|
||||||
|
"Named plot style tables (.stb) are not supported yet: {name}" => Some(("plot", "named-plot-style-tables-stb-are-not-supported-yet-name")),
|
||||||
|
"{name} was not found in the plot styles folder ({folder})." => Some(("plot", "name-was-not-found-in-the-plot-styles-folder-folder")),
|
||||||
"Plot window: {x0:.2},{y0:.2} to {x1:.2},{y1:.2}" => Some(("common", "plot-window-x0-2-y0-2-to-x1-2-y1-2")),
|
"Plot window: {x0:.2},{y0:.2} to {x1:.2},{y1:.2}" => Some(("common", "plot-window-x0-2-y0-2-to-x1-2-y1-2")),
|
||||||
"Plot window: {x1:.2},{y1:.2} to {x2:.2},{y2:.2}" => Some(("common", "plot-window-x1-2-y1-2-to-x2-2-y2-2")),
|
"Plot window: {x1:.2},{y1:.2} to {x2:.2},{y2:.2}" => Some(("common", "plot-window-x1-2-y1-2-to-x2-2-y2-2")),
|
||||||
"Plugin '{id}' failed to load: {e}" => Some(("common", "plugin-id-failed-to-load-e")),
|
"Plugin '{id}' failed to load: {e}" => Some(("common", "plugin-id-failed-to-load-e")),
|
||||||
|
|||||||
@@ -431,6 +431,9 @@ pub struct PlotDialogState {
|
|||||||
/// The selected setup references a style table that is not loaded.
|
/// The selected setup references a style table that is not loaded.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub style_missing: bool,
|
pub style_missing: bool,
|
||||||
|
/// Why the selected table could not be loaded, shown under the Table row.
|
||||||
|
#[serde(skip)]
|
||||||
|
pub style_error: Option<String>,
|
||||||
/// Named page setups in the document (refreshed when the dialog opens).
|
/// Named page setups in the document (refreshed when the dialog opens).
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub page_setups: Vec<String>,
|
pub page_setups: Vec<String>,
|
||||||
@@ -489,6 +492,7 @@ impl Default for PlotDialogState {
|
|||||||
show_plot_styles: false,
|
show_plot_styles: false,
|
||||||
plot_styles: Vec::new(),
|
plot_styles: Vec::new(),
|
||||||
style_missing: false,
|
style_missing: false,
|
||||||
|
style_error: None,
|
||||||
page_setups: Vec::new(),
|
page_setups: Vec::new(),
|
||||||
selected_setup: String::new(),
|
selected_setup: String::new(),
|
||||||
name_input: None,
|
name_input: None,
|
||||||
@@ -670,6 +674,17 @@ fn check_enabled<'a>(
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Why the selected plot style table is not loaded, under the Table row —
|
||||||
|
/// the file's own problem, or where a copy would be found.
|
||||||
|
fn style_note<'a>(s: &'a PlotDialogState) -> Element<'a, Message> {
|
||||||
|
match &s.style_error {
|
||||||
|
Some(error) if s.style_missing => {
|
||||||
|
text(error.clone()).size(10).style(muted_style).into()
|
||||||
|
}
|
||||||
|
_ => Space::new().height(0).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn panel<'a>(content: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
|
fn panel<'a>(content: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
|
||||||
container(content)
|
container(content)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
@@ -1241,6 +1256,7 @@ pub fn view_window(
|
|||||||
PlotDlgMsg::Style,
|
PlotDlgMsg::Style,
|
||||||
width,
|
width,
|
||||||
),
|
),
|
||||||
|
style_note(s),
|
||||||
check_enabled(
|
check_enabled(
|
||||||
t!("Plot with plot styles"),
|
t!("Plot with plot styles"),
|
||||||
s.apply_plot_styles,
|
s.apply_plot_styles,
|
||||||
|
|||||||
Reference in new issue
Block a user