12 Commits
10 changed files with 288 additions and 162 deletions
+2
View File
@@ -1 +1,3 @@
template/*.pdf template/*.pdf
tests/
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Dominykas Svetikas
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+8
View File
@@ -1,3 +1,11 @@
/// Whether to number figures per section or not.
/// If true, figures will be numbered like "1.1", "1.2", "2.1", etc.
/// If false, figures will be numbered like "1", "2", "3", etc.
///
/// TODO: figure out how to deal with numbering on sections such as
/// Appendix and other non-standard sections.
#let __FIG_PER_SECTION = state("svetikas.lt/ktu-paper/figure-numbering-per-section", false)
#let __DEBUG_AUTHOR_TABLE = state("$ktu-template-DEBUG_AUTHOR_TABLE", false) #let __DEBUG_AUTHOR_TABLE = state("$ktu-template-DEBUG_AUTHOR_TABLE", false)
#let debug-author-table() = { #let debug-author-table() = {
__DEBUG_AUTHOR_TABLE.update(true) __DEBUG_AUTHOR_TABLE.update(true)
+137
View File
@@ -0,0 +1,137 @@
// This file includes logic for displaying figures in the document, including numbering, captions, and references. It also includes logic for handling figure numbering per section or globally, based on the configuration set in `config.typ`.
#import "config.typ": __FIG_PER_SECTION
/// Localizations for various recognized referencable figure types.
/// This is used to provide the correct supplement text for each type of figure
/// when generating references, captions and outlines.
#let __figureNames = (
table: (
supplement: "lentelė",
),
image: (
supplement: "pav",
),
raw: (
supplement: "kodo frag",
),
equation: (
supplement: "lygtis",
),
)
/// Helper function to get the supplement text for a given figure type.
#let __fig-supplement(func) = {
return __figureNames.at(repr(func)).supplement
}
#let __fig-func-to-name(func) = {
if func == table {
return "lentelė"
}
if func == image {
return "pav."
}
if func == grid {
return "pav."
}
return func
}
/// Helper function to resolve figure numbers (either global "1" or section-based "1.1").
/// It takes a figure item and referenced location and returns the appropriate number as a string.
#let __get-figure-number(fig-item, loc) = {
if type(fig-item) != content {
panic("can only get figure number for content elements")
}
if fig-item.func() != figure {
panic("can only get figure number for figure elements")
}
let perSection = __FIG_PER_SECTION.at(loc)
if perSection {
let chapter = counter(heading).at(loc).at(0, default: 0)
let figNum = counter(figure.where(kind: fig-item.kind)).at(loc).at(0, default: 1)
str(chapter) + "." + str(figNum)
} else {
let caption = fig-item.caption
if caption != none and caption.numbering != none {
str(caption.counter.at(loc).at(0))
} else {
str(counter(figure.where(kind: fig-item.kind)).at(loc).at(0))
}
}
}
/// Helper function to get the number of a locatable element (figure, table, etc.)
/// based on its location in the document. It returns the number as a string, formatted according to the current numbering scheme.
#let __get-locatable-element-number(element) = context {
if type(element) != content {
panic("can only reference elements of type content")
}
let loc = element.location()
let elem = query(loc)
let fig = elem.at(0)
if fig.func() != figure {
panic("referenced element does not have a figure function")
}
__get-figure-number(fig, loc)
}
/// Formats a reference to a figure, table, or other numbered element.
#let __format-ref(it) = {
let el = it.element
if el == none {
return it
}
// Override equation references
if el.func() == math.equation {
link(
el.location(),
numbering(el.numbering, ..counter(math.equation).at(el.location()))
)
// Override references to figures
} else if el.func() == figure {
let fig = el.body
let figType = fig.func()
let numStr = __get-figure-number(el, el.location())
link(el.location(), [
(žr. #numStr #__fig-func-to-name(figType))
])
} else {
it
}
}
/// Formats a figure with a caption and number.
#let __format-figure(it) = {
let separator = [.]
let caption = it.caption
// If caption is not provided, just show the figure body
if caption == none {
return it.body
}
let counter = caption.counter
let supplement = caption.supplement
if it.body.func() == math.equation {
supplement = __fig-supplement("equation")
} else {
supplement = __fig-supplement(it.kind)
}
set text(size: 11pt)
let fig-num = context __get-figure-number(it, here())
// Tables have captions at the top
if it.kind == table {
align(left)[*#fig-num #supplement#separator* #caption.body]
it.body
} else {
it.body
align(center)[*#fig-num #supplement#separator* #caption.body]
}
}
+1 -1
View File
@@ -201,4 +201,4 @@
#h(58%) _Patvirtinta elektroniniu būdu_ #h(58%) _Patvirtinta elektroniniu būdu_
] ]
} }
+63 -152
View File
@@ -1,9 +1,18 @@
#import "@preview/codly:1.3.0": codly, codly-init #import "@preview/codly:1.3.0": codly, codly-init
#import "config.typ": debug-author-table, enable-heading-experiment, __HEADING_EXPERIMENT #import "config.typ": debug-author-table, enable-heading-experiment, __HEADING_EXPERIMENT, __FIG_PER_SECTION
#import "fragments.typ": ( #import "fragments.typ": (
ktu-table, ktu-table,
ktu-heading-page-centered, ktu-heading-page-normal, ktu-academic-honestly-declaration-page ktu-heading-page-centered, ktu-heading-page-normal, ktu-academic-honestly-declaration-page
) )
#import "figures.typ": (
__fig-supplement,
__get-figure-number,
__get-locatable-element-number,
__format-ref,
__format-figure,
)
/** /**
* Paruošta pagal "Rašto ..." * Paruošta pagal "Rašto ..."
@@ -26,26 +35,6 @@
[#text] [#text]
} }
#let figureDefinitions = (
table: (
outlineFormat: (number) => [*#number lentelė.*],
captionFormat: (number) => [*#number lentelė.*],
),
)
#let custom-figure(
kind,
caption: none,
body
) = {
figure(
body,
kind: kind,
caption: caption,
supplement: "CUSTOM-FIGURE"
)
}
#let appendix-item(body) = { #let appendix-item(body) = {
heading( heading(
metadata("appendix-heading") + "priedas. " + body, metadata("appendix-heading") + "priedas. " + body,
@@ -54,14 +43,6 @@
) )
} }
// Stores localized names for various figure types
#let figureNames = (
table: ("lentelė"),
image: ("pav"),
code: ("kodo frag"),
equation: ("lygtis"),
)
// Utility function to round numbers with padded zeros // Utility function to round numbers with padded zeros
#let round-and-pad(number, decimals) = { #let round-and-pad(number, decimals) = {
let rounded = calc.round(number, digits: decimals) let rounded = calc.round(number, digits: decimals)
@@ -89,17 +70,20 @@
integer-part + padded-decimal integer-part + padded-decimal
} }
/// Returns a number for the given reference target. /// Returns a number for the given reference target.
/// - target (label): Label to get the reference number for. /// - target (label): Label to get the reference number for.
#let ref-no(target) = context { #let ref-no(target) = context {
if type(target) != label {
panic("ref-no expects a label as input")
}
let loc = locate(target) let loc = locate(target)
let elem = query(target) let elem = query(target)
let item = elem.at(0) let item = elem.at(0)
// If it's a figure, return the figure number // If it's a figure, return the figure number
if item.func() == figure { if item.func() == figure {
link(loc, [#counter(figure.where(kind: item.kind)).at(loc).at(0)]) link(loc, [#__get-figure-number(item, loc)])
} else if item.func() == heading { } else if item.func() == heading {
let fullValue = counter(heading).at(loc) let fullValue = counter(heading).at(loc)
@@ -116,20 +100,6 @@
} }
} }
#let __ref-no-element(element) = context {
// Ensure that we pass an element
if type(element) != content {
panic("can only reference elements of type content")
}
let loc = element.location()
let elem = query(loc)
let fig = elem.at(0)
if fig.func() != figure {
panic("referencing unknown type: " + fig.func())
}
fig.caption.counter.at(loc).at(0)
}
// Constructs a reference from multiple targets // Constructs a reference from multiple targets
#let custom-ref(..targets) = context { #let custom-ref(..targets) = context {
let collected = (:) let collected = (:)
@@ -187,7 +157,7 @@
} }
collected.at(elementName).push( collected.at(elementName).push(
(loc, fig.caption.counter.at(loc).at(0)) (loc, __get-figure-number(fig, loc))
) )
} }
@@ -252,7 +222,10 @@
} else if (o.target == figure.where(kind: image)) { } else if (o.target == figure.where(kind: image)) {
show outline.entry: it => link( show outline.entry: it => link(
it.element.location(), it.element.location(),
it.indented([*#__ref-no-element(it.element) #figureNames.image.*], [ #it.inner()], gap: 0pt), it.indented(
[*#__get-locatable-element-number(it.element) #__fig-supplement(image).*],
[ #it.inner()], gap: 0pt
),
) )
o o
@@ -260,7 +233,10 @@
} else if (o.target == figure.where(kind: table)) { } else if (o.target == figure.where(kind: table)) {
show outline.entry: it => link( show outline.entry: it => link(
it.element.location(), it.element.location(),
it.indented([*#__ref-no-element(it.element) #figureNames.table.*], [ #it.inner()], gap: 0pt), it.indented(
[*#__get-locatable-element-number(it.element) #__fig-supplement(table).*],
[ #it.inner()], gap: 0pt
),
) )
o o
@@ -268,7 +244,10 @@
} else if (o.target == figure.where(kind: raw)) { } else if (o.target == figure.where(kind: raw)) {
show outline.entry: it => link( show outline.entry: it => link(
it.element.location(), it.element.location(),
it.indented([*#__ref-no-element(it.element) #figureNames.code.*], [ #it.inner()], gap: 0pt), it.indented(
[*#__get-locatable-element-number(it.element) #__fig-supplement(raw).*],
[ #it.inner()], gap: 0pt
),
) )
o o
@@ -286,7 +265,10 @@
show outline.entry: it => link( show outline.entry: it => link(
it.element.location(), it.element.location(),
it.indented([*#__ref-no-element(it.element) unknown.*], it.inner(), gap: 0pt), it.indented(
[*#__get-locatable-element-number(it.element) unknown.*],
it.inner(), gap: 0pt
),
) )
o o
} }
@@ -294,19 +276,6 @@
outline(depth: depth, indent: indent, target: target, title: title) outline(depth: depth, indent: indent, target: target, title: title)
} }
#let func-to-name(func) = {
if func == table {
return "lentelė"
}
if func == image {
return "pav."
}
if func == grid {
return "pav."
}
return func
}
// Doesn't work in Typst 0.12.0 due to relative paths // Doesn't work in Typst 0.12.0 due to relative paths
#let bibliography-list(path) = { #let bibliography-list(path) = {
bibliography(path, title: "Literatūros sąrašas", full: true) bibliography(path, title: "Literatūros sąrašas", full: true)
@@ -361,7 +330,7 @@
body body
} }
#let __page_rules(body) = { #let __page_rules(font: "Times New Roman", body) = {
// Page size and margins // Page size and margins
set page( set page(
paper: "a4", paper: "a4",
@@ -390,7 +359,7 @@
// Set text font // Set text font
set text( set text(
font: "Times New Roman", font: font,
size: 12pt, size: 12pt,
lang: "lt", lang: "lt",
// Change where the bounding box is drawn for the text // Change where the bounding box is drawn for the text
@@ -411,8 +380,11 @@
body body
} }
#let setup-page(body) = { #let setup-page(
show: __page_rules.with() font: "Times New Roman",
body
) = {
show: __page_rules.with(font: font)
// Pagal formaliuosius rašto darbų reikalavimus // Pagal formaliuosius rašto darbų reikalavimus
@@ -456,7 +428,13 @@
if it.numbering != none and it.level == 1 { if it.numbering != none and it.level == 1 {
// abipusė lygiuotė // abipusė lygiuotė
set par(justify: true) set par(justify: true)
//set align
// Reset figure counters if numbering per section is enabled
if __FIG_PER_SECTION.get() {
counter(figure.where(kind: image)).update(0)
counter(figure.where(kind: table)).update(0)
counter(figure.where(kind: raw)).update(0)
}
// po antraštės - 10 pt // po antraštės - 10 pt
block[#counter(heading).display() #it.body] block[#counter(heading).display() #it.body]
@@ -487,94 +465,27 @@
} }
} }
// Override references for equations // Reference style and numbering
show ref: it => { show ref: it => __format-ref(it)
let el = it.element
if el == none { // Figure captions and numbering
return it show figure: it => __format-figure(it)
}
// Override equation references // Bibliography citing style
if el.func() == math.equation {
link(
el.location(),
numbering(el.numbering, ..counter(math.equation).at(el.location()))
)
// Override references to figures
} else if el.func() == figure {
let fig = el.body
let figType = fig.func()
link(el.location(), [
(žr. #numbering(el.numbering, ..counter(figure.where(kind: figType)).at(el.location())) #func-to-name(figType))
])
} else {
it
}
}
// Change captions for figures
show figure: it => {
let separator = [.]
let caption = it.caption
// If caption is not provided, just show the figure body
if caption == none {
it.body
return
}
let counter = caption.counter
let supplement = caption.supplement
if it.body.func() == math.equation {
supplement = figureNames.equation
} else if it.kind == image {
supplement = figureNames.image
} else if it.kind == table {
supplement = figureNames.table
} else if it.kind == raw {
supplement = figureNames.code
}
// Tables have captions at the top
set text(size: 11pt)
if it.kind == table {
{set align(left)
[*#context counter.display(caption.numbering) #supplement#separator* #caption.body]}
it.body
} else {
it.body
[*#context counter.display(caption.numbering) #supplement#separator* #caption.body]
}
}
// Lentelės pirma eilutė
/*
show table.cell.where(y: 0): it => context {
set align(left)
set par(justify: false)
set text(size: TableHeadSize.get()) // Šrifto dydis
v(3pt)
// Paryškintas
[*#it*]
v(3pt)
}
show table.cell: it => context {
set align(left)
set par(justify: false)
set text(size: TableCellSize.get())
v(3pt)
[#it]
v(3pt)
}*/
// Set bibliography and citing style
set bibliography(style: "assets/iso690-numeric-lt.csl") set bibliography(style: "assets/iso690-numeric-lt.csl")
// Return the body
body body
} }
/// Configure the template for a KTU paper.
/// - font (str): Font to use for the paper.
/// - figureNumberingPerSection (bool): Whether to number figures per section rather than globally.
#let ktu-paper(
font: "Times New Roman",
figureNumberingPerSection: false,
body
) = context {
show: setup-page.with(font: font)
__FIG_PER_SECTION.update(figureNumberingPerSection)
body
}
+5 -3
View File
@@ -1,12 +1,14 @@
#import "@local/ktu-paper:0.1.0": ( #import "@local/ktu-paper:0.2.1": (
setup-page, setup-code, ktu-paper, setup-code,
ktu-table-of-contents, ktu-picture-list, ktu-table-list, ktu-table-of-contents, ktu-picture-list, ktu-table-list,
ktu-heading-page-normal, ktu-heading-page-normal,
ktu-table, ktu-table,
custom-ref, ref-no custom-ref, ref-no
) )
#show: setup-page.with() #show: ktu-paper.with(
font: "Times New Roman",
)
#show: setup-code.with() #show: setup-code.with()
#show math.equation: it => { #show math.equation: it => {
+5 -3
View File
@@ -1,5 +1,5 @@
#import "@local/ktu-paper:0.1.0": ( #import "@local/ktu-paper:0.2.1": (
setup-page, setup-code, ktu-paper, setup-code,
ktu-table-of-contents, ktu-picture-list, ktu-table-list, ktu-table-of-contents, ktu-picture-list, ktu-table-list,
ktu-heading-page-normal, ktu-heading-page-normal,
ktu-table, ktu-table,
@@ -7,7 +7,9 @@
custom-ref, ref-no custom-ref, ref-no
) )
#show: setup-page.with() #show: ktu-paper.with(
font: "Times New Roman",
)
#show: setup-code.with() #show: setup-code.with()
#show figure: it => { #show figure: it => {
+43
View File
@@ -0,0 +1,43 @@
#!/bin/bash
# Clean up previous test results
if [ -d "tests/original-res" ]; then
rm -r tests/original-res
fi
if [ -d "tests/modified-res" ]; then
rm -r tests/modified-res
fi
if [ -d "tests/modified" ]; then
rm -r tests/modified
fi
if [ -d "tests/original/src" ]; then
rm -r tests/original/src
fi
mkdir -p tests/original-res
mkdir -p tests/modified-res
# Copy current template
cp -r src/ tests/original/src
cp -r tests/original tests/modified
# replace the "#import "@local/ktu-paper:0.1.0""
# with "#import "./src/lib.typ"" in the modified files
find tests/modified -type f -name "*.typ" -exec \
sed -i 's/#import "@local\/ktu-paper:0.1.0"/#import ".\/src\/lib.typ"/g' {} \;
find tests/modified -type f -name "ataskaita.typ" -exec \
sed -i 's/setup-page/ktu-paper/g' {} \;
# Compile the original document
typst compile --root tests/original tests/original/ataskaita.typ "tests/original-res/{0p}.png"
# Compile the modified document
typst compile --root tests/modified tests/modified/ataskaita.typ "tests/modified-res/{0p}.png"
typst compile --root tests/modified tests/modified/ataskaita.typ "tests/modified.pdf"
# Compare the results
diff -r tests/original-res tests/modified-res
if [ $? -eq 0 ]; then
echo "No differences found between original and modified results."
else
echo "Differences found between original and modified results."
fi
+3 -3
View File
@@ -1,9 +1,9 @@
[package] [package]
name = "ktu-paper" name = "ktu-paper"
version = "0.1.0" version = "0.2.1"
compiler = "0.12.0" compiler = "0.15.0"
entrypoint = "src/lib.typ" entrypoint = "src/lib.typ"
repository = "https://git.svetikas.lt/JustAnyone/dotfiles" repository = "https://github.com/svetikas/ktu-paper"
authors = ["Dominykas Svetikas <https://svetikas.lt>"] authors = ["Dominykas Svetikas <https://svetikas.lt>"]
license = "MIT" license = "MIT"
description = "KTU report template to use for writing lab reports or final thesis." description = "KTU report template to use for writing lab reports or final thesis."