diff --git a/oi-wiki-export-typst/constants.typ b/oi-wiki-export-typst/constants.typ index 5a1c06e..8c78b97 100644 --- a/oi-wiki-export-typst/constants.typ +++ b/oi-wiki-export-typst/constants.typ @@ -9,5 +9,14 @@ #let RAW_EM = 1.125em // Page dimensions minus margin -#let VISIBLE_WIDTH = 21cm - 1in -#let VISIBLE_HEIGHT = 29.7cm - 1.5in +#let serif-font = ( + "New Computer Modern", + "Noto Serif CJK SC", + "Source Han Serif SC", +) +#let sans-font = ( + "New Computer Modern", + "Noto Sans CJK SC", + "Source Han Sans SC", +) +#let emph-font = ("New Computer Modern", "LXGW Wenkai") diff --git a/oi-wiki-export-typst/oi-wiki-export.typ b/oi-wiki-export-typst/oi-wiki-export.typ index 794728c..9f75ea1 100644 --- a/oi-wiki-export-typst/oi-wiki-export.typ +++ b/oi-wiki-export-typst/oi-wiki-export.typ @@ -4,6 +4,12 @@ #import "constants.typ": * #import "oi-wiki.typ": page-header /* END imports */ +#show ref: it => { + if query(it.target).len() == 0 { + return text(fill: red, "<未找到引用" + str(it.target) + ">") + } + it +} /* BEGIN meta */ #set text( @@ -16,27 +22,33 @@ #set page( header: none, paper: "a4", - margin: (top: .8in, inside: .4in, bottom: .7in, outside: .6in), header-ascent: .3in, fill: luma(95%), ) #align(center + horizon)[ // OI-Wiki logo - #image.decode("", height: 4cm) + #image.decode( + "", + height: 4cm, + ) #text( 25pt, - font: ("New Computer Modern", "Noto Serif CJK SC"), + font: serif-font, weight: 700, )[OI Wiki (Beta)] #v(4cm) #text( 18pt, - font: ("New Computer Modern", "Noto Serif CJK SC"), + font: serif-font, )[ OI Wiki 项目组 - #datetime.today().display("[year] 年 [month padding:none] 月 [day padding:none] 日") + #( + datetime + .today() + .display("[year] 年 [month padding:none] 月 [day padding:none] 日") + ) ] ] @@ -56,7 +68,7 @@ #set text( ROOT_EM, - font: ("New Computer Modern", "Noto Serif CJK SC"), + font: serif-font, ) #set par( @@ -66,13 +78,16 @@ // issues: https://github.com/typst/typst/issues/311 // https://github.com/typst/typst/issues/1410 // first-line-indent: 2em, + linebreaks: "optimized", + justify: true, ) +#show raw.where(block: true): set par(justify: false) #set block(spacing: .8em) #set strong(delta: 0) #show strong: set text( - font: ("New Computer Modern", "Noto Sans CJK SC"), + font: sans-font, // New Computer Modern: 400 |----->700 // Noto Sans CJK: 400 500<-| 700 // DejaVu Sans Mono: 400 |----->700 @@ -82,75 +97,56 @@ ) #set heading(numbering: "1.1") -#show heading: set block(spacing: 0em) #show heading: set text( - font: ("New Computer Modern", "Noto Sans CJK SC"), + font: sans-font, weight: 551, ) -#show heading.where(level: 1): set text(25pt) -#show heading.where(level: 2): set text(20pt) -#show heading.where(level: 3): set text(17pt) -#show heading.where(level: 4): set text(14pt) -#show heading.where(level: 5): set text(12pt) -#show heading.where(level: 6): set text(10pt) -#show heading: it => { - // NOTE: dynamic spacing? - // v(1fr, weak: true) - v(1.4em) - it - v(.2em) -} +#show heading.where(level: 1): set text(18pt) #show heading.where(level: 2): it => { - v(2em) - align(center)[#it] - v(2em) + set text(16pt) + align(center, it) } +#show heading.where(level: 3): set text(14pt) +#show heading.where(level: 4): set text(12pt) +#show heading.where(level: 5): set text(11pt) +#show heading.where(level: 6): set text(10pt) -#show emph: set text( - font: ("New Computer Modern", "LXGW Wenkai") -) +#show emph: set text(font: emph-font) -#show math.equation: set text( - font: ("New Computer Modern Math", "LXGW Wenkai") -) +#show math.equation: set text(font: ("New Computer Modern Math", "LXGW Wenkai")) #show raw: set text( RAW_EM, font: ("DejaVu Sans Mono", "LXGW Wenkai"), ) + #show raw.where(block: false): it => highlight( fill: luma(95%), - it + it, ) /* END article formatting */ /* BEGIN outline */ -#show outline.entry.where( - level: 1 -): it => { - v(20pt, weak: true) - text(14pt)[#strong(it)] +#show outline.entry.where(level: 1): it => { + v(2em, weak: true) + text(12pt, font: sans-font, it) } -#outline(indent: auto) +#outline(indent: 2em) /* END outline */ /* BEGIN main */ -#set page( - header: page-header -) +#set page(header: page-header) #counter(page).update(1) #show heading.where(level: 1): it => { set text( 25pt, - font: ("New Computer Modern", "Noto Serif CJK SC"), + font: serif-font, weight: 700, ) - set par( - first-line-indent: 0em, - ) + set par(first-line-indent: 0em) align(horizon)[ 第#counter(heading).display("一")章 @@ -176,7 +172,7 @@ #show list: set block(width: 100%) #set enum( indent: 1em, - body-indent: -.5em -.278em + 1em, + body-indent: -.5em - .278em + 1em, ) #show enum: set block(width: 100%) @@ -185,7 +181,6 @@ #show footnote.entry: it => { set text(9pt) - show parbreak: none it } @@ -202,6 +197,6 @@ #align( center + horizon, - text(17pt)[https://oi-wiki.org] + text(17pt)[https://oi-wiki.org], ) /* END back cover */ diff --git a/oi-wiki-export-typst/oi-wiki.typ b/oi-wiki-export-typst/oi-wiki.typ index b1406ce..7f0c95d 100644 --- a/oi-wiki-export-typst/oi-wiki.typ +++ b/oi-wiki-export-typst/oi-wiki.typ @@ -6,60 +6,66 @@ #import "@preview/tablex:0.0.8": tablex #import "@preview/tiaoma:0.2.0" -#import "@preview/mitex:0.2.3": mi, mitex -/* END imports */ +#import "@preview/mitex:0.2.4": mi, mitex +#import "@preview/codelst:2.0.2": sourcecode -#let page-header = locate( - loc => { - if calc.odd(loc.page()) { - // NOTE: not able to programatically hide headings on new chapters for now - // issue: https://github.com/typst/typst/issues/1613 - let section = query(selector(heading.where(level: 2)).before(loc), loc) - if section == () { - return none - } - let sect-number(..headings) = { - let levels = headings.pos() +/* END imports */ - if levels.len() > 1 { - [#levels.at(0).#levels.at(1)] - } else { - none - } - } +#let page-header = context { + let loc = here() + if calc.odd(loc.page()) { + // NOTE: not able to programatically hide headings on new chapters for now + // issue: https://github.com/typst/typst/issues/1613 + let section = query(selector(heading.where(level: 2)).before(loc)) + if section == () { + return none + } - text(9pt, number-width: "tabular")[ - #emph[ - #counter(heading).display(sect-number) - #h(1em) - #smallcaps(section.last().body) - ] - #h(1fr) - #counter(page).display("1") - ] - } else { - let chapters = query(selector(heading.where(level: 1)).before(loc), loc) - // HACK: don't add headers in outlines (Chapter 0) - // This is only a workaround. Detailed mechanism of typst's pagebreaks - // needs to be further researched. - let chapter-counter = counter(heading.where(level: 1)).at(loc) - if chapter-counter == (0,) { - return none + let sect-number(..headings) = { + let levels = headings.pos() + + if levels.len() > 1 { + [#levels.at(0).#levels.at(1)] + } else { + none } + } - text(9pt, number-width: "tabular")[ - #counter(page).display("1") - #h(1fr) - 第#counter(heading.where(level: 1)).display("一")章 + text(9pt, number-width: "tabular")[ + #emph[ + #counter(heading).display(sect-number) #h(1em) - #chapters.last().body + #smallcaps(section.last().body) ] + #h(1fr) + #counter(page).display("1") + ] + } else { + let chapters = query(selector(heading.where(level: 1)).before(loc)) + // HACK: don't add headers in outlines (Chapter 0) + // This is only a workaround. Detailed mechanism of typst's pagebreaks + // needs to be further researched. + let chapter-counter = counter(heading.where(level: 1)).at(loc) + if chapter-counter == (0,) { + return none } - }, + + text(9pt, number-width: "tabular")[ + #counter(page).display("1") + #h(1fr) + 第#counter(heading.where(level: 1)).display("一")章 + #h(1em) + #chapters.last().body + ] + } +} ) -#let horizontalrule = align(center, block(sym.ast.op + h(1em) + sym.ast.op + h(1em) + sym.ast.op)) +#let horizontalrule = align( + center, + block(sym.ast.op + h(1em) + sym.ast.op + h(1em) + sym.ast.op), +) #let blockquote(content) = { set text(fill: luma(50%)) @@ -75,135 +81,25 @@ #let authors(authors) = blockquote[*Authors:* #authors] #let kbd(string) = { - let key = box(outset: .2em, inset: (x: .1em), fill: luma(95%), stroke: ( - bottom: (paint: luma(50%), thickness: 2pt, cap: "round"), - x: (paint: luma(50%), thickness: 1pt, cap: "round"), - ), radius: .2em, raw(string)) + let key = box( + outset: .2em, + inset: (x: .1em), + fill: luma(95%), + stroke: ( + bottom: (paint: luma(50%), thickness: 2pt, cap: "round"), + x: (paint: luma(50%), thickness: 1pt, cap: "round"), + ), + radius: .2em, + raw(string), + ) h(.5em) + key + h(.5em) } -#let codeblock(code, lang: str, unwrapped: false) = { - let radius = if unwrapped { - (bottom: .2em) - } else { - .2em - } - let stroke = if unwrapped { - ( - top: (thickness: 1pt, paint: luma(75%), dash: "dashed"), - bottom: 1pt + luma(75%), - x: 1pt + luma(75%), - ) - } else { - 1pt + luma(75%) - } - - // Code block with line numbers - // Issue: https://github.com/typst/typst/issues/344 - // Reference: https://gist.github.com/mpizenberg/c6ed7bc3992ee5dfed55edce508080bb - let lines = code.replace("\t", " ").split("\n") - let digits = str(lines.len()).len() - - // Width of glyphs in DejaVu Sans Mono is 1233 units - let glyph-width = (1233 / 2048) * 0.8 * RAW_EM - let number-width = (digits + 2) * glyph-width - let track-width = (digits + 5) * glyph-width - - grid( - columns: 2, - column-gutter: -100%, - // Background & line numbers - block( - width: 100%, - radius: radius, - inset: (left: track-width, y: .5em), - fill: luma(95%), - stroke: stroke, - { - set text( - 0.8 * RAW_EM, - font: ("DejaVu Sans Mono", "LXGW WenKai"), - fill: luma(75%), - ) - - for (i, line) in lines.enumerate() { - box( - width: 0pt, - inset: (right: track-width - number-width), - align(right, str(i + 1)), - ) - hide(line) - linebreak() - } - }, - ), - // The code itself - block( - width: 100%, - inset: (left: track-width, y: .5em), - raw(block: true, lang: lang, code), - ), - ) +#let img-auto(src, alt: str) = { + image(src, alt: alt) } -// Auto-sized image. -// NOTE: optimized image sizing is in progress -// issue: https://github.com/typst/typst/issues/436 -#let img-auto(src, alt: str) = style(styles => { - let img = image(src) - let (width, height) = measure(img, styles) - - let max-image-width = VISIBLE_WIDTH - ROOT_EM * 8 - let max-image-height = VISIBLE_HEIGHT / 2 - ROOT_EM * 8 - - if width / height > max-image-width / max-image-height { - set image(width: calc.min(width, max-image-width)) - - v(.8em) - align(center, img) - v(.8em) - } else { - set image(height: calc.min(height, max-image-height)) - - v(.8em) - align(center, img) - v(.8em) - } - // BEGIN trigonometric solution - // let hori = VISIBLE_WIDTH.pt() - // let radius = hori / 2 - // let ratio = width / height - // let vert = hori / ratio - // let diag = calc.sqrt(radius * radius + vert * vert) - // let factor = diag / radius - // set image(width: calc.min(width, VISIBLE_WIDTH / factor)) - // figure(img, caption: alt) - // END trigonometric solution - // BEGIN another trigonometric solution - // let endpoint = MAX_IMAGE_HEIGHT - MAX_IMAGE_WIDTH / 2 - // let max-ratio = MAX_IMAGE_WIDTH / endpoint - // if width / height > max-ratio { - // set image(width: calc.min(width / 2, max-image-width)) - // figure(img, caption: alt) - // } else { - // let r = max-ratio / 2 - // let v = max-ratio / (width / height) - // let b1 = calc.sqrt((v - 1) * (v - 1) + r * r) - // let c1 = calc.sqrt(v * v + r * r) - // let a = 1 - // let b = r - // let B = calc.acos((a * a + c1 * c1 - b1 * b1) / (2 * a * c1)) - // let A = calc.asin(a * calc.sin(B) / b) - // let C = 180deg - A - B - // let c = (a * calc.sin(C) / calc.sin(A)) - // let f = c1 / c - // set image(width: calc.min(width / 2, max-image-width / f)) - // figure(img, caption: alt) - // } - // END another trigonometric solution -}) - #let svg-math(svg, display: false) = style(styles => { let img = image.decode(svg) let (width, height) = measure(img, styles) @@ -222,7 +118,11 @@ grid(columns: (1fr, .75in, 1fr, .5in), rows: .5in, ..content) } -#let links-cell(content) = block(width: 100%, height: 100%, align(horizon, content)) +#let links-cell(content) = block( + width: 100%, + height: 100%, + align(horizon, content), +) #let qrcode(arg) = tiaoma.qrcode(arg, width: .4in) #let tablex-custom(columns: (), aligns: (), ..cells) = { @@ -232,18 +132,11 @@ center, block( radius: .2em, - inset: (x: .5em), stroke: 1pt + luma(75%), - tablex( + table( columns: columns, - column-gutter: 1fr, - // NOTE: repeat-header has no effect when the table is in a container - // it is also a little buggy right now, so we are not enabling it at this moment - // issue: https://github.com/PgBiel/typst-tablex/issues/43 - repeat-header: false, align: (col, row) => aligns.at(col), stroke: 1pt + luma(75%), - auto-vlines: false, ..cells, ), ), @@ -265,8 +158,6 @@ inset: (x: 1em, y: .5em), radius: (top: .2em), )[ - #show parbreak: none - #strong(items.at(0)) ] diff --git a/oi-wiki-export-typst/pymdownx-details.typ b/oi-wiki-export-typst/pymdownx-details.typ index d35818d..51f42c7 100644 --- a/oi-wiki-export-typst/pymdownx-details.typ +++ b/oi-wiki-export-typst/pymdownx-details.typ @@ -1,29 +1,55 @@ +#import "@preview/gentle-clues:1.1.0": * +#import "constants.typ": * /* BEGIN constants */ -#let note-color = (bright: cmyk(10%, 5%, 0%, 0%), dark: cmyk(40%, 20%, 0%, 0%)) -#let abstract-color = (bright: cmyk(10%, 0%, 0%, 0%), dark: cmyk(40%, 0%, 0%, 0%)) -#let info-color = (bright: cmyk(10%, 0%, 5%, 0%), dark: cmyk(40%, 0%, 20%, 0%)) -#let tip-color = (bright: cmyk(10%, 0%, 10%, 0%), dark: cmyk(40%, 0%, 40%, 0%)) -#let success-color = (bright: cmyk(5%, 0%, 10%, 0%), dark: cmyk(20%, 0%, 40%, 0%)) -#let question-color = (bright: cmyk(0%, 0%, 10%, 0%), dark: cmyk(0%, 0%, 40%, 0%)) -#let warning-color = (bright: cmyk(0%, 5%, 10%, 0%), dark: cmyk(0%, 20%, 40%, 0%)) -#let failure-color = (bright: cmyk(0%, 10%, 10%, 0%), dark: cmyk(0%, 40%, 40%, 0%)) -#let danger-color = (bright: cmyk(0%, 10%, 5%, 0%), dark: cmyk(0%, 40%, 20%, 0%)) -#let bug-color = (bright: cmyk(0%, 10%, 0%, 0%), dark: cmyk(0%, 40%, 0%, 0%)) -#let example-color = (bright: cmyk(5%, 10%, 0%, 0%), dark: cmyk(20%, 40%, 0%, 0%)) -#let quote-color = (bright: cmyk(10%, 10%, 0%, 0%), dark: cmyk(40%, 40%, 0%, 0%)) +#let note-color = (bright: cmyk(10%, 5%, 0%, 0%), dark: cmyk(40%, 20%, 0%, 0%)) +#let abstract-color = ( + bright: cmyk(10%, 0%, 0%, 0%), + dark: cmyk(40%, 0%, 0%, 0%), +) +#let info-color = (bright: cmyk(10%, 0%, 5%, 0%), dark: cmyk(40%, 0%, 20%, 0%)) +#let tip-color = (bright: cmyk(10%, 0%, 10%, 0%), dark: cmyk(40%, 0%, 40%, 0%)) +#let success-color = ( + bright: cmyk(5%, 0%, 10%, 0%), + dark: cmyk(20%, 0%, 40%, 0%), +) +#let question-color = ( + bright: cmyk(0%, 0%, 10%, 0%), + dark: cmyk(0%, 0%, 40%, 0%), +) +#let warning-color = ( + bright: cmyk(0%, 5%, 10%, 0%), + dark: cmyk(0%, 20%, 40%, 0%), +) +#let failure-color = ( + bright: cmyk(0%, 10%, 10%, 0%), + dark: cmyk(0%, 40%, 40%, 0%), +) +#let danger-color = ( + bright: cmyk(0%, 10%, 5%, 0%), + dark: cmyk(0%, 40%, 20%, 0%), +) +#let bug-color = (bright: cmyk(0%, 10%, 0%, 0%), dark: cmyk(0%, 40%, 0%, 0%)) +#let example-color = ( + bright: cmyk(5%, 10%, 0%, 0%), + dark: cmyk(20%, 40%, 0%, 0%), +) +#let quote-color = ( + bright: cmyk(10%, 10%, 0%, 0%), + dark: cmyk(40%, 40%, 0%, 0%), +) -#let note-icon = image.decode("") -#let abstract-icon = image.decode("") -#let info-icon = image.decode("") -#let tip-icon = image.decode("") -#let success-icon = image.decode("") -#let question-icon = image.decode("") -#let warning-icon = image.decode("") -#let failure-icon = image.decode("") -#let danger-icon = image.decode("") -#let bug-icon = image.decode("") -#let example-icon = image.decode("") -#let quote-icon = image.decode("") +#let note-icon = image.decode("") +#let abstract-icon = image.decode("") +#let info-icon = image.decode("") +#let tip-icon = image.decode("") +#let success-icon = image.decode("") +#let question-icon = image.decode("") +#let warning-icon = image.decode("") +#let failure-icon = image.decode("") +#let danger-icon = image.decode("") +#let bug-icon = image.decode("") +#let example-icon = image.decode("") +#let quote-icon = image.decode("") /* END constants */ #let details(type: str, unwrap: false, ..items) = { @@ -31,7 +57,7 @@ if items.len() != 2 { panic("#details receives exactly two content blocks") } - + let (title, content) = items let (color, icon) = if type == "abstract" { (abstract-color, abstract-icon) } else if type == "info" { @@ -57,42 +83,11 @@ } else { (note-color, note-icon) } - - block[ - #block( - width: 100%, - fill: color.bright, - stroke: ( - top: 1pt + color.dark, - x: 1pt + color.dark - ), - below: 0em, - inset: (x: 1em, y: .5em), - radius: (top: .2em), - )[ - #show parbreak: [] - - #box(height: 1.25em, baseline: .25em, icon) - #h(.5em) - #strong(items.at(0)) - ] - - #if not unwrap { - block( - width: 100%, - stroke: ( - top: (thickness: 1pt, paint: color.dark, dash: "dashed"), - bottom: 1pt + color.dark, - x: 1pt + color.dark - ), - above: 0em, - inset: (x: 1em, y: .5em), - radius: (bottom: .2em), - - items.at(1) - ) - } else { - items.at(1) - } - ] + clue( + title: title, + icon: icon, + accent-color: color.dark, + title-font: sans-font, + content, + ) }