commit c7730985d2dcc547bd9dd27cb3cfc23f4452ff2a Author: liuyihui Date: Fri Jan 5 00:55:01 2024 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a136337 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pdf diff --git a/in-dexter.typ b/in-dexter.typ new file mode 100644 index 0000000..6dd799d --- /dev/null +++ b/in-dexter.typ @@ -0,0 +1,92 @@ +// Copyright 2023 Rolf Bremer, Jutta Klebe +// Use of this code is governed by the License in the LICENSE.txt file. +// For a 'how to use this package', see the accompanying .md, .pdf + .typ documents. + +// Index Entry; used to mark an entry in the document to be included in the Index. +// An optional initial may be provided. +#let index( + content, + initial: none +) = locate(loc => [#metadata((initial: initial, content: content, location: loc.position()))]) + +// Create the index page. +#let make-index(title: none, outlined: false) = { + + // This function combines the text(s) of a content. + let content-text(content) = { + let ct = "" + if content.has("text") { + ct = content.text + } + else { + for cc in content.children { + if cc.has("text") { + ct += cc.text + } + } + } + return ct + } + + locate(loc => { + let elements = query(, loc) + let pages = (:) + let words = (:) + for el in elements { + let ct = content-text(el.value.content) + + // Have we already know that entry text? If not, + // add it to our list of entry words + if pages.keys().contains(ct) != true { + pages.insert(ct, ()) + } + + // Add the new page entry to the list. + let ent = (page: el.value.location.page) + if not pages.at(ct).contains(ent){ + pages.at(ct).push(ent) + } + } + + for el in elements { + let ct = content-text(el.value.content) + if words.keys().contains(ct) != true { + words.insert(ct, ()) + words.at(ct) = (content: el.value.content, initial: el.value.initial, pages: pages.at(ct)) + } + } + + // Sort the entries. + let initial-sorted(ct) = { + let char = words.at(ct).initial + if char == none { + char = content-text(words.at(ct).content) + } + return char + } + let sortedkeys = words.keys().sorted(key: initial-sorted) + + // Output. + let register = "" + if title != none { heading(outlined: outlined, numbering: none, title) } + for sk in sortedkeys [ + // Use class specific formatting for the page numbers. + #let formattedPageNumbers = words.at(sk).pages.map(en => { + link((page: en.page, x:0pt, y:0pt))[#strong[#en.page]] + }) + + #let firstCharacter = content-text(words.at(sk).content).first() + #if (words.at(sk).initial != none) { + firstCharacter = words.at(sk).initial + } + + #if firstCharacter != register { + heading(level: 2, numbering: none, outlined: false, firstCharacter) + register = firstCharacter + } + #words.at(sk).content + #box(width: 1fr) + #formattedPageNumbers.join(", ") + ] + }) +} diff --git a/lapreprint.typ b/lapreprint.typ new file mode 100644 index 0000000..936d934 --- /dev/null +++ b/lapreprint.typ @@ -0,0 +1,398 @@ +#import "@preview/i-figured:0.2.3" + +#let count-words(it) = { + let fn = repr(it.func()) + if fn == "sequence" { it.children.map(count-words).sum() } + else if fn == "heading" { count-words(it.body) } + else if fn == "figure" { count-words(it.caption.body) } + else if fn == "text" { it.text.split().len() } + else if fn in ("styled") { count-words(it.child) } + else if fn in ("highlight", "item", "strong", "link") { count-words(it.body) } + else if fn == "equation" { 1 } + else { 0 } +} + +#let lapreprint( + // The paper's title. + title: "Paper Title", + subtitle: none, + + // An array of authors. For each author you can specify a name, orcid, and affiliations. + // affiliations should be content, e.g. "1", which is shown in superscript and should match the affiliations list. + // Everything but but the name is optional. + authors: (), + // This is the affiliations list. Include an id and `name` in each affiliation. These are shown below the authors. + affiliations: (), + // The paper's abstract. Can be omitted if you don't have one. + abstract: none, + // The short-title is shown in the running header + short-title: none, + // The short-citation is shown in the running header, if set to auto it will show the author(s) and the year in APA format. + short-citation: auto, + // The venue is show in the footer + venue: none, + // An image path that is shown in the top right of the page. Can also be content. + logo: none, + // A DOI link, shown in the header on the first page. Should be just the DOI, e.g. `10.10123/123456` ,not a URL + doi: none, + heading-numbering: "1.a.i", + // Show an Open Access badge on the first page, and support open science, default is true, because that is what the default should be. + open-access: true, + // A list of keywords to display after the abstract + keywords: (), + // The "kind" of the content, e.g. "Original Research", this is shown as the title of the margin content on the first page. + kind: none, + // Content to put on the margin of the first page + // Should be a list of dicts with `title` and `content` + margin: (), + paper-size: "us-letter", + // A color for the theme of the document + theme: blue.darken(30%), + // Date published, for example, when you publish your preprint to an archive server. + // To hide the date, set this to `none`. You can also supply a list of dicts with `title` and `date`. + date: datetime.today(), + // Feel free to change this, the font applies to the whole document + font-face: "Noto Sans", + // The path to a bibliography file if you want to cite some external works. + bibliography-file: none, + bibliography-style: "apa", + // The outline + outline-show: true, + outline-depth: 2, + // Language of the document + lang: "en", + // The paper's content. + body +) = { + + /* Logos */ + let orcidSvg = ``` ```.text + + let spacer = text(fill: gray)[#h(8pt) | #h(8pt)] + + let dates; + if (type(date) == "datetime") { + dates = ((title: "Published", date: date),) + }else if (type(date) == "dictionary") { + dates = (date,) + } else { + dates = date + } + date = dates.at(0).date + + // Create a short-citation, e.g. Cockett et al., 2023 + let year = if (date != none) { ", " + date.display("[year]") } + if (short-citation == auto and authors.len() == 1) { + short-citation = authors.at(0).name.split(" ").last() + year + } else if (short-citation == auto and authors.len() == 2) { + short-citation = authors.at(0).name.split(" ").last() + " & " + authors.at(1).name.split(" ").last() + year + } else if (short-citation == auto and authors.len() > 2) { + short-citation = authors.at(0).name.split(" ").last() + " " + emph("et al.") + year + } + + // Set document metadata. + set document(title: title, author: authors.map(author => author.name)) + + if short-title == none { short-title = title } + set page( + paper-size, + margin: (left: 20%, bottom: 1.5cm), + header: locate(loc => { + if(loc.page() == 1) { + let headers = ( + if (open-access) {smallcaps[Open Access]}, + if (doi != none) { link("https://doi.org/" + doi, "https://doi.org/" + doi)} + ) + return align(left, text(size: 8pt, fill: gray, headers.filter(header => header != none).join(spacer))) + } else { + return align(right, text(size: 8pt, fill: gray.darken(50%), + (short-title, short-citation).join(spacer) + )) + } + }), + footer: block( + width: 100%, + stroke: (top: 1pt + gray), + inset: (top: 8pt, right: 2pt), + [ + #grid(columns: (75%, 25%), + align(left, text(size: 9pt, fill: gray.darken(50%), + ( + if(venue != none) {emph(venue)}, + if(date != none) { + if (lang == "en") { + date.display("[month repr:short] [day], [year]") + } else { + [#date.year() 年 #date.month() 月 #date.day() 日] + } + } + ).filter(t => t != none).join(spacer) + )), + align(right)[ + #text( + size: 9pt, fill: gray.darken(50%) + )[ + #counter(page).display() of #locate((loc) => {counter(page).final(loc).first()}) + ] + ] + ) + ] + ) + ) + + // Set the body font. + set text(font: font-face, size: 10pt) + + // Configure lists. + set enum(indent: 10pt, body-indent: 9pt) + set list(indent: 10pt, body-indent: 9pt) + + // Configure i-figured + show figure: i-figured.show-figure + show figure.where( + kind: table + ): set figure.caption(position: top) + show math.equation: i-figured.show-equation + + // Configure Reference Link + show link: it => [#text(fill: theme)[#it]] + show ref: it => [#text(fill: theme)[#it]] + show ref: it => { + let el = it.element + if (el != none) { + let fn = repr(el.func()) + if (fn == "equation") {link( + it.target, + if (lang == "en") {"Eq."} else {"式 "} + + numbering( + el.numbering, + ..counter(math.equation).at(el.location()) + ) + )} else if (fn == "heading") {link( + it.target, + if (lang == "en") {"Section "} else {"第 "} + + numbering( + el.numbering, + ..counter(heading).at(el.location()) + ) + if (lang == "zh") {" 节"} + )} + else { it } + } else {it} + } + + // Configure headings. + set heading(numbering: heading-numbering) + show heading: it => locate(loc => { + // Find out the final number of the heading counter. + let levels = counter(heading).at(loc) + set text(10pt, weight: 400) + if it.level == 1 [ + // First-level headings are centered smallcaps. + // We don't want to number of the acknowledgment section. + #let is-ack = it.body in ([Acknowledgment], [Acknowledgement]) + // #set align(center) + #set text(if is-ack { 10pt } else { 12pt }) + #show: smallcaps + #v(20pt, weak: true) + #if it.numbering != none and not is-ack { + numbering(heading-numbering, ..levels) + h(7pt, weak: true) + } + #it.body + #v(13.75pt, weak: true) + + #for kind in (image, table, raw) { + counter(figure.where(kind: "i-figured-" + repr(kind))).update(0) + } + #counter(math.equation).update(0) + ] else if it.level == 2 [ + // Second-level headings are run-ins. + #set par(first-line-indent: 0pt) + #v(10pt, weak: true) + #if it.numbering != none { + numbering(heading-numbering, ..levels) + h(7pt, weak: true) + } + #it.body + #v(10pt, weak: true) + ] else if it.level == 3 [ + #set par(first-line-indent: 0pt) + #v(10pt, weak: true) + #if it.numbering != none { + numbering(heading-numbering, ..levels) + } + #it.body + #v(10pt, weak: true) + ] else [ + // Fourth level headings are run-ins too, but different. + #set par(first-line-indent: 0pt) + #if it.level >= 4 { + numbering(heading-numbering, ..levels) + } + #it.body + ] + }) + + + if (logo != none) { + place( + top, + dx: -33%, + float: false, + box( + width: 27%, + { + if (type(logo) == "content") { + logo + } else { + image(logo, width: 100%) + } + }, + ), + ) + } + + + // Title and subtitle + box(inset: (bottom: 2pt), text(17pt, weight: "bold", fill: theme, title)) + if subtitle != none { + parbreak() + box(text(14pt, fill: gray.darken(30%), subtitle)) + } + + // Authors and affiliations + linebreak() + if authors.len() > 0 { + box(inset: (y: 10pt), { + authors.map(author => { + text(11pt, weight: "semibold", author.name) + h(1pt) + if "affiliations" in author { + super(author.affiliations) + } + if "orcid" in author { + link("https://orcid.org/" + author.orcid)[#box(height: 1.1em, baseline: 13.5%)[#image.decode(orcidSvg)]] + } + }).join(", ", last: ", and ") + }) + } + + if affiliations.len() > 0 { + linebreak() + box(inset: (bottom: 10pt), { + affiliations.map(affiliation => { + super(affiliation.id) + h(1pt) + affiliation.name + }).join(", ") + }) + } + + place( + left + bottom, + dx: -25%, + dy: -10pt, + box(width: 22%, { + if (kind != none) { + show par: set block(spacing: 0em) + text(11pt, fill: theme, weight: "semibold", smallcaps(kind)) + parbreak() + } + if (dates != none) { + let formatted-dates + + grid(columns: (40%, 60%), gutter: 7pt, + ..dates.zip(range(dates.len())).map((formatted-dates) => { + let d = formatted-dates.at(0); + let i = formatted-dates.at(1); + return ( + if (i == 0){ + strong[#text(size: 7pt, fill: theme, d.title)] + } else { + text(size: 7pt, fill: theme, d.title) + }, + if (lang == "en") { + text(size: 7pt, d.date.display("[month repr:short] [day], [year]")) + } else {[ + #set text(7pt) + #d.date.year()-#d.date.month()-#d.date.day() + ]} + ) + }).flatten() + ) + } + v(2em) + grid(columns: 1, gutter: 2em, ..margin.map(side => { + text(size: 7pt, { + if ("title" in side) { + text(fill: theme, weight: "bold", side.title) + [\ ] + } + set enum(indent: 0.1em, body-indent: 0.25em) + set list(indent: 0.1em, body-indent: 0.25em) + side.content + }) + })) + }), + ) + + + let abstracts + if (type(abstract) == "content") { + abstracts = (title: "Abstract", content: abstract) + } else { + abstracts = abstract + } + if abstracts != none { + box(inset: (top: 16pt, bottom: 16pt), stroke: (top: 1pt + gray, bottom: 1pt + gray), { + abstracts.map(abs => { + set par(justify: true) + text(fill: theme, weight: "semibold", size: 9pt, abs.title) + parbreak() + abs.content + }).join(parbreak()) + }) + if (keywords.len() > 0) { + text(size: 9pt, { + if (lang == "en") { + text(fill: theme, weight: "semibold", "Keywords") + } else { + text(fill: theme, weight: "semibold", "关键词") + } + h(8pt) + keywords.join(", ") + }) + } + v(10pt) + } + + if (outline-show) {outline( + title: if (lang == "en") {"Content"} else {"目录"}, + indent: 2em, + depth: outline-depth, + )} + + pagebreak() + + // Display the paper's contents. + show par: set block(spacing: 1.5em) + set page( + margin: (left: auto, bottom: 1.5cm), + ) + + // show: rest => { + // let n = count-words(rest) + // if (lang == "en") { + // rest + align(right, [(#n words)]) + // } else { + // rest + align(right, [(#n 字)]) + // } + // } + + body + + if (bibliography-file != none) { + show bibliography: set text(8pt) + bibliography(bibliography-file, title: text(10pt, "References"), style: bibliography-style) + } +} diff --git a/lib.typ b/lib.typ new file mode 100644 index 0000000..b5b0e4b --- /dev/null +++ b/lib.typ @@ -0,0 +1,5 @@ +#import "utils.typ": * +#import "table.typ": * +#import "in-dexter.typ": index, make-index +#import "lapreprint.typ": lapreprint +#import "slides.typ": slides-theme, slide, matrix-slide, title-slide, slide-outline, slide-outline-length diff --git a/slides.typ b/slides.typ new file mode 100644 index 0000000..4262dfa --- /dev/null +++ b/slides.typ @@ -0,0 +1,200 @@ +#import "@preview/polylux:0.3.1": * +#import themes.university as uni + +#let count-length(it) = { + let fn = repr(it.func()) + if fn == "sequence" { it.children.map(count-length).sum() } + else if fn == "heading" { count-length(it.body) } + else if fn == "figure" { count-length(it.caption.body) } + else if fn == "text" { it.text.len() } + else if fn in ("styled") { count-length(it.child) } + else if fn in ("highlight", "item", "strong", "link") { count-length(it.body) } + else if fn == "equation" { count-length(it.body) } + else { 0 } +} + +#let progress-barline = locate( loc => { + if uni.uni-progress-bar.at(loc) { + let cell = block.with( width: 100%, height: 100%, above: 0pt, below: 0pt, breakable: false ) + let colors = uni.uni-colors.at(loc) + + utils.polylux-progress( ratio => { + grid( + rows: 2pt, columns: (ratio * 100%, 1fr), + cell(fill: colors.a), + cell(fill: colors.b) + ) + }) + } else { [] } +}) + +#let header-text( + title: none, + new-section: none +) = { + if title != none { + if new-section != none { + utils.register-section(new-section) + } + + locate( loc => { + let colors = uni.uni-colors.at(loc) + block(fill: colors.c, inset: (x: .5em), grid( + columns: (1fr, auto), + align(top + left, heading(level: 2, text(fill: colors.a, title))), + align(top + right, text(fill: colors.a.lighten(65%), utils.current-section)) + )) + }) + } else { [] } +} + +#let slides-theme( + aspect-ratio: "16-9", + short-title: none, + short-author: none, + short-date: none, + color-a: rgb("#0C6291"), + color-b: rgb("#A63446"), + color-c: rgb("#FBFEF9"), + progress-bar: true, + body +) = { + uni.university-theme( + aspect-ratio: aspect-ratio, + short-title: short-title, + short-author: short-author, + short-date: short-date, + color-a: color-a, + color-b: color-b, + color-c: color-c, + progress-bar: progress-bar, + body + ) +} + +#let title-slide( + title: [], + subtitle: none, + authors: (), + institution-name: "University", + date: none, +) = { + let authors = if type(authors) == "array" { authors }else { (authors,) } + + let content = locate( loc => { + let colors = uni.uni-colors.at(loc) + + align(center + horizon, { + block( + inset: 0em, + breakable: false, + { + text(size: 1.8em, fill: colors.a, strong(title)) + if subtitle != none { + parbreak() + text(size: 1.2em, fill: colors.a, subtitle) + } + } + ) + set text(size: .8em) + grid( + columns: (1fr,) * calc.min(authors.len(), 3), + column-gutter: 1em, + row-gutter: 1em, + ..authors.map(author => text(fill: black, author)) + ) + v(1em) + if institution-name != none { + parbreak() + text(size: .9em, institution-name) + } + if date != none { + parbreak() + text(size: .8em, date) + } + }) + }) + + logic.polylux-slide(content) +} + +#let slide( + title: none, + new-section: none, + body +) = { + uni.slide(title: title, header: header-text(title: title, new-section: new-section), footer: none, new-section: new-section, body) +} + +#let matrix-slide( + title: none, + new-section: none, + columns: none, + rows: none, + ..bodies +) = { + let header = { + set align(top) + grid(rows: (auto, auto), row-gutter: 3mm, progress-barline, header-text(title: title, new-section: new-section)) + } + + let footer = { + set text(size: 10pt) + set align(center + bottom) + let cell(fill: none, it) = rect( + width: 100%, height: 100%, inset: 1mm, outset: 0mm, fill: fill, stroke: none, + align(horizon, text(fill: white, it)) + ) + locate( loc => { + let colors = uni.uni-colors.at(loc) + + show: block.with(width: 100%, height: auto, fill: colors.b) + grid( + columns: (25%, 1fr, 15%, 10%), + rows: (1.5em, auto), + cell(fill: colors.a, uni.uni-short-author.display()), + cell(uni.uni-short-title.display()), + cell(uni.uni-short-date.display()), + cell(logic.logical-slide.display() + [~/~] + utils.last-slide-number) + ) + }) + } + + set page( + margin: ( top: 2em, bottom: 1em, x: 0em ), + header: header, + footer: footer, + footer-descent: 0em, + header-ascent: .6em, + ) + + uni.matrix-slide(..bodies) +} + +#let slide-outline-length(loc) = { + let section-state = state("polylux-sections", ()) + let sections = section-state.final(loc) + sections.len() - 1 +} + +#let slide-outline(num) = { + set text(font: "Cascadia Mono") + locate(loc => { + let section-state = state("polylux-sections", ()) + let sections = section-state.final(loc) + for p in range(0, num) { + let section = sections.at(p) + let content = { + let url = link(section.loc, section.body) + let page = logic.logical-slide.at(section.loc).at(0) + let length = 46 - str(page).len() - count-length(section.body) + let dots = " " + "." * length + [#grid( + columns: (auto, 1fr, 1fr), + url, align[#dots], align(right)[#page] + )] + } + [+ #content ] + } + }) +} diff --git a/table.typ b/table.typ new file mode 100644 index 0000000..03c57b2 --- /dev/null +++ b/table.typ @@ -0,0 +1,19 @@ +#import "@preview/tablex:0.0.6": tablex, rowspanx, colspanx, hlinex, vlinex + +#let three-line-table(columns, headers, contents, caption, lang: "en",rows: auto) = {figure( + tablex( + columns: columns, + rows: rows, + align: center + horizon, + auto-lines: false, + repeat-header: true, + hlinex(stroke: 1.5pt), + ..headers, + hlinex(stroke: 0.75pt), + ..contents, + hlinex(stroke: 1.5pt), + ), + kind: table, + supplement: [#if (lang == "en") {"Table"} else {"表"}], + caption: caption, +)} \ No newline at end of file diff --git a/typst.toml b/typst.toml new file mode 100644 index 0000000..00260bf --- /dev/null +++ b/typst.toml @@ -0,0 +1,4 @@ +[package] +name = "custom" +version = "0.1.0" +entrypoint = "lib.typ" diff --git a/utils.typ b/utils.typ new file mode 100644 index 0000000..15bf70e --- /dev/null +++ b/utils.typ @@ -0,0 +1,85 @@ +#import "@preview/showybox:2.0.1": showybox +#import "@preview/physica:0.9.0" as ph +#import "@preview/pinit:0.1.2": * + +#let sp = [#h(0.5em)] +#let pm = [#sym.plus.minus] +#let le = [#sym.lt.eq.slant] +#let ge = [#sym.gt.eq.slant] +#let sim = [#sym.tilde.op] +#let inf = [#sym.infinity] +#let rm(body) = {math.upright(body)} +#let iso(b, A) = [#ph.isotope(b, a: A)] +#let isoz(b, A, Z) = [#ph.isotope(b, a: A, z: Z)] +#let isobox(body, width: 25pt, fill: white) = {block( + fill: fill, + stroke: black, + width: width, + height: width, + align(center + horizon)[#body] +)} + +#let box-quote(body, width: 100%, inset: 5pt, outset: 0pt) = {block( + fill: green.lighten(80%), + width: width, + inset: inset, + outset: outset, + radius: 4pt, + stroke: green.darken(60%), + breakable: true, + body +)} + +#let box-confusion(body, width: 100%, inset: 8pt, outset: 0pt) = {block( + fill: red.lighten(80%), + width: width, + inset: inset, + outset: outset, + radius: 4pt, + stroke: red.darken(60%), + breakable: false, + body +)} + +#let box-title(content, title: none) = {showybox( + title: title, + frame: ( + title-color: red.darken(30%), + border-color: red.darken(30%), + body-color: red.lighten(90%), + radius: 0pt, + thickness: 2pt, + ), + above: 0.5em, + below: 0.5em, + breakable: false, + content +)} + +#let mark-quote(body) = { + rect(fill: luma(240), stroke: (left: 0.25em), inset: (left: 0.5em), body) +} + +#let pinit-highlight-equation-from(height: 2em, extended-height: 1.4em, pos: bottom, fill: rgb(0, 180, 255), highlight-pins, point-pin, body) = { + pinit-highlight(..highlight-pins, dy: -extended-height / 2, extended-height: extended-height, fill: rgb(..fill.components().slice(0, -1), 40)) + pinit-point-from( + fill: fill, + pin-dx: -0.6em, + pin-dy: if pos == bottom { 0.8em } else { -0.6em }, + body-dx: 0pt, + body-dy: if pos == bottom { -0.05em } else { -1.4em }, + offset-dx: -0.6em, + offset-dy: if pos == bottom { 0.8em + height } else { -0.6em - height }, + point-pin, + rect( + inset: if pos == bottom { 0.4em } else { 0.4em }, + stroke: ( + bottom: if pos == bottom { 0em } else { 0.12em } + fill, + top: if pos == bottom { 0.12em } else { 0em } + fill + ), { + set text(fill: fill) + body + } + ) + ) +}