I would like to automatically append a period to every heading in this example document, except when the content already ends with a conflicting sentence-terminal punctuation mark ([.?!:]). My current code with help from posters on How to control whitespace before and after run-in headings works only when the heading contains no math, markup, etc. or even a colon:
#set par(justify: true, first-line-indent: 1em, spacing: 0.6em)
#let body-size = 11pt
#set heading(numbering: "1.1.")
#show heading.where(level: 1): set align(center)
#show heading.where(level: 1): set text(size: 16pt, weight: "bold")
// #show heading.where(level: 1): smallcaps // Does not work here.
#show heading.where(level: 2): set text(size: body-size)
#show heading.where(level: 3): set text(size: body-size, weight: "regular")
// #show heading.where(level: 3): emph // Does not work here.
#show selector.or(..(1, 2, 3).map(n => heading.where(level: n))): it => {
counter(heading).display()
sym.space
it.body
// let message = "Have to convert to string with t4t package after all"
// assert("text" in it.body.fields(), message: message)
if it.body.text.last() not in ".?!" [.]
}
// Need to be after selector.or:
#show heading.where(level: 1): smallcaps
#show heading.where(level: 3): emph
// Why does emph have no effect if before selector.or (likewise smallcaps for H1), but boldface and size change are effective?
= Introduction
#lorem(10)
== Motivation?
Looks good with question mark? yes.
=== Background, etc.
Looks good with period.
=== Hello!
Looks good with exclamation mark!
== Failures
// === Colon: bad
Colon is bad.
// === $x=y$ math
Math is mad.
// === Em dash---bad?
Dash --- bad, as RU speakers say.
// === En dash is --
En dash is bad.
// === What #emph[else] is bad?
It appears that all that is not strictly plain text without markup significance is bad.
// All give: error: sequence does not have field "text"
// from 17 │ if it.body.text.last() not in ".?!" [.] // Did not use ".?!:" because colons cause failure.
// Presumably solution is string conversion with t4t, which is beyond this newcomer's skill.
Why does emph have no effect if before selector.or (likewise smallcaps for H1), but boldface and size change are effective?
As I already said in How to control whitespace before and after run-in headings - #7 by Andrew, constructive show rules must be defined after destructive show rules. Otherwise, you destroy one show rule with another. More specifically, if you don’t just lay out it, then it’s a destructive show rule.
Presumably solution is string conversion with t4t, which is beyond this newcomer’s skill.
How is using a package is beyond newcomer’s skill? If you read the documentation, it should be easy. The t4t package also has docs. Though it’s notperfect, at least right now.
#import "@preview/t4t:0.4.3": get
#let body-size = 11pt
#set par(justify: true, first-line-indent: 1em, spacing: 0.6em)
#set heading(numbering: "1.1.")
#show selector.or(..(2, 3).map(n => heading.where(level: n))): it => {
counter(heading).display()
sym.space
it.body
if get.text(it).last() not in ".?!:" [.]
}
#show heading.where(level: 1): set align(center)
#show heading.where(level: 1): set text(size: 16pt, weight: "bold")
#show heading.where(level: 1): smallcaps
#show heading.where(level: 2): set text(size: body-size)
#show heading.where(level: 3): set text(size: body-size, weight: "regular")
#show heading.where(level: 3): emph
= Introduction
#lorem(10)
== Motivation?
Looks good with question mark? yes.
=== Background, etc.
Looks good with period.
=== Hello!
Looks good with exclamation mark!
== Failures
=== Colon: bad
Colon is bad.
=== $x=y$ math
Math is mad.
=== Em dash---bad?
Dash --- bad, as RU speakers say (indeed, kinda).
=== En dash is --
En dash is bad.
=== What #emph[else] is bad?
It appears that all that is not strictly plain text without markup significance is bad.
To add to this, destructive show rules should generally be avoided as it makes proper layouting and accessibility features that much more difficult to get right.
Edit: this is bad, see below
One way to turn the current show rule into a [more] constructive one would be something like this, but do note that it will look different from the current destructive implementation:
#show selector.or(..(1, 2, 3).map(n => heading.where(level: n))): it => {
let title = get.text(it.body)
if title.last() in ".?!" {
return it
}
let (body, ..rest) = it.fields()
heading(..rest, body + ".")
}
While the message is helpful, the show rule is bad. It doesn’t just look different, it looks nothing like the desired output and introduces several bugs: it converts headings back to block-level containers, it duplicates them, which results in headings having wrong numberings and the PDF’s Document output also have all the duplicate headings (well, and the standard hackyness of being easy to trigger an infinite show rule recursion without a good base condition). Hence, worse layouting and accessibility.
You’re right, I forgot about duplication. I guess currently there isn’t a way to get rid of this problem (even with a hacky solution, I tried something similar to this but to no avail), but i’d be happy to be proven wrong.
Yeah, a to somewhat robustly get rid of this is by explicitly adding/checking for a label, but I figured the solution above was nice enough without this
I don’t understand this, you’ve created the problem which is isolated in your example. If the example is disregarded, there is no problem. And it’s not the point of the topic. The existing solution(s) work as expected. But I guess a better way of converting block to inline would be even better.