// Титульный лист практической работы (СФУ). // Экспортирует функцию title-page(number) — вызывается из main.typ перед // каждой работой: #title-page(2), #title-page(3), ... Тема подставляется // из словаря topics ниже. Данные студента/преподавателя — в словаре task. // Подключается первой страницей в main.typ. #let topics = ( "2": "Проектирование трассы", "3": "Конструирование тоннельных обделок сводчатого очертания", "6": "Расчёт параметров буровзрывных работ", "7": "Расчёт элементов проходческих работ", "8": "Расчёт временной крепи тоннеля", ) #let title-page(work-number) = { let topic = topics.at(str(work-number), default: none) if topic == none { panic("Нет темы для практической работы №" + str(work-number) + " — добавьте её в словарь topics") } let task = ( ministry: "Министерство науки и высшего образования РФ", university_prename: "Федеральное государственное автономное\nобразовательное учреждение высшего образования\n", university_shorten: "«СИБИРСКИЙ ФЕДЕРАЛЬНЫЙ УНИВЕРСИТЕТ»", institute: "Институт цветных металлов", department: "Шахтное и подземное строительство", work_type: "ПРАКТИЧЕСКАЯ РАБОТА №" + str(work-number), topic: topic, teacher: "Г. С. Курчин", group: "ГГ21-10", student_short_name: "А.С. Ткаченко", city: "Красноярск", year: "2026 г.", ) // ── Страница ─────────────────────────────────────────────────────────── // Поля симметричные: центр текстовой колонки совпадает с центром листа, // поэтому для центрирования достаточно обычного align(center). [ #set page(paper: "a4", margin: (x: 20mm, y: 20mm), header: none, footer: none, numbering: none, foreground: none) #set text(font: "Times New Roman", size: 14pt, lang: "ru") #set par(justify: false, first-line-indent: 0pt, leading: 0.65em) // ── Поле: значение на черте + подпись под чертой ─────────────────────── // Одна и та же геометрия у всех полей листа: черта значения — underline // на базовой линии текста, пустая черта — line на той же высоте // (blank-line-dy). Оба варианта лежат в слоте фиксированной высоты, // поэтому подписи всегда на одинаковом расстоянии от своих черт, а // черты соседних полей — на одной горизонтали. // value = none → пустая черта (для подписи), требуется width. #let label-size = 10pt #let field-stroke = 0.7pt + black #let slot-height = 16pt // высота слота со значением #let blank-line-dy = 11.5pt // уровень пустой черты = уровень underline #let label-gap = 2.8mm // зазор от черты до подписи #let label-lift = 10.5pt // компенсация верхнего интервала строки подписи // ── Личная подпись студента ───────────────────────────────────────────── // Картинка повёрнута на 10° по часовой, фон прозрачный. // Путь от корня проекта (как у остальных include в main.typ); // отдельная компиляция: typst compile --root . chapters/title-page.typ #let student-signature = "/assets/images/signature.png" #let signature-height = 13mm // высота картинки подписи #let signature-gap = -1mm // зазор до черты; < 0 — подпись заходит на черту #let signature-dx = 0mm // ручная подгонка: + вправо, − влево #let signature-dy = 2mm // ручная подгонка: + вниз, − вверх // Единый помощник. Одна и та же геометрия у всех полей листа: // черты соседних полей всегда лежат на одной горизонтали, подпись — // всегда под чертой на одинаковом расстоянии. // value = none → пустая черта (для подписи), требуется width; // signature → картинка подписи над пустой чертой. #let field(value, label, width: 100%, signature: none) = box(width: width)[ #grid( columns: 100%, inset: 0pt, align: center + top, rows: (0pt, slot-height, auto), // строка нулевой высоты: подпись висит над чертой, ничего не сдвигая [ #if signature != none [ #move( dx: signature-dx, dy: blank-line-dy - signature-gap - signature-height + signature-dy, )[ #align(center)[#image(signature, height: signature-height)] ] ] ], // черта со значением (или пустая черта для подписи) [ #if value == none [ #move(dy: blank-line-dy)[#line(length: 100%, stroke: field-stroke)] ] else [ #underline(offset: 2pt, stroke: field-stroke)[#value] ] ], // подпись под чертой [ #v(label-gap + slot-height - blank-line-dy - label-lift) #text(size: label-size)[#label] ], ) ] // ── Шапка ────────────────────────────────────────────────────────────── #align(center)[ #text(size: 14pt)[#task.ministry] #linebreak() #text(size: 12pt)[#task.university_prename] *#task.university_shorten* ] #v(5mm) // ── Институт ─────────────────────────────────────────────────────────── #align(center)[#field(task.institute, [институт], width: 135mm)] #v(4mm) // ── Кафедра ──────────────────────────────────────────────────────────── #align(center)[#field(task.department, [кафедра], width: 145mm)] #v(25mm) // ── Тип работы ───────────────────────────────────────────────────────── #align(center)[#text(size: 16pt, weight: "bold")[#task.work_type]] #v(0mm) // ── Тема ─────────────────────────────────────────────────────────────── #align(center)[ #grid( columns: 100%, inset: 0pt, align: center + top, [ #underline(offset: 2pt, stroke: field-stroke)[#task.topic] ], [ #v(label-gap + 2pt) #text(size: label-size)[тема] ], ) ] #v(45mm) // ── Подписи ──────────────────────────────────────────────────────────── // Колонки: роль | группа | зазор | подпись | зазор | ФИО. // Черты для подписи и подчёркнутые значения в одной строке таблицы // находятся на одном уровне благодаря общей геометрии field(). // Колонка подписи 33 мм — вмещает картинку подписи целиком // (ячейки таблицы обрезают содержимое по своей ширине). #table( columns: (34mm, 49mm, 4mm, 33mm, 4mm, 46mm), stroke: none, inset: (x: 0pt, y: 7mm), align: (left, center, center, center, center, center), table.cell(colspan: 2)[Преподаватель], [], field(none, [подпись, дата], width: 33mm), [], field(task.teacher, [инициалы, фамилия]), [Студент], field(task.group, [номер группы, зачетной книжки]), [], field(none, [подпись, дата], width: 33mm, signature: student-signature), [], field(task.student_short_name, [инициалы, фамилия]), ) // ── Город, год — по центру листа, у нижнего поля ─────────────────────── #place(bottom + center)[#task.city #task.year] ] }