cetz-package / cetz

CeTZ: ein Typst Zeichenpaket - A library for drawing stuff with Typst.
https://cetz-package.github.io
GNU Lesser General Public License v3.0
825 stars 35 forks source link

Custom style roots #133

Closed jneug closed 1 year ago

jneug commented 1 year ago

Package authors will want to provide custom style roots to allow styling their custom elements.

For example, I am working ob a package to draw finite automata and would like to add the roots state and transition, to allow global styling of these elements.

These additions might be useful in this context:

jneug commented 1 year ago

I just realized, that elements already support a style and before key.

Those already should allow users to add custom styles to the context, shouldn't they?

johannes-wolf commented 1 year ago

Yes, both callbacks allow for modifying the context's style.

jneug commented 1 year ago

I guess this can be closed then.

Is there any specific reason, why the style callback existed? It seems manipulating the styles could be done via before as well.

Maybe for semantic reasons a default-styles callback could still be useful.

fenjalien commented 1 year ago

I would like to add that set-style doesn't actually check if the given style is valid, it'll add it to the style dictionary anyway. So you should be able to give it your defaults in some kind of setup function.

set-style(state: (a: ...), transition: (b: ...))

Or at least in theory idk if it will break without trying it exactly.

johannes-wolf commented 1 year ago

That should work. In lib/axes.typ I merge a local defaut-style dictionary with the result of styles.resolve(..) to be able to have custom style roots without polluting the global style.

jneug commented 1 year ago

I would like to add that set-style doesn't actually check if the given style is valid, it'll add it to the style dictionary anyway. So you should be able to give it your defaults in some kind of setup function.

set-style(state: (a: ...), transition: (b: ...))

Or at least in theory idk if it will break without trying it exactly.

Yes, but that would require me to control the call to canvas. But I just provide some new draw functions, to draw states and transitions. The package user will call canvas and probably set-style before any of the state functions is called.

Something like this:

#cetz.canvas({
  import cetz.draw: set-style
  import finite.draw: state, transition

  set-style(
    state: (fill: luma(240), stroke: 2pt + gray)
  )

  state((0,0), "q0", start:true)
  state((6,2), "q1")
  state((9,-1), "q2", stop:true)

  transition("q0", "q1", label:"0")
  transition("q0", "q0", label:"0,1")
  transition("q1", "q0", label:"0,1")
  transition("q1", "q2", label:"0")
})

But it works by adding a before callback to my state element:

((
   // ...
  before: (ctx) => {
    if "state" not in ctx.style {
      ctx.style.insert("state", default-style.state)
    } else {
      ctx.style.state = styles.resolve(default-style.state, ctx.style.state)
    }
    return ctx
  },
  // ...
),)

Looking at axes.typ, that solution is similar, but the defaults never get stored in the context. Using util.merge-dictionary and resolvewith root: none seems to be pretty much the same, or am I missing something?

johannes-wolf commented 1 year ago

Yes, should be the same.

jneug commented 1 year ago

Thanks for the clarifications.

I think I'm ready to publish my package soon. Needs some polish and proper documentation, but the drawing part works at least.

You can have a look at it here: https://github.com/jneug/typst-finite

johannes-wolf commented 1 year ago

Look very nice! :)

johannes-wolf commented 1 year ago

Should we create a follow up ticket for implementing said functionality (passing root styles into canvas calls) or can this be closed?

jneug commented 1 year ago

I think this can be closed, since there are already multiple ways of solving this problem.

Maybe a kind of best practice could be mentioned in the developer docs.

johannes-wolf commented 1 year ago

Thank you. I will create a ticket.