7 Commits

Author SHA1 Message Date
2787bed48d fix Matěj damage 2025-10-03 11:09:23 +02:00
a691d84e1b implement attachments 2025-10-03 11:07:22 +02:00
Matej-Zucha-TUL
a0c75deba0 Change example document signature to reflect reality better 2025-10-03 09:26:49 +02:00
Matej-Zucha-TUL
1a7418d2cd Add optional consultant parameter 2025-10-03 09:25:37 +02:00
2e300ded3c add pronouns picking for english 2025-10-02 23:28:07 +02:00
0e9fbcdc93 update doc comment for the tultemplate2 function 2025-10-02 23:27:43 +02:00
bfe08ac9b4 fix some image/table list problems 2025-10-02 23:26:48 +02:00
8 changed files with 244 additions and 46 deletions

167
template/attachments.typ Normal file
View File

@@ -0,0 +1,167 @@
#import "utils.typ": assert_type_signature, is_none
#import "lang.typ": get_lang_item
#let attachment_data = state("attachment_data");
#let attach_link(name, link) = {
assert_type_signature(link, "string", "attach link argument");
assert_type_signature(name, "string", "attach link name argument");
("link", link, name)
}
#let attach_content(name, inner_content) = {
assert_type_signature(inner_content, "content", "attach content argument");
assert_type_signature(name, "string", "attach content name argument");
("content", inner_content, name)
}
#let attach_pdf(name, filepath) = {
assert_type_signature(filepath, "string", "attach pdf argument");
assert_type_signature(name, "string", "attach pdf name argument");
("pdf", filepath, name)
}
#let attach_file_reference(name, filename) = {
assert_type_signature(filename, "string", "attach file reference filename argument");
assert_type_signature(name, "string", "attach file reference name argument");
("ref", filename, name)
}
#let make_content_anchor(idx) = {
"attachment_" + str(idx + 1)
}
#let generate_attachment_content(attachment, idx) = {
let attachment_type = attachment.at(0);
if attachment_type == "content" {
let anchor = make_content_anchor(idx);
[#metadata(attachment.at(1)) #label(anchor)];
}
}
#let generate_attachment_info(attachment, idx) = {
let attachment_type = attachment.at(0);
if type(attachment_type) != str {
panic("invalid attachment - wrap the attach using: attach_content, attach_pdf, ...");
}
if attachment_type == "content" {
let anchor = make_content_anchor(idx);
"(\"content\",\"" + anchor + "\",\"" + attachment.at(2) + "\")"
} else if attachment_type == "pdf" {
"(\"pdf\",\"" + attachment.at(1) + "\",\"" + attachment.at(2) + "\")"
} else if (
attachment_type == "pdf" or
attachment_type == "link" or
attachment_type == "ref"
) {
"(" + attachment.map((v) => { "\"" + v + "\"" }).join(",") + ",)"
} else {
panic("unknown attachment type '" + attachment_type + "'");
}
}
#let attachments(attachments) = {
assert_type_signature(
attachments, "array[array[string | content]] | array[string | content]", "attachments"
);
context {
if not is_none(attachment_data.get()) {
panic("re-definition of attachments - attachments must only be defined once");
}
if attachments.len() == 0 {
attachment_data.update("false");
} else {
attachment_data.update({
"(" + if type(attachments) == array and type(attachments.at(0)) == array {
for (idx, attachment) in attachments.enumerate() {
(generate_attachment_info(attachment, idx),)
}.join(", ")
} else {
generate_attachment_info(attachments, 0)
} + ",)"
})
if type(attachments) == array and type(attachments.at(0)) == array {
for (idx, attachment) in attachments.enumerate() {
generate_attachment_content(attachment, idx);
}
} else {
generate_attachment_content(attachments, 0);
}
}
}
}
#let list_entry(language, entry, is_embedded) = {
let entry_type = entry.at(0);
entry.at(2);
if entry_type == "link" {
": ";
link(entry.at(1));
} else if entry_type == "ref" {
": soubor ";
raw(entry.at(1));
}
if is_embedded {
text(
" (" + get_lang_item(language, "attached_bellow") + ")",
style: "italic",
fill: black.lighten(50%),
);
}
}
#let attachment_list(language) = {
context {
let data = attachment_data.get();
if is_none(data) {
return;
}
let data = eval(data);
heading(get_lang_item(language, "attachments"), numbering: none);
// listing
let has_embedded = false;
let enum_items = ();
for attachment in data {
let attachment_type = attachment.at(0);
let is_embedded = false;
if attachment_type == "content" or attachment_type == "pdf" {
has_embedded = true;
is_embedded = true;
}
enum_items.push(list_entry(language, attachment, is_embedded));
}
enum(..enum_items.map((v) => { enum.item(v) }), spacing: 1em);
if has_embedded {
pagebreak(weak: true);
}
// embedded
set page(footer: none);
for (idx, attachment) in data.enumerate() {
let attachment_type = attachment.at(0);
if attachment_type == "content" {
heading(
level: 2,
get_lang_item(language, "attachment") + " " + str(idx + 1),
numbering: none,
outlined: false,
);
query(label(attachment.at(1))).at(0).value;
} else if attachment_type == "pdf" {
import "@preview/muchpdf:0.1.1": muchpdf
page(place(center + horizon, heading(
level: 2,
get_lang_item(language, "attachment") + " " +
str(idx + 1) + " " +
get_lang_item(language, "next_page_attachment"),
numbering: none,
outlined: false,
)), margin: 0em);
set page(margin: 0em);
muchpdf(read("../" + attachment.at(1), encoding: none), width: 100%);
}
}
}
}

