Skip to content

PDF stage 4.3: clipping#561

Open
andiwand wants to merge 1 commit into
mainfrom
pdf-stage-4.3-clipping
Open

PDF stage 4.3: clipping#561
andiwand wants to merge 1 commit into
mainfrom
pdf-stage-4.3-clipping

Conversation

@andiwand

@andiwand andiwand commented Jun 25, 2026

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

Stage 4.3 of the in-house PDF renderer: honour the clipping path in the SVG output (ISO 32000-1 8.5.4). Stacked on main after stage 4.2 (#560).

What changed

  • Graphics state tracks the current clip — the intersection of an ordered list of regions. W/W* set a pending clip that the next painting (or n) operator installs; that operator's own paint is clipped only by the previous clip, so paint_path snapshots before committing.
  • The clip is part of the saved/restored state, so q/Q and form-XObject invocation scope it like the CTM. Each PathElement carries a clip snapshot.
  • Form-XObject /BBox clipping (deferred in stage 2) lands here: parsed onto the XObject and intersected through the form /Matrix + CTM, scoped by the invocation's save/restore.
  • HTML: each page's clip regions become nested <clipPath> defs (intersection chained via clip-path), emitted once in a hidden <svg> and referenced per path; shared prefixes deduped.

Tests

Extractor-level (inline content streams): clip rect limits a later fill; even-odd rule; a clip excludes its own paint; nested clips intersect; q/Q save/restore; form /BBox clips its content. Full suite green.

Track the clipping path in the graphics state and honour it in the SVG
output (ISO 32000-1 8.5.4).

- `W`/`W*` set a pending clip that the next painting or `n` operator
  installs as the intersection with the current clip. The painting
  operator that installs a clip is itself clipped only by the *previous*
  clip (the new region scopes following content), so `paint_path`
  snapshots the clip before committing the pending one.
- The clip is part of the saved/restored state, so `q`/`Q` and
  form-XObject invocation scope it like the CTM. Each emitted
  `PathElement` carries a snapshot of the clip in force.
- Form-XObject `/BBox` clipping (deferred in stage 2) lands here via the
  same machinery: the box is parsed onto the `XObject` and intersected
  into the clip — mapped through the form `/Matrix` + CTM — under the
  invocation's save/restore.
- HTML: each page's clip regions become nested `<clipPath>` defs
  (intersection expressed by chaining `clip-path` from one to the next),
  emitted once in a hidden `<svg>` and referenced per path. Dedups
  shared prefixes.

Tests: clip rect limits a later fill; even-odd rule; a clip excludes its
own paint; nested clips intersect; `q`/`Q` save/restore; form `/BBox`
clips its content.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7Lp7cZPX84gvGmaGA6bHq

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 23cac8f2f2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +67 to +70
/// The clip in force when this path was painted: the intersection of these
/// regions (empty = unclipped). Snapshotted from the graphics state at paint
/// time so the renderer can install it without replaying the clip stack.
std::vector<ClipPath> clip;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply clipping to text output as well

When a stream sets a clip and then paints text, such as 0 0 50 50 re W n BT ... Tj ET, or when text is inside a form clipped by /BBox, the current clipping path applies to that text too. This patch snapshots the clip only on PathElement; TextElement carries no clip and the HTML path writes text spans without any clip-path, so these PDFs still render text outside the clipped region even though vector paths are clipped.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant