Skip to content
This repository has been archived by the owner on Jul 5, 2024. It is now read-only.

Commit

Permalink
Add optional per-label number mode
Browse files Browse the repository at this point in the history
  • Loading branch information
EpicEricEE committed Apr 30, 2024
1 parent 1a8864e commit 498c9c5
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 29 deletions.
154 changes: 125 additions & 29 deletions equate/src/equate.typ
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
// Element function for alignment points.
#let align-point = $&$.body.func()

// Element function for a counter update.
#let counter-update = counter(math.equation).update(1).func()

// Sub-numbering state.
#let state = state("equate/sub-numbering", false)

// Extract lines and trim spaces.
#let to-lines(equation) = {
let lines = if equation.body.has("children") {
Expand Down Expand Up @@ -39,7 +42,15 @@
if number == none {
return math.equation(block: true, numbering: _ => none, line.join())
}


// Short circuit if number is a counter update.
if type(number) == content and number.func() == counter-update {
return {
number
math.equation(block: true, numbering: _ => none, line.join())
}
}

// Start of equation block.
let x-start = here().position().x

Expand Down Expand Up @@ -73,28 +84,55 @@

// Replace "fake labels" with a hidden figure that is labelled
// accordingly.
#let replace-labels(lines, sub-numbering, numbering, supplement) = {
#let replace-labels(
lines,
sub-numbering,
number-mode,
numbering,
supplement,
has-label
) = {
// Main equation number.
let main-number = counter(math.equation).get()


// Indices of numbered lines in this equation.
let numbered = if number-mode == "line" or has-label {
range(lines.len())
} else {
lines.enumerate()
.filter(((i, line)) => {
if line.len() == 0 { return false }
if line.last().func() != raw { return false }
if line.last().lang != "typc" { return false }
if line.last().text.match(regex("^<.+>$")) == none { return false }
return true
})
.map(((i, _)) => i)
}

lines.enumerate()
.map(((i, line)) => {
if line.len() == 0 { return line }

let last = line.last()
if last.func() != raw { return line }
if last.lang != "typc" { return line }
if last.text.match(regex("^<.+>$")) == none { return line }
if last.text.match(regex("^<.+>$")) == none { return line }

// Remove trailing spacing (before label).
if line.at(-2, default: none) == [ ] { line.remove(-2) }


// Append sub-numbering only if there are multiple numbered lines.
let nums = main-number + if numbered.len() > 1 {
(numbered.position(n => n == i) + 1,)
}

// We use a figure with kind "equation" to make the sub-equation
// referenceable with the correct supplement. The numbering is stored
// in the figure body as metadata, as a counter would only show a
// single number.
line.at(-1) = [#figure(
metadata(main-number + if lines.len() > 1 { (i + 1,) }),
metadata(nums),
kind: math.equation,
numbering: numbering,
supplement: supplement
Expand All @@ -114,12 +152,12 @@
if lines.all(line => align-point() not in line) {
return lines
}

// Store widths of each part between alignment points.
let part-widths = lines.map(line => line
.split(align-point())
.map(part => measure(equation(part.join())).width))

// Get maximum width of each part.
let part-widths = for i in range(calc.max(..part-widths.map(points => points.len()))) {
(calc.max(..part-widths.map(line => line.at(i, default: 0pt))), )
Expand All @@ -128,7 +166,7 @@
// Add spacers for each part, so that the part widths are the same for all lines.
let lines = lines.map(line => {
let parts = line.split(align-point())

let spaced(i, spacing) = {
let spacing = if spacing > 0ptbox(width: spacing) }
if calc.even(i) {
Expand All @@ -139,7 +177,7 @@
parts.at(i).join() + spacing
}
}

parts.enumerate()
.map(((i, part)) => {
// Add spacer to make part the correct width.
Expand Down Expand Up @@ -187,11 +225,32 @@
}
line
})

lines
}

#let equate(sub-numbering: false, debug: false, body) = {
// Applies show rules to the given body, so that block equations can span over
// page boundaries while retaining alignment. The equation number is stepped
// and displayed at every line, optionally with sub-numbering.
//
// Parameters:
// - sub-numbering: Whether to add sub-numbering to the equation.
// - number-mode: Whether to number all lines or only lines containing a label.
// Must be either "line" or "label".
// - debug: Whether to show alignment spacers for debugging.
//
// Returns: The body with the applied show rules.
#let equate(
sub-numbering: false,
number-mode: "line",
debug: false,
body
) = {
assert(
number-mode in ("line", "label"),
message: "Invalid number mode. Must be either 'line' or 'label'."
)

show math.equation.where(block: true): it => {
// Allow a way to make default equations.
if it.has("label") and it.label == <equate:revoke> {
Expand All @@ -204,7 +263,7 @@
stroke: 0.4pt,
fill: yellow
) if debug

// Main equation number.
let main-number = counter(math.equation).get().first()

Expand All @@ -217,7 +276,7 @@
} else {
text.dir
}

// Resolve number position in x-direction.
let number-align = if it.number-align.x in (left, right) {
it.number-align.x
Expand All @@ -226,22 +285,51 @@
} else if text-dir == rtl {
if it.number-align.x == start { right } else { left }
}

let lines = replace-labels(to-lines(it), sub-numbering, it.numbering, it.supplement)
let equation = math.equation.with(block: it.block, numbering: none)

let lines = replace-labels(
to-lines(it),
sub-numbering,
number-mode,
it.numbering,
it.supplement,
it.has("label")
)

// Indices of numbered lines in this equation.
let numbered = if number-mode == "line" or it.has("label") {
range(lines.len())
} else {
// Find lines that have a replaced label.
lines.enumerate()
.filter(((i, line)) => {
if line.len() == 0 { return false }
if line.last().func() != figure { return false }
if line.last().body == none { return false }
if line.last().body.func() != metadata { return false }
return true
})
.map(((i, _)) => i)
}

// Short-circuit for single-line equations.
if lines.len() == 1 {
if it.numbering == none { return it }
if numbering(it.numbering, 1) == none { return it }

let number = if numbered.len() > 0 {
numbering(it.numbering, main-number)
} else {
// Step back counter as this equation should not be counted.
counter(math.equation).update(n => n - 1)
}

return {
// Update state to allow correct referencing.
state.update(_ => sub-numbering)

layout-line(
lines.first(),
number: numbering(it.numbering, main-number),
number: number,
number-align: number-align
)

Expand All @@ -253,8 +341,12 @@

// Calculate maximum width of all numberings in this equation.
let max-number-width = if it.numbering == none { 0pt } else {
calc.max(..range(lines.len()).map(i => {
let nums = if sub-numbering {(main-number, i + 1)} else {(main-number + i,)}
calc.max(0pt, ..range(numbered.len()).map(i => {
let nums = if sub-numbering and numbered.len() > 1 {
(main-number, i + 1)}
else {
(main-number + i,)
}
measure(numbering(it.numbering, ..nums)).width
}))
}
Expand All @@ -267,12 +359,16 @@
columns: 1,
row-gutter: par.leading,
..realign(lines).enumerate().map(((i, line)) => {
let sub-number = numbered.position(n => n == i)
let number = if it.numbering == none {
none
} else if sub-numbering {
numbering(it.numbering, main-number, i + 1)
} else if sub-number == none {
// Step back counter as this equation should not be counted.
counter(math.equation).update(n => n - 1)
} else if sub-numbering and numbered.len() > 1 {
numbering(it.numbering, main-number, sub-number + 1)
} else {
numbering(it.numbering, main-number + i)
numbering(it.numbering, main-number + sub-number)
}

layout-line(
Expand All @@ -295,7 +391,7 @@
// always introduced an additional numbered equation that
// stepped the counter.
counter(math.equation).update(n => {
n - if sub-numbering { lines.len() } else { 1 }
n - if sub-numbering and numbered.len() > 1 { numbered.len() } else { 1 }
})
}
}
Expand Down Expand Up @@ -327,15 +423,15 @@
},
..nums
)

let supplement = if it.supplement == auto {
it.element.supplement
} else {
it.supplement
}

link(it.element.location(), if supplement not in ([], none) [#supplement~#num] else [#num])
}

body
}
Binary file added equate/tests/number-mode/ref/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 46 additions & 0 deletions equate/tests/number-mode/test.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#import "../../src/lib.typ": equate

#set page(width: 6cm, height: auto, margin: 1em)
#show: equate.with(number-mode: "label")

// Test correct counter incrementation with number-mode "label".

#set math.equation(numbering: "(1.1)")

$ a + b #<label> $

$ c + d \
e + f #<label> \
g + h \
i + j #<label> $

$ k + l $

#set math.equation(numbering: "(1a)")

$ m + n \
o + p $ <label>

$ q + r \
s + t $ <equate:revoke>

#show: equate.with(sub-numbering: true, number-mode: "label")

#set math.equation(numbering: "(1.1)")

$ a + b $ <label>

$ c + d \
e + f #<label> \
g + h \
i + j #<label> $

$ k + l $

#set math.equation(numbering: "(1a)")

$ m + n \
o + p $ <label>

$ q + r \
s + t $ <equate:revoke>

0 comments on commit 498c9c5

Please sign in to comment.