Skip to content

Bound cancellation during Standard 14 text layout #541

Open

Description

@yhay81

Context

Review of #538 found that the shared Standard 14 layout code can scan an input-sized paragraph without polling its operation budget: newline splitting, a single linebreaks iterator step, and trailing whitespace trimming each may traverse long input without an intervening poll. DESIGN.md F5/F6 and section 9 require long input-derived operations to observe the caller's deadline and cancellation.

Goal

Make Standard 14 text layout interruptible and budgeted during long individual paragraphs while preserving line-breaking and valid layout behavior.

Scope

In: shared Standard 14 layout and its underlying line-break traversal if needed, with deterministic long-input tests. Also cover the shared PDF text decoder used by forms where it scans long UTF-8 input without polling. Out: annotation-specific rendering and embedded-font shaping.

Paths

  • crates/pyropdf-engine/src/standard/, crates/pyropdf-engine/src/forms/text.rs, and narrowly related shared text code;
  • targeted Rust layout tests and relevant text tests;
  • manifests only if a dependency change is necessary.

Acceptance

  • Long paragraphs, whitespace runs, newline scans, and UTF-8 decoding poll cancellation and deadline during processing, including inside line-break discovery, without silent truncation.
  • Deterministic mid-operation cancellation and deadline tests return typed errors; ordinary layout matches existing results.
  • Full project checks and current-head CI pass.

Notes

See the review of #538. Checking only between lines does not interrupt a single input-sized iterator step. Do not duplicate the parser or silently change Unicode line-breaking semantics.

Activity

  1. added
    area:enginecrates/pyropdf-engine: interpretation, rendering, writing, understanding
    on Nov 1, 2026
  2. added
    in-progressClaimed; the latest comment names the branch
    on Nov 1, 2026
  3. yhay81 commented on Nov 1, 2026

    @yhay81
    OwnerAuthor

    Claimed on branch claude/541-standard14-layout-cancel.

  4. yhay81 commented on Nov 1, 2026

    @yhay81
    OwnerAuthor

    Handoff: branch claude/541-standard14-layout-cancel, PR #546 (Part of #541; waiting for owner review, not merged).

    Changed: new standard/scan.rs with polled, step-bounded scans. These cover newline search, trailing-whitespace trimming, line-break discovery (linebreaks run one step at a time behind a context that restarts at breaks or safe pairs, with same-class runs cut to their ends), and grapheme discovery. layout, single_line, graphemes, grapheme_count (it now takes &Budget), ops::push_string, and the forms UTF-8 decoder append_lossy now use these scans. New LayoutError::BreakContext is returned for a break context longer than MAX_BREAK_CONTEXT (1024 chars) after compression.

    Checks: python3 tools/check.py is green locally. Tests added: differential proptests against linebreaks, the grapheme iterator, and from_utf8_lossy; deterministic mid-scan cancellation and deadline tests on each scan and on public layout. CI on the PR head is pending.

    Still open (why the PR says Part of): inside one call, unicode-segmentation's GraphemeCursor scans back over the preceding run for emoji ZWJ, conjunct, and regional-indicator decisions, and that scan does not poll. Chunks that do not start at offset 0 would avoid it, but GraphemeCursor 1.13.3 then gets RI parity wrong (proptest seed 🇦🇦🇦🇦, step 1). Next action: owner review of #546. The owner also needs to decide whether that residual should become an upstream issue or be accepted, and whether the BreakContext refusal is acceptable.

  5. yhay81 commented on Nov 1, 2026

    @yhay81
    OwnerAuthor

    Review of PR #546 at 98f86e26b5c107bc06f55df324990a1078c5474d found additional work before #541's acceptance can be met:

    • standard/scan.rs grows the input-derived context and window strings without charging their allocations to the operation's memory budget. Bound and charge the buffers, and test a constrained memory budget.
    • standard/layout.rs copies the entire prefix preceding the first newline with one push_str; a long prefix is not interruptible during that copy. Copy it in polled steps and test cancellation during the prefix copy.
    • The documented GraphemeCursor backward scan still cannot poll during a long emoji, Indic conjunct, or regional-indicator context. The current mid-operation tests check the forward polling but do not establish cancellation inside that backward scan.
    • BreakContext rejects some previously valid input at a fixed threshold and maps the refusal to a generic PDF error. Its compatibility and error semantics remain to be resolved.

    The PR is a partial change, does not close this issue, and should not unblock #168 until the remaining conditions are addressed. The owner should decide any upstream proposal before publication to the dependency repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:enginecrates/pyropdf-engine: interpretation, rendering, writing, understandingin-progressClaimed; the latest comment names the branch

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions