diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index a0fc9d203..c912121bb 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -3591,6 +3591,7 @@ view = .opening-ocs-discussions-for-help-and-questions = يجري فتح مناقشات OCS للمساعدة والأسئلة... .opening-ocs-web = يجري فتح OCS Web... .opening-the-command-reference = فتح مرجع الأوامر... + .browser-cannot-open-typed-path = لا يستطيع المتصفح فتح مسار مكتوب؛ اختر الملف بدلاً من ذلك. .already-loaded-different-contents-kept = "__ocs_fmt_0__" محمّل بالفعل بمحتوى مختلف؛ تم الاحتفاظ بالملف الموجود. .opening-patreon-page = يجري فتح صفحة Patreon... .opening-feedback-page = يجري فتح صفحة الملاحظات... diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index e25d96e9c..350edd209 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -3578,6 +3578,7 @@ view = .opening-ocs-discussions-for-help-and-questions = Отваряне на OCS дискусии за помощ и въпроси... .opening-ocs-web = Отваряне на OCS уебсайта... .opening-the-command-reference = Отваряне на справочника за команди... + .browser-cannot-open-typed-path = Браузърът не може да отвори въведен път; изберете файла. .already-loaded-different-contents-kept = "__ocs_fmt_0__" вече е зареден с различно съдържание; съществуващият файл е запазен. .opening-patreon-page = Отваряне на страницата в Patreon... .opening-feedback-page = Отваряне на страницата за обратна връзка... diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 362059822..37491cf0e 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -3576,6 +3576,7 @@ view = .opening-ocs-discussions-for-help-and-questions = Otevírání diskuzí OCS pro nápovědu a dotazy... .opening-ocs-web = Otevírání webu OCS... .opening-the-command-reference = Otevírání referenční příručky příkazů... + .browser-cannot-open-typed-path = Prohlížeč nemůže otevřít zadanou cestu; vyberte soubor. .already-loaded-different-contents-kept = "__ocs_fmt_0__" je již načten s jiným obsahem; existující soubor byl zachován. .opening-patreon-page = Otevírání stránky Patreon... .opening-feedback-page = Otevírá se stránka zpětné vazby... diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index b86aa4d78..e8b42cb09 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -3577,6 +3577,7 @@ view = .opening-ocs-discussions-for-help-and-questions = Eröffnung OCS Diskussionen für Hilfe und Fragen... .opening-ocs-web = Öffnen OCS Web... .opening-the-command-reference = Öffnen der Befehlsreferenz... + .browser-cannot-open-typed-path = Der Browser kann einen eingegebenen Pfad nicht öffnen; wähle die Datei. .already-loaded-different-contents-kept = "__ocs_fmt_0__" ist bereits mit anderem Inhalt geladen; die vorhandene Datei wurde behalten. .opening-patreon-page = Eröffnungsseite Patreon... .opening-feedback-page = Feedbackseite öffnen... diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 831613a02..da15c6c18 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -3573,6 +3573,7 @@ view = .opening-ocs-discussions-for-help-and-questions = Άνοιγμα των συζητήσεων OCS για βοήθεια και ερωτήσεις... .opening-ocs-web = Άνοιγμα του OCS Web... .opening-the-command-reference = Άνοιγμα της αναφοράς εντολών... + .browser-cannot-open-typed-path = Ο browser δεν μπορεί να ανοίξει πληκτρολογημένη διαδρομή· διάλεξε το αρχείο. .already-loaded-different-contents-kept = Το "__ocs_fmt_0__" είναι ήδη φορτωμένο με διαφορετικό περιεχόμενο· διατηρήθηκε το υπάρχον αρχείο. .opening-patreon-page = Άνοιγμα της σελίδας Patreon... .opening-feedback-page = Άνοιγμα της σελίδας σχολίων... diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index c89652f51..e03920dd0 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -3574,6 +3574,7 @@ view = .opening-ocs-discussions-for-help-and-questions = Opening OCS Discussions for help and questions... .opening-ocs-web = Opening OCS Web... .opening-the-command-reference = Opening the command reference... + .browser-cannot-open-typed-path = The browser cannot open a typed path; pick the file instead. .already-loaded-different-contents-kept = "__ocs_fmt_0__" is already loaded with different contents; the existing file was kept. .opening-patreon-page = Opening Patreon page... .opening-feedback-page = Opening feedback page... diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 1822f48fd..fa089ec95 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -3579,6 +3579,7 @@ view = .opening-ocs-discussions-for-help-and-questions = Abriendo Debates de OCS para obtener ayuda y hacer preguntas... .opening-ocs-web = Abriendo OCS Web... .opening-the-command-reference = Abriendo la referencia de comandos... + .browser-cannot-open-typed-path = El navegador no puede abrir una ruta escrita; elige el archivo. .already-loaded-different-contents-kept = "__ocs_fmt_0__" ya está cargado con contenido diferente; se conservó el archivo existente. .opening-patreon-page = Abriendo la página de Patreon... .opening-feedback-page = Abriendo la página de comentarios... diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 7c48dcf57..c5d6c959d 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -3578,6 +3578,7 @@ view = .opening-ocs-discussions-for-help-and-questions = OCS-keskustelujen avaaminen apua ja kysymyksiä varten... .opening-ocs-web = Avataan OCS Web... .opening-the-command-reference = Avataan komentoviittausta... + .browser-cannot-open-typed-path = Selain ei voi avata kirjoitettua polkua; valitse tiedosto. .already-loaded-different-contents-kept = "__ocs_fmt_0__" on jo ladattu eri sisällöllä; olemassa oleva tiedosto säilytettiin. .opening-patreon-page = Avataan Patreon-sivua... .opening-feedback-page = Palautesivua avataan... diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index bdd70e550..fd3df4345 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -3579,6 +3579,7 @@ view = .opening-ocs-discussions-for-help-and-questions = Ouverture OCS Discussions pour l'aide et les questions... .opening-ocs-web = Ouverture du site Web OCS... .opening-the-command-reference = Ouverture de la référence des commandes... + .browser-cannot-open-typed-path = Le navigateur ne peut pas ouvrir un chemin saisi ; choisissez le fichier. .already-loaded-different-contents-kept = "__ocs_fmt_0__" est déjà chargé avec un contenu différent ; le fichier existant a été conservé. .opening-patreon-page = Ouverture de la page Patreon... .opening-feedback-page = Ouverture de la page de rétroaction... diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index 42f3ea823..1fa68c289 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -3567,6 +3567,7 @@ view = .opening-ocs-discussions-for-help-and-questions = OCS मदद और प्रश्नों के लिए चर्चा खोलना। .opening-ocs-web = OCS वेब खोलना ... .opening-the-command-reference = कमांड संदर्भ खोला जा रहा है... + .browser-cannot-open-typed-path = ब्राउज़र टाइप किए गए पथ को नहीं खोल सकता; इसके बजाय फ़ाइल चुनें। .already-loaded-different-contents-kept = "__ocs_fmt_0__" अलग सामग्री के साथ पहले से लोड है; मौजूदा फ़ाइल रखी गई। .opening-patreon-page = Patreon पृष्ठ खोला जा रहा है… .opening-feedback-page = प्रतिक्रिया पृष्ठ खोलना diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 36ac51541..a531b905e 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -3578,6 +3578,7 @@ view = .opening-ocs-discussions-for-help-and-questions = A OCS beszélgetések megnyitása segítségért és kérdésekért... .opening-ocs-web = A OCS web megnyitása... .opening-the-command-reference = A parancsreferencia megnyitása... + .browser-cannot-open-typed-path = A böngésző nem nyithat meg beírt útvonalat; válaszd a fájlt. .already-loaded-different-contents-kept = A(z) "__ocs_fmt_0__" már betöltve eltérő tartalommal; a meglévő fájl megőrizve. .opening-patreon-page = Patreon oldal megnyitása... .opening-feedback-page = Visszajelzési oldal megnyitása... diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 3a4184327..461745a15 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -3578,6 +3578,7 @@ view = .opening-ocs-discussions-for-help-and-questions = Apertura delle discussioni OCS per assistenza e domande... .opening-ocs-web = Apertura del Web OCS... .opening-the-command-reference = Apertura del riferimento dei comandi... + .browser-cannot-open-typed-path = Il browser non può aprire un percorso digitato; scegli il file. .already-loaded-different-contents-kept = "__ocs_fmt_0__" è già caricato con contenuti diversi; il file esistente è stato mantenuto. .opening-patreon-page = Apertura della pagina Patreon... .opening-feedback-page = Apertura della pagina di feedback... diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index db8a03a10..2effbe3d0 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -3567,6 +3567,7 @@ view = .opening-ocs-discussions-for-help-and-questions = ヘルプと質問のため OCS ディスカッションを開いています... .opening-ocs-web = OCS Web を開いています... .opening-the-command-reference = コマンドリファレンスを開いています... + .browser-cannot-open-typed-path = ブラウザは入力されたパスを開けません。ファイルを選択してください。 .already-loaded-different-contents-kept = "__ocs_fmt_0__" は既に別の内容で読み込まれています。既存のファイルを保持しました。 .opening-patreon-page = Patreon ページを開いています... .opening-feedback-page = フィードバックページを開いています... diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 3d8dc8530..8dd5eb4e3 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -3578,6 +3578,7 @@ view = .opening-ocs-discussions-for-help-and-questions = OCS 열기 도움말 및 질문에 대한 토론... .opening-ocs-web = OCS 웹을 여는 중... .opening-the-command-reference = 명령 참조 열기... + .browser-cannot-open-typed-path = 브라우저는 입력한 경로를 열 수 없습니다. 파일을 선택하세요. .already-loaded-different-contents-kept = "__ocs_fmt_0__"이(가) 이미 다른 내용으로 로드되었습니다. 기존 파일을 유지했습니다. .opening-patreon-page = Patreon 페이지를 여는 중... .opening-feedback-page = 피드백 페이지를 여는 중... diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index a4440d24f..1b43bbbdb 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -3577,6 +3577,7 @@ view = .opening-ocs-discussions-for-help-and-questions = Openen OCS Discussie voor hulp en vragen... .opening-ocs-web = Openen OCS Web... .opening-the-command-reference = Commandoreferentie openen... + .browser-cannot-open-typed-path = De browser kan een getypt pad niet openen; kies het bestand. .already-loaded-different-contents-kept = "__ocs_fmt_0__" is al geladen met andere inhoud; het bestaande bestand is bewaard. .opening-patreon-page = Patreon pagina openen... .opening-feedback-page = Herinneringspagina openen... diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index ae42e91bc..5e81ccbea 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -3578,6 +3578,7 @@ view = .opening-ocs-discussions-for-help-and-questions = Otwieranie dyskusji OCS w celu uzyskania pomocy i pytań... .opening-ocs-web = Otwieranie OCS Web... .opening-the-command-reference = Otwieranie dokumentacji poleceń... + .browser-cannot-open-typed-path = Przeglądarka nie otworzy wpisanej ścieżki; wybierz plik. .already-loaded-different-contents-kept = "__ocs_fmt_0__" jest już wczytany z inną zawartością; zachowano istniejący plik. .opening-patreon-page = Otwieranie strony Patreon... .opening-feedback-page = Otwieram stronę z opiniami... diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 2f69511a1..779c72c8e 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -3579,6 +3579,7 @@ view = .opening-ocs-discussions-for-help-and-questions = Abrindo Discussões do OCS para ajuda e perguntas... .opening-ocs-web = Abrindo OCS Web... .opening-the-command-reference = Abrindo a referência de comandos... + .browser-cannot-open-typed-path = O navegador não pode abrir um caminho digitado; escolha o ficheiro. .already-loaded-different-contents-kept = "__ocs_fmt_0__" já está carregado com conteúdo diferente; o ficheiro existente foi mantido. .opening-patreon-page = Abrindo página do Patreon... .opening-feedback-page = Abrindo página de comentários... diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index a386e0e85..038085939 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -3580,6 +3580,7 @@ view = .opening-ocs-discussions-for-help-and-questions = Открытие OCS Дискуссии о помощи и вопросах .opening-ocs-web = Открытие OCS Веб-сайт .opening-the-command-reference = Открытие справочника команд... + .browser-cannot-open-typed-path = Браузер не может открыть введённый путь; выберите файл. .already-loaded-different-contents-kept = "__ocs_fmt_0__" уже загружен с другим содержимым; существующий файл сохранён. .opening-patreon-page = Открытие страницы Patreon... .opening-feedback-page = Открываем страницу обратной связи... diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 71c3720ff..8f3e2888c 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -3529,6 +3529,7 @@ view = .opening-ocs-discussions-for-help-and-questions = Yardım ve sorular için OCS Tartışmaları açılıyor... .opening-ocs-web = OCS Web açılıyor... .opening-the-command-reference = Komut başvurusu açılıyor... + .browser-cannot-open-typed-path = Tarayıcı yazılan bir yolu açamaz; dosyayı seçin. .already-loaded-different-contents-kept = "__ocs_fmt_0__" zaten farklı içerikle yüklü; mevcut dosya tutuldu. .opening-patreon-page = Patreon sayfası açılıyor... .opening-feedback-page = Geri bildirim sayfası açılıyor... diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 4e8995f33..f88df1476 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -3562,6 +3562,7 @@ view = .opening-ocs-discussions-for-help-and-questions = 开幕OCS 讨论帮助和问题... .opening-ocs-web = 打开OCS网络... .opening-the-command-reference = 正在打开命令参考... + .browser-cannot-open-typed-path = 浏览器无法打开键入的路径;请改为选择文件。 .already-loaded-different-contents-kept = "__ocs_fmt_0__"已以不同内容加载;保留了现有文件。 .opening-patreon-page = 打开帕特伦的页面... .opening-feedback-page = 打开反馈页面... diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 0acf500ad..b61891c32 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -3574,6 +3574,7 @@ view = .opening-ocs-discussions-for-help-and-questions = 開幕OCS 討論幫助和問題... .opening-ocs-web = 開啟OCS網路... .opening-the-command-reference = 正在開啟命令參考... + .browser-cannot-open-typed-path = 瀏覽器無法開啟輸入的路徑;請改為選擇檔案。 .already-loaded-different-contents-kept = "__ocs_fmt_0__"已以不同內容載入;保留了現有檔案。 .opening-patreon-page = 開啟帕特倫的頁面... .opening-feedback-page = 開啟反饋頁面... diff --git a/src/app/commands/display.rs b/src/app/commands/display.rs index b020e0d15..379d8a4da 100644 --- a/src/app/commands/display.rs +++ b/src/app/commands/display.rs @@ -1601,7 +1601,16 @@ impl OpenCADStudio { // then runs from `DataLinkCsvPicked`. #[cfg(target_arch = "wasm32")] { - let _ = path; + // Say the typed argument is superseded — a silent + // redirect looks like the input was lost. + if !path.trim().is_empty() { + self.command_line.push_info( + crate::t!( + "The browser cannot open a typed path; pick the file instead." + ) + .as_ref(), + ); + } return Some(Task::perform( crate::io::pick_datalink_csv_web(), Message::DataLinkCsvPicked, diff --git a/src/app/control/mod.rs b/src/app/control/mod.rs index 6262cf732..e215ad0c2 100644 --- a/src/app/control/mod.rs +++ b/src/app/control/mod.rs @@ -755,9 +755,13 @@ impl OpenCADStudio { // stays pending until the open settles instead of // completing before the drawing exists. let task = self.update(Message::PollWebControl); - if self.opening.as_ref().is_none_or(|opening| opening.name != name) { - // Another open holds the slot; the poll opens this - // entry once it frees, after this operation settles. + // Another open holds the slot; the poll opens this entry + // once it frees, after this operation settles. Report + // `queued` whenever the slot was busy at submit — the + // name check skipped same-named in-flight opens, making + // the op look completed with an empty result before the + // queued entry had opened. + if self.opening.is_some() { self.set_control_result(json!({"queued": true, "name": name})); } return Ok(task); diff --git a/src/app/record_api.rs b/src/app/record_api.rs index a87fcf47c..388d3e6b7 100644 --- a/src/app/record_api.rs +++ b/src/app/record_api.rs @@ -1111,11 +1111,27 @@ fn requested_handle(request: &Value) -> Result { /// bools and null stay exact; objects and arrays recurse. fn json_values_match(stored: &Value, expected: &Value) -> bool { match (stored, expected) { - (Value::Number(stored), Value::Number(expected)) => match (stored.as_f64(), expected.as_f64()) { - (Some(stored), Some(expected)) => stored == expected, - // Out-of-range or arbitrary-precision numbers keep exact equality. - _ => stored == expected, - }, + (Value::Number(stored), Value::Number(expected)) => { + // Integers compare exactly (a u64 beyond f64 precision must not + // round-trip-match a float); mixed integer/float matches only + // when the float is whole and equals the integer. + match (stored.as_u64(), expected.as_u64()) { + (Some(stored), Some(expected)) => stored == expected, + (Some(stored), None) => match expected.as_f64() { + Some(expected) => expected.fract() == 0.0 && expected >= 0.0 && stored as f64 == expected, + None => false, + }, + (None, Some(expected)) => match stored.as_f64() { + Some(stored) => stored.fract() == 0.0 && stored >= 0.0 && expected as f64 == stored, + None => false, + }, + _ => match (stored.as_i64(), expected.as_i64(), stored.as_f64(), expected.as_f64()) { + (Some(stored), Some(expected), _, _) => stored == expected, + (_, _, Some(stored), Some(expected)) => stored == expected, + _ => stored == expected, + }, + } + } (Value::Object(stored), Value::Object(expected)) => { stored.len() == expected.len() && stored.iter().all(|(key, value)| { diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index d3f699d06..8385b3e2b 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -1059,9 +1059,33 @@ impl OpenCADStudio { } if e.recovery_available { if let Some(opening) = self.opening.as_mut() { - opening.recovery_error = Some(e.message); + let recovery_message = e.message.clone(); + opening.recovery_error = Some(recovery_message.clone()); opening.recovery_read_stats = e.read_stats; self.active_modal = Some(super::ModalKind::RecoveryPrompt); + // A control-routed open (automation) must not park + // behind a modal nobody may answer: settle the + // operation as failed with the parse error. The + // prompt stays for a human; automation retries with + // the recovery op if it wants the content. + // A control-routed open (automation) must not park + // behind a modal nobody may answer: settle the + // operation as failed with the parse error. The + // prompt stays for a human; automation can retry + // through the recovery flow if it wants the content. + #[cfg(target_arch = "wasm32")] + { + let settled = serde_json::json!({ + "ok": false, + "status": "failed", + "code": "recovery_required", + "error": format!( + "the drawing is damaged and needs a recovery decision: {}", + recovery_message + ), + }); + self.set_control_result(settled); + } return Task::none(); } } diff --git a/src/io/pdf_export.rs b/src/io/pdf_export.rs index 90607f297..f0e2c0ce0 100644 --- a/src/io/pdf_export.rs +++ b/src/io/pdf_export.rs @@ -2597,11 +2597,18 @@ mod tests { "tied wipeout must mask the wire: {black} !< {last_wipe}" ); // Below the wire (DRAWORDER under): depth wins and the wire paints - // back on top of the mask. + // back on top of the mask. The page BACKGROUND is also a white + // rectangle: skip past its `re` operator so the probe cannot + // accidentally measure the background if a future serializer + // paints it. let mut page = test_page(vec![line_wire("70040", 0.0, BLACK)]); page.content.wipeouts = vec![wipeout(-0.5)]; let stream = render_stream(page); - let wipe = fill_op_pos(&stream, "1 1 1 rg", 0); + let background = stream + .windows(2) + .position(|w| w == b"re") + .expect("the page background rectangle serializes"); + let wipe = fill_op_pos(&stream, "1 1 1 rg", background); let black = stroke_op_pos(&stream, "0 0 0 RG", 0); assert!( wipe < black, diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index bb082a2c1..852b9567d 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -1887,6 +1887,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Opening OCS Discussions for help and questions..." => Some(("view", "opening-ocs-discussions-for-help-and-questions")), "Opening OCS Web..." => Some(("view", "opening-ocs-web")), "Opening the command reference..." => Some(("view", "opening-the-command-reference")), + "The browser cannot open a typed path; pick the file instead." => Some(("display", "browser-cannot-open-typed-path")), "\"{name}\" could not be sized from its header and decoding it exceeded the {limit} MB image limit of this build — it cannot be attached." => Some(("file", "image-refusal-unknown-size")), "\"{name}\" is already loaded with different contents; the existing file was kept." => Some(("view", "already-loaded-different-contents-kept")), "Opening Patreon page..." => Some(("view", "opening-patreon-page")), diff --git a/src/scene/model/image_model.rs b/src/scene/model/image_model.rs index 1a267826d..0c53619b0 100644 --- a/src/scene/model/image_model.rs +++ b/src/scene/model/image_model.rs @@ -665,9 +665,14 @@ fn native_footprint(format: Option, color: Option 4, }); // `DynamicImage::into_rgba8` only MOVES for Rgba8; every other layout - // allocates the RGBA destination while the native buffer is still - // alive, so both coexist (image 0.25 buffer.rs cast_in_color_space). - let peak_bpp = if native_bpp == 4 { 4 } else { native_bpp + 4 }; + // — including the 4 B/px La16 — allocates the RGBA destination while + // the native buffer is still alive, so both coexist (image 0.25 + // buffer.rs cast_in_color_space). + let peak_bpp = if matches!(color, Some(image::ColorType::Rgba8)) { + 4 + } else { + native_bpp + 4 + }; // GIF decodes the frame into its own buffer while the screen-sized // output buffer is live — two RGBA-sized allocations coexist. let peak_bpp = match format { @@ -1261,6 +1266,39 @@ pub use web_fetch::{ mod tests { use super::*; + /// The decode peak per layout: into_rgba8 is a MOVE for Rgba8 only — + /// every other layout pays native + RGBA coexisting. GIF additionally + /// carries the screen-sized output beside a (clamped) frame buffer. + #[test] + fn native_footprint_peaks_match_the_coexistence_math() { + use image::ColorType; + let png = image::ImageFormat::Png; + let gif = image::ImageFormat::Gif; + let cases = [ + (ColorType::L8, 5u64), + (ColorType::La8, 6), + (ColorType::Rgb8, 7), + (ColorType::Rgba8, 4), + (ColorType::L16, 6), + // 4 B/px but NOT a move — the La16 hole this test pins. + (ColorType::La16, 8), + (ColorType::Rgb16, 10), + (ColorType::Rgba16, 12), + (ColorType::Rgb32F, 16), + (ColorType::Rgba32F, 20), + ]; + for (color, expected) in cases { + assert_eq!( + native_footprint(Some(png), Some(color)).peak_bpp, + expected, + "{color:?}" + ); + } + assert_eq!(native_footprint(Some(gif), Some(ColorType::Rgba8)).peak_bpp, 8); + assert_eq!(native_footprint(None, None).peak_bpp, 8); + } + + /// Encode a small deterministic PNG entirely in memory — the same codec /// `decode_from_memory` reads, so no fixture file is needed. fn png_bytes(w: u32, h: u32, tint: u8) -> Vec { diff --git a/src/scene/paper.rs b/src/scene/paper.rs index 2ca274a1e..56ef559d5 100644 --- a/src/scene/paper.rs +++ b/src/scene/paper.rs @@ -566,7 +566,7 @@ impl Scene { // canvas path re-reads the label per frame but this plot path used to // clone the stale value, so every top-level hatch exported at depth 0 // and DRAWORDER (SORTENTS) never reached the PDF (#1345). Refresh the - // per-entity label here exactly like `hatch_models_for_view`. + // per-entity label here exactly like `synced_hatch_models`. let depths = self.draw_depth_map(); for (&handle, model) in self.hatches.iter() { let Some(source) = self.document.get_entity(handle) else { diff --git a/web/ocs-control.js b/web/ocs-control.js index 4fe95d299..c72e47c57 100644 --- a/web/ocs-control.js +++ b/web/ocs-control.js @@ -21,7 +21,10 @@ let readyResolve; const ready = new Promise(resolve => { readyResolve = resolve; }); const settle = () => { - readyResolve(window.ocsControl); + // apiObject is assigned to window.ocsControl at the END of this IIFE; + // resolve with the local reference so a synchronous bridge (script + // injected late) cannot settle `ready` with undefined. + readyResolve(apiObject); window.dispatchEvent(new Event("ocs-ready")); }; const poll = setInterval(() => { @@ -68,5 +71,7 @@ return reply; } - window.ocsControl = {ready, request}; + const apiObject = {ready, request}; + + window.ocsControl = apiObject; })();