WIP!: break up lib.typ

This commit is contained in:
2026-01-17 15:45:36 +02:00
parent 549d7f060f
commit 3cde131ed6
6 changed files with 438 additions and 314 deletions
+15 -314
View File
@@ -3,273 +3,8 @@
#let universities = yaml("config/universities.yaml") #let universities = yaml("config/universities.yaml")
// Template formatting functions {{{1 #import "/src/helpers.typ": *
#import "/src/style.typ": *
/// bold text
#let bold(content) = text(weight: "bold")[#content]
/// numberless heading
#let nheading(title) = heading(depth: 1, numbering: none, title)
/// fill horizontal space with a box and not an empty space
#let hfill(width) = box(
width: width,
repeat(""),
) // NOTE: This is a HAIR SPACE (U+200A), not a regular space
/// make underlined cell with filled value
#let uline(align: center, content) = underline[
#if align != left { hfill(1fr) }
#content
#if align != right { hfill(1fr) }
]
/// month name from its number
#let month_gen(month) = (
"січня",
"лютого",
"березня",
"квітня",
"травня",
"червня",
"липня",
"серпня",
"вересня",
"жовтня",
"листопада",
"грудня",
).at(month - 1)
#let is-cyr(c) = regex("[\p{Cyrillic}]") in c
#let gender-form(verb, gender: "p") = {
(
"author": ("m": "Виконав", "f": "Виконала", "p": "Виконали"),
"mentor": ("m": "Перевірив", "f": "Перевірила", "p": "Перевірили"),
)
.at(verb)
.at(if gender == "m" or gender == "f" { gender } else { "p" }, default: "p")
}
#let pz-lb-title(type, number: none) = {
let type-title = (
"ЛБ": [Звіт \ з лабораторної роботи],
"ПЗ": [Звіт \ з практичної роботи],
"КР": [Контрольна робота],
"РФ": [Реферат], // зрада
).at(type, default: type)
if number != none { [#type-title #number] } else { [#type-title] }
}
// Helper functions {{{1
/// captioned image with label derived from path:
/// - "image.png" = @image
/// - "img/image.png" = @image
/// - "img/foo/image.png" = @foo_image
/// - "img/foo/foo_image.png" = @foo_image
/// the caption will be modified based on a conditional positional value:
/// - `none`: no change
/// - some value: "`caption` (за даними `value`)"
/// - no value: "`caption` (рисунок виконано самостійно)"
/// additional named arguments will be passed to original `image` function
#let img(path, caption, ..sink) = {
let parts = path.split(".").first().split("/")
let label_string = if (
parts.len() <= 2 or parts.at(-1).starts-with(parts.at(-2))
) {
// ("image",), (_, "image") and (.., "img", "img_image")
parts.last()
} else {
// (.., "img", "image") = "img_image"
parts.at(-2) + "_" + parts.at(-1)
}.replace(" ", "_")
let caption = if sink.pos().len() == 0 {
caption
} else if sink.pos().first() == none {
caption + " (рисунок виконано самостійно)"
} else {
[#caption (за даними #sink.pos().first())]
}
[#figure(
image(path, ..sink.named()),
caption: caption,
) #label(label_string)]
}
#let spacing = 0.95em // spacing between lines
#let num-to-alpha = "абвгдежиклмнпрстуфхцшщюя".split("") // 0 = "", 1 = "а"
/// DSTU 3008:2015 Style
/// -> content
/// - it (content): Content to apply the style to.
/// - skip (int): Do not show page number for this number of pages.
/// - offset (int): Adjust all page numbers by this amount.
#let dstu-style(
it,
skip: 0,
offset: 0,
) = {
// General Styling {{{1
set page(
paper: "a4",
number-align: top + right,
margin: (top: 20mm, right: 10mm, bottom: 20mm, left: 25mm),
numbering: (i, ..) => if i > skip { numbering("1", i + offset) },
)
set text(
lang: "uk",
size: 14pt,
hyphenate: false,
font: ("Times New Roman", "Liberation Serif"),
)
set par(
justify: true,
spacing: spacing,
leading: spacing,
first-line-indent: (amount: 1.25cm, all: true),
)
set block(spacing: spacing)
set underline(evade: false)
// Enums & Lists {{{1
// First level
set enum(
indent: 1.25cm,
body-indent: 0.5cm,
numbering: i => { num-to-alpha.at(i) + ")" },
)
// Second level and further nesting
show enum: it => {
set enum(indent: 0em, numbering: "1)")
it
}
// Lists are not intended for multiple levels, use `enum`
set list(indent: 1.35cm, body-indent: 0.5cm, marker: [--])
// Figures {{{1
show figure: it => {
v(spacing * 2, weak: true)
it
v(spacing * 2, weak: true)
}
set figure.caption(separator: [ -- ])
show figure.where(kind: table): set figure.caption(position: top)
show figure.caption.where(kind: table): set align(left)
show figure.where(kind: raw): set figure.caption(position: top)
show figure.where(kind: raw): set align(left)
// Numbering {{{1
show heading.where(level: 1): it => {
counter(math.equation).update(0)
counter(figure.where(kind: raw)).update(0)
counter(figure.where(kind: image)).update(0)
counter(figure.where(kind: table)).update(0)
it
}
set figure(numbering: i => numbering("1.1", counter(heading).get().at(0), i))
set math.equation(numbering: i => numbering("(1.1)", counter(heading).get().at(0), i))
// Headings {{{1
set heading(numbering: "1.1")
show heading: it => if it.level == 1 {
set align(center)
set text(size: 14pt, weight: "semibold")
pagebreak(weak: true)
upper(it)
v(spacing * 2, weak: true)
} else {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(auto)
#it.body
]
v(spacing * 2, weak: true)
}
show heading.where(level: 3): it => {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(it.numbering)
#it.body
]
v(spacing * 2, weak: true)
}
// listings {{{3
show raw.where(block: true): it => {
let new_spacing = 0.5em
set block(spacing: new_spacing)
set par(spacing: new_spacing, leading: new_spacing)
set text(
size: 11pt,
weight: "semibold",
font: ("Courier New", "Liberation Mono"),
)
v(spacing * 2.5, weak: true)
pad(it, left: 1.25cm)
v(spacing * 2.5, weak: true)
}
it
// }}}
}
/// DSTU 3008:2015 Appendices Style
/// -> content
/// - it (content): Content to apply the style to.
#let appendices-style(it) = /* {{{ */ {
// Numbering
counter(heading).update(0)
set heading(numbering: (i, ..n) => upper(num-to-alpha.at(i)) + numbering(".1.1", ..n))
set figure(numbering: i => upper(num-to-alpha.at(counter(heading).get().at(0))).i)
set math.equation(numbering: i => upper(num-to-alpha.at(counter(heading).get().at(0))).i)
// Heading supplement (Heading name shown when citing with @ref)
set heading(supplement: [Додаток])
// Headings
show heading: it => if it.level == 1 {
set align(center)
set text(size: 14pt, weight: "regular")
pagebreak(weak: true)
bold([ДОДАТОК #counter(heading).display(auto)])
linebreak()
it.body
v(spacing * 2, weak: true)
} else {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(auto)
#it.body
]
v(spacing * 2, weak: true)
}
it
} // }}}
// Coursework template {{{1 // Coursework template {{{1
@@ -340,7 +75,7 @@
#set align(left) #set align(left)
#set par(first-line-indent: 0pt) #set par(first-line-indent: 0pt)
#gender-form("author", gender: author.gender) ст. гр. #author.edu_program\-#author.group #gender-form("author", gender: gender-get(author)) ст. гр. #author.edu_program\-#author.group
\ \
Керівник:\ Керівник:\
@@ -603,11 +338,14 @@
appendices-style(appendices) appendices-style(appendices)
} }
// #include "src/layouts.typ"
// Practice and Laboratory works template {{{1 // Practice and Laboratory works template {{{1
/// DSTU 3008:2015 Template for NURE /// DSTU 3008:2015 Template for NURE
/// -> content /// -> content
/// - doc (content): Content to apply the template to. /// - doc (content): Content to apply the template to.
/// - layout: ("default" | "simple"): Title page layout variant.
/// - university: "ХНУРЕ": University metadata. Optional. /// - university: "ХНУРЕ": University metadata. Optional.
/// - subject: str: Subject shortcode. /// - subject: str: Subject shortcode.
/// - type: ("ЛБ" | "ПЗ" | "КР" | "РФ" | str): Work type. /// - type: ("ЛБ" | "ПЗ" | "КР" | "РФ" | str): Work type.
@@ -617,6 +355,7 @@
/// - mentors ((name: str, degree: str, gender: ("m" | "f" | "p" | none)),): List of mentors. Optional. /// - mentors ((name: str, degree: str, gender: ("m" | "f" | "p" | none)),): List of mentors. Optional.
#let pz-lb( #let pz-lb(
doc, doc,
layout: "simple",
university: "ХНУРЕ", university: "ХНУРЕ",
subject: none, subject: none,
type: none, type: none,
@@ -627,6 +366,9 @@
) = { ) = {
// TODO: add actually relevant asserts // TODO: add actually relevant asserts
import "src/layouts/pz-lb/minimal.typ": *
import "src/layouts/pz-lb/simple.typ": *
let edu_program = authors.first().edu_program let edu_program = authors.first().edu_program
let uni = universities.at(university) let uni = universities.at(university)
@@ -636,52 +378,11 @@
// page 1 {{{2 // page 1 {{{2
align(center)[ if layout == "simple" {
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ \ simple(uni, edu_program, subject, type, number, title, authors, mentors)
#upper(uni.name) } else {
minimal(uni, edu_program, subject, type, number, title, authors, mentors)
\ \ }
Кафедра #uni.edu_programs.at(edu_program).department_gen
\ \ \
#pz-lb-title(type, number: number)
з дисципліни: "#uni.subjects.at(subject, default: subject)"
#if title != none [\ з теми: "#eval(title, mode: "markup")"]
\ \ \ \
#columns(2)[
#set align(left)
#set par(first-line-indent: 0pt)
#if authors.len() == 1 {
[#gender-form("author", gender: if "gender" in a.keys() { a.gender } else { none }):\ ]
let a = authors.first()
[ст. гр. #a.edu_program\-#a.group\ #a.name\ ]
if a.variant != none [Варіант: #a.variant]
} else if authors.len() > 1 [
#gender-form("author"):\
#for a in authors [ст. гр. #a.edu_program\-#a.group\ #a.name\ ]
]
#colbreak()
#set align(right)
#if mentors.len() == 1 {
[#gender-form("mentor", gender: if "gender" in m.keys() { m.gender } else { none }):\ ]
let m = mentors.first()
if "degree" in m.keys() and m.degree != none [#m.degree\ ]
[#m.name\ ]
} else if mentors.len() > 1 [
#gender-form("mentor"):\
#for mentor in mentors { [#mentor.degree\ #mentor.name\ ] }]
]
#v(1fr)
Харків -- #datetime.today().display("[year]")
]
pagebreak(weak: true) pagebreak(weak: true)
+98
View File
@@ -0,0 +1,98 @@
// Template formatting functions {{{1
/// bold text
#let bold(content) = text(weight: "bold")[#content]
/// numberless heading
#let nheading(title) = heading(depth: 1, numbering: none, title)
/// fill horizontal space with a box and not an empty space
#let hfill(width) = box(
width: width,
repeat(""),
) // NOTE: This is a HAIR SPACE (U+200A), not a regular space
/// make underlined cell with filled value
#let uline(align: center, content) = underline[
#if align != left { hfill(1fr) }
#content
#if align != right { hfill(1fr) }
]
/// month name from its number
#let month_gen(month) = (
"січня",
"лютого",
"березня",
"квітня",
"травня",
"червня",
"липня",
"серпня",
"вересня",
"жовтня",
"листопада",
"грудня",
).at(month - 1)
#let is-cyr(c) = regex("[\p{Cyrillic}]") in c
#let gender-form(verb, gender: "p") = {
(
"author": ("m": "Виконав", "f": "Виконала", "p": "Виконали"),
"mentor": ("m": "Перевірив", "f": "Перевірила", "p": "Перевірили"),
)
.at(verb)
.at(if gender == "m" or gender == "f" { gender } else { "p" }, default: "p")
}
#let gender-get(a) = if "gender" in a.keys() { a.gender } else { "p" }
#let pz-lb-title(type, number: none) = {
let type-title = (
"ЛБ": [Звіт \ з лабораторної роботи],
"ПЗ": [Звіт \ з практичної роботи],
"КР": [Контрольна робота],
"РФ": [Реферат], // зрада
).at(type, default: type)
if number != none { [#type-title #number] } else { [#type-title] }
}
// Helper functions {{{1
/// captioned image with label derived from path:
/// - "image.png" = @image
/// - "img/image.png" = @image
/// - "img/foo/image.png" = @foo_image
/// - "img/foo/foo_image.png" = @foo_image
/// the caption will be modified based on a conditional positional value:
/// - `none`: no change
/// - some value: "`caption` (за даними `value`)"
/// - no value: "`caption` (рисунок виконано самостійно)"
/// additional named arguments will be passed to original `image` function
#let img(path, caption, ..sink) = {
let parts = path.split(".").first().split("/")
let label_string = if (
parts.len() <= 2 or parts.at(-1).starts-with(parts.at(-2))
) {
// ("image",), (_, "image") and (.., "img", "img_image")
parts.last()
} else {
// (.., "img", "image") = "img_image"
parts.at(-2) + "_" + parts.at(-1)
}.replace(" ", "_")
let caption = if sink.pos().len() == 0 {
caption
} else if sink.pos().first() == none {
caption + " (рисунок виконано самостійно)"
} else {
[#caption (за даними #sink.pos().first())]
}
[#figure(
image(path, ..sink.named()),
caption: caption,
) #label(label_string)]
}
+61
View File
@@ -0,0 +1,61 @@
#import "/src/helpers.typ": gender-form, gender-get, pz-lb-title
#let minimal(
uni,
edu_program,
subject,
type,
number,
title,
authors,
mentors,
) = {
align(center)[
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ \
#upper(uni.name)
\ \
Кафедра #uni.edu_programs.at(edu_program).department_gen
\ \ \
#pz-lb-title(type, number: number)
з дисципліни: "#uni.subjects.at(subject, default: subject)"
#if title != none [\ з теми: "#eval(title, mode: "markup")"]
\ \ \ \
#columns(2)[
#set align(left)
#set par(first-line-indent: 0pt)
#if authors.len() == 1 {
let a = authors.first()
[#gender-form("author", gender: gender-get(a)):\ ]
[ст. гр. #a.edu_program\-#a.group\ #a.name\ ]
if a.variant != none [Варіант: #a.variant]
} else if authors.len() > 1 [
#gender-form("author"):\
#for a in authors [ст. гр. #a.edu_program\-#a.group\ #a.name\ ]
]
#colbreak()
#set align(right)
#if mentors.len() == 1 {
let m = mentors.first()
[#gender-form("mentor", gender: gender-get(m)):\ ]
if "degree" in m.keys() and m.degree != none [#m.degree\ ]
[#m.name\ ]
} else if mentors.len() > 1 [
#gender-form("mentor"):\
#for m in mentors {
if "degree" in m.keys() and m.degree != none [#m.degree\ ]
[#m.name\ ]
}]
]
#v(1fr)
Харків -- #datetime.today().display("[year]")
]
}
+66
View File
@@ -0,0 +1,66 @@
// Title page layouts for pz-lb {{{1
// This module provides different title page layouts for practice/lab work templates
#import "/src/helpers.typ": gender-form, gender-get, pz-lb-title
/// Default DSTU title page layout (ministry + university header, 2-column author/mentor info)
/// Returns: content for the title page
/// Simple title page layout (university header only, single column layout)
/// Returns: content for the title page
#let simple(
uni,
edu_program,
subject,
type,
number,
title,
authors,
mentors,
) = {
align(center)[
#upper(uni.name)
Кафедра #uni.edu_programs.at(edu_program).department_gen
\ \ \
#pz-lb-title(type, number: number)
з дисципліни: "#uni.subjects.at(subject, default: subject)"
#if title != none [\ з теми: "#eval(title, mode: "markup")"]
\ \ \
#if authors.len() == 1 {
let a = authors.first()
[#gender-form("author", gender: gender-get(a)):\ #a.name]
[ст. гр. #a.edu_program\-#a.group]
if a.variant != none [Варіант: #a.variant]
} else if authors.len() > 1 [
#gender-form("author"):\
#for a in authors [#a.name (ст. гр. #a.edu_program\-#a.group)\ ]
]
\
#if mentors.len() == 1 {
let m = mentors.first()
[#gender-form("mentor", gender: gender-get(m)):\ ]
if "degree" in m.keys() and m.degree != none [#m.degree ]
[#m.name]
} else if mentors.len() > 1 [
#gender-form("mentor"):\
#for m in mentors {
if "degree" in m.keys() and m.degree != none [#m.degree\ ]
[#m.name\ ]
}
]
#v(1fr)
#datetime.today().display("[year]")
]
}
// vim:sts=2:sw=2:fdm=marker:cms=/*%s*/
+157
View File
@@ -0,0 +1,157 @@
#let spacing = 0.95em // spacing between lines
/// symbols used for numbering according to DSTU 3008:2015
#let ukr-enum = "абвгдежиклмнпрстуфхцшщюя".split("") // 0 = "", 1 = "а"
/// DSTU 3008:2015 Style
/// -> content
/// - it (content): Content to apply the style to.
/// - skip (int): Do not show page number for this number of pages.
/// - offset (int): Adjust all page numbers by this amount.
#let dstu-style(
it,
skip: 0,
offset: 0,
) = {
// General Styling {{{1
set page(
paper: "a4",
number-align: top + right,
margin: (top: 20mm, right: 10mm, bottom: 20mm, left: 25mm),
numbering: (i, ..) => if i > skip { numbering("1", i + offset) },
)
set text(lang: "uk", size: 14pt, hyphenate: false, font: ("Times New Roman", "Liberation Serif"))
set par(justify: true, spacing: spacing, leading: spacing, first-line-indent: (
amount: 1.25cm,
all: true,
))
set block(spacing: spacing)
set underline(evade: false)
// Enums & Lists {{{1
// First level
set enum(indent: 1.25cm, body-indent: 0.5cm, numbering: i => { ukr-enum.at(i) + ")" })
// Second level and further nesting
show enum: it => {
set enum(indent: 0em, numbering: "1)")
it
}
// Lists are not intended for multiple levels, use `enum`
set list(indent: 1.35cm, body-indent: 0.5cm, marker: [--])
// Figures {{{1
show figure: it => {
v(spacing * 2, weak: true)
it
v(spacing * 2, weak: true)
}
set figure.caption(separator: [ -- ])
show figure.where(kind: table): set figure.caption(position: top)
show figure.caption.where(kind: table): set align(left)
show figure.where(kind: raw): set figure.caption(position: top)
show figure.where(kind: raw): set align(left)
// Numbering {{{1
show heading.where(level: 1): it => {
counter(math.equation).update(0)
counter(figure.where(kind: raw)).update(0)
counter(figure.where(kind: image)).update(0)
counter(figure.where(kind: table)).update(0)
it
}
set figure(numbering: i => numbering("1.1", counter(heading).get().at(0), i))
set math.equation(numbering: i => numbering("(1.1)", counter(heading).get().at(0), i))
// Headings {{{1
set heading(numbering: "1.1")
show heading: it => if it.level == 1 {
set align(center)
set text(size: 14pt, weight: "semibold")
pagebreak(weak: true)
upper(it)
v(spacing * 2, weak: true)
} else {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(auto)
#it.body
]
v(spacing * 2, weak: true)
}
show heading.where(level: 3): it => {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(it.numbering)
#it.body
]
v(spacing * 2, weak: true)
}
// listings {{{3
show raw.where(block: true): it => {
let new_spacing = 0.5em
set block(spacing: new_spacing)
set par(spacing: new_spacing, leading: new_spacing)
set text(size: 11pt, weight: "semibold", font: ("Courier New", "Liberation Mono"))
v(spacing * 2.5, weak: true)
pad(it, left: 1.25cm)
v(spacing * 2.5, weak: true)
}
it
// }}}
}
/// DSTU 3008:2015 Appendices Style
/// -> content
/// - it (content): Content to apply the style to.
#let appendices-style(it) = /* {{{ */ {
// Numbering
counter(heading).update(0)
set heading(numbering: (i, ..n) => upper(ukr-enum.at(i)) + numbering(".1.1", ..n))
set figure(numbering: i => upper(ukr-enum.at(counter(heading).get().at(0))).i)
set math.equation(numbering: i => upper(ukr-enum.at(counter(heading).get().at(0))).i)
// Heading supplement (Heading name shown when citing with @ref)
set heading(supplement: [Додаток])
// Headings
show heading: it => if it.level == 1 {
set align(center)
set text(size: 14pt, weight: "regular")
pagebreak(weak: true)
bold([ДОДАТОК #counter(heading).display(auto)])
linebreak()
it.body
v(spacing * 2, weak: true)
} else {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(auto)
#it.body
]
v(spacing * 2, weak: true)
}
it
} // }}}
+41
View File
@@ -0,0 +1,41 @@
#import "lib.typ": *
#set document(title: "Тест лейаутів", author: "Іванов І.І.")
#let authors = (
(
name: "Іванов І.І.",
full_name_gen: "Іванова Івана Івановича",
edu_program: "ПЗПІ",
group: "23-1",
variant: 5,
),
)
#let mentors = (
(name: "Петров П.П.", degree: "доц."),
)
// Тест default лейауту
#show: pz-lb.with(
layout: "default",
subject: "БД",
type: "ЛБ",
number: 2,
// title: "SQL запити",
authors: authors,
mentors: mentors,
)
#pagebreak()
// Тест simple лейауту
#show: pz-lb.with(
layout: "simple",
subject: "БД",
type: "ЛБ",
number: 2,
// title: "SQL запити",
authors: authors,
mentors: mentors,
)