Because the current approach has problems: https://github.com/typst/typst/issues/241
#Styling
1477 messages · Page 2 of 2 (latest)
Oh, it's already works like that...
All red
would "Hello" be centered in 8.6 and larger and bold in 8.7?
Maybe it would be good to define styles for these elements separately. And default will be some "common" styles. Not sure how to define "common", maybe "styles, which are applied to all pages", or just some default styles, which should be explicitly defined for customization
- the default behavior
- no (default style)
I don't see how that's compatible with all red
red in all cases
not all text red
I think it would be quite frustrating if you would have to define the document font twice, for body text and header/footer.
Oh yeah, true
yeah still. why would the footnote pick up the set text red but not the center alignment of the caption?
both are just styles
maybe your session expired?
private tab, oh... maybe that's why?
some other websites refuse to work when opening in private tab in Firefox.
you need to use the share link with r and not the project link with p then
if you are logged in you are redirected to the p link
Maybe define some list of "safe to inherit" styles, like font, colors?
Though feels hacky
yeah does feel hacky
and if someone makes their headings red, we are back to square one
what is "r" and what is "p"? I'm just opening the link.
oh my bad I sent the wrong link
classic ;)
share links always start with r or w and normal project links with p. anonymous users can access a project through its share links but not through its canonical link.
whereas logged in users that have entered the project through a share link at least once become "external project members". they can access the project through the p link, see it on their dashboard and can continue to open it even if the share link is revoked. (unless the owner kicks them through the share menu)
oh... Is this a secret recipe? Gotta remember that. Might be useful.
This one is tricky because I know what the issue is
Is your question what it should be? Or what I think it is now?
Because the footnote will be placed in a separate place. Implicitly inheriting the caption's layout just because the footnote happens to be defined inside a caption is not great. But for... Yeah, this is kinda... yeah
At least with #show figure.caption: set text(red) you're explicitly saying that [Hi #footnote[Hello]] must be red. So this makes much more sense, at least on the 1st thought.
I guess with the layout I can see your point. But with the heading boldness it's really the same as a text color.
Gotta get the highest score on that test. I was studying for this the whole last night (reading through the docs)!
What about 1,2,8 red and rest black, so that only the style that is in place when the page is created is used (for the first page only styles before any content)
okay, well how do you configure it then? 😅
I think the styling should not be applied because conceptually, we're not placing the content where the styles are active, the footer should be used to configure the styling, if the footnotes land int he margin as margin notes, then the margin need as way to configure figure the style
I think margin and footer are different areas when it comes ot styles than the usual global document scope which go into the implicit pages
The marker however
Should be Red
Since it's on the page where we have those styles active
yeah sure
still, how would you make footnote text red then?
I think we'd need something like show page.footer: set text(red)
Or more specifically show footnote.entry: set text(red)
but why would the footer be affected by this show page.footer? since this show rule would just be in the "global document scope"?
Good point
No, exactly the same thing. You just wanna cite something, add some context information to the heading by adding a footnote to it. So why should the implicit heading styling be applied to the footnote that just exists to provide some info/context? But since you're again setting the #show heading: set text(red) explicitly, you are signing up for the footnote to be also red. At least potentially. My first thought was that it will apply. Now it's a bit more mixed.
I'm not sure
#1175894504146993304 message
#1175894504146993304 message
(I somehow replied to slightly different message)
I think it all comes down to footers and margins being special here
how would the compiler distinguish 7 and 8?
Nothing ever goes into the footer or margin unless we shove it there explicitly
what I think is most important is that you don't have to specify global stuff like fonts etc multiple times. it should just work.
I don't know how the compiler works in this regard, but there would have to be some kind of "content start boundary" before the heading, so the heading show rule doesn't affect the top-level style. The show-all rule in 8 isn't bound to a specific element and should affect the top-level styles. I can imagine that this doesn't align with how the styling works now though
The show-all rule isn't a problem, but the template is a custom element with yet another show rule, so it would present a content start boundary.
Your proposal is actually very close to my thinking, but defining "content start boundary" well is tricky
And then there is also the problem that not all "prefix styles" remain active throughout the full document. Consider e.g.
#set text(font: "Helvetica")
#{
set text(red)
[I am the first content]
}
More here though. #footnote[Hi]
The first content boundary is right before the word "I", but the red text should not affect the footnote because it doesn't remain in effect until the end of the page.
Given a suitable definition of "content boundary", what might work is taking some sort of intersection of the styles at the first content start boundary and at the last content end boundary.
What happens currently, is that we take the intersection of the styles of all elements on the page. The difference here would be that we don't take the styles of "I" itself, but somehow the styles right before it.
What might also be possible is to continue taking the intersection of all elements and somehow cut off part of these styles that come from constructor calls (like text(red)[..]) and show rules. But then 8 will again not be red.
Can't all scopes that contain any content be defined as content themselves? Then the boundary could maybe already be at #{
then show: it => { .. } would also introduce a content boundary though
My 2ct: Red for all but 8.3 or for none of them would feel sensible, I think.
To circumvent the #show heading: set text(size: 50pt, weight: "bold") and figure problems, footnotes might get the properties of “generic text on the page scope” (whatever that means), but scaled down.
It doesn't really need one though as it always ends when the outer scope ends anyway, so maybe this scope can be removed somehow?
I think assigning extra meaning to {..} is a bit dangerous. Suddenly something stops working because you've factored out a function.
“generic text on the page scope” (whatever that means)
that's the big question. what is generic text on the page scope.
Ehh, maybe everything that was applied to text using a set-rule before the first main-body content of the current page?
Maybe I fail to see the potential problems, but to some extent that's already the case, as you can't factor out set rules.
Other than that, maybe the handling of a template as a type/element is itself already suboptimal as that would kind of imply that a template element is placed in the document, whereas it should rather surround the document to be able to apply styles to it
[...] as you can't factor out set rules.
You can usingshow:. And it will behave 100% the same as if the set rules were inline in the function.
Other than that, maybe the handling of a template as a type/element is itself already suboptimal as that would kind of imply that a template element is placed in the document, whereas it should rather surround the document to be able to apply styles to it
Maybe, though I think it could be a nice pattern, since it allows set rules on the template parameters and get rules in extra template elements to retrieve them.
Funnily enough, I think \begin{document} would sort of fix our problems since it establishes a clear content boundary ^^
Could an element explicitly define which styles it inherits (i.e., inherit text font but not colour)
I think we need to consider where a style's scope ends. With show: ... the scope of the rule goes conceptually beyond the end of the page—beyond the end of the code really; it's very meta. show heading: ... however, very explicitly is scoped to the heading. And if you were to add more content after the heading, it would not be styled as the heading
The 'if you were to add more content' point really drives this home for me. If, instead of writing #footnote[], I explicitly just appended at 'the end of the page' (where is that? 🤔); what styles would apply then? I think using those styles in the footnote entry could prove to be intuitive and predictable
One issue is what happens when a block with different styles is broken accross pages
Arguably its styles should not be applied to the footnote entry, but that isn't so true for some elements (like #chapter[...] and such)
I think an important distinction for an element is whether it should come 'above' pages or within them
Maybe to define content that should encapsulate pages and whose styles should be inherited within, an explicit call to page (or a new pages? 🧑🍳) must be made:
[0m[35m#let[0m [0m[3m[34mtemplate[0m(
[0m[36m..[0mwhatever,
content
) [0m[36m=[0m {
[0m[35mset[0m [0m[3m[34mglobal_rule[0m(value: [0m[32m"something"[0m) [0m[30m// This is applied to footnote entries, headers and footers (even if defined outside of this call to `template`)[0m
[0m[3m[34mpage[0m([0m[36m..[0marguments_if_you_want)[
[0m[1m[36m=[0m Look at my template!
[0m[1m[36m===[0m It's so cool!
[0m[35m#show[0m [0m[3m[34mraw[0m: [0m[35mset[0m [0m[3m[34mtext[0m(red) [0m[30m// This is not applied to footnotes, headers, or footers.[0m
[0m[37m#body[0m
]
}
The problem in this case isn't the show: template, but rather the show: in type template cause it's basically a show template: ..
So it is just like show heading in that respect
I'm not following here. What does it have to do with blocks?
Well if I have a block with set text(red) and it spans across a page boundary, should the footer/header/footnote entries on that boundary be styled as the block? oftentimes not, as with #proof[...] or #note[...], but sometimes it is so, as with #template[(everything)] or #chapter[...]
Do you mean block in the sense of {..}/[..] or #block[..]?
Mostly block
of course there will be semantically similar elements that aren't block-level in practice
I think styles that are local to a block shouldn't apply to footnotes
usually
It becomes a mess where the footnotes pick up random local styles
for sure
perhaps I'm grouping into blocks some elements that aren't actually
(template, chapter, ..)
yeah, they aren't really
since I asked yesterday I've come up with a scheme for the page styles that actually works really well. I'm still figuring out some edge case details, but it behaves pretty intuitively (at least to me). it's a tad complex to explain, but let me try:
because pages aren't (usually) explicitly typed in the markup, it becomes necessary to otherwise define the hierarchy of document elements in relation to pages; whether they encompass pages (such as chapters)—and thus their styles are inherited by the special page elements, or they come within pages (even when broken into multiple parts), such as equations, lists (usually), code blocks, etc. that then keep their styles only within their content
How it works: As currently, we collect the styles that are shared by all elements on the page. As a fallback if there are no elements, we use the styles active at the pagebreak that introduced the page (or at the very start, we use the default styles). Then, to produce our page styles, we filter this list of styles according to a few rules:
- Globally active styles (default or from World impl) are always kept
- Other styles are only kept if they are
outside && (initial || liftable) - "Outside" means they were not produced within a show rule. (Still tweaking whether I reenable outside-ness when someone uses
set pagewithin a show rule to "break free". However just pagebreak definitely won't break free because ofshow heading: it => pagebreak() + it. There could also be some explicit function call to break free like proposed above.) - "Initial" means they were active at the pagebreak that introduced the page. Since these are intuitively already active, they should be kept in any case. (E.g.
text(red, page(..))makes the footer red.). - "Liftable" means they can be lifted to the page-level even though they weren't yet active at the very beginning. Set rule styles are liftable as opposed to direct constructor calls. (E.g. for
set page(..); set text(red)the red text is kept even though it comes after the weak pagebreak from set page whereas forset page(..); text(red)[..]the header isn't red.
This all sounds complex, but I think it won't feel complex to the user. It's also actually not particularly complex to implement.
one way to do this is by using the existing page() function to declare that the styles that clearly do apply to the whole page, as the page is explicitly within their scope, should also apply to the special elements within that page
This is sort of the idea of "initial". Originally I had the condition initial || (outside && liftable). That works almost always as well, but had the small remaining problem that in:
#set page(numbering: "1")
#show heading: it => pagebreak(weak: true) + it
= Introduction
// No more content!
The numbering was still heading-styled because the pagebreak and all of the page content was contained in the heading.
Due to this I changed the condition to require outside always. This means an element show rule can't configure pages anymore. To support this, one could either require an explicit opt-in like you propose or use the way where set page implicitly breaks free, but pagebreak doesn't.
These rules all make sense to me. I think they could definitely be framed in a way that's understandable to users
I think this would've also broken long code blocks or equations, so your change was definitely the way to go. I'm still a little concerned about liftable though; what if I do #set line(stroke: red), just because it happens to be convenient for some figure, and then I don't have a reason to set it back yet, because I'm not using any lines. Initially this seems fine, then I finish the current page and suddenly the next page's header breaks because it has a decorative line in it
I think simply 'is this a set rule?' is not enough to determine whether the rule should be liftable. If I'm using the codelst package and it happens to use a set rule in its implementation, everything is all fine and dandy as long as my code blocks span less than one page, but once they break twice, suddenly that rule applies to the middle page entirely
Sometimes you just need to switch text language or direction in the middle of a page, but you (probably¹..?) wouldn't want your header to change unless you explicitly gave it a new value.
¹ - it's definitely possible to write your header such that it looks good in all of your documents' main langauges, but it's still not obvious whether you want to impact the headers and footers after switching languages. I think the principle of least surprise leads me to prefer opt-in here (with show page.header: set text(lang: ...)?)
I think I need to clarify something: It is not about the shared styles of every element on one page. But rather by every element on one page run.
And a page run is everything between two pagebreaks or set page thingies.
So pagebreaks that occur naturally during layout have no effect at all on this.
I think this will just teach people to scope their rules, which is a good practice.
Oh that's very interesting. I think this makes a very big difference then—this could just work
Yeah, I should've been clearer about that. A few people's comment now make more sense to me.
I’m not sure if this has been discussed already, but are there plans to make it possible to manipulate content after styling (show rules) have been applied?
The need for this kind of thing comes up in e.g. https://github.com/Jollywatt/typst-wordometer/issues/2, where we need to manually walk the content tree.
There aren't currently plans for this. It's a bit complicated because the realized content would be quite complex to handle for a user. It has all the styles that can differ for each individual element and metadata for introspection interspersed with it.
Yeah. I sense it would be a bad idea to open that Pandora’s box.
That said, I still haven’t figured out a good way of implementing a word count. Typst might need something like that built in!
E.g. adding a contextual function that provides access to the plain text contained in an element, as a string without any styling, would make a wordcount possible.
Yes, I believe it needs some support from the core, whatever concrete thing that may be
@drowsy apex if you have ideas for the page stuff, I'm all ears but it's quite tricky
but I didn't really want to keep it open as a bug because I think no matter how you define it, there will be some ambiguous cases (though I'd be happy to be proven wrong)
i mean, the current behavior isn't that bad, in the sense that at least the most egregious bug cases are gone, it's just a bit unexpected
at least the way i described it is how i think a user would see it
I agree it isn't a simple problem though
especially from a technical PoV
the thing is, the styles don't really go beyond the content block
rather, the page shrinks to just the content in the content block, conceptually
maybe we are just missing some extra sugar in the #show: syntax 😛
I'm not sure making show: magic is great either
Right now it's very well defined
Also, I assume you'd want both code and content blocks to behave the same right?
And making code blocks block the page styles might have some rather unfortunate consequences
For example, the font is often configured in a code block
I guess it all depends on how you would change show:
anyway, I do low key hate how I solved it in the end (essentially with a bunch of special cases and ifs), so don't get me wrong, I'm open to discussing it
you mean of the entire page? like keeping the full contents of the page inside the code block? or do you mean just #set text(font: ...) (i.e. just the code mode invocation)?
Let's assume Typst had page numbering enabled by default
I mean this:
#let template(body) = {
set text(font: "My Font")
body
}
#show: template
ah yeah, im not considering a distinction between code and content block here
thats why i added "ignoring how template functions are defined at the moment" to my answer
the distinction i was envisioning here was between using top-level styles (#show: ..., #set outside of any #[ scopes ]) and non-top-level styles (inside a #[ scope ])
so #show: would sort of "promote" the template's styles to work just like top-level #set
since the #show: itself is top-level
here "top-level" purely from a syntactic / user PoV
so the {..} in template scopes it, but show: promotes it?
what if I add more indirection?
another show: in the template, you mean?
#let hi(body) = {
set text(font: "My Font")
body
}
#let template(body) = { hi(body) }
#show: template
hmmm
would be rather surprising if that behaved differently
i think we'd recursively apply the top-level semantics
so you could show: hi
but not hi(body)
so you mean this case would behave differently from the original one?
yes
that would break my expectations a lot
that's fair
to be fair that isnt strictly required, it's possible to only change the first half of the equation
but i fear that could be inconsistent or limiting
or maybe not
maybe just promoting templates is enough and im overthinking it
I gotta go, my sister wants to do something
yea sure
you're not supposed to be here anyway
😂
gotta have your fair share of rest as well 😎
Could we model this as there being "hidden" header and footer elements that are placed before/after the top-level content?
Ex:
#set page(numbering: "1")
#body
Would behave like:
#set page(numbering: "1")
#hidden-header-element
#body
#hidden-footer-element
With a rule that hidden-header-element places itself as close before the first content on each page as possible, so it comes after any pre-content show rules on any page, and hidden-footer-element does the inverse?
Then the difference would be:
#func[ #body ]
---
#show: func
#body
Respectively becoming:
#hidden-header-element
#func[ #body ]
#hidden-footer-element
---
#func[
#hidden-header-element
#body
#hidden-footer-element
]
Which I think matches our intuition?
I think that's just one way of thinking about PgSuper's proposal. There's still some inherent difference in the handling of a function call and show: in your proposal, which isn't there in the current system.
The function call is completely gone by the time Typst resolves styling
I think I'm not envisioning show: template as "promoting" the template to the top-level, but instead that the hidden elements are treated like real content and inserted before realization starts at all. Then the function call retains its scope because the structure remains nested, e.g. {document: [head, func(body), foot]}, vs. func({document: [head, body, foot])})
Maybe it's a fundamental problem that header and footer are (apparently?) thought/meant/implemented to be on the same level as the content of the actual document? For me, header and footer are information about a page, you shouldn't even have access to them from within the document.
Rather, it should maybe be thought that there is a document call that wraps all content and is responsible for layouting. And if you want to apply set rules to header/footer you can't do that from within the main content, you have do it before the document call.
(Or from "within" the call. Would be cool if you could change the document function to make your own layout or so.)
I think the biggest problem is not headers and footers, but top-level content vs explicit pages
it shows in the same way, styles get complicated because of this
Here is a concrete proposal.
If you only write text, there will be no header or footer. There are no issues as to where set rules should apply to.
Hello World
If you want header and footer, you have to wrap everything in something I call header-footer-layout for now. If you put set rules before that, they DO apply to header and footer.
set text(50pt)
show: header-footer-layout.with(header: ...)
Hello world
If you put the set rule after (i.e. as argument to the function call), they are scoped as usual and don't apply to header and footer.
show: header-footer-layout.with(...)
set text(50pt)
Hello world
What do you mean with explicit pages? Calls to page? Can you give an example for the issue? @prime quail
your example kind of illustrates what i mean in a differnt way
consider the show rule to simply be a call to page yes
with set rules always being scoped plus my model above i don't see any issues
the problem is that this doesn't always match intuition, there are a few times laurenz has posted stuff in here where people weren't really in agreement what certain rules should do in edge cases
hmm, can you provide an example? what i've seen so for is that people want to break out of their scope ("edit their parent/sibling") and i think it just shouldn't be allowed. there should be other ways/syntax (such as my proposal above) to do the things that people want to do when they want to break out of scopes.
discord doesn't let me search only in this thread so i actually can't provide an example at this moment
here is one example
this doesn't even talk about footers, but things that end up in footnotes
and there are plenty of other examples somewhere in here
Ok so I guess you're talking about how footnotes should be styled? Like in the style of where they are referenced or differently. I think differently. I don't think of the footnotes as being a sibling of the main content, they are ABOVE it. So I think, just as with the header/footer, you need to have a special "footnote provider" function. Styles applied before that provider call affect footnotes. Styles inside the main content don't affect footnotes.
This has also other advantages. For example, sometimes people want footnotes within some environment (like a theorem box) to appear at the end of the box, not at the end of the page. You can easily do that by wrapping your theorem content in a footnote provider, and then wrapping that in your block or whatever.
Or when you have columns, and you want to be able to decide whether footnotes are displayed per column or per page. It just becomes a matter of whether you wrap columns in a footnote-provider or the other way around.
That's bad, then you can only apply a template function once
Sounds like you might be on the way to my proposal though, by suggesting 'show:' is treated differently
Having to add a footnote provider just to add a footnote seems quite inconvenient and complicated to me. I really like the fact that you can just start writing in Typst, without and \begin{document} shenanigans.
Yeah I agree, but maybe one can provide some default fallback or so. I'm just trying to preserve thr semantics because I think throwing away the hierarchy will bite you sooner or later.
i agree that's the next-best solution, that show: calls don't open a scope
The problem already manifests itself without any show: though. Just a simple set text(font: ..) followed by a footnote vs text(font: ..)[all the content] raises the question of what the footnote font is in both cases.
If we want to set things up in the hierarchy from below in the hierarchy with clear semantics maybe clear selectors are a solution, like I guess discussed before:
set footnote.text(...)
set footnote > text(...)
set header > text(...)
Those would be okay for specifically styling footnotes. But what I want is more that footnotes are affected by all your global styles, like set text(font: ..), by default, without you having to think about it. Having to set the footnote font separately is not DRY and easy to miss.
I guess my take is that:
- it doesn't matter where the
footnotecall is (think of it asthrow), but the where the "footnote provider" (even if not explicit function/type) is (think of it as thecatch) - the footnote "provider" lives in the global scope, hence footnote style can only be edited in the global scope. so e.g.
set text(...)at the very beginning of the source file will affect footnotes. - but
set text(...)in any deeper scope won't affect footnotes show: my-templatedoesn't open a scope, but keeps the current (usually global) scope (at least when it comes to set rules)- if you STILL want to access the footnote provider from within the deep hierarchy, use some sort of new selector which provides access to the global scope
essentially a page selector could become the selector that provides access to the global scope
so set page > text(...) would set everything (footnotes, header, footer, etc.)
(or just calling set text(...) in the global scope)
or just set > text(...)!
(similar to how it's done for paths. writing some/little/path is relative to where you're currently, but starting with / like /some/little/path is from the root)
show rules appear to have some slightly odd behavior in determining fixed points. In particular, I have a show rule where I have
show [].func(): seq => {
chld = seq.children
// code which conditionally modifies children
if [].func()(chld) == seq {
seq
} else {
[].func()(chld)
}
}
```and this works fine.
However, if I replace that last if else statement with just `[].func()(chld)` it recurses infinitely. From this I am guessing that show rules look for fixpoints via some sort of referential/pointer comparison for "is literally the original object" rather than based on equality and I'm wondering why?
It seems odd and counterintuitive, given that referential equality seems otherwise entirely obfuscated (at least afaik) within the language
Is it purely a matter of performance? I would imagine non-equality should be obvious very fast in most cases and equality could check for referential equality first to make the most common case fast so that show rules could fixed point off structural equality with minimal impact
(You could also do even less performance impact when this is unused by only checking referential equality and if a given rule hits max depth, try again with structural equality, and if that succeeds, tag that as a "structural show rule" for future applications so that the penalty is only incurred at all for rules that use it, though this would have the disadvantage of potentially creating counterintuitive behavior in pathological cases (e.g., if you have structural equality after some number of iterations and then referential equality after *more* iterations, then if structural equality is supported you would expect it to stop there but it would actually continue until referential equality) and also of incurring a performance penalty when structural equality is used.)
there isn't really any equality checking happening in the compiler. rather seq before being passed to your show rule internally gets a marker that this show rule should not be applied to it again
Why not an equality check? It seems reasonable to stop application once application doesn't change the value?
I'm not quite sure what you are proposing exactly. What would be checked against what? Show rule application doesn't happen in a loop, but rather lazily on-demand (if you write show heading: it => block(it) that rule runs first and then block layout runs and then during block layout the default show runs). So we somehow need to transfer the information that it shouldn't be shown again with this rule through the whole thing.
Hmm, I'm not sure I fully understand how show rules work, but I'm talking about cases where the show rule output produces something which matches the show rule, as in my original post for example
It's probably hard to read in isolation, but this is the code where it happens in case you're interested: https://github.com/typst/typst/blob/e4f8e57c534db8a31d51e0342c46b913a7e22422/crates/typst-realize/src/lib.rs#L340
So, i had the thought of working on ancestry/nested selectors.
design wise (api design i guess) i'd want something like .contains, the exact word is probably trivial to change
dedicated syntax (like a > b or whatever) doesn't fit in with .after/.before
I agree
What I think is difficult from a design PoV is that you'd often also like to bind the parent to a variable
(in a show rule)
hm
(i'll experiment more and look at stuff after the exam-thing i'm currently sitting in)
I wonder if ancestry would also allow us to get neighboring content in shoe rules
i think that'd have to be something seperated
like, i'd very much like to do that too, but i think that's too "different"
to be in the same pr
okayyy
would .contains only check direct children or recursively? or would that be a flag or two different functions?
I think it should probably be recursive, though both might be interesting
but non-recursive would be a lot harder to pin down the exact semantics I think
The hardest thing with more specific selector mechanisms is definitely the semantics
There's also the issue of the structure and representation of content not really being stable, so expressing a thing in one version may just not have any use in another because the structure changed and does no longer match the selector
I was just fixing a small 0.13.0 bug and was reminded of the discussion about auto-resolving of contextual properties. In particular, I changed how text.hyphenate is resolved internally (it does not use #[resolve] anymore because it needs to resolve relative to the justification settings of a particular paragraph rather than generic par.justify). This is sort of similar with a tension between things being unstable internally and the pain of resolving all the auto values manually in Typst code.
i feel like a lot would be easier if there was a stable structure, but i guess that discussion can be deferred until there is
anyway, i'll try to build a shitty test impl the next few days :3
i got it working!!!
(source code for that:
For testing, lets search for equations in headings
$a$
= Some Equation: $b$
$c$
#context query(selector(heading).contains(math.equation)).first()
)
tho one thing i ran into wihle testing is that if you don't pay attention: .contains is also valid on a query, and means something different there
maybe the selector should be called something different, just so people don't get confused why their .contains returns a boolean?
i'll go write some documentation and stuff and will pr it later today then :3
(preferrably, before i go to sleep so there might be some reviews on it when i wake up)
Cool, how's the performance looking? I know that's a big concern for Laurenz when it comes to introspection
haven't tested it and am probably also gonna do the tests later because i am getting tired
idk if it'd be smarter to wait with the PR, but also i'd prefer opening it now so i can get feedback while asleep
For the naming, you could reverse the function by doing within and swapping the arguments of selector and that
You can open a draft PR
Doesn't have to pass yet
will do!
will just PR what i have so far, and that's then a thing for later
but yes .within makes sense :3
i do see a contains selector also being useful, but that's easier to hack around when this is implemented
My concern is that adding contain later like when you're building a selector programmatically completely reverses which primary element you're looking for
no, meant that can probably be done reasonably easily using within from inside a document
I'm not quite sure what that means or what that is answering
if you actually need to get all items containing another matching a selector, then that's probably relatively easy using within either way
Ok now I get it, I thought that the contains selector what select the math element
Wait it does
Ok what I mean is that the meaning of the argument of selector depends on the function later called on the returned object:
// selects foo
#selector(foo)
#selector(foo).after(<bar>)
// selects bar
#selector(foo).contains(bar)
If these calls are at different points in code then this becomes hard to reason about which is why I suggested the within variant
yes, within makes moe sense
that's also how it's called in the roadmap
i'm just not gonna rename it now because i'm getting tired and wanna go to sleep
i'll do that tomorrow
Gn :)
have to first go home, but thanks! :3
okay, i changed it into within
how do i generate sample output for tests? (also, is there anything specific i should write a test for/take into account?)
checkout te contribution markdown document
it has a section about tests
running with UPDATE=true will generate new references for tests
with the current implementation, duplicate results can happen if there's nested matching ancestors.
would deduplication in this case be desired?
can you show an example?
#figure[A #figure[#strong[a] #label("strong")]]
#context query(selector(<strong>).within(figure))
leads to this
that's correct since well, the ancestor is matched twice
but also i could see this be really annoying
It doesn't seem to be correct, you should verify that a show rule on that selector would only run once
For example, #show selector(<strong>).within(figure): it => [#it;a] should display only "a" instead of "aa"
p.s. I'm completely out of the loop, just responding to this message
unless i'm misunderstanding it can't be used as a show rule
did that out of caution because honestly i'm not sure about the implications of that, since before and after aren't showable either
well, that's ok, but im thinking more in terms of general semantics
if there were such a show rule it would end up matching twice
i think that should probs not happen
agreed
@flat socket Some thoughts:
-
The
Content::queryfunction shouldn't be used. It's more of a hack that was introduced at some point for figures and I made the concession to have it there. But it's significantly nerfed compared to the properIntrospector::queryas it will query in the raw content and thus can't see through show rules, context, or anything like that. Forwithinto work as expected, it would need to make use of theStartandEndtags to reason about nesting. -
Compared to
beforeandafter, thewithinselector is also one that's much easier to support in show rules, so that would be interesting as well. That's where the bindings thing and the design complexity comes into play (i.e. you might want to write a show rule for a caption in a figure and have as bound variables both the figure and the caption).
about the first: kinda was confused why it existed since i couldn't find any usages ^^'
i'll work on redoing that then, before that i don't think it'd be sensible to even approach the second point?
(approach the second point: working on it with code, sorry for bad wording)
yes, agreed, the first point is more critical
we should probably add some discouraging docs on Content::query
however, on a note of caution: the second point might influence the whole design space, so deferring it could lead to wasted work
in general, this is a very intricate part of the compiler. I appreciate you giving this a shot, but I want to make clear upfront that the road to a merge might be bumpy
honestly i figured.
i'd rather be surprised if it wasn't ^^'
dont rly think there is such a thing, even if the only gain is more experience with the codebase :3
(i am very much the type of person to rewrite something 20 times until im happy enough with it and then throw 90% out after all because i didnt like another unrelated aspect)
(sorry for not doing much work recently, i'm on holiday and also when i do work on it said "work" is currently just... reading code and trying to understand what approach to currently even take ^^')
No worries at all! I was just triaging the PR.
:3
Does it make sense to keep context in the future world where custom elements are possible and their show rule already provides context (i.e., context expressions can be replaced with custom elements)?
I think context is to a custom type like a closure is to a named function.
I wonder if we could have a way to apply built-in default styles individually, this would likely make a use case like this easier for th end user: https://github.com/tingerrr/hydra/issues/34
TLDR: they wrapped hydra in heading to get the same styling, but this inevitably caused it to create a heading in the location it starts searching from, which trips up its redundancy checks
I'm surprised that this doesn't cause a layout divergence
I'm not sure I understand what feature you want
The user wanted to apply heading.level:3 styling to their header, but this requires knowledge about thew default show rules and requires the user to update the those whenever typst changes its default styles
ideally the user could somehow do set styles(heading.where(level: 3)) (imaginary syntax) in the header to achieve the same thing
this is moreso about the defaults given by show: set
like the text size and weight
SO far the user would have to go inot the rust source to understand which text size and weight they'd have to apply, among other things
Basically https://github.com/typst/typst/issues/5574.
i suppose, but also without having to know which styles to get and re-apply
#set text(context heading > text.size)
the more i think about it I'm just asking for styles as values + the above issue + getting more than one style in total/providing default styles like those in the stdlib
I thought you mean you just wanna a dictionary with default values
#default-styles.heading.text.size
well this would probably suffice but it doesn't seem like the easiest thing to use for a novice
I mean, it would definitely be nice to have. But it is also very close to revoke rules, i.e., revoke all. Then you can access the default values. But only for set rules. And then you only need to resolve the above feature to also get styles in show-set rules.
while close it's distinctly different, since it's about getting and re applying rules instead of revoking them
but for now we'd first need styles as values in order to actually retrieve styles in a manner in which they can just be re-applied without any extra work
and then we'd either need to be able to collect styles from just s elector like heading.where(level: 3) or expose the defaults in the stdlib like heading.default-styles() where it returns styles as values in both cases
No, we will have set this(context that.thot) available, so just gotta wait
we already can have styles as values, but not everything
what
and only in the context
you state that as if it has been decided already, did i miss a memo
Yeah, I remember Laurenz said that it's just not possible yet
what is "styles as values"?
context text.size is a style and it's a value
I would like to have that, but am not yet certain how feasible it is
I also would like to have that.
Logically, it's sound I think, but there are certainly implementation challenges
So, it's still possible that some dealbreaker problem arises
The way things work right now, a concrete heading instance is required to resolve built-in styles
whereas heading.where(level: 3) would be a selector
So we would need to match selectors against selectors instead of selectors against elements
Which may be possible, but is confusing, so I'm not sure how it would work exactly
that's mostly because of the current implementation though is it? in theory since styles can be applied with selectors they should also be retrievable with them right?
perhaps yes
As there is no thread dedicated to templates, I think this the correct one for this matter.
A few weeks ago I posted this reply in the forum https://forum.typst.app/t/overriding-template-parameters-missing-social-convention-or-typst-design-flaw/2792/11 which I initially wanted to post here.
There I present a different approach for template packages that suggests a separation of styling and content in order to maintain adaptability of templates. I don't want to repeat the entire post here but for context, the main idea is that a template exposes two functions:
- One for setting up a new style default with
setandshowrules. - And a second one that is responsible for generating content. It should not use such rules directly.
This is instead of just a single function which is what (to my knowledge) all current template packages do.
A user will employ the template in the following way:
#import "template.typ"
#show: template.style // ← we agree on some good canonical names here
// here is a good place to tweak template defaults, even concerning the page setup
#show: template.content
This approach will now even become part of the rewritten package docs (see https://github.com/typst/packages/pull/2164). There is of course the increased burden on the user who has to write two show rules instead of one but I think the gained advantages are worth it in many cases.
Oh, there is actually another approach I can see for the future that I will discuss below.
Typst Forum
Hi everyone (thanks for pinging me @laurmaedje, I was too busy to make a post in the last two weeks)! I recently put a lot of thought into template design and think that the issue is three-fold: a) How can the user of a template tweak styles that are set or shown by the template? b) How can the user manipulate the content (like a title page) ...
I also plan to write a practical guide (when I find time) on how to go about with the style-content-separated scheme because there are some nice things one can do with it. Moreover, I've been working on a template for preprints which explores several methods to give natural and full control to the user.
I believe that it is important to do some proper research on how to best write templates since this knowledge will shape the landscape of how Typst documents are created and used. A big thing with LaTeX for example is the stiffness of document classes that are hard to tweak by (up to semi-experienced) users. Typst could bring a better user experience when working with templates − also in this regard!
Also this is something that I feel needs to be done rather soon than late to prevent bad habits becoming the norm.
All of this should be a group effort. I could for example publish my template as a research-project for testing (it still has open issues) with typst-community and then everyone can contribute to it and discuss options.
Interesting, I've actually went to great lengths to make my template configurable and I have a similar approach
All my styles (where manageable) are confined to their own template functions which are applied in the top level template
Interesting! Do you have a good example to check out?
One sec
Some styles are applied atthe top level in there, others only within their components directly
No way to hook into those yet with default args
Not sure if I want to go that far since its mostly meant as an escape hatch, at which point is it still the same template if a user can change anything about it
This is actually very similar. Not with show rules but with functions but it's totally the same idea, I'd say from first glance.
Now, the second possible solution works like this:
A template (hypothetically) defines a type, say project, which is used instead of a function to wrap the content of the document. We use it like this:
#import "template.typ": project
#show: project // or project.with(..)
The default show rule for this type works like the second function from above (template.content). It contains no rules itself but generates content and wraps the document.
Now we need a second ingredient: default show rules for user-defined types. Just like heading brings a default style (bold, a bit larger etc.) out-of-the-box, user-defined types could be able to do the same.
All the rules from the first function from above (template.style) are applied through this default show rule and thus they can be overriden by the user
#import "template.typ": project
#show project: set page(margin: 1cm)
#show: project
This is just with one (mandatory) show rule! This thought is still rather new and not yet thought out but seems promising.
Note that − in comparison to the other approach − one needs to write all overriding configuration in a show project: rule instead of just into the document. So in the end there's a downside to everything.
Yeah I mean that's essentially how it should be at some point
That's the question
fwiw we can probably experiment with this a bit using elembic
it has a default template thing like that for elements. where you can use show-set to override those set rules
will be nice once i can finish implementing custom show-set for completeness
but in general i think the main benefit of custom types wouldnt be that
but rather being able to style specific parts of the template, say, abstract, title page, stuff like that
without having to edit the source code
Yes
I think so too
It's all just special selectors for styling when you think about it
you won't believe what I'm about to tell you 😂
of course I've been using elembic excessively again to experiment :DD
for these things I used it at least
fair enough
im making a template which ill need to use very soon and so far i havent used it but it's been a bit of a pain haha
i think ill probably experiment with it as well soon
cuz its just too convenient to show and set without tons of parameters
Here's a math font size topic, I hope it's appropriate to ask here if it has been discussed as an issue style system changes can fix. The problem is basically this: show math.equation: set text(size: 10pt)
This is for a document where the math font is best matched to text font by reducing equation font size, this should be the standard way to configure it.
The problem is that now the math font size is unchanging regardless of equation context.
What we actually want is to have a "root math font size", like we do for regular text, and still inherit font size changes like text(size: 1.4em). For example: with this style, it breaks equation font size scaling in headings and all other contexts. It's of course a natural consequence of how show rules work.
(The workaround that seems best right now is show math.equation: set text(size: 10em/11) which configures with a ratio to the regular "root text size", this preserves the desired inheritance.)
coming from discussion channel
I think one way is to give set rule access to all of fields that can be content input, like figure.caption, but also figure.supplement, figure.numbering.number or sth similar.
That's something we considered, although it has the problem (which also exists today) of ambiguity between figure.caption to access the element and figure.caption to read the currently set caption property... though usually not a problem for caption as that's usually a per-figure thing, but could be a problem for other properties
Also not all of them directly translate into some concrete thing, they might be mixed together and stuff..
Although Typst has documented that things after the keyword show are selectors, I always look it as an element. The show rule give access to that element so that I can override it or modify it, like that of reading via context.
Another approach may be like context text.size.get() to read the value? Similar to states.
That would break a lot of packages
I always thought that namespacing and instance access both using . is a bad idea
Unfortunately that's how it was done though and I'd say it's a little late to change
nah, sounds very easy to have a deprecation period for it if necessary. question is what would be better
the syntax is quite slick, but i agree it might not lend itself too well to "static members" of the type's scope
elembic uses a function like get(element).property, though mostly by necessity haha
Other languages use :: and while I'm personally a fan I know that it's quite arcane to non-programmers
I like that it visually sticks out, so you know it's not the same thing
Can you show an example when the ambiguity arises?
I just do not come up with examples when both figure.caption happens to represent different things.
Selector version is only use as selector, while the other always come with context.
I don't think there is such a case yet
Is it query ? or show text from figure.caption ?
I think pg meant that adding more such sub elements makes this more likely
Maybe, I'm not sure actuslly
Ah may be something like:
#show: it => context {
show figure.caption: set text(..)
it
}
As we continue to have more subelement, using it in show rule with context does create the ambiguity.
Especially if that subelement is also an element function
I think the ambiguity would remain even with different tokens
Because it's about whether the expression is a a selector or the value of the get rule
At least with consistent usage of the static access token like in other languages I guess
Hmm, it is obvious that the element after the show keyword should be selectors. However, the ambiguity is that: whether the show rule apply to the subelement or apply to the accessed value of the subelement, imo
Like in the case of numbering:
#show figure: it => context {
show figure.numbering: set text(fill: red)
it
}
(hypothetical show rule)
If we allow figure.numbering to be an element, then this is ambiguous.
Say, the current numbering pattern for figure is "(1)", then the question is: whether the show rule should change the captions to have red text color or show the string "(1)" to be red.
Yeah I think that's the gist here
i suppose you might take content which isnt directly placed into the document, although that is rare
alternatively, it might be joined with some other content to make a "larger" part... and then you'd still want a way to target it regardless of the element's fields
but yes, the syntactical ambiguity in context is also a problem
continuing here
@peak raven for example, table can receive table.cell arguments. it uses the fields of table.cell , particularly their positions , stroke, colspan and rowspan, to place it.
The show rule only runs after the cell is placed, so the show rule cant change those arguments.
the stroke is because it is the table that draws stroke as it has to handle stroke between multiple cells, as well as interaction with rowspans, table headers and footers etc.
set rules, however, can change those arguments. set rules are considered before table layout starts.
So the reason is that show rules give access to the data that depends on interaction between elements, which only can be known when it is laid out. Am I right?
basically show rules are only used to change how an element is displayed.
not the element itself , with all of its implications.
in the past, set rules inside show rules would work in some cases due to implementation details, but ever since 0.11 introduced proper show-set rules, it was made more consistent , and generally that will no longer work
so , you can consider that interactions between elements, but also any code that just needs to read the element's fields in general, will not benefit from show rules
they are designed as a content => content function kind of thing
not content => (data, content)
(nevermind the fact that content is data, in a way, but you get my point... :p)
in other words, they have a very local effect
the context available, from the perspective of a single sub-element (say, a show rule on figure.caption instead of figure), is too limited...
it is kind of the same thing though in Typst. cell is a field on table (of type type) just like columns is a field on it (of type content, element table). what is different is get rules, but those don't really have a pendant in other languages. personally I do not particularly like :: and am quite glad we don't have it.
Ah of course, I didn';t think of it that way, I don't use dynamic languages often
I get that part about ::, I'm definitely not advocating for it here
The ambiguity with get and fields on elements is definitely a bit unfortunate
Though in practice barely a problem thus far I think
I could imagine adding some way to disambiguate down the road
Is it desirable to have this show-set rule as part of the standard library?
#show highlight: set text(overhang: false)
```This would improve the default look of highlighted text in justified paragraphs (because currently the end edge of the paragraph appears ragged due to the combination of overhang + highlight). But it's also a somewhat hidden rule that you may not know you should override if you need to disable it for some reason.
Do you mean set text(overhang: false)?
Might be sensible. Alternatively, the overhang could be ignored for highlighting purposes.
Yes, sorry
We should probably also disable overhang for non-par inline layout to fix https://github.com/typst/typst/issues/702
That could not be done with a show-set rule though, right?
One option would be for it to be ignored for non-pars in the code. The other would be for it to only be enabled via show-set for pars. The latter could maybe work via show-set.
In particular, since compiler built-in show set can also be conditional in code (e.g. only when justification is enabled) which is not possible in Typst code.
Doesn't this work?
#show par.where(justify: true): set text(overhang: true)
Probably does actually. I thought for a second that justify was on text.
Also related is https://github.com/typst/typst/issues/6090
Many default lengths in Typst are expressed in points rather than ems (cancel.stroke, table.inset, etc.). I tend to think almost all default lengths should be in ems instead. Am I missing something or would that be better? If so, I'll be willing to make a PR to change the ones I can find.
I think points are nice and simple. I haven't heard anybody complain about this tbh.
It would definitely make sense, since cancel should presumably emulate the width of actual font elements
One example where the current situation is bad is for slides, where the font size is usually increased by a lot. So for example tables might have very thin lines in comparison.
This is also an issue with some plotting packages and it used to be an issue with Curryst but we decided to express all lengths in terms of ems recently.
That makes sense. Perhaps it would make sense to start with making a list of all current absolute length defaults to get an overview?
I can do that in the upcoming days
Well, it actually wasn't that long to do manually. I may have missed some cases that are hidden behind auto. Overall, I am surprised that so few default lengths are in points.
As of Typst 0.14.0, according to the online documentation. Apart from text.size and page.{width,height} I see no reason not to switch to em lengths.
Point lengths
footnote.separatortable.{inset,stroke}{table,grid}.{hline,vline}.stroketext.size(duh)math.cancel.{length,stroke}page.{width,height}{cirle,ellipse}.inset{circle,curve,ellipse,polygon,regular,rect,square}.stroke(hidden behindauto)line.strokeline.length{rect,square}.insetstroke.thickness
Em lengths
This list does not include parameters defaulting to auto, which determine their values based on font metrics.
{list,enum}.body-indentterms.{separator,hanging-indent}figure.gapfootnote.{clearance,gap,indent}outline.entry.filloutline.entry.indented.gappar.{leading,spacing}math.cases.gapmath.mat.{row-gap,column-gap}math.vec.gapblock.spacingfloat.clearancepolygon.size
Null lengths
This include the length part of relative lengths.
{list,enum,terms}.indentpar.{justification-limits,first-line-indent,hanging-indent}highlight.extent{overline,strike,underline}.extenttext.{tracking,spacing,baseline}math.accent.sizemath.lr.sizemath.mat.gap(but AFAIK the default is never used)math.stretchbox.baselinecolumns.guttergrid.inset(documented as(:)btw, which is nor very informative)move.{dx,dy}pad.*page.{header-ascent,footer-descent}float.{dx,dy}repeat.gapcircle.radiusline.starttiling.spacing
Just a wild idea. It would useful to allow transforming functions in set rules to change the current value based on the previous one. As an example:
// A higher level set rule, potentially from a different file/scope/context
#set rect(fill: ..)
// Now whatever the color is, brighten it by 50%
#set rect(fill: clr => clr.brighten(50%))
Under the hood, the new fill will directly be resolved by applying this function to the former fill value. This is conceptionally very similar to parameter folding which is currently already applied to stroke and em values. Precisely, it is like secretly allowing values of type function in set rules and the folding rule for functions is that they are invoked on the previous value.
(I posted this in #1175895383600275516 but it rather belongs here I think)
What would it do with parameters that already take a function, like heading.supplement, list.marker, or the table/grid stuff? Though I know Laurenz said he didn't like that pattern generally so I'm not sure if that's going to be phased out in the future.
It would not be supported with parameters that take a function. For once there aren't many of those in the standard library and also "folding" a function seems like a very niche application to me and by far not as useful as for other types.
I think it would be useful but I would design it a bit differently. You can find it by searching for "could potentially also allow contextual values in other places" in https://laurmaedje.github.io/posts/types-and-context/
That sounds very good!
Regarding #discussions message:
This unfortunately fell a little short in my talk (maybe should have looked at my notes after all), but having these fragments well-documented and well-discoverable is an essential part of the idea. In fact, I would like to add a section with all the built-in show rules to the documentation (including how to write them in Typst even for those implemented in Rust) and this list would be searchable by selector. In addition, every element page would list all built-in show rules relevant to that element. That would also make existing built-in show-set styles discoverable. Autocomplete-wise, I think it would also be possible to suggest the appropriate fragments.
What I like about using ancestry instead of hard-coding fragments on elements is precisely that it decouples the two concerns. The different parts of the default visual representation are, in my opinion, not a concern of the heading itself, but rather of its default show rule (and would be documented as such). If I have a template with a custom headnig show rule, I might decide that my particular heading style would benefit from additional fragments, which users of my template could then also override, without the heading element itself being aware. Or maybe just one fragment of the heading would benefit from additional nested subfragments. In my vision, it's all very mix and match instead of being rigidly defined on the heading element.
I see, that would be great
would fragments be shared between elements, e.g., if I override a hypothetical number fragment without ancestry selectors could it affect multiple elements?
(bikeshed; 'component' could be a good name if you're not set on 'fragment' as it literally means that which is to be composed)
yes, they would be shared
I'm not necessarily set on fragment. Component could theoretically also work, but I would think of components as the larger more meaningful things (e.g. headings, figures) while fragments would be the generic parts they are composed of.
Depending on which concrete fragments we end up with and how many, it also doesn't necessarily have to be in a module (I know that some people at the meetup where put off by the fragment. part.)
This is something I still feel weird about. Apart maybe from highly generic things like inner, I don't like that a fragment can have a completely different meaning depending on where it comes from.
regardless of the name it would probably be helpful when fragments are added to prefix them with something in the documentation to differentiate them from elements. Something like 'document element' and 'style fragment/component'. Otherwise it might be confusing that although element/fragment/component are more or less synonyms they refer to different types of decomposition.
well, it wouldn't really be used in a way where it's a completely different meaning. rather, it would be used when it's the same meaning. why define three different scoped numbers elements when they all refer to the same concept?
I share Malo's concern, template authors are bound to have (slightly) different interpretations of the meaning/usage of a specific fragment which might respond differently to generic show rules. Or, if the set of provided fragment is too limited (are new fragments allowed?) authors might be tempted to abuse generic fragments like how <div> is often excessively overloaded
But perhaps that's just up to us then, to find the right granularity
Fragments would be just bare, fieldless elements, so yes, you could create your own
I don't think people could misuse a hypothetical numbering fragment for example
It's all a bit theoretical though without us coming up with a concrete list of fragments that the standard library would profit from.
One might develop a document with package A using generic fragment show rules and later find that their rules are incompatible with how they are interpreted by a package B they want to add
That's bound to happen already
I don't think that's inherently a problem of fragments
It just happens with package APIs that are developed independently
I'm sure we can solve this with time when preferred APIs crystalize
That is true but fragments would increase the amount show targets which independent packages are encouraged to interact with, so it might make the problem more common
That's true, but I don't think this can be avoided
After all custom elements are firmly planned and pose the same problem, so I think the solution is still to guide standardization using popular packages
perhaps there could be an selector for packages so that you can exclude misbehaving packages or target only specific packages
Maybe disallowing stuff like show fragment.inner: … could help (as in always requiring a parent for show rules on builtin fragments)
I think that would be counter to the intention of sharing fragments, as Laurenz expresses in the message above ^
I'm also curious, are fragments intended a step towards custom elements later to be replaced by them, or are they something separate to exist side by side with custom elements?
I think they won't replace them as elements are supposed to be built from them
If they would be replaced by custom elements the composable styling problem of builtin elements would not be solved either
Here is a wild idea / feature request.
Use case: you have an element and want to apply a content/style rule when multiple of those elements appear consecutively. Similar to how multiple cites are grouped together natively in Typst.
Inspired by the fact that we can fake this functionality today by abusing that list.items group together into lists, here's a very short write-up of generalising that to custom grouping behaviour: https://typst.app/project/Re7vSnJDCEAjE88eN8HjDn (review link)
However, I expect Typst devs to have completely different ideas about how grouping rules can be done in the style system. Has it been thought of before? I'm not sure if a good solution belongs to the style system or to custom user types.
(project/R — review, r — read, w — write)