cetz icon indicating copy to clipboard operation
cetz copied to clipboard

Custom marks

Open NeilGirdhar opened this issue 3 months ago • 6 comments

It would be nice to move Fletcher's custom marks code to Cetz and have it here instead so that all Cetz and Fletcher code can use it.

NeilGirdhar avatar Aug 19 '25 09:08 NeilGirdhar

I will look into copying fletchers marks into cetz.

johannes-wolf avatar Aug 19 '25 12:08 johannes-wolf

@johannes-wolf Thank you! But I don't mean copying their marks. I mean copying ideas from their custom mark system.

This is what I have in Fletcher and it works beautifully:

#let add-custom-fletcher-marks() = fletcher.MARKS.update(m => (
  m
    + (
      "square-fork": (
        draw: mark => fletcher.cetz.draw.line(
          (0, 0),
          (4, 2),
          (-4, 2),
          (-4, -2),
          (4, -2),
          stroke: 0pt,
          close: true,
        ),
        fill: auto,
        tip-origin: 4,
        tail-origin: -4,
        tip-end: -1,
        tail-end: -1,
      ),
      "chevron": (
        draw: mark => fletcher.cetz.draw.line(
          (0, 0),
          (4, 2),
          (0, 2),
          (-4, 0),
          (0, -2),
          (4, -2),
          stroke: 0pt,
          close: true,
        ),
        fill: auto,
        tip-origin: 4,
        tail-origin: -4,
        tip-end: -1,
        tail-end: -1,
      ),
      "diamond": (
        draw: mark => fletcher.cetz.draw.line(
          (4, 0),
          (0, 2),
          (-4, 0),
          (0, -2),
          stroke: 0pt,
          close: true,
        ),
      ),
      "semicircle": (
        draw: mark => fletcher.cetz.draw.arc(
          (0, -4),
          start: -90deg,
          stop: 90deg,
          radius: 4,
          stroke: 0pt,
          mode: "PIE",
        ),
        fill: auto,
        tip-origin: 4,
        tip-end: 2,
        tail-end: 2,
      ),
    )
))

I just learned from you about register-mark. Thank you for sharing that! It's not in the manual, but it looks like it is pretty close to Fletcher's system. It may be nice to add:

  • Fletcher's ability to easily reverse marks, and
  • Fletcher's tip debugging functions (shows a blown-up image of the tip with lines showing where the marks start and end). You probably also don't need y-coordinates for tip-end, etc.

NeilGirdhar avatar Aug 19 '25 13:08 NeilGirdhar

It might also be nice to copy Latex's more beautiful bezier version of stealth (left) compared with cetz's (right):

Image

NeilGirdhar avatar Aug 19 '25 13:08 NeilGirdhar

How do I create a reversed version of a custom mark? And what if I just want an alias? E.g.,

#let add-arch-marks() = fletcher.MARKS.update(m => (
  m
    + (
      "arrow-a": (inherit: "latex", fill: auto), // Alias
      "arrow-b": (inherit: "square-fork", fill: auto, rev: true), // Reversed
    )
))

NeilGirdhar avatar Aug 23 '25 05:08 NeilGirdhar

Nicer stealth:

  #let mark-scale = 0.07
  #let stealthy-a = 0.4
  #let stealthy-b = 0.1
  #register-mark("stealthy", style => {
    scale(mark-scale)
    merge-path(
      fill: style.stroke.paint,
      stroke: (thickness: 1pt, join: "round"),
      close: true,
      {
      bezier(
        (2, 0),
        (-1, 1),
        (0, stealthy-a),
        stroke: none,
      )
      bezier(
        (-1, 1),
        (-1, -1),
        (-stealthy-b, 0),
        stroke: none,
      )
      bezier(
        (-1, -1),
        (2, 0),
        (0, -stealthy-a),
        stroke: none,
      )
    }
    )
    anchor("tip", (2, 0))
    anchor("base", (0, 0))
  })

NeilGirdhar avatar Aug 23 '25 06:08 NeilGirdhar

How do I create a reversed version of a custom mark? And what if I just want an alias? E.g.,

#let add-arch-marks() = fletcher.MARKS.update(m => ( m + ( "arrow-a": (inherit: "latex", fill: auto), // Alias "arrow-b": (inherit: "square-fork", fill: auto, rev: true), // Reversed ) ))

Cetz auto-reverses the mark for you, if you pass ..., reverse: true. Example:

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

  register-mark("~", ctx => {
    scale(15%)
    merge-path({
      line((0, -1), (0, 1))
      arc((), start: 90deg, delta: -180deg, radius: 1)
    }, close: true)
    anchor("tip", (1, 0))
    anchor("base", (0, 0))
  })
  
  line((0,0), (1,0), mark: (end: (symbol: "~", reverse: true), start: "~"))
})

Gives: Image

I know that we need a "reversed-tip" and "reversed-base" anchor to make this more flexible.h Also a per-symbol style would be nice, as some marks should be wider by default etc.

johannes-wolf avatar Aug 23 '25 12:08 johannes-wolf