View File

@@ -11,6 +11,7 @@
tablelist,
bibliogr
)
#import "../attachments.typ": attachment_list
#import "../utils.typ": is_none, assert_dict_has, assert_not_none
#let bp(
@@ -18,7 +19,7 @@
faculty_id, faculty_color, language, assignment_document, citation_file,
// document info
title, author, author_gender, supervisor, study_programme, study_branch, abstract_content,
title, author, author_gender, supervisor, consultant, study_programme, study_branch, abstract_content,
keywords,
content
@@ -41,7 +42,7 @@
assert_not_none(author_gender, "author gender");
}
mainpage(faculty_id, language, "bp", title, author, supervisor, study_programme, study_branch);
mainpage(faculty_id, language, "bp", title, author, supervisor, consultant, study_programme, study_branch);
assignment(language, assignment_document);
default_styling(false, faculty_color, {
disclaimer(language, faculty_id, "bp", author, author_gender);
@@ -51,7 +52,9 @@
abbrlist(language);
imagelist(language);
tablelist(language);
content
pagebreak(weak: true);
content;
bibliogr(language, citation_file);
attachment_list(language);
});
}

View File

@@ -12,7 +12,7 @@
language, faculty_id, document_type, citation_file, assignment_document,
// document info
title, author, author_gender, supervisor, study_programme, study_branch, abstract, keywords,
title, author, author_gender, supervisor, consultant, study_programme, study_branch, abstract, keywords,
// content
content,
@@ -38,6 +38,7 @@
author,
author_gender,
supervisor,
consultant,
study_programme,
study_branch,
abstract,

View File

@@ -96,7 +96,7 @@
faculty_id,
language,
document_type,
title, author, supervisor, study_programme, study_branch,
title, author, supervisor, consultant, study_programme, study_branch,
) = {
let info_name_value_padding = 5em;
let info_name_min_width = 10em;
@@ -121,6 +121,7 @@
("study_branch", study_branch, false),
("author", author, true),
("supervisor", supervisor, false),
("consultant", consultant, false),
)
context {
let max_field_name_width = calc.max(..info_fields.map((v) => {
@@ -155,13 +156,13 @@
faculty_id,
language,
document_type,
title, author, supervisor, study_programme, study_branch
title, author, supervisor, consultant, study_programme, study_branch
) = {
import "../utils.typ": has_all_none, map_none
let nonetype = type(none);
page({
if has_all_none((
document_type, title, author, supervisor, study_programme,
document_type, title, author, supervisor, consultant, study_programme,
)) {
place(center + horizon, align(left, faculty_logotype(faculty_id, language)));
} else {
@@ -169,7 +170,7 @@
align({
info(
faculty_id, language, document_type, map_none(title, (v) => v.at(language)),
author, supervisor, map_none(study_programme, (v) => v.at(language)),
author, supervisor, consultant, map_none(study_programme, (v) => v.at(language)),
map_none(study_branch, (v) => v.at(language)),
);
v(5em);
@@ -239,33 +240,35 @@
}
}
// _ OUTLINE
// _ OUTLINE FIGURE INNER
#let _outline_figure_inner(selector, title, body_mapper) = {
show outline.entry: it => {
let entry(selector, element) = {
link(
it.element.location(),
element.location(),
grid(
columns: 3,
gutter: .5em,
stack(
dir: ltr,
text(numbering(
"1. 1",
counter(heading).at(it.element.location()).at(0),
counter(selector).at(it.element.location()).at(0),
"1.1",
counter(heading).at(element.location()).at(0),
counter(selector).at(element.location()).at(0),
)),
h(.5em),
text(body_mapper(it)),
text(body_mapper(element)),
),
box(repeat([.], gap: 0.15em)),
str(it.element.location().page()),
str(element.location().page()),
),
)
}
if not is_none(selector) {
outline(target: selector, title: title);
} else {
outline(title: title);
heading(title, numbering: none);
for el in query(figure.where(kind: selector)) {
if is_none(el.caption) {
continue;
}
entry(figure.where(kind: selector), el);
}
}
@@ -276,7 +279,7 @@
if realcount.final().at(0) == 0 {
return;
}
_outline_figure_inner(figure.where(kind: target), title, (it) => it.element.caption.body);
_outline_figure_inner(target, title, (it) => it.caption.body);
}
}

View File

@@ -10,6 +10,7 @@
imagelist,
tablelist,
)
#import "../attachments.typ": attachment_list
#import "../utils.typ": is_none, assert_not_none, assert_dict_has, assert_in_arr
#let other(
@@ -17,23 +18,22 @@
faculty_id, faculty_color, language, assignment_document, citation_file,
// document info
title, author, _, supervisor, study_programme, study_branch, abstract_content, keywords,
title, author, _, supervisor, consultant, study_programme, study_branch, abstract_content, keywords,
content
) = {
assert_not_none(title, "title");
assert_dict_has((language,), title, "title");
mainpage(faculty_id, language, none, title, author, supervisor, study_programme, study_branch);
mainpage(faculty_id, language, none, title, author, supervisor, consultant, study_programme, study_branch);
default_styling(true, faculty_color, {
toc(language);
abbrlist(language);
imagelist(language);
tablelist(language);
pagebreak(to: "even", weak: true);
content
// bibliography
content;
bibliography(citation_file, style: "../tul_citace.csl");
attachment_list(language);
});
}

View File

@@ -3,6 +3,7 @@
"author": "Autor",
"authors": "Autoři",
"supervisor": "Vedoucí práce",
"consultant": "Konzultant práce",
"study_programme": "Studijní program",
"study_branch": "Studijní obor",
@@ -39,6 +40,10 @@
"abbrs": "Seznam zkratek",
"image_list": "Seznam obrázků",
"table_list": "Seznam tabulek",
"attachments": "Přílohy",
"attachment": "Příloha",
"next_page_attachment": "začíná na další straně",
"attached_bellow": "dále přiloženo",
"place_assignment": "Sem vložte zadání"
},
@@ -47,6 +52,7 @@
"author": "Author",
"authors": "Authors",
"supervisor": "Supervisor",
"consultant": "Consultant",
"study_programme": "Study programme",
"study_branch": "Study branch",
@@ -57,7 +63,7 @@
"toc": "Contents",
"disclaimer": "Declaration",
"disclaimer_content": "I hereby certify, I, myself, have written my {thesis} as an original and primary work using the literature listed below and consulting it with my thesis supervisor and my thesis counsellor.\n\nI acknowledge that my {thesis} is fully governed by Act No. 121/2000 Coll., the Copyright Act, in particular Article 60 School Work.\n\nI acknowledge that the Technical University of Liberec does not infringe my copyrights by using my {thesis} for internal purposes of the Technical University of Liberec.\n\nI am aware of my obligation to inform the Technical University of Liberec on having used or granted license to use the results of my {thesis}; in such a case the Technical University of Liberec may require reimbursement of the costs incurred for creating the result up to their actual amount.\n\nAt the same time, I honestly declare that the text of the printed version of my {thesis} is identical with the text of the electronic version uploaded into the IS STAG.\n\nI acknowledge that the Technical University of Liberec will make my {thesis} public in accordance with paragraph 47b of Act No. 111/1998 Coll., on Higher Education Institutions and on Amendment to Other Acts (the Higher Education Act), as amended.\n\nI am aware of the consequences which may under the Higher Education Act result from a breach of this declaration.",
"disclaimer_content": "{g:I|We} hereby certify, {g:I|we}, {g:myself|ourselves}, have written {g:my|our} {thesis} as an original and primary work using the literature listed below and consulting it with {g:my|our} thesis supervisor and {g:my|our} thesis counsellor.\n\n{g:I|We} acknowledge that {g:my|our} {thesis} is fully governed by Act No. 121/2000 Coll., the Copyright Act, in particular Article 60 School Work.\n\n{g:I|We} acknowledge that the Technical University of Liberec does not infringe {g:my|our} copyrights by using {g:my|our} {thesis} for internal purposes of the Technical University of Liberec.\n\n{g:I|We} {g:am|are} aware of {g:my|our} obligation to inform the Technical University of Liberec on having used or granted license to use the results of {g:my|our} {thesis}; in such a case the Technical University of Liberec may require reimbursement of the costs incurred for creating the result up to their actual amount.\n\nAt the same time, {g:I|we} honestly declare that the text of the printed version of {g:my|our} {thesis} is identical with the text of the electronic version uploaded into the IS STAG.\n\n{g:I|We} acknowledge that the Technical University of Liberec will make {g:my|our} {thesis} public in accordance with paragraph 47b of Act No. 111/1998 Coll., on Higher Education Institutions and on Amendment to Other Acts (the Higher Education Act), as amended.\n\n{g:I|We} {g:am|are} aware of the consequences which may under the Higher Education Act result from a breach of this declaration.",
"disclaimer_replace": {
"bp": {
@@ -72,6 +78,10 @@
"abbrs": "List of abbreviations",
"image_list": "List of images",
"table_list": "List of tables",
"attachments": "Attachments",
"attachment": "Attachment",
"next_page_attachment": "begins on the next page",
"attached_bellow": "attached bellow",
"place_assignment": "Insert your assignment here"
}

View File

@@ -1,4 +1,4 @@
#import "utils.typ": assert_in_dict, assert_in_arr
#import "utils.typ": assert_in_dict, assert_in_arr, map_none, ok_or
#let lang_ids = (
cs: 0,
@@ -22,16 +22,26 @@
}
#let replace_czech_gender(raw, gender) = {
let genders = (
feminine: 1,
masculine: 0,
we: 2,
);
assert_in_dict(gender, genders, "author gender");
raw.replace(regex("\{g:([^|]*)\|([^|]*)\|([^}]*)\}"), (match) => {
if gender == "masculine" {
match.captures.at(0)
} else if gender == "feminine" {
match.captures.at(1)
} else if gender == "we" {
match.captures.at(2)
} else {
panic();
}
match.captures.at(genders.at(gender))
});
}
#let replace_english_pronounce(raw, pronounce) = {
let pronounce = ok_or(pronounce, "me");
let pronouns = (
me: 0,
we: 1,
);
assert_in_dict(pronounce, pronouns, "author gender");
raw.replace(regex("\{g:([^|]*)\|([^}]*)\}"), (match) => {
match.captures.at(pronouns.at(pronounce))
});
}
@@ -39,9 +49,9 @@
let disclaimer = get_lang_item(language, "disclaimer_content");
let replacements = get_lang_item(language, "disclaimer_replace").at(document_type);
if language == "cs" {
let language_genders = ("feminine", "masculine", "we");
assert_in_arr(author_gender, language_genders, "author gender");
disclaimer = replace_czech_gender(disclaimer, author_gender);
} else if language == "en" {
disclaimer = replace_english_pronounce(disclaimer, author_gender);
}
for (key, value) in replacements.pairs() {
disclaimer = disclaimer.replace("{" + key + "}", value);

View File

@@ -7,6 +7,9 @@
// Git: https://git.zumepro.cz/tul/tultemplate2
#import "prototyping.typ": todo, profile
#import "attachments.typ": (
attachments, attach_content, attach_pdf, attach_link, attach_file_reference
)
// TUL Template 2
//
@@ -23,13 +26,13 @@
// - faculty (str): Factulty abbreviation. One of "fs", "ft", "fp", "ef", "fua", "fm", "fzs", "cxi".
// - lang (str): Language code. This can be "cs" or "en".
// - document (str): Type of document. This can be "bp" or "other".
// - title (str): The title of the document.
// - title (dictionary): The title of the document.
// - author (str): The name of the document's author.
// - author_gender (str): The gender of the document's author. Needed only for the `cs` language.
// - supervisor (str): The name of the document's supervisor.
// - programme (str): Study programme.
// - abstract (content): The abstract.
// - keywords (array): The abstract keywords.
// - programme (dictionary): Study programme.
// - abstract (dictionary): The abstract.
// - keywords (dictionary): The abstract keywords.
// - assignment (str): Filepath of the assignment document/page.
// - citations (str): The location of the citation file.
// - content (content): The content of the document
@@ -41,7 +44,7 @@
// document info
title: none, keywords: none, abstract: none, author: none, author_gender: none,
supervisor: none, programme: none, branch: none,
supervisor: none, consultant: none, programme: none, branch: none,
// links
assignment: none, citations: "citations.bib",
@@ -64,6 +67,7 @@
assert_type_signature(author, "string | none", "author argument");
assert_type_signature(author_gender, "string | none", "author gender argument");
assert_type_signature(supervisor, "string | none", "supervisor argument");
assert_type_signature(consultant, "string | none", "consultant argument");
assert_type_signature(
programme, "dictionary[string : string] | none", "study programme argument"
);
@@ -86,8 +90,8 @@
// template call
templates.at(style)(
lang, faculty, document, citations, assignment,
title, author, author_gender, supervisor, programme, branch, abstract, keywords,
content
title, author, author_gender, supervisor, consultant,
programme, branch, abstract, keywords, content
);
import "prototyping.typ": assert_release_ready