diff --git a/lib.typ b/lib.typ index fe1cda0..b3505b3 100644 --- a/lib.typ +++ b/lib.typ @@ -3,273 +3,8 @@ #let universities = yaml("config/universities.yaml") -// 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 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 -} // }}} +#import "/src/helpers.typ": * +#import "/src/style.typ": * // Coursework template {{{1 @@ -340,7 +75,7 @@ #set align(left) #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) } +// #include "src/layouts.typ" + // Practice and Laboratory works template {{{1 /// DSTU 3008:2015 Template for NURE /// -> content /// - doc (content): Content to apply the template to. +/// - layout: ("default" | "simple"): Title page layout variant. /// - university: "ХНУРЕ": University metadata. Optional. /// - subject: str: Subject shortcode. /// - type: ("ЛБ" | "ПЗ" | "КР" | "РФ" | str): Work type. @@ -617,6 +355,7 @@ /// - mentors ((name: str, degree: str, gender: ("m" | "f" | "p" | none)),): List of mentors. Optional. #let pz-lb( doc, + layout: "simple", university: "ХНУРЕ", subject: none, type: none, @@ -627,6 +366,9 @@ ) = { // 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 uni = universities.at(university) @@ -636,52 +378,11 @@ // page 1 {{{2 - 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 { - [#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]") - ] + if layout == "simple" { + simple(uni, edu_program, subject, type, number, title, authors, mentors) + } else { + minimal(uni, edu_program, subject, type, number, title, authors, mentors) + } pagebreak(weak: true) diff --git a/src/helpers.typ b/src/helpers.typ new file mode 100644 index 0000000..c527f77 --- /dev/null +++ b/src/helpers.typ @@ -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)] +} diff --git a/src/layouts/pz-lb/minimal.typ b/src/layouts/pz-lb/minimal.typ new file mode 100644 index 0000000..0c5e8a2 --- /dev/null +++ b/src/layouts/pz-lb/minimal.typ @@ -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]") + ] +} diff --git a/src/layouts/pz-lb/simple.typ b/src/layouts/pz-lb/simple.typ new file mode 100644 index 0000000..405fce6 --- /dev/null +++ b/src/layouts/pz-lb/simple.typ @@ -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*/ diff --git a/src/style.typ b/src/style.typ new file mode 100644 index 0000000..2868df7 --- /dev/null +++ b/src/style.typ @@ -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 +} // }}} diff --git a/test-layouts.typ b/test-layouts.typ new file mode 100644 index 0000000..503ba7f --- /dev/null +++ b/test-layouts.typ @@ -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, +)