From 2d2444fcfa20bdf7a204f5a760f43185507543c4 Mon Sep 17 00:00:00 2001 From: Afonsop Date: Mon, 12 Oct 2026 11:30:47 +0300 Subject: [PATCH] fix: close the review findings on the issue wave MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ported from d719fb77 (#1717); the hatch draw-order, PDF and recovery-settle hunks stay out (main owns those paths). - La16 was still billed at 4 B/px: the no-conversion special case keyed on native_bpp == 4, which La16 also is — but only Rgba8 is a real move in into_rgba8, so La16's decode peak is 8. The case now keys on the color type, and a table test pins every layout's peak (plus GIF's screen+frame 8) so the accounting can't silently drift. - json_values_match: an integer beyond f64 precision no longer matches a float it merely rounds onto; a mixed pair matches only when the float is whole and converts back to exactly that integer (also the fuzz finding of 9262d63f). - ocs-control.js resolves `ready` with the API object instead of window.ocsControl, which is still undefined when the bridge already exists as the script runs (built before the first settle, avoiding the temporal-dead-zone throw of a later const); DATALINK on web says the typed path is superseded by the picker instead of silently discarding it. (cherry picked from commit d719fb77) --- locales/ar-SA/opencadstudio.ftl | 1 + locales/bg-BG/opencadstudio.ftl | 1 + locales/cs-CZ/opencadstudio.ftl | 1 + locales/de-DE/opencadstudio.ftl | 1 + locales/el-GR/opencadstudio.ftl | 1 + locales/en-US/opencadstudio.ftl | 1 + locales/es-ES/opencadstudio.ftl | 1 + locales/fi-FI/opencadstudio.ftl | 1 + locales/fr-FR/opencadstudio.ftl | 1 + locales/hi-IN/opencadstudio.ftl | 1 + locales/hu-HU/opencadstudio.ftl | 1 + locales/it-IT/opencadstudio.ftl | 1 + locales/ja-JP/opencadstudio.ftl | 1 + locales/ko-KR/opencadstudio.ftl | 1 + locales/nl-NL/opencadstudio.ftl | 1 + locales/pl-PL/opencadstudio.ftl | 1 + locales/pt-BR/opencadstudio.ftl | 1 + locales/ru-RU/opencadstudio.ftl | 1 + locales/tr-TR/opencadstudio.ftl | 1 + locales/zh-CN/opencadstudio.ftl | 1 + locales/zh-TW/opencadstudio.ftl | 1 + src/app/commands/display.rs | 11 ++++++++- src/app/record_api.rs | 39 +++++++++++++++++++++++++++++- src/locale_catalog.rs | 1 + src/scene/model/image_model.rs | 43 ++++++++++++++++++++++++++++++--- web/ocs-control.js | 8 ++++-- 26 files changed, 116 insertions(+), 7 deletions(-) diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index d50e290fa..1471c703d 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -284,6 +284,7 @@ command-copy = .mode = COPY أدخل خيار وضع النسخ [Single/Multiple] : common = + .browser-cannot-open-typed-path = لا يستطيع المتصفح فتح مسار مكتوب؛ اختر الملف بدلاً من ذلك. .recover-drawing = استرداد الرسم .restore-it-as-a-recovered-tab-or-discard-the-copy = استعده كعلامة تبويب مستردة أو تجاهل النسخة. .name-has-an-autosave-copy-newer-than-the-last-save = لدى "__ocs_fmt_0__" نسخة حفظ تلقائي أحدث من آخر حفظ. diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 755aaea39..3f383ac42 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -271,6 +271,7 @@ command-copy = .displacement = COPY Задайте преместване: .mode = COPY Въведете режим на копиране [Single/Multiple] : common = + .browser-cannot-open-typed-path = Браузърът не може да отвори въведен път; изберете файла. .recover-drawing = Възстановяване на чертеж .restore-it-as-a-recovered-tab-or-discard-the-copy = Възстановете го като възстановен раздел или отхвърлете копието. .name-has-an-autosave-copy-newer-than-the-last-save = "__ocs_fmt_0__" има копие от автоматично записване, по-ново от последния запис. diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 262adf56c..978750b6c 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -271,6 +271,7 @@ command-copy = .mode = COPY Zadejte režim kopírování [Single/Multiple] : common = + .browser-cannot-open-typed-path = Prohlížeč nemůže otevřít zadanou cestu; vyberte soubor. .recover-drawing = Obnovit výkres .restore-it-as-a-recovered-tab-or-discard-the-copy = Obnovte jej jako obnovenou kartu nebo kopii zahoďte. .name-has-an-autosave-copy-newer-than-the-last-save = "__ocs_fmt_0__" má kopii automatického uložení novější než poslední uložení. diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 3d57aa613..d46247aee 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -272,6 +272,7 @@ command-copy = .mode = COPY Kopiermodus eingeben [Single/Multiple] : common = + .browser-cannot-open-typed-path = Der Browser kann einen eingegebenen Pfad nicht öffnen; wähle die Datei. .recover-drawing = Zeichnung wiederherstellen .restore-it-as-a-recovered-tab-or-discard-the-copy = Als wiederhergestellte Registerkarte öffnen oder die Kopie verwerfen. .name-has-an-autosave-copy-newer-than-the-last-save = Für „__ocs_fmt_0__“ existiert eine automatische Sicherungskopie, die neuer ist als die letzte gespeicherte Version. diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 75ba5980f..8ed57c8cd 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -271,6 +271,7 @@ command-copy = .mode = COPY Επιλέξτε λειτουργία αντιγραφής [Single/Multiple] : common = + .browser-cannot-open-typed-path = Ο browser δεν μπορεί να ανοίξει πληκτρολογημένη διαδρομή· διάλεξε το αρχείο. .recover-drawing = Ανάκτηση σχεδίου .restore-it-as-a-recovered-tab-or-discard-the-copy = Επαναφέρετέ το ως ανακτημένη καρτέλα ή απορρίψτε το αντίγραφο. .name-has-an-autosave-copy-newer-than-the-last-save = Το "__ocs_fmt_0__" έχει αντίγραφο αυτόματης αποθήκευσης νεότερο της τελευταίας αποθήκευσης. diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 3761895de..649a089b3 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -271,6 +271,7 @@ command-copy = .mode = COPY Enter a copy mode option [Single/Multiple] : common = + .browser-cannot-open-typed-path = The browser cannot open a typed path; pick the file instead. .recover-drawing = Recover Drawing .restore-it-as-a-recovered-tab-or-discard-the-copy = Restore it as a recovered tab, or discard the copy. .name-has-an-autosave-copy-newer-than-the-last-save = "__ocs_fmt_0__" has an autosave copy newer than the last save. diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 25dab8961..a89f60e35 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -272,6 +272,7 @@ command-copy = .mode = COPY Indique modo de copia [Single/Multiple] : common = + .browser-cannot-open-typed-path = El navegador no puede abrir una ruta escrita; elige el archivo. .recover-drawing = Recuperar dibujo .restore-it-as-a-recovered-tab-or-discard-the-copy = Restáurelo como una pestaña recuperada o descarte la copia. .name-has-an-autosave-copy-newer-than-the-last-save = "__ocs_fmt_0__" tiene una copia de autoguardado más reciente que la última copia guardada. diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 0c016d547..7ea76797c 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -271,6 +271,7 @@ command-copy = .mode = COPY Valitse kopiointitila [Single/Multiple] : common = + .browser-cannot-open-typed-path = Selain ei voi avata kirjoitettua polkua; valitse tiedosto. .recover-drawing = Palauta piirustus .restore-it-as-a-recovered-tab-or-discard-the-copy = Palauta se palautettuna välilehtenä tai hylkää kopio. .name-has-an-autosave-copy-newer-than-the-last-save = Kohteella "__ocs_fmt_0__" on automaattisen tallennuksen kopio, joka on uudempi kuin viimeisin tallennus. diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 7bc601dac..0340544c1 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -272,6 +272,7 @@ command-copy = .mode = COPY Entrez le mode de copie [Single/Multiple] : common = + .browser-cannot-open-typed-path = Le navigateur ne peut pas ouvrir un chemin saisi ; choisissez le fichier. .recover-drawing = Récupérer le dessin .restore-it-as-a-recovered-tab-or-discard-the-copy = Restaurez-le dans un onglet récupéré ou supprimez la copie. .name-has-an-autosave-copy-newer-than-the-last-save = «__ocs_fmt_0__» dispose d’une copie de sauvegarde automatique plus récente que le dernier enregistrement. diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index b116fc02e..83b952608 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -260,6 +260,7 @@ command-copy = .mode = COPY कॉपी मोड विकल्प दर्ज करें [Single/Multiple] : common = + .browser-cannot-open-typed-path = ब्राउज़र टाइप किए गए पथ को नहीं खोल सकता; इसके बजाय फ़ाइल चुनें। .recover-drawing = चित्र पुनर्प्राप्त करें .restore-it-as-a-recovered-tab-or-discard-the-copy = इसे पुनर्प्राप्त टैब के रूप में पुनर्स्थापित करें, या प्रतिलिपि त्याग दें। .name-has-an-autosave-copy-newer-than-the-last-save = "__ocs_fmt_0__" के पास अंतिम सहेजने से नई ऑटोसेव प्रतिलिपि है। diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index c48f2be16..d54254d94 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -271,6 +271,7 @@ command-copy = .mode = COPY Adja meg a másolási módot [Single/Multiple] : common = + .browser-cannot-open-typed-path = A böngésző nem nyithat meg beírt útvonalat; válaszd a fájlt. .jogged-radius = Megtört sugárméret .none-use-shortcuts-set-key-command = (nincs – használat: SHORTCUTS SET ) .alignment-indented-label-title-case = Igazítás: diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 43404e654..cb599f7a1 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -271,6 +271,7 @@ command-copy = .mode = COPY Specificare modalità di copia [Single/Multiple] : common = + .browser-cannot-open-typed-path = Il browser non può aprire un percorso digitato; scegli il file. .recover-drawing = Recupera disegno .restore-it-as-a-recovered-tab-or-discard-the-copy = Ripristinalo in una scheda recuperata oppure elimina la copia. .name-has-an-autosave-copy-newer-than-the-last-save = "__ocs_fmt_0__" ha una copia di salvataggio automatico più recente dell’ultimo salvataggio. diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index a42983fa8..cd5e7d900 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -262,6 +262,7 @@ command-copy = .mode = COPY コピーモードを入力 [Single/Multiple] : common = + .browser-cannot-open-typed-path = ブラウザは入力されたパスを開けません。ファイルを選択してください。 .recover-drawing = 図面の復元 .restore-it-as-a-recovered-tab-or-discard-the-copy = 復元タブとして開くか、コピーを破棄してください。 .name-has-an-autosave-copy-newer-than-the-last-save = "__ocs_fmt_0__" には最終保存より新しい自動保存コピーがあります。 diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 053dd441f..f09d9cd13 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -271,6 +271,7 @@ command-copy = .mode = COPY 복사 모드 옵션 입력 [Single/Multiple] : common = + .browser-cannot-open-typed-path = 브라우저는 입력한 경로를 열 수 없습니다. 파일을 선택하세요. .recover-drawing = 도면 복구 .restore-it-as-a-recovered-tab-or-discard-the-copy = 복구된 탭으로 열거나 사본을 삭제하세요. .name-has-an-autosave-copy-newer-than-the-last-save = "__ocs_fmt_0__"에 마지막 저장보다 새로운 자동 저장 사본이 있습니다. diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 2d2c9136a..c06f62e67 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -272,6 +272,7 @@ command-copy = .mode = COPY Geef kopieermodus op [Single/Multiple] : common = + .browser-cannot-open-typed-path = De browser kan een getypt pad niet openen; kies het bestand. .recover-drawing = Tekening herstellen .restore-it-as-a-recovered-tab-or-discard-the-copy = Herstel het als een hersteld tabblad of verwijder de kopie. .name-has-an-autosave-copy-newer-than-the-last-save = "__ocs_fmt_0__" heeft een automatisch opgeslagen kopie die nieuwer is dan de laatste opslag. diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 907f8c195..53c7444d7 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -271,6 +271,7 @@ command-copy = .mode = COPY Podaj tryb kopiowania [Single/Multiple] : common = + .browser-cannot-open-typed-path = Przeglądarka nie otworzy wpisanej ścieżki; wybierz plik. .recover-drawing = Odzyskaj rysunek .restore-it-as-a-recovered-tab-or-discard-the-copy = Przywróć go jako odzyskaną kartę lub odrzuć kopię. .name-has-an-autosave-copy-newer-than-the-last-save = "__ocs_fmt_0__" ma kopię autozapisu nowszą niż ostatni zapis. diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index e325800f1..2cc214d18 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -272,6 +272,7 @@ command-copy = .mode = COPY Informe o modo de cópia [Single/Multiple] : common = + .browser-cannot-open-typed-path = O navegador não pode abrir um caminho digitado; escolha o ficheiro. .recover-drawing = Recuperar desenho .restore-it-as-a-recovered-tab-or-discard-the-copy = Restaure-o como uma guia recuperada ou descarte a cópia. .name-has-an-autosave-copy-newer-than-the-last-save = "__ocs_fmt_0__" tem uma cópia de salvamento automático mais recente que o último salvamento. diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index e691735a2..da97b93c5 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -275,6 +275,7 @@ command-copy = .mode = COPY Задайте режим копирования [Single/Multiple] : common = + .browser-cannot-open-typed-path = Браузер не может открыть введённый путь; выберите файл. .recover-drawing = Восстановление чертежа .restore-it-as-a-recovered-tab-or-discard-the-copy = Восстановите его на отдельной вкладке или удалите копию. .name-has-an-autosave-copy-newer-than-the-last-save = У «__ocs_fmt_0__» есть копия автосохранения новее последнего сохранения. diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 7905e0d6a..83f572098 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -257,6 +257,7 @@ command-copy = .mode = COPY Kopyalama modu seçin [Single/Multiple] : common = + .browser-cannot-open-typed-path = Tarayıcı yazılan bir yolu açamaz; dosyayı seçin. .recover-drawing = Çizimi Kurtar .restore-it-as-a-recovered-tab-or-discard-the-copy = Onu kurtarılan bir sekme olarak geri yükleyin veya kopyayı atın. .name-has-an-autosave-copy-newer-than-the-last-save = "__ocs_fmt_0__" son kayıttan daha yeni bir otomatik kayıt kopyasına sahip. diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 622fa0796..373c75975 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -259,6 +259,7 @@ command-copy = .mode = COPY 输入复制模式选项 [Single/Multiple] : common = + .browser-cannot-open-typed-path = 浏览器无法打开键入的路径;请改为选择文件。 .recover-drawing = 恢复图形 .restore-it-as-a-recovered-tab-or-discard-the-copy = 将其恢复为已恢复的标签页,或放弃该副本。 .name-has-an-autosave-copy-newer-than-the-last-save = "__ocs_fmt_0__" 有一份比上次保存更新的自动保存副本。 diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index b80efbbda..9dc7d60cd 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -271,6 +271,7 @@ command-copy = .mode = COPY 輸入複製模式選項 [Single/Multiple] : common = + .browser-cannot-open-typed-path = 瀏覽器無法開啟輸入的路徑;請改為選擇檔案。 .recover-drawing = 恢復圖面 .restore-it-as-a-recovered-tab-or-discard-the-copy = 將其恢復為已恢復的分頁,或捨棄該副本。 .name-has-an-autosave-copy-newer-than-the-last-save = "__ocs_fmt_0__" 有一份比上次儲存更新的自動儲存副本。 diff --git a/src/app/commands/display.rs b/src/app/commands/display.rs index 758e8741d..43191aa5b 100644 --- a/src/app/commands/display.rs +++ b/src/app/commands/display.rs @@ -1805,7 +1805,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/record_api.rs b/src/app/record_api.rs index 9c5e893bf..d2b91e272 100644 --- a/src/app/record_api.rs +++ b/src/app/record_api.rs @@ -1158,7 +1158,7 @@ fn apply_updates( fn json_values_match(actual: &Value, expected: &Value) -> bool { match (actual, expected) { (Value::Number(a), Value::Number(b)) if a.is_f64() || b.is_f64() => { - a.as_f64() == b.as_f64() + numbers_match(a, b) } (Value::Array(a), Value::Array(b)) => { a.len() == b.len() && a.iter().zip(b).all(|(a, b)| json_values_match(a, b)) @@ -1172,6 +1172,28 @@ fn json_values_match(actual: &Value, expected: &Value) -> bool { } } +/// `10.0` matches `10`, but an integer past f64 precision never matches a +/// float it merely rounds onto: a mixed pair is equal only when the float is +/// whole and converts back to exactly that integer. +fn numbers_match(a: &serde_json::Number, b: &serde_json::Number) -> bool { + let (int, float) = match (a.as_f64(), b.as_f64()) { + (Some(x), Some(y)) if a.is_f64() && b.is_f64() => return x == y, + (_, Some(y)) if b.is_f64() => (a, y), + (Some(x), _) => (b, x), + _ => return false, + }; + if !float.is_finite() || float.fract() != 0.0 { + return false; + } + if let Some(i) = int.as_i64() { + i as f64 == float && float as i64 == i + } else if let Some(u) = int.as_u64() { + u as f64 == float && float as u64 == u + } else { + false + } +} + fn patched_table_entry( table: &Table, request: &Value, @@ -1645,6 +1667,21 @@ mod tests { use super::OpenCADStudio; + /// Whole floats match their integer; integers past f64 precision never + /// match the neighbouring float they round onto. + #[test] + fn numbers_match_by_value_without_precision_loss() { + let number = |value: Value| match value { + Value::Number(number) => number, + _ => unreachable!(), + }; + assert!(super::numbers_match(&number(json!(10)), &number(json!(10.0)))); + assert!(!super::numbers_match(&number(json!(10)), &number(json!(10.5)))); + let past_precision = number(json!(9_007_199_254_740_993_u64)); + assert!(!super::numbers_match(&past_precision, &number(json!(9_007_199_254_740_992.0)))); + assert!(super::numbers_match(&number(json!(-3)), &number(json!(-3.0)))); + } + fn execute(app: &mut OpenCADStudio, mut request: Value, id: &str) -> Value { let state = app.automation_op(r#"{"protocol":1,"op":"state"}"#); let object = request.as_object_mut().unwrap(); diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index 839be3806..4d0e403df 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -4188,6 +4188,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Data Extraction Settings" => Some(("common", "data-extraction-settings")), "Add Drawings" => Some(("common", "add-drawings")), "CSV" => Some(("common", "csv")), + "The browser cannot open a typed path; pick the file instead." => Some(("common", "browser-cannot-open-typed-path")), "CSV Files" => Some(("common", "csv-files")), "Link a CSV data source" => Some(("common", "link-a-csv-data-source")), "Pick the file again to load its contents in the browser." => Some(("file", "pick-the-file-again-to-load-its-contents")), diff --git a/src/scene/model/image_model.rs b/src/scene/model/image_model.rs index e7807123b..f01967d9c 100644 --- a/src/scene/model/image_model.rs +++ b/src/scene/model/image_model.rs @@ -773,9 +773,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 { @@ -1369,6 +1374,38 @@ 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/web/ocs-control.js b/web/ocs-control.js index c2a7d0d36..99481bac8 100644 --- a/web/ocs-control.js +++ b/web/ocs-control.js @@ -20,8 +20,12 @@ let readyResolve; const ready = new Promise(resolve => { readyResolve = resolve; }); + // Built before the first settle() can run (`request` is a hoisted + // function declaration): `ready` resolves with this object even when the + // bridge already exists as the script runs. + const apiObject = {ready, request}; const settle = () => { - readyResolve(window.ocsControl); + readyResolve(apiObject); window.dispatchEvent(new Event("ocs-ready")); }; const poll = setInterval(() => { @@ -68,5 +72,5 @@ return reply; } - window.ocsControl = {ready, request}; + window.ocsControl = apiObject; })();