| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- // ==========================================
- // PRESENTATION: ССЫЛКИ И БИБЛИОГРАФИИ
- // ==========================================
- #let ensure-label(lbl, scope) = {
- if type(lbl) != label {
- panic("Scientia / " + scope + ": ожидался label")
- }
- lbl
- }
- // В тексте чаще всего нужен предложный падеж, поэтому он используется без
- // второго аргумента. Для явного выбора достаточно одной буквы:
- // #vref(<figure-label>, "и") или #vref(<figure-label>, "р").
- // Прежние сокращения и полные названия сохранены как понятные альтернативы.
- #let case-aliases = (
- и: "имен",
- им: "имен",
- имен: "имен",
- именительный: "имен",
- р: "род",
- род: "род",
- родительный: "род",
- д: "дат",
- дат: "дат",
- дательный: "дат",
- в: "вин",
- вин: "вин",
- винительный: "вин",
- т: "тв",
- тв: "тв",
- творительный: "тв",
- п: "предл",
- пр: "предл",
- предл: "предл",
- предложный: "предл",
- )
- #let ensure-case(value) = {
- let normalized = if type(value) == str {
- case-aliases.at(value, default: none)
- } else {
- none
- }
- if normalized == none {
- panic(
- "Scientia / reference: неизвестный падеж " + repr(value)
- + "; используйте и, р, д, в, т или п",
- )
- }
- normalized
- }
- #let resolve-ref-element(lbl) = {
- let lbl = ensure-label(lbl, "reference")
- let elems = query(selector(lbl))
- if elems == () { none } else { elems.first() }
- }
- #let classify-ref-element(el) = {
- if el.func() == figure {
- if el.kind == table { "table" } else { "image" }
- } else if el.func() == math.equation {
- "equation"
- } else if el.func() == heading {
- "section"
- } else {
- "other"
- }
- }
- #let noun-forms = (
- image: (
- singular: (имен: "рисунок", род: "рисунка", дат: "рисунку", вин: "рисунок", тв: "рисунком", предл: "рисунке"),
- plural: (имен: "рисунки", род: "рисунков", дат: "рисункам", вин: "рисунки", тв: "рисунками", предл: "рисунках"),
- ),
- table: (
- singular: (имен: "таблица", род: "таблицы", дат: "таблице", вин: "таблицу", тв: "таблицей", предл: "таблице"),
- plural: (имен: "таблицы", род: "таблиц", дат: "таблицам", вин: "таблицы", тв: "таблицами", предл: "таблицах"),
- ),
- equation: (
- singular: (имен: "формула", род: "формулы", дат: "формуле", вин: "формулу", тв: "формулой", предл: "формуле"),
- plural: (имен: "формулы", род: "формул", дат: "формулам", вин: "формулы", тв: "формулами", предл: "формулах"),
- ),
- section: (
- singular: (имен: "раздел", род: "раздела", дат: "разделу", вин: "раздел", тв: "разделом", предл: "разделе"),
- plural: (имен: "разделы", род: "разделов", дат: "разделам", вин: "разделы", тв: "разделами", предл: "разделах"),
- ),
- other: (
- singular: (имен: "элемент", род: "элемента", дат: "элементу", вин: "элемент", тв: "элементом", предл: "элементе"),
- plural: (имен: "элементы", род: "элементов", дат: "элементам", вин: "элементы", тв: "элементами", предл: "элементах"),
- ),
- )
- #let noun-for-case(kind, grammatical-case, plural: false, capitalized: false) = {
- let grammatical-case = ensure-case(grammatical-case)
- let forms = noun-forms.at(kind, default: noun-forms.other)
- let number-forms = if plural { forms.plural } else { forms.singular }
- let noun = number-forms.at(grammatical-case)
- if capitalized {
- let characters = noun.clusters()
- upper(characters.first()) + characters.slice(1).join()
- } else {
- noun
- }
- }
- #let render-resolved-ref(lbl, grammatical-case, capitalized: false) = {
- let el = resolve-ref-element(lbl)
- if el == none {
- text(fill: red)[Ссылка не найдена]
- } else {
- let kind = classify-ref-element(el)
- let prefix = noun-for-case(kind, grammatical-case, capitalized: capitalized)
- if kind == "equation" {
- [#prefix #ref(lbl, supplement: none)]
- } else {
- ref(lbl, supplement: prefix)
- }
- }
- }
- #let join-reference-items(items, conjunction) = {
- if items.len() == 1 {
- items.first()
- } else if items.len() == 2 {
- [#items.first() #conjunction #items.last()]
- } else {
- for (index, item) in items.enumerate() {
- if index > 0 {
- if index == items.len() - 1 { [ #conjunction ] } else { [, ] }
- }
- item
- }
- }
- }
- #let parse-reference-options(options, scope, allow-conjunction: false) = {
- let positional = options.pos()
- let named = options.named()
- let allowed = if allow-conjunction {
- ("grammatical-case", "capitalized", "conjunction")
- } else {
- ("grammatical-case", "capitalized")
- }
- for key in named.keys() {
- if key not in allowed {
- panic("Scientia / " + scope + ": неизвестный параметр " + repr(key))
- }
- }
- if positional.len() > 1 {
- panic("Scientia / " + scope + ": после label допустим только один позиционный падеж")
- }
- if positional.len() == 1 and "grammatical-case" in named {
- panic("Scientia / " + scope + ": падеж задан одновременно позиционно и по имени")
- }
- let grammatical-case = if positional.len() == 1 {
- positional.first()
- } else {
- named.at("grammatical-case", default: "п")
- }
- let capitalized = named.at("capitalized", default: true)
- if type(capitalized) != bool {
- panic("Scientia / " + scope + ".capitalized: ожидалось bool")
- }
- (
- grammatical-case: ensure-case(grammatical-case),
- capitalized: capitalized,
- )
- }
- #let vref(lbl, ..options) = context {
- let parsed = parse-reference-options(options, "vref")
- render-resolved-ref(
- lbl,
- parsed.grammatical-case,
- capitalized: parsed.capitalized,
- )
- }
- #let vrefs(labels, ..options) = context {
- if type(labels) != array or labels.len() == 0 {
- panic("Scientia / vrefs: нужен непустой array labels")
- }
- let parsed = parse-reference-options(options, "vrefs", allow-conjunction: true)
- let grammatical-case = parsed.grammatical-case
- let capitalized = parsed.capitalized
- let conjunction = options.named().at("conjunction", default: [и])
- for item in labels { let _ = ensure-label(item, "vrefs") }
- let resolved = labels.map(resolve-ref-element)
- let groupable = resolved.all(el => el != none) and resolved.len() > 1
- if groupable {
- let kind = classify-ref-element(resolved.first())
- let same-kind = resolved.all(el => classify-ref-element(el) == kind)
- if same-kind {
- let prefix = noun-for-case(
- kind,
- grammatical-case,
- plural: true,
- capitalized: capitalized,
- )
- let rendered = labels.map(lbl => ref(lbl, supplement: none))
- [#prefix #join-reference-items(rendered, conjunction)]
- } else {
- let rendered = labels.map(lbl => render-resolved-ref(lbl, grammatical-case, capitalized: capitalized))
- join-reference-items(rendered, conjunction)
- }
- } else {
- let rendered = labels.map(lbl => render-resolved-ref(lbl, grammatical-case, capitalized: capitalized))
- join-reference-items(rendered, conjunction)
- }
- }
- #let eqref(lbl) = context {
- let el = resolve-ref-element(lbl)
- if el == none {
- text(fill: red)[Ссылка не найдена]
- } else if classify-ref-element(el) != "equation" {
- text(fill: red)[Метка не является формулой]
- } else {
- ref(lbl, supplement: none)
- }
- }
- #let render-bibliographies(sections) = {
- // Typst 0.15: у каждой bibliography собственный heading. Смещение уровня
- // отделяет заголовок библиографии от заголовков отчёта и устраняет конфликт
- // нумерации/outline при нескольких независимых списках.
- show bibliography: set heading(offset: 1)
- for (index, section) in sections.enumerate() {
- if index > 0 and section.at("page-break", default: true) {
- pagebreak()
- }
- bibliography(
- section.sources,
- title: section.title,
- full: section.full,
- style: section.style,
- target: section.target,
- group: section.group,
- )
- }
- }
|