Skip to content

Implement path clipping with exact Bézier curves - #1131

Open
hooyuser wants to merge 1 commit into
cetz-package:0.5.3from
hooyuser:codex/clip-path-pr-0.5.3
Open

Implement path clipping with exact Bézier curves#1131
hooyuser wants to merge 1 commit into
cetz-package:0.5.3from
hooyuser:codex/clip-path-pr-0.5.3

Conversation

@hooyuser

Copy link
Copy Markdown
Contributor

This PR builds on top of the path boolean work from #1091 and is the next step toward addressing #618.

It adds a new clip function to clip path drawables against a closed clipping region:

clip(
  clipping-region,
  body,
  mode: "inside",
  ...
)

Depending on mode ("inside" or "outside"), it keeps only the geometry inside or outside the clipping-region. The body parameter takes the objects you want to clip, and each path drawable inside it is clipped independently against clipping-region. Note that unlike paths in clipping-region, paths in body may contain both open and closed subpaths.

Take mode: "inside" as an example. Under the hood, the stroke and fill of each path drawable in body are handled separately:

  • If its stroke is not none: subpaths (open or closed) are trimmed to keep only the portions inside the clipping region.
  • If its fill is not none:
    • Its closed subpaths together define the filled area according to body-fill-rule. A boolean intersection between that area and the clipping-region produces a newly generated, fill-only path.
    • Its open subpaths do not participate in the Boolean operation.

mode: "outside" follows the same split: it retains the outside portions of stroked paths and uses a boolean difference for filled areas.

To avoid repeating logic from #1091, I also reorganized the relevant Typst and Rust code into shared Typst/Rust modules. This is purely an internal cleanup and doesn't change the behavior of the existing boolean function.


Example: string diagram of snake equation

Someone on Discord asked how to draw a string diagram and came up with a solution using boolean operations (discussion link). With clip, we now have a more straightforward way to achieve this kind of effect.

#cetz.canvas({
  import cetz.draw: *

  let padded-anchor(name, position) = cetz.draw.group(name: name, {
    import cetz.draw: *

    let padding = 0.3
    anchor("default", position)
    anchor("north", (position.at(0), position.at(1) + padding))
    anchor("south", (position.at(0), position.at(1) - padding))
    anchor("east", (position.at(0) + padding, position.at(1)))
    anchor("west", (position.at(0) - padding, position.at(1)))
  })

  let path = {
    line("F_in", (4, 2))
    bezier((4, 2), "epsilon.east", (4, 3))
    bezier("epsilon.west", (2, 2), (2, 3))
    bezier((2, 2), "eta.east", (2, 1))
    bezier("eta.west", (0, 2), (0, 1))
    line((0, 2), "F_out")
  }

  on-layer(10, {
    circle((1, 1), radius: 3mm, name: "eta", fill: white)
    content((), $eta$)

    circle((3, 3), radius: 3mm, name: "epsilon", fill: white)
    content((), $epsilon$)

    padded-anchor("F_in", (4, 0))
    content("F_in.south", $F$)

    padded-anchor("F_out", (0, 4))
    content("F_out.north", $F$)

    path
  })

  let background(fill) = {
    rect(
      (-0.5, 0),
      (4.5, 4),
      stroke: none,
      fill: fill,
    )
  }

  let aqua-region = {
    merge-path(
      {
        path
        line("F_out", (-0.5, 4), (-0.5, 0))
      },
      close: true,
    )
  }

  on-layer(0, {
    background(lime)
    clip(aqua-region, background(aqua))
  })
})

cetz

@hooyuser hooyuser closed this Aug 26, 2026
@hooyuser
hooyuser deleted the codex/clip-path-pr-0.5.3 branch August 26, 2026 04:46
@hooyuser
hooyuser restored the codex/clip-path-pr-0.5.3 branch August 26, 2026 04:49
@hooyuser hooyuser reopened this Aug 26, 2026
@johannes-wolf
johannes-wolf self-requested a review August 27, 2026 18:18
@johannes-wolf johannes-wolf added this to the 0.5.3 milestone Aug 28, 2026
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.

2 participants