diff --git a/src/figures.typ b/src/figures.typ index d6a01ef..7367992 100644 --- a/src/figures.typ +++ b/src/figures.typ @@ -7,7 +7,9 @@ /// Localizations for the recognized referenceable figure kinds, keyed by `repr(func)`. /// - supplement: used in captions (the caption adds the separator, e.g. "1 pav."). -/// - ref-name: used in references, e.g. "(žr. 1 pav.)". +/// - ref-name: used in references, e.g. "(žr. 1 pav.)". Either a string or a +/// function `count => string`, called with the number of elements mentioned +/// together, e.g. "(žr. 1 priedą)" vs "(žr. 1, 2 priedus)". /// - single-name: full word used when the figure is the only one of its kind and /// therefore unnumbered, e.g. "Paveikslas. ..." and "(žr. paveikslas)". #let __figure-kinds = ( @@ -15,6 +17,12 @@ image: (supplement: "pav", ref-name: "pav.", single-name: "paveikslas"), raw: (supplement: "kodo frag", ref-name: "kodo frag.", single-name: "kodo fragmentas"), equation: (supplement: "lygtis", ref-name: "formulė", single-name: "lygtis"), + // Appendices are headings, not figures, but they use the same referencing + appendix: ( + supplement: "priedas", + ref-name: count => if count == 1 { "priedą" } else { "priedus" }, + single-name: "priedas", + ), ) /// Figure kinds registered by the document author via `ktu-paper(figureKinds: ...)`. @@ -160,10 +168,32 @@ } /// Appendix headings carry an "appendix-heading" metadata marker as their -/// first child and are numbered differently from normal headings. +/// first child (or as their whole body, when the appendix has no title) and +/// are numbered separately from normal headings. #let __is-appendix-heading(item) = { let body = item.body - body.has("children") and body.children.at(0, default: none) == metadata("appendix-heading") + if body.has("children") { + body.children.at(0, default: none) == metadata("appendix-heading") + } else { + body == metadata("appendix-heading") + } +} + +/// Returns the title of an appendix heading element (everything after the +/// marker), or `none` if the appendix has no title. +#let __appendix-title(item) = { + let body = item.body + if not body.has("children") { return none } + let rest = body.children.slice(1) + if rest.len() == 0 { none } else { rest.join() } +} + +/// Returns the number (int) of an appendix heading element: its position among +/// all appendices, in document order. Must be called in a context. +#let __appendix-number(item) = { + query(selector(heading).before(item.location(), inclusive: true)) + .filter(h => __is-appendix-heading(h)) + .len() } /// Parses a dotted number such as "12" or "1.12.5" into an array of ints. @@ -245,20 +275,24 @@ let number = if kind == figure { if __is-single-unnumbered(item) { panic( - "ref-no: " + str(target) + " is the only " + __figure-key(item) + "ref-no: " + + str(target) + + " is the only " + + __figure-key(item) + " in the document and is therefore unnumbered; " - + "refer to it with @" + str(target) + " (or custom-ref) instead", + + "refer to it with @" + + str(target) + + " (or custom-ref) instead", ) } __get-figure-number(item, loc) } else if kind == math.equation { "(" + __get-equation-number(loc) + ")" } else if kind == heading { - let numbers = counter(heading).at(loc) if __is-appendix-heading(item) { - str(numbers.at(1)) + str(__appendix-number(item)) } else { - numbers.map(str).join(".") + counter(heading).at(loc).map(str).join(".") } } else { panic("referencing unknown type: " + repr(kind)) @@ -270,7 +304,8 @@ /// Create a custom reference for multiple targets of the same type. /// - targets (label...): Labels to create a custom reference for. #let custom-ref(..targets) = context { - let group-name = none + let group-key = none + let group-name = none // a string, or a function of the number of mentions let single-name = none // set when the target is a lone, unnumbered figure let entries = () // (location, number) pairs @@ -278,8 +313,9 @@ let item = query(target).first() let loc = item.location() - let (name, number) = if item.func() == math.equation { - (__figure-kinds.equation.ref-name, __get-equation-number(loc)) + // (kind key used for grouping, ref-name spec, number) + let (key, name, number) = if item.func() == math.equation { + ("equation", __figure-kinds.equation.ref-name, __get-equation-number(loc)) } else if item.func() == figure { let kind = __figure-key(item) let kinds = __all-figure-kinds() @@ -287,14 +323,17 @@ panic("unregistered figure kind `" + kind + "`: register it with ktu-paper(figureKinds: ...)") } if __is-single-unnumbered(item) { single-name = kinds.at(kind).single-name } - (kinds.at(kind).ref-name, __get-figure-number(item, loc)) + (kind, kinds.at(kind).ref-name, __get-figure-number(item, loc)) + } else if item.func() == heading and __is-appendix-heading(item) { + ("appendix", __figure-kinds.appendix.ref-name, str(__appendix-number(item))) } else { panic("referencing unknown type: " + repr(item.func())) } - if group-name != none and group-name != name { + if group-key != none and group-key != key { panic("custom-ref does not support grouping different elements together") } + group-key = key group-name = name entries.push((loc, number)) } @@ -305,7 +344,8 @@ [(žr. #link(entries.first().at(0), single-name))] } else { let links = __sort-entries(entries).map(e => link(e.at(0), e.at(1))) - [(žr. #links.join(", ") #group-name)] + let name = if type(group-name) == function { group-name(entries.len()) } else { group-name } + [(žr. #links.join(", ") #name)] } } } @@ -323,6 +363,8 @@ link(loc, "(" + __get-equation-number(loc) + ")") } else if el.func() == figure and __figure-key(el) in __all-figure-kinds() { custom-ref(it.target) + } else if el.func() == heading and __is-appendix-heading(el) { + custom-ref(it.target) } else { it } diff --git a/src/lib.typ b/src/lib.typ index 26f78ca..3b90765 100644 --- a/src/lib.typ +++ b/src/lib.typ @@ -20,6 +20,11 @@ __format-ref, __format-figure, + __figure-kinds, + + __is-appendix-heading, + __appendix-title, + __appendix-number, ) /** @@ -53,11 +58,30 @@ heading(level: level, numbering: none, body) } -#let appendix-item(body) = { +/// Insert an appendix (priedas). +/// +/// Appendices are numbered 1, 2, 3, ... in the order they appear in the +/// document, so place them in the order they are first mentioned in the text. +/// Each appendix should start on a new page: put a `#pagebreak()` before it. +/// "N priedas" is written in the top right +/// corner. The optional title is written below it like a chapter title (bold), +/// exactly as given: start it with a capital letter, the rest in lowercase. +/// Appendices are included in the table of contents. +/// +/// Give it a label to refer to it from the text: `#appendix-item[Pavadinimas] ` +/// and then `@pr-konfig` or `#custom-ref()` gives "(žr. 1 priedą)". +/// - title (content | none): Optional appendix title. +/// -> heading +#let appendix-item(..title) = { + assert( + title.named().len() == 0 and title.pos().len() <= 1, + message: "appendix-item takes at most one argument: the appendix title", + ) + let title = title.pos().at(0, default: none) heading( - metadata("appendix-heading") + "priedas. " + body, + if title == none { metadata("appendix-heading") } else { metadata("appendix-heading") + title }, level: 2, - numbering: (..any) => {str(any.at(1))} + numbering: none, ) } @@ -116,18 +140,25 @@ show outline.entry: it => { // Recompute the gap based on the prefix content, so that the outline entry is indented // from the left regardless of the length of the prefix (based on the requirement) - let prefixContent = it.prefix() - let measurement = measure(prefixContent) - let target = targets.at(it.level - 1, default: 0.64cm) - let newGap = target - measurement.width - if it.element.body.has("children") and it.element.body.children.at(0) == metadata("appendix-heading") { - target = 0.64cm + let isAppendix = __is-appendix-heading(it.element) + + // Appendices are listed as "N priedas. Pavadinimas", the number being the prefix + let prefixContent = if isAppendix { + context [#__appendix-number(it.element)] + } else { + it.prefix() } + let measurement = measure(prefixContent) + let target = if isAppendix { 0.64cm } else { targets.at(it.level - 1, default: 0.64cm) } let newGap = target - measurement.width // Uppercase body if need be let bodyContent = it.element.body - if isUpper and (it.level == 1 or it.element.numbering == none) { + if isAppendix { + let title = __appendix-title(it.element) + let name = __figure-kinds.appendix.supplement + bodyContent = if title == none { [#name] } else { [#name. #title] } + } else if isUpper and (it.level == 1 or it.element.numbering == none) { bodyContent = upper(bodyContent) } @@ -359,6 +390,19 @@ // TODO: intervalas tarp eilučių – 1,15 kiekvienai antraštei + // Priedas: kiekvienas pradedamas naujame lape, dešiniajame viršutiniame + // kampe rašoma "N priedas", pavadinimas (jei yra) – kaip skyriaus pavadinimas + if __is-appendix-heading(it) { + align(right, block(below: 10pt, text(weight: "regular")[ + #__appendix-number(it) #__figure-kinds.appendix.supplement + ])) + let title = __appendix-title(it) + if title != none { + block(below: 10pt, title) + } + return + } + // Antraštė be nr. if it.numbering == none { // Centruota lygiuotė by default, but can be overridden by the user @@ -397,14 +441,8 @@ } } - // Determine whether the heading has appending metadata attached and adjust its display accordingly - let isAppendixHeading = it.body.has("children") and it.body.children.at(0) == metadata("appendix-heading") - if isAppendixHeading { - block[#counter(heading).display(it.numbering) #it.body] - } - // Poskyris ir skyrelis - if not isAppendixHeading and it.numbering != none and it.level != 1 { + if it.numbering != none and it.level != 1 { // abipusė lygiuotė set par(justify: true) @@ -446,6 +484,9 @@ /// Use them with `figure(kind: "command", ...)`. Keys: /// - `supplement` (required): text after the number in captions, e.g. "1 komanda.". /// - `ref-name`: text used in references, e.g. "(žr. 1 komanda)". Defaults to `supplement`. +/// Can also be a function `count => string`, called with the number of elements +/// referenced together, for kinds whose wording depends on it, e.g. +/// `count => if count == 1 { "komandą" } else { "komandas" }`. /// - `single-name`: full word used when the figure is the only one of its kind /// and is left unnumbered, e.g. "Komanda." and "(žr. komanda)". Defaults to `supplement`. /// Currently only tables and images are left unnumbered when single, so this only matters for kinds added there.