From 1970935587d9a85656dfb943533701b5b600dff5 Mon Sep 17 00:00:00 2001 From: Raj Patel Date: Sun, 11 Oct 2026 23:28:01 -0600 Subject: [PATCH] M14: edits let go of the previous working file before writing the next (gap 13) (#1279) * M0: keep the camera edge test NaN-safe under clippy's manual_range_contains Clippy 1.97 flags `d < 0.35 || d > PI - 0.35` in the camera edge hit test. Its suggested `!(0.35..=PI - 0.35).contains(&d)` would count a NaN direction as a hit, which the two comparisons did not, so the NaN check is kept explicitly. Behaviour is unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011AsuqvnFXhY9ASL1bA2jtm * M14: edits let go of the previous working file before writing the next (gap 13) Every edit writes a new working file: the original plus one appended revision. The previous one stayed alive in the working bytes, the display bytes and the renderer while the next was written, so after a few edits a 1 GiB file peaked at three copies. Each refresh path (full, comments/form, content, undo, redo, after save) now releases them first: the working and display bytes fall back to the original (already held by the editor) and the renderer is replaced by an empty one until the refresh rebuilds it. A refresh that still fails after rollback draws the original instead of nothing. Save still writes from the editor, never from these. Measured on a synthetic 1 GiB file (release build, Linux): five edits peak at 2.1 GiB instead of 3.1 GiB, in 8.5 s instead of 16 s. Saving still peaks at three copies; reading from disk on demand stays blocked on the renderer (hayro parses one contiguous buffer, gap 2). gaps.md, the parity notes and the roadmap record both. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011AsuqvnFXhY9ASL1bA2jtm --------- Co-authored-by: Claude --- ROADMAP.md | 1 + crates/engine/src/camera_edges.rs | 3 +- crates/engine/src/lib.rs | 40 +++++++++++++++--- crates/engine/src/tests.rs | 69 +++++++++++++++++++++++++++++++ docs/gaps.md | 15 ++++++- docs/roadmap.md | 4 +- parity/acrobat-features.toml | 2 +- 7 files changed, 123 insertions(+), 11 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index d3c03d3a4..e8cd36145 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -137,6 +137,7 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md ## Progress log +- **2026-10-12 (M14, gap 13, large-file memory):** Measured on synthetic 1 GiB and 100,000-page files. Edits let go of the previous working file (working bytes, display bytes, renderer) before writing the next, so a 1 GiB file peaks at two copies while editing instead of three (five edits: 3.1 → 2.1 GiB, 16 → 8.5 s). Saving still peaks at three; reading from disk on demand is blocked on the renderer (gap 2). Estimates unchanged. - **2026-10-12 (M2, short text redraws):** Same-line redraws that restart a matching three-glyph prefix after more than one glyph-height of progress split before placement, so short doubled words no longer interleave. The existing redraw-position tolerance is reused; controls cover negative TJ movement, combining marks, RTL, CJK, vertical and turned text. Estimates unchanged. - **2026-10-11 (M10.5, focused follow-up to #599):** Carry the architectural/ANSI/large ISO paper presets, current-page matching at 100%, and previewed PDF scale into the current print dialog and `doc_print`. Retains the #847/#1007 spooler and background discovery. Driver substitution, physical calibration, vector Windows printing and managed-endpoint restrictions remain separate gaps. Overall estimates unchanged. diff --git a/crates/engine/src/camera_edges.rs b/crates/engine/src/camera_edges.rs index 7084dd5e5..fd8dbd5f2 100644 --- a/crates/engine/src/camera_edges.rs +++ b/crates/engine/src/camera_edges.rs @@ -232,7 +232,8 @@ fn support(a: Point, b: Point, edges: &[Option], w: usize, h: usize) -> f6 edges.get(y as usize * w + x as usize).copied().flatten().is_some_and(|e| { // Either way across: dark-to-light or light-to-dark. let d = (e.dir - normal).rem_euclid(std::f32::consts::PI); - d < 0.35 || d > std::f32::consts::PI - 0.35 + // A NaN direction is no hit, as the two comparisons it replaces said. + !d.is_nan() && !(0.35..=std::f32::consts::PI - 0.35).contains(&d) }) }); hits += hit as usize; diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index fc7c8797b..bb5aeaa79 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -3208,7 +3208,9 @@ impl Session { ed.keys = keys; } Self::adopt_keys(doc); - let _ = guard(|| Self::refresh(doc)); + if !matches!(guard(|| Self::refresh(doc)), Ok(Ok(()))) { + Self::show_released(doc); + } return Err(e); } doc.dirty = true; @@ -3237,7 +3239,7 @@ impl Session { let current_keys = std::mem::replace(&mut editor.keys, keys); editor.redo.push((label.clone(), current, current_keys, scope)); Self::adopt_keys(doc); - Self::refresh_scoped(doc, scope)?; + Self::refresh_scoped(doc, scope).inspect_err(|_| Self::show_released(doc))?; doc.dirty = true; doc.generation += 1; Ok(label) @@ -3252,7 +3254,7 @@ impl Session { let current_keys = std::mem::replace(&mut editor.keys, keys); editor.undo.push((label.clone(), current, current_keys, scope)); Self::adopt_keys(doc); - Self::refresh_scoped(doc, scope)?; + Self::refresh_scoped(doc, scope).inspect_err(|_| Self::show_released(doc))?; doc.dirty = true; doc.generation += 1; Ok(label) @@ -3277,6 +3279,10 @@ impl Session { if scope == Scope::Content { return Self::refresh_content(doc, None); } + // Metadata edits keep the renderer, which holds the working file anyway. + if scope != Scope::Metadata { + Self::release_working(doc); + } let Some(editor) = doc.editor.as_mut() else { return Ok(()) }; // Comment edits, and form edits (a script may change annotation properties), may change // annotation dictionaries, invalidating cached appearances. @@ -3369,6 +3375,7 @@ impl Session { /// from undo/redo, re-reads both: always correct, rarely paid). The cached display /// appearances are kept: content edits leave every annotation dictionary alone. fn refresh_content(doc: &mut Document, edit: Option<&Edit>) -> Result<(), EditError> { + Self::release_working(doc); let Some(editor) = doc.editor.as_mut() else { return Ok(()) }; // A script may have laid an XFA form out again (rows added, subforms shown). if doc.xfa.is_some() @@ -3401,6 +3408,28 @@ impl Session { Ok(()) } + /// Let go of the working file before the next one is written (gap 13). The working file is + /// the original plus one appended revision, so on a 1 GiB file keeping the old one (in the + /// working bytes, the display bytes and the renderer) while writing the new one held three + /// copies at once; this keeps two. Until the refresh finishes, the working and display bytes + /// are the original's (already held by the editor, so free) and the renderer draws nothing. + /// A failed edit rolls back and refreshes again, which replaces all three; where a refresh + /// still fails, [`Self::show_released`] draws the original rather than nothing. Save writes + /// from the editor, never from these. + fn release_working(doc: &mut Document) { + let Some(original) = doc.editor.as_ref().map(|e| e.cos.bytes().clone()) else { return }; + // Dropping the pool stops its workers; each lets go of the file when it exits. + doc.renderer = RenderPool::new_inline(Arc::new(Vec::new()), doc.config.clone()); + doc.display = original.clone(); + doc.bytes = original; + } + + /// After a refresh failed: draw the display bytes it left (the original, once released), + /// so the page still shows a document. + fn show_released(doc: &mut Document) { + doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone()); + } + /// Rebuild working bytes, inspection and renderer from the current edit state. fn refresh(doc: &mut Document) -> Result<(), EditError> { // A full refresh may follow structural or annotation changes: cached appearances @@ -3408,6 +3437,7 @@ impl Session { if let Some(editor) = doc.editor.as_mut() { editor.appearances.clear(); } + Self::release_working(doc); let Some(editor) = doc.editor.as_mut() else { return Ok(()) }; // A script may have laid an XFA form out again (rows added, subforms shown). if doc.xfa.is_some() @@ -3484,7 +3514,7 @@ impl Session { } doc.dirty = false; doc.generation += 1; - Self::refresh(doc) + Self::refresh(doc).inspect_err(|_| Self::show_released(doc)) } /// Enter the permissions (owner) password of a document opened with its user password, @@ -3542,7 +3572,7 @@ impl Session { Self::adopt_keys(doc); doc.dirty = false; doc.generation += 1; - Self::refresh(doc) + Self::refresh(doc).inspect_err(|_| Self::show_released(doc)) } /// The document's file changed on disk (a LaTeX or Typst build rewrote it, #431): open diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index d2f78e616..196cd94ca 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -3881,3 +3881,72 @@ fn fonts_added_by_a_content_edit_show_after_refresh_fonts() { session.refresh_fonts(id); assert_eq!(format!("{:?}", session.get(id).unwrap().info.fonts), fonts); } + +/// Gap 13: every edit writes a new working file (the original plus one revision). Before the +/// next one is written, the current one is let go of by the working bytes, the display bytes and +/// the renderer: on a 1 GiB file, keeping it is a whole extra gigabyte at the peak of each edit. +#[test] +fn the_working_file_is_let_go_of_before_the_next_is_written() { + let (mut s, id) = session_with(2); + s.apply(id, Edit::RotatePages { pages: vec![1], degrees: 90 }).unwrap(); + let doc = s.doc_mut(id).unwrap(); + let (working, original) = (Arc::downgrade(&doc.bytes), doc.editor.as_ref().unwrap().cos.bytes().clone()); + assert!(!Arc::ptr_eq(&doc.bytes, &original), "an edited working file is a buffer of its own"); + Session::release_working(doc); + assert!(Arc::ptr_eq(&doc.bytes, &original) && Arc::ptr_eq(&doc.display, &original)); + // The old renderer's workers let go of the file when they exit, just after. + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10); + while working.upgrade().is_some() { + assert!(std::time::Instant::now() < deadline, "the working file is still held"); + std::thread::sleep(std::time::Duration::from_millis(5)); + } + // The refresh that follows brings everything back. + Session::refresh(doc).unwrap(); + assert_eq!(s.get(id).unwrap().info.pages[1].rotation, 90); + let _ = shown(&s, id, 1); +} + +/// Every refresh path (full, comments, content, undo, redo) lets go of the previous working file +/// and still shows the current state. +#[test] +fn edits_let_go_of_the_previous_working_file() { + let (mut s, id) = session_with(2); + let shape = Shape::Rectangle { rect: [20.0, 20.0, 60.0, 60.0] }; + let comment = || { + Edit::AddAnnotation(NewAnnotation { + page: 0, + style: Style::default_for(&shape), + shape: shape.clone(), + contents: String::new(), + author: "Test".into(), + }) + }; + let watermark = + || Edit::AddWatermark { pages: vec![0], settings: Watermark { text: "Draft".into(), ..Watermark::default() }, file: None, replace: false }; + s.apply(id, Edit::RotatePages { pages: vec![1], degrees: 90 }).unwrap(); + // A full refresh, a comment refresh, a content refresh, then undo and redo. + let steps: [(&str, Box); 5] = [ + ("rotate", Box::new(|s: &mut Session| s.apply(id, Edit::RotatePages { pages: vec![0], degrees: 90 }).unwrap())), + ("comment", Box::new(move |s: &mut Session| s.apply(id, comment()).unwrap())), + ("watermark", Box::new(move |s: &mut Session| s.apply(id, watermark()).unwrap())), + ("undo", Box::new(|s: &mut Session| drop(s.undo(id).unwrap()))), + ("redo", Box::new(|s: &mut Session| drop(s.redo(id).unwrap()))), + ]; + for (name, step) in steps { + let (bytes, display) = { + let doc = s.get(id).unwrap(); + (Arc::downgrade(&doc.bytes), Arc::downgrade(&doc.display)) + }; + step(&mut s); + // The old renderer's workers let go of the file when they exit, just after. + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10); + while bytes.upgrade().is_some() || display.upgrade().is_some() { + assert!(std::time::Instant::now() < deadline, "{name}: the previous working file is still held"); + std::thread::sleep(std::time::Duration::from_millis(5)); + } + } + // What is shown is still the current state. + let doc = s.get(id).unwrap(); + assert_eq!(doc.info.annotations.len(), 1); + let _ = shown(&s, id, 0); +} diff --git a/docs/gaps.md b/docs/gaps.md index 831ddaa99..65c1c6685 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Where PdfCraft falls short of Acrobat Pro -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (Word invoice regression coverage, #773; Office fidelity still partial; gap 2: hayro patch counts after the native-gradient backport) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (gap 13: large-file memory measured, edits keep two copies instead of three, file-backed input blocked on gap 2) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS) > **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (gap 1 closed by #836; fixed issues removed from gaps 3, 6 and 16) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS) > **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (document-font subset completion and curved/shared image clips in the contributor fork; gap 2: hayro patch counts after the native-gradient backport) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS) @@ -218,7 +218,17 @@ update the numbers. memory budget, with budgets checked on Tier-1 platforms. - **Evidence:** #307 brought a 9,156-page file from > 10 GiB to ≈ 500 MiB inspection, but file-backed input and global memory budgets remain open; blurry fonts at common resolutions - (#730). + (#730). Measured 2026-10-12 on synthetic files (release build, Linux): a 1 GiB file opens at + 1.0 GiB peak in 0.7 s, so viewing holds the whole file once; after edits it held two copies and + peaked at three (3.1 GiB after five edits), because the previous working file stayed alive while + the next was written. Edits now let go of it first: five edits peak at 2.1 GiB (8.5 s, was 16 s). + Saving still peaks at three copies (the saved file is assembled in memory beside the working + one), and display appearances for comments without one add a copy. A 100,000-page file (28 MB) + opens at 403 MiB in 5.5 s and each edit re-inspects it for ≈ 5 s. +- **Blocked part:** reading a GB file from disk on demand (`ByteSource`) needs every reader to + take ranges, but the `hayro` renderer parses one contiguous buffer (`Pdf::new(PdfData)`), so it + waits on gap 2's renderer decision. Until then the budget is one copy of the file to view and two + to edit. - **Estimate:** 25–45 h. **Doc:** this file. ### 14. Arabic not mirrored; no Hindi, Indonesian, Korean, Vietnamese @@ -309,6 +319,7 @@ update the numbers. | Date | Change | Summary | |---|---|---| +| 2026-10-12 | minor | Gap 13: measured large-file memory; edits no longer keep the previous working file while writing the next (peak 3 → 2 copies); file-backed input recorded as blocked on gap 2 | | 2026-10-11 | minor | Word invoice failure #773 has synthetic regression coverage; broader Office conversion remains gap 8. | | 2026-10-11 | minor | Contributor fork: mixed-style visual regions, source-font space reuse and a read-only textless background. Embedded sfnt source faces and Mac subset Unicode mappings keep edit baselines stable; live frames follow Apply/Save geometry as text grows or shrinks. Engine/tool/UI regressions and private all-page checks cover the changes; full matching font import, CFF, range styles, shaping and private Chinese typing checks added; legacy programs and general fidelity remain open; estimates unchanged | | 2026-10-11 | patch | Gap 2: 23 hayro-interpret and 10 hayro patches after axial and radial shadings became native gradients (backport of hayro 0.8) | diff --git a/docs/roadmap.md b/docs/roadmap.md index 0b6f57539..143ba103f 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -94,9 +94,9 @@ total matches the parity estimate. | M11 | Optimize, preflight, PDF/A/X/UA, print production | 200–350 | 18% | 80–140 | Done: new `optimize` crate: Reduce File Size and the PDF Optimizer (images measured where drawn, bicubic downsampling, JPEG/ZIP recompression only when smaller, discard objects and user data, Flate clean-up, resource merging, object streams). Missing: fonts and transparency panels, space audit, preflight, PDF/A/X/UA | | M12 | Accessibility, compare, measure, search, XFA | 200–380 | 30% | 90–160 | Done: new `a11y` crate with the Accessibility Checker (all 32 rules, report, Fix/Skip/Explain, options dialog and results panel, agent tools).
Done since: 2D distance, perimeter and area measurements with persistent viewport calibration, snapping, live information and CSV export. Missing: autotag, Tags/Order/Content panels, Reading Order tool, alt-text workflow, compare, geospatial/3D measurement, search index, XFA | | M13 | Automation (MCP, Action Wizard, CLI) + AI providers | 60–120 | 45% | 25–45 | Done: MCP resources (document info, text, page images); headless tool table (123 tools incl. signing, optimizing, initial view, links, stamps, data exchange, comment review, forms authoring and scripts, redaction, sanitize, print, add content), opt-in MCP server over stdio, CLI `run`/`tools` (closed stdout pipes exit cleanly), UI control channel with drag. Missing: Action Wizard, AI providers
Done since: CLI run and MCP reject missing, empty, repeated and option-valued --root arguments before any tool/script execution or MCP initialization. A single-hyphen directory name remains valid; synthetic CLI regressions preserve a would-be overwritten file. `text_find` also caps the per-page match ranges it materializes at the remaining result limit plus one continuation match. | -| M14 | 1.0 polish: performance, localization, installers | 120–250 | 15% | 80–150 | Done: PhotoCraft's translation system (`i18n/`: TSV catalogs, `tl!`, command-id and plural entries, system-language detection, strict catalog tests); every dialog, panel and notice goes through `tl!`. Catalogs of ≈ 2,000–2,140 entries for Japanese, Simplified and Traditional Chinese, Russian, Bulgarian, German, Spanish, French, Telugu, Hungarian, Ukrainian, Italian, Brazilian Portuguese and Arabic (logical-order catalog put into display order; layout not yet mirrored); Czech covers the menus. Signed and notarized macOS builds, signed Windows installers (x64, x86, ARM64), AppImage, deb, rpm, Flatpak and FreeBSD packages. #307 (bounded large-PDF memory): shared resource indexes and parser metadata, lazy inspection, bounded caches; a 9,156-page file inspects in ≈ 1.8 s at ≈ 500 MiB instead of exceeding 10 GiB. Shortcut display uses localized modifier/key names across menus, toolbar, palette, Help and tooltips (#496). Missing: a right-to-left layout, a Chinese UI face in releases, Hindi/Indonesian/Korean/Vietnamese, file-backed input and global memory budgets, performance budgets, keyboard-only operation | +| M14 | 1.0 polish: performance, localization, installers | 120–250 | 15% | 80–150 | Done: PhotoCraft's translation system (`i18n/`: TSV catalogs, `tl!`, command-id and plural entries, system-language detection, strict catalog tests); every dialog, panel and notice goes through `tl!`. Catalogs of ≈ 2,000–2,140 entries for Japanese, Simplified and Traditional Chinese, Russian, Bulgarian, German, Spanish, French, Telugu, Hungarian, Ukrainian, Italian, Brazilian Portuguese and Arabic (logical-order catalog put into display order; layout not yet mirrored); Czech covers the menus. Signed and notarized macOS builds, signed Windows installers (x64, x86, ARM64), AppImage, deb, rpm, Flatpak and FreeBSD packages. #307 (bounded large-PDF memory): shared resource indexes and parser metadata, lazy inspection, bounded caches; a 9,156-page file inspects in ≈ 1.8 s at ≈ 500 MiB instead of exceeding 10 GiB. Edits let go of the previous working file before writing the next (a 1 GiB file peaks at two copies while editing, not three; gap 13). Shortcut display uses localized modifier/key names across menus, toolbar, palette, Help and tooltips (#496). Missing: a right-to-left layout, a Chinese UI face in releases, Hindi/Indonesian/Korean/Vietnamese, file-backed input and global memory budgets, performance budgets, keyboard-only operation | | M13 | Automation (MCP, Action Wizard, CLI) + AI providers | 60–120 | 45% | 25–45 | Done: MCP resources (document info, text, page images); headless tool table (123 tools incl. signing, optimizing, initial view, links, stamps, data exchange, comment review, forms authoring and scripts, redaction, sanitize, print, add content), opt-in MCP server over stdio, CLI `run`/`tools` (closed stdout pipes exit cleanly), UI control channel with drag. Missing: Action Wizard, AI providers | -| M14 | 1.0 polish: performance, localization, installers | 120–250 | 15% | 80–150 | Done: PhotoCraft's translation system (`i18n/`: TSV catalogs, `tl!`, command-id and plural entries, system-language detection, strict catalog tests); every dialog, panel and notice goes through `tl!`. Catalogs of ≈ 2,000–2,140 entries for Japanese, Simplified and Traditional Chinese, Russian, Bulgarian, German, Spanish, French, Telugu, Hungarian, Ukrainian, Italian, Brazilian Portuguese and Arabic (logical-order catalog put into display order; layout not yet mirrored); Czech covers the menus. Signed and notarized macOS builds, signed Windows installers (x64, x86, ARM64), AppImage, deb, rpm, Flatpak and FreeBSD packages. #307 (bounded large-PDF memory): shared resource indexes and parser metadata, lazy inspection, bounded caches; a 9,156-page file inspects in ≈ 1.8 s at ≈ 500 MiB instead of exceeding 10 GiB. Shortcut display uses localized modifier/key names across menus, toolbar, palette, Help and tooltips (#496). Traditional Chinese accessibility statuses, rule semantics and Taiwan form/table terms have catalog regressions. Missing: a right-to-left layout, a Chinese UI face in releases, Hindi/Indonesian/Korean/Vietnamese, file-backed input and global memory budgets, performance budgets, keyboard-only operation | +| M14 | 1.0 polish: performance, localization, installers | 120–250 | 15% | 80–150 | Done: PhotoCraft's translation system (`i18n/`: TSV catalogs, `tl!`, command-id and plural entries, system-language detection, strict catalog tests); every dialog, panel and notice goes through `tl!`. Catalogs of ≈ 2,000–2,140 entries for Japanese, Simplified and Traditional Chinese, Russian, Bulgarian, German, Spanish, French, Telugu, Hungarian, Ukrainian, Italian, Brazilian Portuguese and Arabic (logical-order catalog put into display order; layout not yet mirrored); Czech covers the menus. Signed and notarized macOS builds, signed Windows installers (x64, x86, ARM64), AppImage, deb, rpm, Flatpak and FreeBSD packages. #307 (bounded large-PDF memory): shared resource indexes and parser metadata, lazy inspection, bounded caches; a 9,156-page file inspects in ≈ 1.8 s at ≈ 500 MiB instead of exceeding 10 GiB. Edits let go of the previous working file before writing the next (a 1 GiB file peaks at two copies while editing, not three; gap 13). Shortcut display uses localized modifier/key names across menus, toolbar, palette, Help and tooltips (#496). Traditional Chinese accessibility statuses, rule semantics and Taiwan form/table terms have catalog regressions. Missing: a right-to-left layout, a Chinese UI face in releases, Hindi/Indonesian/Korean/Vietnamese, file-backed input and global memory budgets, performance budgets, keyboard-only operation | | | **Total** | **2,085–3,840** | **≈ 49% ready** | **≈ 770–1,400** | Area view of the same work: [target-app-parity.md](target-app-parity.md#by-feature-area) | ## Upcoming milestones, ranked diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index 2707f8c1a..0f2ede795 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -395,7 +395,7 @@ status = "planned" commands = [] tools = [] evidence = [] -notes = "ByteSource (M1.5); files are buffered whole today" +notes = "ByteSource (M1.5); files are buffered whole today. Viewing holds the file once and editing twice: edits let go of the previous working file before writing the next (a 1 GiB file peaked at three copies after a few edits, now two). Saving still peaks at three. Reading from disk on demand waits on the renderer (hayro parses one contiguous buffer, gap 2)." [[feature]] id = "core.pdf20-utf8-strings"