Skip to content

Support drawing and inserting content across page boundaries (no hard page-edge clipping) #54

Description

@markm39

Problem

Ink and inserted content stop hard at page boundaries. A stroke drawn across the end of one page and onto the next is clipped at the page edge, because:

  • Each page is rendered by one pooled engine surface (ContinuousEnginePool, default 3 engines), positioned at pageIndex * pageHeight in the notebook stack.
  • A stroke gesture is routed to a single page's engine, and the engine draws only within its own surface bounds.
  • SerializedNotebookData stores strokes per page, so there is no representation for a stroke that spans two pages.

For a continuous-notebook product this is a visible seam: users writing a long derivation or diagram near a page end have to lift and reposition, and host-app overlays (images, text boxes, via the 0.3.4 overlay prop) inherit the same per-page mental model.

Goal

Drawing and content placement behave as if the notebook were one continuous surface — no dead zone or clipping at page ends — while preserving the engine-pool performance model (fixed engine count, no GPU context churn mid-scroll, flat allocations during long-document scrolling).

Proposed direction

Keep pages as the storage and rendering unit; make spanning a presentation/capture concern:

  1. Capture in notebook coordinates. The active stroke is captured in continuous notebook space (page-agnostic Y), not page-local space.
  2. Live rendering across adjacent engines. While the active stroke's bounding region overlaps a page boundary, forward the live stroke points to both adjacent engines (each applies its own page offset). The pool already guarantees the neighboring page's engine is mounted around the settled page, so no extra engine or GPU context is needed — this should keep the perf envelope intact.
  3. Split at pen-up. On stroke end, split the stroke at page boundaries into per-page segments, each stored in its page's stroke list. The engine already has stroke-splitting machinery for the pixel eraser (splitStrokeAtPoint, and serialization v2's originalAlphaMod handling for consistent opacity after splits) that this can build on.
  4. Group id for logical identity. Segments carry a shared strokeGroupId so selection, move, and erase treat the original stroke as one object even though storage is per page. Serialization gains an optional field (backward compatible: absent id means standalone stroke; older payloads load unchanged, and a version bump only if the field can't be ignored by older readers).

Behaviors that must keep working (acceptance)

  • Perf invariants: engine count stays fixed; no per-boundary allocations while drawing; Pencil latency unchanged when the stroke is entirely inside one page (the common case should pay zero cost). Existing benchmark suites (replayed strokes, scroll sampling, multi-page) should show no regression.
  • Eraser: pixel eraser applied to one segment near the boundary behaves consistently with the same erase on a non-split stroke; erasing across the boundary works in one gesture.
  • Selection: lasso/selection spanning a boundary selects and moves the whole group; moving a group across a boundary re-splits at the new position. Selection touch rects (selectionTouchRects) already compute in notebook space, which should help.
  • Trailing page growth: drawing off the end of the last page into the trailing blank page appends the next page mid-stroke (or at pen-up) instead of clipping.
  • Zoom: boundary-spanning live strokes render correctly in the zoomed preview path (constant-memory surface from 0.3.2) and after settle.
  • Export: per-page export/PDF rendering includes each page's segment exactly to the page edge (no double-draw, no gap). Batch export unchanged in shape.
  • Serialization round-trip: save/load preserves groups; loading a spanning group renders identically.
  • Android parity: same behavior in the GL/Ganesh path; the shared C++ engine should carry the split/group logic so both platforms inherit it.
  • Overlay/content spanning: host apps positioning overlay content (images, text boxes) across a boundary need nothing new from the engine beyond documented notebook-space coordinates — confirm and document that the overlay viewport transform imposes no page clipping of its own.

Open questions

  • Should page backgrounds (ruled/grid/PDF) remain visually distinct at the boundary while ink flows across, or should there be an optional "continuous background" mode? (Product call; PDF-backed pages presumably always keep hard boundaries — spanning could be disabled for PDF notebooks in v1.)
  • Mid-stroke page append vs pen-up append for trailing-page growth: mid-stroke feels better but mutates the page list during an active gesture; needs care with pool reassignment suppression until pen-up.
  • Does the settle-handoff path need to defer pool reassignment while a boundary-spanning stroke is active (drawing while the scroll settles across a boundary)?

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions