From a691d84e1b0df8c2ca0f7e1e98328c775f989940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Mekina?= Date: Fri, 3 Oct 2025 11:06:50 +0200 Subject: [PATCH] implement attachments --- template/attachments.typ | 167 ++++++++++++++++++++++++++++++++++++ template/classic/bp.typ | 4 +- template/classic/common.typ | 4 +- template/classic/other.typ | 6 +- template/lang.json | 8 ++ template/template.typ | 3 + 6 files changed, 186 insertions(+), 6 deletions(-) create mode 100644 template/attachments.typ diff --git a/template/attachments.typ b/template/attachments.typ new file mode 100644 index 0000000..71d4aca --- /dev/null +++ b/template/attachments.typ @@ -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%); + } + } + } +} diff --git a/template/classic/bp.typ b/template/classic/bp.typ index 15b0c6e..e341afd 100644 --- a/template/classic/bp.typ +++ b/template/classic/bp.typ @@ -11,6 +11,7 @@ tablelist, bibliogr ) +#import "../attachments.typ": attachment_list #import "../utils.typ": is_none, assert_dict_has, assert_not_none #let bp( @@ -52,7 +53,8 @@ imagelist(language); tablelist(language); pagebreak(weak: true); - content + content; bibliogr(language, citation_file); + attachment_list(language); }); } diff --git a/template/classic/common.typ b/template/classic/common.typ index 09c1124..ee679ec 100644 --- a/template/classic/common.typ +++ b/template/classic/common.typ @@ -251,7 +251,7 @@ stack( dir: ltr, text(numbering( - "1. 1", + "1.1", counter(heading).at(element.location()).at(0), counter(selector).at(element.location()).at(0), )), @@ -263,7 +263,7 @@ ), ) } - heading(title, numbering: none, outlined: false); + heading(title, numbering: none); for el in query(figure.where(kind: selector)) { if is_none(el.caption) { continue; diff --git a/template/classic/other.typ b/template/classic/other.typ index 3bd9707..183d528 100644 --- a/template/classic/other.typ +++ b/template/classic/other.typ @@ -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( @@ -31,9 +32,8 @@ imagelist(language); tablelist(language); pagebreak(to: "even", weak: true); - content - - // bibliography + content; bibliography(citation_file, style: "../tul_citace.csl"); + attachment_list(language); }); } diff --git a/template/lang.json b/template/lang.json index dc3e69f..16f2d12 100644 --- a/template/lang.json +++ b/template/lang.json @@ -40,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í" }, @@ -74,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" } diff --git a/template/template.typ b/template/template.typ index 53644b2..13f2545 100644 --- a/template/template.typ +++ b/template/template.typ @@ -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 //