Skip to content

Commit a56ab54

Browse files
authoredMar 19, 2025
modern-g7-32:0.1.0 (typst#2039)
1 parent fe24ad7 commit a56ab54

20 files changed

+1662
-0
lines changed
 

‎packages/preview/modern-g7-32/0.1.0/LICENSE

+674
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# modern-g7-32
2+
3+
![header](assets/header.png)
4+
5+
Шаблон для оформления работ в соответствии с ГОСТ 7.32-2017. Он был создан для автоматизации рутинных процессов при работе с научными работами. Шаблон может быть полезен студентам вуза при оформлении лабораторных, курсовых и дипломных работ.
6+
7+
<a href="https://typst.app/universe/package/modern-g7-32">![Typst Universe](https://img.shields.io/badge/dynamic/xml?url=https://typst.app/universe/package/modern-g7-32&query=/html/body/div/main/div[2]/aside/section[2]/dl/dd[3]&logo=typst&label=universe)</a>
8+
<a href="https://github.com/typst-g7-32/modern-g7-32/actions"><img src="https://github.com/typst-g7-32/modern-g7-32/actions/workflows/ci.yml/badge.svg" alt="License badge"></a>
9+
<a href="https://github.com/typst-g7-32/modern-g7-32/blob/main/LICENSE"><img src="https://img.shields.io/github/license/typst-g7-32/modern-g7-32" alt="License badge"></a>
10+
<a href="https://typst-gost.ru"><img src="https://img.shields.io/website?url=https%3A%2F%2Ftypst-gost.ru" alt="Website badge"></a>
11+
12+
## Быстрый старт
13+
Чтобы использовать этот шаблон, импортируйте его, как показано ниже:
14+
```typst
15+
#import "@preview/modern-g7-32": gost, abstract, title-templates, annexes, annex-heading
16+
17+
#show: gost.with(
18+
ministry: "Наименование министерства (ведомства) или другого структурного образования, в систему которого входит организация-исполнитель",
19+
organization: (full: "Полное наименование организации — исполнителя НИР", short: "Сокращённое наименование организации"),
20+
udk: "индекс УДК",
21+
research-number: "регистрационный номер НИР",
22+
report-number: "регистрационный номер отчета",
23+
approved-by: (
24+
name: "Фамилия И.О.",
25+
position: "Должность, сокращ. наимен. орг", year: 2017
26+
), // Гриф согласования
27+
agreed-by: (
28+
name: "Фамилия И.О.",
29+
position: "Должность, сокращ. наимен. орг", year: auto
30+
), // Гриф утверждения, год подставляется из аргумента year
31+
report-type: "отчёт",
32+
about: "О научно-исследовательской работе",
33+
research: "Наименование НИР",
34+
bare-subject: false, // Можно убрать "по теме"
35+
subject: "Наименование отчёта",
36+
manager: (name: "Фамилия И.О.", position: "Должность"),
37+
stage: (type: "вид отчёта", num: 1),
38+
federal: "Наименование федеральной программы",
39+
part: 2, // Номер книги отчёта
40+
city: "Город",
41+
year: auto, // Можно поменять год, auto - текущий год
42+
text-size: (default: 14pt, small: 10pt), // Можно указать размеры текста
43+
indent: 1.25cm, // Можно указать отступ
44+
hide-title: false, // Можно скрыть титульник
45+
performers: (
46+
"Организация",
47+
// Можно указать организацию, к которой относятся следующие исполнители
48+
(name: "И.О. Фамилия", position: "Должность", part: "введение, раздел 1"), // Можно добавить выполненную часть
49+
(name: "И.О. Фамилия", position: "Должность"),
50+
"Другая организация",
51+
(name: "И.О. Фамилия", position: "Должность"),
52+
(name: "И.О. Фамилия", position: "Должность", co-performer: true) // Поддерживаются соисполнители
53+
), // Если исполнитель один - он будет перенесён на титульный лист
54+
)
55+
```
56+
57+
## Документация
58+
Документация к проекту доступна на [сайте](https://typst-gost.ru/docs).
59+
60+
Если вам требуется помощь с шаблоном, можете обратиться на почту support@typst-gost.ru.
61+
62+
## Возможности
63+
* Формирование титульного листа
64+
* Встроенные шаблоны титульных листов
65+
* Пользовательские шаблоны титульных листов
66+
* Автоматическое создание списка исполнителей
67+
* Оформление структурных заголовков
68+
* Автоматическая генерация реферата
69+
* Автоматизированная сборка содержания
70+
* Форматирование и нумерация элементов отчёта
71+
* Оформление списка использованных источников
72+
* Автоматическое оформление и нумерация приложений
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#import "headings.typ": structural-heading-titles
2+
3+
#let get-count(kind) = {
4+
assert(kind in (page, image, table, ref, "annex"), message: "Невозможно определить количество этих элементов")
5+
let target-counter = none
6+
let caption = none
7+
if kind == page {
8+
target-counter = counter(page)
9+
caption = "с."
10+
} else if kind == "annex" {
11+
target-counter = counter("annex")
12+
caption = "прил."
13+
} else if kind == ref {
14+
caption = "ист."
15+
} else if kind == image {
16+
target-counter = counter("image")
17+
caption = "рис."
18+
} else if kind == table {
19+
target-counter = counter("table")
20+
caption = "табл."
21+
}
22+
23+
let count = 0
24+
if kind == cite {
25+
count = target-counter.final().dedup().len()
26+
} else if kind == ref {
27+
count = query(selector(ref)).filter(it => it.element == none).map(it => it.target).dedup().len()
28+
} else {
29+
count = target-counter.final().first()
30+
}
31+
32+
if count != 0 {
33+
repr(count) + " " + caption
34+
}
35+
}
36+
37+
#let abstract(..keywords, body) = {
38+
[
39+
#heading(structural-heading-titles.abstract, outlined: false) <abstract>
40+
41+
#context {
42+
let counts = (get-count(page), get-count(image), get-count(table), get-count(ref), get-count("annex"))
43+
counts = counts.filter(it => it != none)
44+
[Отчёт #counts.join(", ")]
45+
}
46+
47+
#{
48+
set par(first-line-indent: 0pt)
49+
upper(keywords.pos().join(", "))
50+
}
51+
52+
#text(body)
53+
]
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#import "@preview/numberingx:0.0.1": formatter
2+
3+
#let is-heading-in-annex(heading) = state("annexes", false).at(heading.location())
4+
5+
#let get-element-numbering(current-heading-numbering, element-numbering) = {
6+
let current-numbering = formatter("{upper-russian}.{1}")(current-heading-numbering.first())
7+
formatter(str(current-numbering)+".{1}")(element-numbering)
8+
}
9+
10+
#let annex-heading(status, level: 1, body) = {
11+
heading(level: level)[(#status)\ #body]
12+
}
13+
14+
#let annexes(content) = {
15+
[#none <annexes>]
16+
17+
set heading(
18+
numbering: formatter("{upper-russian}.{1}"),
19+
hanging-indent: 0pt
20+
)
21+
22+
show heading: set align(center)
23+
show heading: it => {
24+
assert(it.numbering != none, message: "В приложениях не может быть структурных заголовков или заголовков без нумерации")
25+
counter("annex").step()
26+
block[#upper([приложение]) #numbering(it.numbering, ..counter(heading).at(it.location())) \ #text(weight: "medium")[#it.body]]
27+
}
28+
29+
show heading.where(level: 1): it => {
30+
counter(figure.where(kind: image)).update(0)
31+
counter(figure.where(kind: table)).update(0)
32+
counter(figure.where(kind: raw)).update(0)
33+
counter(math.equation).update(0)
34+
35+
pagebreak(weak: true)
36+
it
37+
}
38+
39+
set figure(numbering: it => context {
40+
let current-heading = counter(heading).get()
41+
get-element-numbering(current-heading, it)
42+
})
43+
44+
set math.equation(numbering: it => context {
45+
let current-heading = counter(heading).get()
46+
[(#get-element-numbering(current-heading, it))]
47+
})
48+
49+
state("annexes").update(true)
50+
counter(heading).update(0)
51+
content
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#import "../utils.typ": fetch-field, sign-field
2+
#import "title.typ": per-line, if-present, detailed-sign-field, agreed-field, approved-field, approved-and-agreed-fields
3+
#import "title-templates.typ": custom-title-template as from-module
4+
5+
#let title-utils = (per-line, if-present, fetch-field, sign-field, detailed-sign-field, agreed-field, approved-field, approved-and-agreed-fields)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#let structural-heading-titles = (
2+
performers: [Список исполнителей],
3+
abstract: [Реферат],
4+
contents: [Содержание],
5+
terms: [Термины и определения],
6+
abbreviations: [Перечень сокращений и обозначений],
7+
intro: [Введение],
8+
conclusion: [Заключение],
9+
references: [Список использованных источников],
10+
)
11+
12+
#let structure-heading-style = it => {
13+
align(center)[#upper(it)]
14+
}
15+
16+
#let structure-heading(body) = {
17+
structure-heading-style(heading(numbering: none)[#body])
18+
}
19+
20+
#let headings(text-size, indent) = body => {
21+
show heading: set text(size: text-size)
22+
set heading(numbering: "1.1")
23+
24+
show heading: it => {
25+
if it.body not in structural-heading-titles.values() {
26+
pad(it, left: indent)
27+
} else {
28+
it
29+
}
30+
}
31+
32+
show heading.where(level: 1): it => {
33+
pagebreak(weak: true)
34+
it
35+
}
36+
37+
let structural-heading = structural-heading-titles.values().fold(selector, (acc, i) => acc.or(heading.where(body: i, level: 1)))
38+
39+
show structural-heading: set heading(numbering: none)
40+
show structural-heading: it => {
41+
pagebreak(weak: true)
42+
structure-heading-style(it)
43+
}
44+
45+
show heading: set block(below: 2em, above: 2em)
46+
47+
body
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#import "headings.typ": structural-heading-titles
2+
#import "../utils.typ": sign-field, fetch-field
3+
4+
#let fetch-performers(performers) = {
5+
let performers-args = ("name*", "position*", "co-performer", "part", "organization")
6+
if type(performers) == array {
7+
let current-organization = none;
8+
let result = ()
9+
for (i, performer) in performers.enumerate() {
10+
if type(performer) == str {
11+
current-organization = performer
12+
continue
13+
}
14+
let performer = fetch-field(performer, performers-args, hint: "исполнителя №" + str(i + 1))
15+
performer.organization = current-organization
16+
if performer.co-performer == none {
17+
performer.co-performer = false
18+
}
19+
result.push(performer)
20+
}
21+
return result
22+
} else if type(performers) == dictionary {
23+
let performer = fetch-field(performers, performers-args, hint: "исполнителя")
24+
return (performer, )
25+
} else {
26+
panic("Некорректный тип поля исполнителей")
27+
}
28+
}
29+
30+
#let group-organizations(performers) = {
31+
set par(spacing: 0.5em)
32+
let organizations = performers.map(performer => performer.organization).dedup().filter(org => org != none)
33+
let organizations-with-performers = organizations.map(organization => (organization, performers.filter(performer => performer.organization == organization))).to-dict()
34+
let without-organization = performers.filter(performer => performer.organization == none)
35+
for performer in without-organization {
36+
[#sign-field(performer.name, performer.position, part: performer.part)]
37+
}
38+
for (organization, performers) in organizations-with-performers.pairs() {
39+
[#block([#organization:])]
40+
for performer in performers {
41+
sign-field(performer.name, performer.position, part: performer.part)
42+
}
43+
44+
}
45+
}
46+
47+
#let performers-page(performers) = {
48+
heading(structural-heading-titles.performers, outlined: false)
49+
50+
let not-co-performers = performers.filter(performer => performer.co-performer == false)
51+
let co-performers = performers.filter(performer => performer.co-performer == true)
52+
53+
group-organizations(not-co-performers)
54+
55+
let contains-co-performers = performers.any(performer =>
56+
( if "co-performer" in performer.keys() and performer.co-performer != none {
57+
performer.co-performer
58+
} else {
59+
false
60+
})
61+
)
62+
63+
if contains-co-performers {
64+
block[Соисполнители:]
65+
group-organizations(co-performers)
66+
}
67+
pagebreak(weak: true)
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#let template-names = ("default", "mai-university-lab")
2+
3+
#let title-template-factory(template, arguments-function) = {
4+
return (..arguments) => template(..arguments-function(..arguments))
5+
}
6+
7+
#let custom-title-template(module) = {
8+
title-template-factory(module.template, module.arguments)
9+
}
10+
11+
#let templates = {
12+
let result = (:)
13+
for template in template-names {
14+
import "/src/title-templates/" + template + ".typ" as module
15+
result.insert(template, title-template-factory(module.template, module.arguments))
16+
}
17+
result
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#import "../utils.typ": fetch-field, unbreak-name
2+
#import "performers.typ": performers-page
3+
4+
#let detailed-sign-field(title, name, position, year) = {
5+
assert(type(name) == str, message: "Некорректный тип поля name в detailed-sign-field, должен быть строкой")
6+
assert(type(position) == str, message: "Некорректный тип поля position в detailed-sign-field, должен быть строкой")
7+
assert(type(year) in (int, type(none)), message: "Некорректный тип поля year в detailed-sign-field, должен быть целым числом")
8+
let year-cell = []
9+
let line-end = 7
10+
if year != none {
11+
year-cell = table.cell(align: right)[#year г.]
12+
line-end = 6
13+
}
14+
table(
15+
stroke: none,
16+
align: left,
17+
inset: (x: 0%),
18+
columns: (8pt, 2fr, 2pt, 10pt, 2fr, auto, 45pt),
19+
table.cell(colspan: 7)[#upper(title)],
20+
table.cell(colspan: 7)[#position],
21+
table.cell(colspan: 5)[], table.cell(colspan: 2, align: right)[#unbreak-name(name)],
22+
table.hline(start: 0, end: 5),
23+
table.cell(align: right)[«], [], table.cell(align: left)[»], [], [], [], year-cell,
24+
table.hline(start: 1, end: 2), table.hline(start: 4, end: line-end)
25+
)
26+
}
27+
28+
#let align-function = align
29+
30+
#let per-line(align: center, indent: v(1fr), force-indent: false, ..values) = {
31+
let result = ()
32+
for value in values.pos() {
33+
let rule = false
34+
if type(value) in (array, dictionary) {
35+
let data = fetch-field(value, ("value*", "when-rule", "when-present"), default: (when-present: "any", when-rule: none), hint: "линии")
36+
assert(not (data.when-rule != none and data.when-present != "any"), message: "Должно быть выбрано только одно правило пояивления when-rule или when-present")
37+
if data.when-rule != none {
38+
rule = data.when-rule
39+
}
40+
if data.when-present != "any" {
41+
rule = (data.when-present, ).flatten().all(elem => elem != none)
42+
}
43+
value = data.value
44+
} else {
45+
rule = value != none
46+
}
47+
if rule {
48+
result.push(value)
49+
}
50+
}
51+
52+
if result != () {
53+
align-function(align)[
54+
#grid[#for elem in result {[#elem \ ]}]
55+
]
56+
}
57+
if force-indent or result != () {
58+
indent
59+
}
60+
}
61+
62+
#let if-present(rule: array.all, indent: v(1fr), ..targets, body) = {
63+
assert(rule in (array.all, array.any), message: "Правило сравнения указано неверно, должно быть array.all или array.any")
64+
let check = (target => target != none)
65+
if rule(targets.pos(), check) {
66+
body
67+
indent
68+
}
69+
}
70+
71+
#let approved-field(approved-by) = {
72+
if approved-by.name != none [
73+
#detailed-sign-field("согласовано", approved-by.name, approved-by.position, approved-by.year)
74+
]
75+
}
76+
77+
#let agreed-field(agreed-by) = {
78+
if agreed-by.name != none [
79+
#detailed-sign-field("утверждаю", agreed-by.name, agreed-by.position, agreed-by.year)
80+
]
81+
}
82+
83+
#let approved-and-agreed-fields(approved-by, agreed-by) = {
84+
if-present(rule: array.any, approved-by.name, agreed-by.name)[
85+
#grid(
86+
columns: (1fr, 1fr),
87+
align: (left, right),
88+
gutter: 15%,
89+
approved-field(approved-by),
90+
agreed-field(agreed-by)
91+
)
92+
]
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#import "lib.typ": gost
2+
#import "component/title-templates.typ": templates as title-templates
3+
#import "component/abstract.typ": abstract
4+
#import "component/annexes.typ": annexes, annex-heading
5+
#import "component/headings.typ": structure-heading
6+
#import "component/custom-title-template.typ"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#import "style.typ": gost-style
2+
#import "utils.typ": fetch-field
3+
#import "component/title-templates.typ": templates
4+
#import "component/performers.typ": performers-page, fetch-performers
5+
6+
#let gost-common(title-template, title-arguments, city, year, hide-title, performers, force-performers) = {
7+
set par(justify: false)
8+
9+
title-arguments = title-arguments.named()
10+
11+
title-arguments.insert("year", year)
12+
13+
let show-performers-page = false
14+
if performers != none {
15+
performers = fetch-performers(performers)
16+
if (performers.len() > 1 or force-performers) {
17+
show-performers-page = true
18+
} else {
19+
title-arguments.insert("performer", performers.first())
20+
}
21+
}
22+
23+
if not hide-title {
24+
block(
25+
width: 100%,
26+
title-template(..title-arguments),
27+
breakable: false,
28+
)
29+
pagebreak(weak: true)
30+
}
31+
32+
if show-performers-page { performers-page(performers) }
33+
}
34+
35+
#let gost(
36+
title-template: templates.default,
37+
text-size: (default: 14pt, small: 10pt),
38+
indent: 1.25cm,
39+
city: none,
40+
year: auto,
41+
hide-title: false,
42+
performers: none,
43+
force-performers: false,
44+
..title-arguments,
45+
body
46+
) = {
47+
let table-counter = counter("table")
48+
let image-counter = counter("image")
49+
let citation-counter = counter("citation")
50+
let annex-counter = counter("annex")
51+
52+
show figure.where(kind: image): it => {
53+
image-counter.step()
54+
it
55+
}
56+
show figure.where(kind: table): it => {
57+
table-counter.step()
58+
it
59+
}
60+
61+
if year == auto {
62+
year = int(datetime.today().display("[year]"))
63+
}
64+
65+
text-size = fetch-field(text-size, ("default*", "small"))
66+
67+
show: gost-style.with(text-size: text-size.default, small-text-size: text-size.small, indent: indent, year: year, city: city, hide-title: hide-title)
68+
69+
gost-common(title-template, title-arguments, city, year, hide-title, performers, force-performers)
70+
71+
body
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#import "component/headings.typ": headings, structural-heading-titles
2+
#import "component/annexes.typ": is-heading-in-annex
3+
4+
#let gost-style(year: none, city: "", hide-title: false, text-size: 14pt, small-text-size: 10pt, indent: 1.25cm, body) = {
5+
if small-text-size == none { small-text-size = text-size - 4pt }
6+
[#metadata(small-text-size) <small-text-size>]
7+
8+
set page(
9+
margin: (left: 30mm, right: 15mm, top: 20mm, bottom: 20mm)
10+
)
11+
12+
set text(
13+
size: text-size,
14+
lang: "ru",
15+
hyphenate: false
16+
)
17+
18+
set par(
19+
justify: true,
20+
first-line-indent: (
21+
amount: indent,
22+
all: true,
23+
),
24+
spacing: 1.5em
25+
)
26+
27+
set outline(indent: indent, depth: 3)
28+
show outline: set block(below: indent / 2)
29+
show outline.entry: it => {
30+
show linebreak: [ ]
31+
if is-heading-in-annex(it.element) {
32+
let body = it.element.body
33+
link(
34+
it.element.location(),
35+
it.indented(
36+
none,
37+
[Приложение #it.prefix() #it.element.body] + sym.space + box(width: 1fr, it.fill) + sym.space + sym.wj + it.page()
38+
)
39+
)
40+
} else {
41+
it
42+
}
43+
}
44+
45+
set ref(supplement: none)
46+
set figure.caption(separator: "")
47+
48+
set math.equation(numbering: "(1)")
49+
50+
show figure: pad.with(bottom: 0.5em)
51+
52+
show image: set align(center)
53+
show figure.where(kind: image): set figure(supplement: [Рисунок])
54+
55+
show figure.where(
56+
kind: table
57+
): it => {
58+
set block(breakable: true)
59+
set figure.caption(position: top)
60+
it
61+
}
62+
show figure.caption.where(kind: table): set align(left)
63+
show table.cell: set align(left)
64+
// TODO: Расположить table.header по центру и сделать шрифт жирным
65+
66+
set list(marker: [–], indent: indent, spacing: 1em)
67+
set enum(indent: indent, spacing: 1em)
68+
69+
set page(footer: context [
70+
#let page = here().page()
71+
#align(center)[#{
72+
if page == 1 {
73+
if hide-title {page} else {[#city #year]}
74+
}
75+
else {page}
76+
}]
77+
])
78+
79+
set bibliography(style: "gost-r-705-2008-numeric", title: structural-heading-titles.references)
80+
81+
show: headings(text-size, indent)
82+
body
83+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#import "../component/title.typ": detailed-sign-field, per-line, approved-and-agreed-fields
2+
#import "../utils.typ": sign-field, fetch-field
3+
4+
#let arguments(..args, year: auto) = {
5+
let args = args.named()
6+
args.organization = fetch-field(
7+
args.at("organization", default: none),
8+
("*full", "short"),
9+
hint: "организации"
10+
)
11+
args.approved-by = fetch-field(
12+
args.at("approved-by", default: none),
13+
("name*", "position*", "year"),
14+
default: (year: auto),
15+
hint: "согласования"
16+
)
17+
args.agreed-by = fetch-field(
18+
args.at("agreed-by", default: none),
19+
("name*", "position*", "year"),
20+
default: (year: auto),
21+
hint: "утверждения"
22+
)
23+
args.stage = fetch-field(args.at(
24+
"stage", default: none),
25+
("type*", "num"),
26+
hint: "этапа"
27+
)
28+
args.manager = fetch-field(
29+
args.at("manager", default: none),
30+
("position*", "name*"),
31+
hint: "руководителя"
32+
)
33+
34+
if args.approved-by.year == auto {
35+
args.approved-by.year = year
36+
}
37+
if args.agreed-by.year == auto {
38+
args.agreed-by.year = year
39+
}
40+
return args
41+
}
42+
43+
#let template(
44+
ministry: none,
45+
organization: (full: none, short: none),
46+
udk: none,
47+
research-number: none,
48+
report-number: none,
49+
approved-by: (name: none, position: none, year: auto),
50+
agreed-by: (name: none, position: none, year: none),
51+
report-type: "Отчёт",
52+
about: none,
53+
bare-subject: false,
54+
research: none,
55+
subject: none,
56+
part: none,
57+
stage: none,
58+
federal: none,
59+
manager: (position: none, name: none),
60+
performer: none,
61+
) = {
62+
per-line(
63+
force-indent: true,
64+
ministry,
65+
(value: upper(organization.full), when-present: organization.full),
66+
(value: upper[(#organization.short)], when-present: organization.short),
67+
)
68+
69+
per-line( // TODO: Вынести подобные элементы в модуль стандартных титульных компонентов с обработкой аргументов
70+
force-indent: true,
71+
align: left,
72+
(value: [УДК: #udk], when-present: udk),
73+
(value: [Рег. №: #research-number], when-present: research-number),
74+
(value: [Рег. № ИКРБС: #report-number], when-present: report-number),
75+
)
76+
77+
approved-and-agreed-fields(approved-by, agreed-by)
78+
79+
per-line(
80+
align: center,
81+
indent: v(2fr),
82+
(value: upper(report-type), when-present: report-type),
83+
(value: upper(about), when-present: about),
84+
(value: research, when-present: research),
85+
(value: [по теме:], when-rule: not bare-subject),
86+
(value: upper(subject), when-present: subject),
87+
(
88+
value: [(#stage.type)],
89+
when-rule: (stage.type != none and stage.num == none)),
90+
(
91+
value: [(#stage.type, этап #stage.num)],
92+
when-present: (stage.type, stage.num)
93+
),
94+
(value: [\ Книга #part], when-present: part),
95+
(federal)
96+
)
97+
98+
if manager.name != none {
99+
sign-field(manager.at("name"), [Руководитель НИР,\ #manager.at("position")])
100+
}
101+
102+
if performer != none {
103+
sign-field(performer.at("name", default: none), [Исполнитель НИР,\ #performer.at("position", default: none)], part: performer.at("part", default: none))
104+
}
105+
106+
v(0.5fr)
107+
}
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#import "../component/title.typ": detailed-sign-field, per-line, approved-and-agreed-fields
2+
#import "../utils.typ": sign-field, fetch-field
3+
4+
#let arguments(..args, year: auto) = {
5+
let args = args.named()
6+
args.organization = fetch-field(
7+
args.at("organization", default: none),
8+
default: (
9+
full: "Московский авиационный институт",
10+
short: "Национальный исследовательский университет"),
11+
("*full", "short"),
12+
hint: "организации"
13+
)
14+
args.approved-by = fetch-field(
15+
args.at("approved-by", default: none),
16+
("name*", "position*", "year"),
17+
default: (year: auto),
18+
hint: "согласования"
19+
)
20+
args.agreed-by = fetch-field(
21+
args.at("agreed-by", default: none),
22+
("name*", "position*", "year"),
23+
default: (year: auto),
24+
hint: "утверждения"
25+
)
26+
args.stage = fetch-field(args.at(
27+
"stage", default: none),
28+
("type*", "num"),
29+
hint: "этапа"
30+
)
31+
args.manager = fetch-field(
32+
args.at("manager", default: none),
33+
("position*", "name*"),
34+
hint: "руководителя"
35+
)
36+
37+
if args.approved-by.year == auto {
38+
args.approved-by.year = year
39+
}
40+
if args.agreed-by.year == auto {
41+
args.agreed-by.year = year
42+
}
43+
return args
44+
}
45+
46+
#let template(
47+
ministry: none,
48+
organization: (
49+
full: "Московский авиационный институт",
50+
short: "Национальный исследовательский университет"
51+
),
52+
institute: (number: none, name: none),
53+
department: (number: none, name: none),
54+
udk: none,
55+
research-number: none,
56+
report-number: none,
57+
approved-by: (name: none, position: none, year: auto),
58+
agreed-by: (name: none, position: none, year: none),
59+
report-type: "Отчёт",
60+
about: "О лабораторной работе",
61+
part: none,
62+
bare-subject: false,
63+
research: none,
64+
subject: none,
65+
stage: none,
66+
manager: (position: none, name: none),
67+
performer: none,
68+
) = {
69+
grid(
70+
columns: (0.2fr, 1fr),
71+
gutter: 5pt,
72+
inset: 0pt,
73+
grid.cell(align: top+center)[#image("logo/mai.svg", width: 100%)],
74+
grid.cell(align: horizon)[
75+
#per-line(
76+
indent: none,
77+
ministry,
78+
(value: upper(text(size: 18pt)[#organization.full]),
79+
when-present: organization.full),
80+
(value: [#upper(organization.short)],
81+
when-present: organization.short),
82+
)
83+
]
84+
)
85+
86+
v(1fr)
87+
88+
per-line(
89+
indent: v(2fr),
90+
force-indent: true,
91+
align: center,
92+
(value: [Институт №#institute.number – «#institute.name»],
93+
when-present: (institute.number, institute.name)),
94+
(value: [Кафедра #department.number – «#department.name»],
95+
when-present: (department.number, department.name)),
96+
)
97+
98+
per-line(
99+
align: left,
100+
(value: [УДК: #udk], when-present: udk),
101+
(value: [Рег. №: #research-number], when-present: research-number),
102+
(value: [Рег. № ИКРБС: #report-number], when-present: report-number),
103+
)
104+
105+
approved-and-agreed-fields(approved-by, agreed-by)
106+
107+
per-line(
108+
align: center,
109+
indent: v(2fr),
110+
(value: upper(report-type), when-present: report-type),
111+
(value: upper(about), when-present: about),
112+
(value: research, when-present: research),
113+
(value: [по теме:], when-rule: not bare-subject),
114+
(value: upper(subject), when-present: subject),
115+
(
116+
value: [(#stage.type)],
117+
when-rule: (stage.type != none and stage.num == none)),
118+
(
119+
value: [(#stage.type, этап #stage.num)],
120+
when-present: (stage.type, stage.num)
121+
),
122+
(value: [\ Книга #part], when-present: part),
123+
)
124+
125+
if manager.name != none {
126+
sign-field(manager.at("name"), [Принял:\ #manager.at("position")])
127+
}
128+
129+
if performer != none {
130+
sign-field(performer.at("name", default: none), [Выполнил:\ #performer.at("position", default: none)], part: performer.at("part", default: none))
131+
}
132+
133+
v(0.5fr)
134+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#let small-text = body => context {
2+
let target-size = query(<small-text-size>).first().value
3+
set text(size: target-size)
4+
body
5+
}
6+
7+
#let fetch-field(field, expected-keys, default: (:), hint: "") = {
8+
let expected-keys-arg-error = "Ожидаемые ключи должны быть списком строк, например '(arg1*, arg2), здесь arg1 - обязательный агрумент, а arg2 - необязательный'"
9+
10+
assert(type(expected-keys) == array, message: expected-keys-arg-error)
11+
assert(expected-keys.map(elem => type(elem)).all(elem => elem == str), message: expected-keys-arg-error)
12+
13+
assert(type(default) == dictionary, message: "Стандартные значения должны быть определены в словаре, например: 'default: (arg1: false)'")
14+
assert(default.len() <= expected-keys.len(), message: "Количество стандартных значений должно быть не больше числа ожидаемых аргументов")
15+
16+
let get-default(key) = (key, default.at(key, default: none))
17+
18+
let clean-expected-keys = expected-keys.map(key => key.replace("*", ""))
19+
let required-keys = expected-keys.filter(key => key.at(-1) == "*").map(key => key.slice(0, -1))
20+
let not-required-keys = expected-keys.filter(key => key.at(-1) != "*")
21+
22+
if type(field) == type(none) {
23+
return clean-expected-keys.map(get-default).to-dict()
24+
}
25+
26+
if type(field) == dictionary {
27+
for key in required-keys {
28+
assert(key in field.keys(), message: "В словаре " + hint + " отсутствует обязательный ключ '" + key + "'")
29+
}
30+
for key in field.keys() {
31+
assert(key in clean-expected-keys, message: "В словаре " + hint + " обнаружен неожиданный ключ '" + key + "', допустимые ключи: " + repr(expected-keys))
32+
}
33+
let result = clean-expected-keys.map(get-default).to-dict()
34+
for (key, value) in field {
35+
result.insert(key, value)
36+
}
37+
return result
38+
}
39+
40+
else if type(field) == array {
41+
assert(field.len() >= required-keys.len(), message: "В списке " + hint + " указаны не все обязательные элементы: " + repr(required-keys))
42+
assert(field.len() <= expected-keys.len(), message: "В списке " + hint + " указано слишком много аргументов, требуемые: " + repr(expected-keys))
43+
let result = (:)
44+
for (i, key) in clean-expected-keys.enumerate() {
45+
result.insert(key, field.at(i, default: default.at(key, default: none)))
46+
}
47+
return result
48+
}
49+
50+
else if type(field) in (str, int, length) {
51+
let result = clean-expected-keys.map(get-default).to-dict()
52+
result.insert(clean-expected-keys.at(0), field)
53+
return result
54+
}
55+
56+
else {
57+
panic("Некорректный тип поля " + repr(type(field)) + "(" + repr(field) + ") используйте словарь, список или строку для значения " + hint)
58+
}
59+
}
60+
61+
#let unbreak-name(name) = {
62+
if name == none { return }
63+
return name.replace(" ", "\u{00A0}")
64+
}
65+
66+
#let sign-field(name, position, part: none, details: "подпись, дата") = {
67+
let part-cell = []
68+
if part != none {
69+
part-cell = table.cell(align: top)[(#small-text[#part])]
70+
}
71+
72+
set par(justify: false)
73+
table(
74+
stroke: none,
75+
inset: (x: 0pt, y: 3pt),
76+
columns: (5fr, 1fr, 3fr, 1fr, 3fr),
77+
[#position], [], [], [], table.cell(align: bottom)[#unbreak-name(name)],
78+
table.hline(start: 2, end: 3),
79+
[], [], table.cell(align: center)[#small-text[#details]], [], part-cell
80+
)
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#import "@preview/modern-g7-32:0.1.0": gost, abstract
2+
3+
#show: gost.with(
4+
ministry: "Наименование министерства (ведомства) или другого структурного образования, в систему которого входит организация-исполнитель",
5+
organization: (
6+
full: "Полное наименование организации — исполнителя НИР",
7+
short: "Сокращённое наименование организации"),
8+
udk: "индекс УДК",
9+
research-number: "регистрационный номер НИР",
10+
report-number: "регистрационный номер отчета",
11+
approved-by: (
12+
name: "Фамилия И.О.",
13+
position: "Должность, сокращ. наимен. орг"
14+
),
15+
agreed-by: (
16+
name: "Фамилия И.О.",
17+
position: "Должность, сокращ. наимен. орг"
18+
),
19+
report-type: "отчёт",
20+
about: "О научно-исследовательской работе",
21+
subject: "Наименование отчёта",
22+
manager: (name: "Фамилия И.О.", position: "Должность"),
23+
city: "Город",
24+
performers: (
25+
(name: "И.О. Фамилия", position: "Должность"),
26+
(name: "И.О. Фамилия", position: "Должность")
27+
),
28+
)
29+
30+
#abstract("шаблон", "гост", "typst", "демонстрация", "ключевые слова")[
31+
Некоторый текст, относящийся к реферату
32+
]
33+
34+
#outline()
35+
36+
= Введение
37+
Некоторый текст, относящийся к введению
38+
= Раздел
39+
== Подраздел
40+
=== Пункт
41+
= Заключение
42+
Некоторый текст, относящийся к заключению
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[package]
2+
name = "modern-g7-32"
3+
version = "0.1.0"
4+
entrypoint = "src/export.typ"
5+
authors = [
6+
"Pavel Eliseev <forgenet@inbox.ru>",
7+
"Arseniy Zasypkin <imcatcatcher@mail.ru>"
8+
]
9+
license = "GPL-3.0-only"
10+
description = "Template for academic documents in compliance with GOST 7.32‑2017"
11+
homepage = "https://typst-gost.ru"
12+
repository = "https://github.com/typst-g7-32/modern-g7-32"
13+
keywords = ["gost", "7.32", "2017", "paper", "document", "russian"]
14+
categories = ["report", "components", "scripting"]
15+
disciplines = ["education"]
16+
compiler = "0.13.0"
17+
exclude = [".github", "docs", "tests", "assets"]
18+
19+
[template]
20+
path = "template"
21+
entrypoint = "main.typ"
22+
thumbnail = "assets/thumbnail.png"

0 commit comments

Comments
 (0)
Please sign in to comment.