├── .gitattributes ├── meta.json ├── .gitignore ├── src ├── tutorial │ ├── other-file.typ │ ├── packages │ │ └── m1.typ │ ├── scripting-color.typ │ ├── reference-table.typ │ ├── reference-outline.typ │ ├── reference-math-mode.typ │ ├── reference-wasm-plugin.typ │ ├── writing-math.typ │ ├── IDE-autocomplete.png │ ├── reference-bibliography.typ │ ├── reference-counter-state.typ │ ├── reference-layout.typ │ ├── reference-length.typ │ ├── systempropertiesadvanced.png │ ├── writing.typ │ ├── latex-look.typ │ ├── reference-calculation.typ │ ├── reference-data-process.typ │ ├── reference-type-builtin.typ │ ├── reference-date.typ │ ├── stateful │ │ ├── s2.typ │ │ ├── s3.typ │ │ ├── q0.typ │ │ ├── s1.typ │ │ └── q1.typ │ ├── stateful-v0.12.0 │ │ ├── s2.typ │ │ ├── s3.typ │ │ ├── q0.typ │ │ ├── s1.typ │ │ └── q1.typ │ ├── mod.typ │ ├── writing-chinese.typ │ ├── reference-utils.typ │ ├── reference-visualization.typ │ ├── reference-color.typ │ ├── reference-syntax-analysis.typ │ ├── scripting-composite.typ │ ├── scripting-shape.typ │ ├── scripting-main.typ │ ├── reference-math-symbols.typ │ ├── scripting-block-and-expression.typ │ ├── scripting-variable.typ │ ├── scripting-control-flow.typ │ ├── scripting-literal.typ │ ├── scripting-content.typ │ ├── writing-markup.typ │ └── figure-time-travel.typ ├── graph │ ├── table.typ │ ├── digraph.typ │ ├── electronics.typ │ ├── state-machine.typ │ ├── statistics.typ │ ├── mod.typ │ └── solid-geometry.typ ├── template │ ├── book.typ │ ├── paper.typ │ ├── slides.typ │ └── mod.typ ├── misc │ ├── code-theme.typ │ ├── code-syntax.typ │ ├── text-processing.typ │ ├── mod.typ │ └── font-setting.typ ├── prefaces │ ├── mod.typ │ ├── license.typ │ └── acknowledgement.typ ├── science │ ├── algorithm.typ │ ├── chemical.typ │ ├── theorem.typ │ └── mod.typ ├── topics │ ├── template-book.typ │ ├── template-cv.typ │ ├── writing-math.typ │ ├── template-paper.typ │ ├── writing-plugin-lib.typ │ ├── writing-component-lib.typ │ ├── call-externals.typ │ └── mod.typ ├── intermediate │ └── mod.typ ├── ebook.typ ├── figures.typ ├── book.typ ├── introduction.typ └── mod.typ ├── _typos.toml ├── assets └── files │ ├── 香風とうふ店.jpg │ ├── editor-vscode.png │ ├── editor-webapp.png │ └── info-icon.svg ├── typ ├── book │ ├── variables.typ │ └── typst.toml ├── templates │ ├── term.typ │ ├── theme-style.toml │ ├── side-notes.typ │ ├── template-link.typ │ ├── circuit-board.svg │ ├── ebook.typ │ ├── rustacean-flat-gesture.svg │ └── page.typ ├── embedded-typst │ ├── example.typ │ └── lib.typ └── typst-meta │ └── docs.typ ├── scripts ├── build.sh └── build.ps1 ├── .gitmodules ├── .zed └── settings.json ├── .vscode └── settings.json ├── crates └── embedded-typst │ └── Cargo.toml ├── .github └── workflows │ ├── build.yml │ └── gh_pages.yml ├── README.md ├── Cargo.toml └── LICENSE /.gitattributes: -------------------------------------------------------------------------------- 1 | .zed/settings.json linguist-language=json5 -------------------------------------------------------------------------------- /meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "contributors": ["Myriad-Dreamin"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | target/ 3 | .DS_Store 4 | src/*.pdf 5 | .idea 6 | -------------------------------------------------------------------------------- /src/tutorial/other-file.typ: -------------------------------------------------------------------------------- 1 | 2 | 一段文本 3 | #set text(fill: red) 4 | 另一段文本 5 | -------------------------------------------------------------------------------- /_typos.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | extend-ignore-identifiers-re = ["typ", "typc", "typm"] 3 | -------------------------------------------------------------------------------- /src/graph/table.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [制表]) 4 | -------------------------------------------------------------------------------- /src/graph/digraph.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [拓扑图]) 4 | -------------------------------------------------------------------------------- /src/template/book.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [书籍模板]) 4 | -------------------------------------------------------------------------------- /src/template/paper.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [论文模板]) 4 | -------------------------------------------------------------------------------- /src/graph/electronics.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [电路图]) 4 | -------------------------------------------------------------------------------- /src/graph/state-machine.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [状态机]) 4 | -------------------------------------------------------------------------------- /src/graph/statistics.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [统计图]) 4 | -------------------------------------------------------------------------------- /src/misc/code-theme.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [自定义代码主题]) 4 | -------------------------------------------------------------------------------- /src/prefaces/mod.typ: -------------------------------------------------------------------------------- 1 | #import "/src/book.typ" 2 | 3 | #import "../mod.typ": code, exec-code 4 | -------------------------------------------------------------------------------- /src/science/algorithm.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [伪算法]) 4 | -------------------------------------------------------------------------------- /src/science/chemical.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [化学方程式]) 4 | -------------------------------------------------------------------------------- /src/science/theorem.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [定理环境]) 4 | -------------------------------------------------------------------------------- /assets/files/香風とうふ店.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typst-doc-cn/tutorial/HEAD/assets/files/香風とうふ店.jpg -------------------------------------------------------------------------------- /src/misc/code-syntax.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [自定义代码高亮规则]) 4 | -------------------------------------------------------------------------------- /src/template/slides.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [演示文稿(PPT)]) 4 | -------------------------------------------------------------------------------- /src/topics/template-book.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [制作一个书籍模板]) 4 | -------------------------------------------------------------------------------- /src/topics/template-cv.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [制作一个CV模板]) 4 | -------------------------------------------------------------------------------- /src/topics/writing-math.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [编写一篇数学文档]) 4 | -------------------------------------------------------------------------------- /src/tutorial/packages/m1.typ: -------------------------------------------------------------------------------- 1 | 2 | XXX 3 | #let add(x, y) = x + y 4 | YYY 5 | #let sub(x, y) = x - y 6 | -------------------------------------------------------------------------------- /src/tutorial/scripting-color.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: "颜色类型") 4 | -------------------------------------------------------------------------------- /src/misc/text-processing.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [读取外部文件和文本处理]) 4 | -------------------------------------------------------------------------------- /src/topics/template-paper.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [制作一个IEEE模板]) 4 | -------------------------------------------------------------------------------- /src/topics/writing-plugin-lib.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [制作一个外部插件]) 4 | -------------------------------------------------------------------------------- /src/tutorial/reference-table.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.ref-page.with(title: [参考:表格]) 4 | -------------------------------------------------------------------------------- /src/topics/writing-component-lib.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [制作一个组件库]) 4 | -------------------------------------------------------------------------------- /src/tutorial/reference-outline.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.ref-page.with(title: [参考:文档大纲]) 4 | -------------------------------------------------------------------------------- /assets/files/editor-vscode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typst-doc-cn/tutorial/HEAD/assets/files/editor-vscode.png -------------------------------------------------------------------------------- /assets/files/editor-webapp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typst-doc-cn/tutorial/HEAD/assets/files/editor-webapp.png -------------------------------------------------------------------------------- /src/tutorial/reference-math-mode.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.ref-page.with(title: [参考:数学模式]) 4 | -------------------------------------------------------------------------------- /src/tutorial/reference-wasm-plugin.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.ref-page.with(title: [参考:WASM插件]) 4 | -------------------------------------------------------------------------------- /src/tutorial/writing-math.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: "数学排版") 4 | 5 | == 数学排版 6 | -------------------------------------------------------------------------------- /src/topics/call-externals.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [在Typst内执行Js、Python、Typst等]) 4 | -------------------------------------------------------------------------------- /src/tutorial/IDE-autocomplete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typst-doc-cn/tutorial/HEAD/src/tutorial/IDE-autocomplete.png -------------------------------------------------------------------------------- /src/tutorial/reference-bibliography.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.ref-page.with(title: [参考:导入和使用参考文献]) 4 | -------------------------------------------------------------------------------- /src/tutorial/reference-counter-state.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.ref-page.with(title: [参考:计数器和状态]) 4 | -------------------------------------------------------------------------------- /src/misc/mod.typ: -------------------------------------------------------------------------------- 1 | #import "/src/book.typ" 2 | #import "/typ/templates/page.typ" 3 | 4 | #import "../mod.typ": code, exec-code 5 | -------------------------------------------------------------------------------- /src/science/mod.typ: -------------------------------------------------------------------------------- 1 | #import "/src/book.typ" 2 | #import "/typ/templates/page.typ" 3 | 4 | #import "../mod.typ": code, exec-code 5 | -------------------------------------------------------------------------------- /src/template/mod.typ: -------------------------------------------------------------------------------- 1 | #import "/src/book.typ" 2 | #import "/typ/templates/page.typ" 3 | 4 | #import "../mod.typ": code, exec-code 5 | -------------------------------------------------------------------------------- /src/tutorial/reference-layout.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.ref-page.with(title: [参考:布局函数]) 4 | 5 | 已经移至《度量与布局》。 6 | -------------------------------------------------------------------------------- /src/tutorial/reference-length.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.ref-page.with(title: [参考:长度单位]) 4 | 5 | 已经移至《度量与布局》。 6 | -------------------------------------------------------------------------------- /src/topics/mod.typ: -------------------------------------------------------------------------------- 1 | #import "/src/book.typ" 2 | #import "/typ/templates/page.typ" 3 | 4 | #import "../mod.typ": code, exec-code, refs 5 | -------------------------------------------------------------------------------- /src/graph/mod.typ: -------------------------------------------------------------------------------- 1 | #import "/src/book.typ" 2 | #import "/typ/templates/page.typ" 3 | 4 | #import "../mod.typ": code, exec-code, todo-box 5 | -------------------------------------------------------------------------------- /src/tutorial/systempropertiesadvanced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typst-doc-cn/tutorial/HEAD/src/tutorial/systempropertiesadvanced.png -------------------------------------------------------------------------------- /typ/book/variables.typ: -------------------------------------------------------------------------------- 1 | 2 | // It is in default A4 paper size 3 | #let page-width = 595.28pt 4 | 5 | // default target is "pdf" 6 | #let target = "pdf" 7 | -------------------------------------------------------------------------------- /typ/book/typst.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "book" 3 | version = "0.2.3" 4 | entrypoint = "lib.typ" 5 | authors = ["Myriad-Dreamin"] 6 | license = "Apache-2.0" 7 | description = "A simple tool for creating modern online books in pure typst." 8 | -------------------------------------------------------------------------------- /assets/files/info-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/intermediate/mod.typ: -------------------------------------------------------------------------------- 1 | #import "/src/book.typ" 2 | #import "../mod.typ": code as _code, exec-code as _exec-code, pro-tip, refs, todo-box, typst-func 3 | #import "/typ/templates/page.typ": main-color 4 | #import "/typ/embedded-typst/lib.typ": default-cjk-fonts, default-fonts, svg-doc 5 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cargo build --release --target wasm32-unknown-unknown 4 | 5 | INSTALL_PATH="assets/artifacts/embedded_typst.wasm" 6 | if [ -f "$INSTALL_PATH" ]; then 7 | rm "$INSTALL_PATH" 8 | fi 9 | 10 | mv target/wasm32-unknown-unknown/release/embedded_typst.wasm $INSTALL_PATH 11 | -------------------------------------------------------------------------------- /src/ebook.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/shiroa:0.2.3": * 2 | #import "/typ/templates/ebook.typ" 3 | 4 | #show: ebook.project.with( 5 | title: "typst-tutorial-cn", 6 | display-title: "Typst中文教程", 7 | spec: "book.typ", 8 | // set a resolver for inclusion 9 | styles: ( 10 | inc: it => include it, 11 | ), 12 | ) 13 | -------------------------------------------------------------------------------- /scripts/build.ps1: -------------------------------------------------------------------------------- 1 | cargo build --release --target wasm32-unknown-unknown --manifest-path ./crates/embedded-typst/Cargo.toml --features typst-plugin 2 | $InstallPath = "assets/artifacts/embedded_typst.wasm" 3 | if (Test-Path $InstallPath) { 4 | Remove-Item $InstallPath 5 | } 6 | Move-Item target/wasm32-unknown-unknown/release/embedded_typst.wasm $InstallPath 7 | -------------------------------------------------------------------------------- /src/prefaces/license.typ: -------------------------------------------------------------------------------- 1 | 2 | #import "mod.typ": * 3 | #page({ 4 | set text(size: 10.5pt) 5 | set block(spacing: 1.5em) 6 | 7 | show "TERMS AND CONDITIONS FOR USE": it => { 8 | v(0.5em) 9 | it 10 | } 11 | show "APPENDIX": it => { 12 | v(1fr) 13 | it 14 | } 15 | eval(read("/LICENSE"), mode: "markup") 16 | v(5em) 17 | }) 18 | -------------------------------------------------------------------------------- /src/misc/font-setting.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [字体设置]) 4 | 5 | #let table-lnk(name, ref, it, scope: (:), res: none, ..args) = ( 6 | align(center + horizon, link("todo", name)), 7 | it, 8 | align( 9 | horizon, 10 | { 11 | set heading(bookmarked: false, outlined: false) 12 | eval(it.text, mode: "markup", scope: scope) 13 | }, 14 | ), 15 | ) 16 | -------------------------------------------------------------------------------- /src/tutorial/writing.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: "编写一篇进阶文档") 4 | 5 | // document 6 | // list again 7 | // figure again 8 | // footnote 9 | // quote 10 | // smallcaps 11 | // upper 12 | // lower 13 | // heading 14 | // - outlined 15 | // - bookmarked 16 | // outline 17 | // par 18 | // parbreak 19 | // table again 20 | // lorem 21 | // color again 22 | // read/query/metadata 23 | // # sys.version 24 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "assets/typst-fonts"] 2 | path = assets/typst-fonts 3 | url = https://github.com/typst-doc-cn/fonts 4 | branch = typst-v0.10.0 5 | [submodule "assets/artifacts"] 6 | path = assets/artifacts 7 | url = https://github.com/typst-doc-cn/tutorial-artifacts 8 | branch = v0.1.0 9 | [submodule "assets/fonts"] 10 | path = assets/fonts 11 | url = https://github.com/typst-doc-cn/fonts 12 | branch = tutorial-v0.1.0 13 | -------------------------------------------------------------------------------- /.zed/settings.json: -------------------------------------------------------------------------------- 1 | // Folder-specific settings 2 | // 3 | // For a full list of overridable settings, and general information on folder-specific settings, 4 | // see the documentation: https://zed.dev/docs/configuring-zed#settings-files 5 | { 6 | "lsp": { 7 | "tinymist": { 8 | "initialization_options": { 9 | "exportPdf": "onSave", 10 | "outputPath": "$root/target/typst/$name" 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/tutorial/latex-look.typ: -------------------------------------------------------------------------------- 1 | #let latex-look(content) = { 2 | // #set page(margin: 1.75in) 3 | set par( 4 | leading: 0.55em, 5 | first-line-indent: 0em, 6 | justify: true, 7 | ) 8 | set text(font: "New Computer Modern") 9 | show raw: set text(font: "New Computer Modern Mono") 10 | show par: set block(spacing: 0.55em) 11 | show heading: set block( 12 | above: 1.4em, 13 | below: 1em, 14 | ) 15 | 16 | content 17 | } 18 | -------------------------------------------------------------------------------- /src/prefaces/acknowledgement.typ: -------------------------------------------------------------------------------- 1 | 2 | #import "mod.typ": * 3 | 4 | #show: book.page.with(title: "致谢") 5 | 6 | #let MyriadDreamin = link("https://github.com/Myriad-Dreamin")[Myriad Dreamin] 7 | #let OrangeX4 = link("https://github.com/OrangeX4")[OrangeX4] 8 | #let GiggleDing = link("https://github.com/GiggleDing")[GiggleDing] 9 | 10 | 本文档的主要部分由#MyriadDreamin 编写。 11 | 12 | #OrangeX4 参与了基本教程部分的勘误。 13 | 14 | #GiggleDing 编写了《参考:常用数学符号》。 15 | 16 | 在此表示感谢。 17 | -------------------------------------------------------------------------------- /typ/templates/term.typ: -------------------------------------------------------------------------------- 1 | 2 | #let term-state = state("term", (:)) 3 | #let reset-term-state = term-state.update(it => (:)) 4 | 5 | #let _term(term-list, term, en: none) = ( 6 | context { 7 | let s = term-state.get() 8 | if term in s { 9 | [「#term-list.at(term)#("」")] 10 | } else { 11 | let en-term = term 12 | if en != none { 13 | en-term = en 14 | } 15 | [「#term-list.at(term)」(#en-term#(")")] 16 | } 17 | } 18 | + term-state.update(it => { 19 | it.insert(term, "") 20 | it 21 | }) 22 | ) 23 | -------------------------------------------------------------------------------- /typ/embedded-typst/example.typ: -------------------------------------------------------------------------------- 1 | 2 | #import "lib.typ": * 3 | 4 | #let doc = svg-doc(``` 5 | #set page(header: [ 6 | #set text(size: 20pt) 7 | The book compiled by embedded typst 8 | ]) 9 | #set text(size: 30pt) 10 | 11 | #v(1em) 12 | = The first section 13 | #lorem(120) 14 | = The second section 15 | #lorem(120) 16 | ```) 17 | 18 | #let query-result = doc.header.at(0) 19 | 20 | The selected element is: 21 | #query-result.func#[[#[#query-result.body.text]]] 22 | 23 | #grid(columns: (1fr, 1fr), column-gutter: 0.6em, row-gutter: 1em, ..doc.pages.map(data => image(bytes(data))).map(rect)) 24 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "tinymist.fontPaths": ["${workspaceFolder}/assets/fonts"], 3 | "tinymist.exportPdf": "onSave", 4 | "tinymist.outputPath": "$root/target/$dir/$name", 5 | "tinymist.formatterMode": "typstyle", 6 | "editor.pasteAs.preferences": [ 7 | "typst.link.uri", 8 | "typst.link.image", 9 | "typst.link" 10 | ], 11 | "tinymist.preview.invertColors": "auto", 12 | "tinymist.lint.enabled": true, 13 | "tinymist.lint.when": "onSave", 14 | "[typst]": { 15 | "editor.inlayHints.enabled": "off" 16 | }, 17 | "files.watcherExclude": { 18 | "**/target": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/tutorial/reference-calculation.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.ref-page.with(title: [参考:数值计算]) 4 | 5 | #let table-lnk(name, ref, it, scope: (:), res: none, ..args) = ( 6 | align(center + horizon, link("todo", name)), 7 | it, 8 | align( 9 | horizon, 10 | { 11 | set heading( 12 | bookmarked: false, 13 | outlined: false, 14 | ) 15 | eval(it.text, mode: "markup", scope: scope) 16 | }, 17 | ), 18 | ) 19 | 20 | // calc.* 21 | 22 | #todo-box[不再需要等待了] 23 | 等待#link("https://github.com/typst/typst/pull/3489")[PR: Begin migration of calc functions to methods] 24 | -------------------------------------------------------------------------------- /src/tutorial/reference-data-process.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.ref-page.with(title: [参考:数据读写与数据处理]) 4 | 5 | #let table-lnk(name, ref, it, scope: (:), res: none, ..args) = ( 6 | align(center + horizon, link("todo", name)), 7 | it, 8 | align( 9 | horizon, 10 | { 11 | set heading(bookmarked: false, outlined: false) 12 | eval(it.text, mode: "markup", scope: scope) 13 | }, 14 | ), 15 | ) 16 | 17 | == read 18 | 19 | == 数据格式 20 | 21 | == 字节数组 22 | 23 | == image.decode 24 | 25 | == 字符串Unicode 26 | 27 | == 正则匹配 28 | 29 | == wasm插件 30 | 31 | == metadata 32 | 33 | == typst query 34 | -------------------------------------------------------------------------------- /src/tutorial/reference-type-builtin.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.ref-page.with(title: [参考:内置类型]) 4 | 5 | == 空类型 6 | 7 | == type 8 | 9 | == content 10 | 11 | == function 12 | 13 | == arguments 14 | 15 | == module 16 | 17 | == plugin 18 | 19 | == regex 20 | 21 | == label 22 | 23 | == reference 24 | 25 | == selector 26 | 27 | == version 28 | -------------------------------------------------------------------------------- /typ/templates/theme-style.toml: -------------------------------------------------------------------------------- 1 | 2 | [light] 3 | color-scheme = "light" 4 | main-color = "#000" 5 | dash-color = "#20609f" 6 | code-theme = "" 7 | 8 | [rust] 9 | color-scheme = "light" 10 | main-color = "#262625" 11 | dash-color = "#2b79a2" 12 | code-theme = "" 13 | 14 | [coal] 15 | color-scheme = "dark" 16 | main-color = "#98a3ad" 17 | dash-color = "#2b79a2" 18 | code-theme = "/assets/files/tokyo-night.tmTheme" 19 | 20 | [navy] 21 | color-scheme = "dark" 22 | main-color = "#bcbdd0" 23 | dash-color = "#2b79a2" 24 | code-theme = "/assets/files/tokyo-night.tmTheme" 25 | 26 | [ayu] 27 | color-scheme = "dark" 28 | main-color = "#c5c5c5" 29 | dash-color = "#0096cf" 30 | code-theme = "/assets/files/tokyo-night.tmTheme" 31 | -------------------------------------------------------------------------------- /typ/templates/side-notes.typ: -------------------------------------------------------------------------------- 1 | 2 | #let side-attrs = state("tuturial-side-note-attrs", (:)) 3 | 4 | #let side-note(dy: -0.65em, content) = context { 5 | let loc = here() 6 | let attr = side-attrs.get() 7 | // side-notes.update(it => { 8 | // let p = str(loc.page()) 9 | // let arr = it.at(p, default: ()) 10 | // arr.push((loc.position().y, content)) 11 | // it.insert(p, arr) 12 | // it 13 | // }) 14 | box( 15 | width: 0pt, 16 | box( 17 | width: attr.width, 18 | place( 19 | left, 20 | dy: dy, 21 | dx: attr.left - loc.position().x, 22 | { 23 | set text(size: 10.5pt) 24 | content 25 | }, 26 | ), 27 | ), 28 | ) 29 | } 30 | 31 | -------------------------------------------------------------------------------- /crates/embedded-typst/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "embedded-typst" 3 | description = "Run typst inside of typst" 4 | authors.workspace = true 5 | version.workspace = true 6 | license.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | 11 | [lib] 12 | crate-type = ["cdylib"] 13 | 14 | [dependencies] 15 | typst.workspace = true 16 | typst-svg.workspace = true 17 | reflexo-typst.workspace = true 18 | serde_json.workspace = true 19 | flate2 = "1" 20 | tar = "0.4" 21 | 22 | wasm-minimal-protocol = { git = "https://github.com/astrale-sharp/wasm-minimal-protocol", optional = true } 23 | 24 | [features] 25 | typst-plugin = ["wasm-minimal-protocol"] 26 | 27 | default = ["typst-plugin"] 28 | 29 | [lints] 30 | workspace = true 31 | -------------------------------------------------------------------------------- /src/tutorial/reference-date.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.ref-page.with(title: [参考:时间类型]) 4 | 5 | #let table-lnk(name, ref, it, scope: (:), res: none, ..args) = ( 6 | align(center + horizon, link("todo", name)), 7 | it, 8 | align( 9 | horizon, 10 | { 11 | set heading(bookmarked: false, outlined: false) 12 | eval(it.text, mode: "markup", scope: scope) 13 | }, 14 | ), 15 | ) 16 | 17 | == 日期 18 | 19 | 日期表示时间长河中的一个具体的时间戳。#ref-bookmark[`datetime`] 20 | 21 | #code(```typ 22 | 一个值 #datetime(year: 2023, month: 4, day: 19).display() 偷偷混入了我们内容之中。 23 | ```) 24 | 25 | == 时间间隔 26 | 27 | 时间间隔表示两个时间戳之间的时间差。#ref-bookmark[`duration`] 28 | 29 | #code(```typ 30 | 一个值 #duration(days: 3, hours: 10).seconds()s 偷偷混入了我们内容之中。 31 | ```) 32 | 33 | == typst-pdf时间戳 34 | 35 | ```typ 36 | set document(date: auto) 37 | ``` 38 | -------------------------------------------------------------------------------- /src/tutorial/stateful/s2.typ: -------------------------------------------------------------------------------- 1 | 2 | #let curr-heading = state("curr-heading", ()) 3 | 4 | #set text(size: 8pt) 5 | 6 | #let set-heading(content) = { 7 | show heading.where(level: 3): it => { 8 | show regex("[\p{hani}\s]+"): underline 9 | it 10 | } 11 | show heading: it => { 12 | show regex("KiraKira"): box("★", baseline: -20%) 13 | show regex("FuwaFuwa"): box("✎", baseline: -20%) 14 | it 15 | } 16 | 17 | content 18 | } 19 | 20 | #let set-text(content) = { 21 | show regex("feat|refactor"): emph 22 | content 23 | } 24 | 25 | #show: set-text 26 | 27 | #set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt)) 28 | 29 | == 雨滴书v0.1.2 30 | === KiraKira 样式改进 31 | feat: 改进了样式。 32 | === FuwaFuwa 脚本改进 33 | feat: 改进了脚本。 34 | 35 | == 雨滴书v0.1.1 36 | refactor: 移除了LaTeX。 37 | 38 | feat: 删除了一个多余的文件夹。 39 | 40 | == 雨滴书v0.1.0 41 | feat: 新建了两个文件夹。 42 | 43 | -------------------------------------------------------------------------------- /src/tutorial/stateful-v0.12.0/s2.typ: -------------------------------------------------------------------------------- 1 | 2 | #let curr-heading = state("curr-heading", ()) 3 | 4 | #set text(size: 8pt) 5 | 6 | #let set-heading(content) = { 7 | show heading.where(level: 3): it => { 8 | show regex("[\p{hani}\s]+"): underline 9 | it 10 | } 11 | show heading: it => { 12 | show regex("KiraKira"): box("★", baseline: -20%) 13 | show regex("FuwaFuwa"): box("✎", baseline: -20%) 14 | it 15 | } 16 | 17 | content 18 | } 19 | 20 | #let set-text(content) = { 21 | show regex("feat|refactor"): emph 22 | content 23 | } 24 | 25 | #show: set-text 26 | 27 | #set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt)) 28 | 29 | == 雨滴书v0.1.2 30 | === KiraKira 样式改进 31 | feat: 改进了样式。 32 | === FuwaFuwa 脚本改进 33 | feat: 改进了脚本。 34 | 35 | == 雨滴书v0.1.1 36 | refactor: 移除了LaTeX。 37 | 38 | feat: 删除了一个多余的文件夹。 39 | 40 | == 雨滴书v0.1.0 41 | feat: 新建了两个文件夹。 42 | 43 | -------------------------------------------------------------------------------- /src/tutorial/stateful/s3.typ: -------------------------------------------------------------------------------- 1 | 2 | #let curr-heading = state("curr-heading", ()) 3 | 4 | #set text(size: 8pt) 5 | 6 | #let set-heading(content) = { 7 | show heading.where(level: 3): it => { 8 | show regex("[\p{hani}\s]+"): underline 9 | it 10 | } 11 | show heading: it => { 12 | show regex("KiraKira"): box("★", baseline: -20%) 13 | show regex("FuwaFuwa"): box("✎", baseline: -20%) 14 | it 15 | } 16 | 17 | content 18 | } 19 | 20 | #let set-text(content) = { 21 | show regex("feat|refactor"): emph 22 | content 23 | } 24 | 25 | #show: set-heading 26 | #show: set-text 27 | 28 | #set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt)) 29 | 30 | == 雨滴书v0.1.2 31 | === KiraKira 样式改进 32 | feat: 改进了样式。 33 | === FuwaFuwa 脚本改进 34 | feat: 改进了脚本。 35 | 36 | == 雨滴书v0.1.1 37 | refactor: 移除了LaTeX。 38 | 39 | feat: 删除了一个多余的文件夹。 40 | 41 | == 雨滴书v0.1.0 42 | feat: 新建了两个文件夹。 43 | 44 | -------------------------------------------------------------------------------- /src/tutorial/stateful-v0.12.0/s3.typ: -------------------------------------------------------------------------------- 1 | 2 | #let curr-heading = state("curr-heading", ()) 3 | 4 | #set text(size: 8pt) 5 | 6 | #let set-heading(content) = { 7 | show heading.where(level: 3): it => { 8 | show regex("[\p{hani}\s]+"): underline 9 | it 10 | } 11 | show heading: it => { 12 | show regex("KiraKira"): box("★", baseline: -20%) 13 | show regex("FuwaFuwa"): box("✎", baseline: -20%) 14 | it 15 | } 16 | 17 | content 18 | } 19 | 20 | #let set-text(content) = { 21 | show regex("feat|refactor"): emph 22 | content 23 | } 24 | 25 | #show: set-heading 26 | #show: set-text 27 | 28 | #set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt)) 29 | 30 | == 雨滴书v0.1.2 31 | === KiraKira 样式改进 32 | feat: 改进了样式。 33 | === FuwaFuwa 脚本改进 34 | feat: 改进了脚本。 35 | 36 | == 雨滴书v0.1.1 37 | refactor: 移除了LaTeX。 38 | 39 | feat: 删除了一个多余的文件夹。 40 | 41 | == 雨滴书v0.1.0 42 | feat: 新建了两个文件夹。 43 | 44 | -------------------------------------------------------------------------------- /src/tutorial/stateful/q0.typ: -------------------------------------------------------------------------------- 1 | 2 | #let curr-heading = state("curr-heading", ()) 3 | 4 | #set text(size: 8pt) 5 | 6 | #let set-heading(content) = { 7 | show heading.where(level: 3): it => { 8 | show regex("[\p{hani}\s]+"): underline 9 | it 10 | } 11 | show heading: it => { 12 | show regex("KiraKira"): box("★", baseline: -20%) 13 | show regex("FuwaFuwa"): box("✎", baseline: -20%) 14 | it 15 | } 16 | 17 | set page(header: { 18 | set text(size: 5pt); 19 | [这是页眉] 20 | }) 21 | 22 | content 23 | } 24 | 25 | #let set-text(content) = { 26 | show regex("feat|refactor"): emph 27 | content 28 | } 29 | 30 | #show: set-heading 31 | #show: set-text 32 | 33 | #set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt)) 34 | 35 | == 雨滴书v0.1.2 36 | === KiraKira 样式改进 37 | feat: 改进了样式。 38 | === FuwaFuwa 脚本改进 39 | feat: 改进了脚本。 40 | 41 | == 雨滴书v0.1.1 42 | refactor: 移除了LaTeX。 43 | 44 | feat: 删除了一个多余的文件夹。 45 | 46 | == 雨滴书v0.1.0 47 | feat: 新建了两个文件夹。 48 | -------------------------------------------------------------------------------- /src/tutorial/stateful-v0.12.0/q0.typ: -------------------------------------------------------------------------------- 1 | 2 | #let curr-heading = state("curr-heading", ()) 3 | 4 | #set text(size: 8pt) 5 | 6 | #let set-heading(content) = { 7 | show heading.where(level: 3): it => { 8 | show regex("[\p{hani}\s]+"): underline 9 | it 10 | } 11 | show heading: it => { 12 | show regex("KiraKira"): box("★", baseline: -20%) 13 | show regex("FuwaFuwa"): box("✎", baseline: -20%) 14 | it 15 | } 16 | 17 | // @typstyle off 18 | set page(header: { 19 | set text(size: 5pt); 20 | [这是页眉] 21 | }) 22 | 23 | content 24 | } 25 | 26 | #let set-text(content) = { 27 | show regex("feat|refactor"): emph 28 | content 29 | } 30 | 31 | #show: set-heading 32 | #show: set-text 33 | 34 | #set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt)) 35 | 36 | == 雨滴书v0.1.2 37 | === KiraKira 样式改进 38 | feat: 改进了样式。 39 | === FuwaFuwa 脚本改进 40 | feat: 改进了脚本。 41 | 42 | == 雨滴书v0.1.1 43 | refactor: 移除了LaTeX。 44 | 45 | feat: 删除了一个多余的文件夹。 46 | 47 | == 雨滴书v0.1.0 48 | feat: 新建了两个文件夹。 49 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: tutorial::ci 2 | on: push 3 | jobs: 4 | render_pdf: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v4 8 | - uses: typst-community/setup-typst@v3 9 | - run: git submodule update --init --recursive 10 | - name: Download & install shiroa 11 | run: | 12 | curl --proto '=https' --tlsv1.2 -LsSf https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.3.1-rc2/shiroa-installer.sh | sh 13 | - name: Build Book 14 | run: | 15 | shiroa build --font-path ./assets/typst-fonts/ --font-path ./assets/fonts/ --path-to-root /tutorial/ -w . src 16 | - run: typst compile --root . --font-path ./assets/typst-fonts/ --font-path ./assets/fonts/ ./src/ebook.typ 17 | - uses: actions/upload-artifact@v4 18 | id: artifact-upload-step 19 | with: 20 | name: ebook 21 | path: src/ebook.pdf 22 | - run: echo 'Artifact URL is ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/${{ steps.artifact-upload-step.outputs.artifact-id }}' 23 | -------------------------------------------------------------------------------- /typ/typst-meta/docs.typ: -------------------------------------------------------------------------------- 1 | 2 | 3 | #let iterate-scope(env, scope, belongs) = { 4 | let p = (..scope.path, scope.name).join(".") 5 | scope.insert("belongs", belongs) 6 | env.scoped-items.insert(p, scope) 7 | 8 | if "scope" in scope { 9 | let belongs = (kind: "scope", name: scope.name) 10 | for c in scope.scope { 11 | env = iterate-scope(env, c, belongs) 12 | } 13 | } 14 | 15 | return env 16 | } 17 | 18 | #let iterate-docs(env, route, path) = { 19 | if route.body.kind == "func" { 20 | env.funcs.insert(route.body.content.name, route) 21 | } else if route.body.kind == "type" { 22 | env.types.insert(route.body.content.name, route) 23 | 24 | // iterate-scope() 25 | if "scope" in route.body.content { 26 | let belongs = (kind: "type", name: route.body.content.name) 27 | for c in route.body.content.scope { 28 | env = iterate-scope(env, c, belongs) 29 | } 30 | } 31 | } 32 | 33 | for ch in route.children { 34 | env = iterate-docs(env, ch, path + (route.title,)) 35 | } 36 | 37 | return env 38 | } 39 | 40 | #let load-docs() = { 41 | let m = json("/assets/artifacts/typst-docs-v0.11.0.json") 42 | let env = (funcs: (:), types: (:), scoped-items: (:)) 43 | for route in m { 44 | env = iterate-docs(env, route, ()) 45 | } 46 | 47 | return env 48 | } 49 | 50 | #let typst-v11 = load-docs() 51 | -------------------------------------------------------------------------------- /typ/templates/template-link.typ: -------------------------------------------------------------------------------- 1 | 2 | // #import "supports-text.typ": plain-text 3 | #import "@preview/shiroa:0.2.3": plain-text 4 | 5 | #let make-unique-label(it, disambiguator: 1) = label({ 6 | let k = plain-text(it).trim() 7 | if disambiguator > 1 { 8 | k + "_d" + str(disambiguator) 9 | } else { 10 | k 11 | } 12 | }) 13 | 14 | #let label-disambiguator = state("label-disambiguator", (:)) 15 | #let update-label-disambiguator(k) = label-disambiguator.update(it => { 16 | it.insert(k, it.at(k, default: 0) + 1) 17 | it 18 | }) 19 | #let get-label-disambiguator(loc, k) = make-unique-label(k, disambiguator: label-disambiguator.at(loc).at(k)) 20 | 21 | #let heading-reference(it, d: 1) = make-unique-label(it.body, disambiguator: d) 22 | 23 | #let enable-heading-hash = state("enable-heading-hash", true) 24 | 25 | #let heading-hash(it, hash-color: blue) = { 26 | let title = plain-text(it.body) 27 | if title != none { 28 | let title = title.trim() 29 | update-label-disambiguator(title) 30 | context if enable-heading-hash.get() { 31 | let loc = here() 32 | let dest = get-label-disambiguator(loc, title) 33 | let h = measure(it.body).height 34 | place( 35 | left, 36 | dx: -16pt, 37 | [ 38 | #set text(fill: hash-color) 39 | #link(loc)[\#] #dest 40 | ], 41 | ) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/tutorial/mod.typ: -------------------------------------------------------------------------------- 1 | #import "/src/book.typ" 2 | #import "/typ/templates/page.typ": is-web-target, main-color 3 | #import "/typ/embedded-typst/lib.typ": default-cjk-fonts, default-fonts, svg-doc 4 | #import "../mod.typ": ( 5 | code as _code, exec-code as _exec-code, exercise, mark, pro-tip, ref-bookmark, ref-cons-signature, ref-func-signature, 6 | ref-method-signature, refs, term, todo-box, todo-color, typst-func, 7 | ) 8 | 9 | #let eval-local(it, scope, res) = if res != none { 10 | res 11 | } else { 12 | eval(it.text, mode: "markup", scope: scope) 13 | } 14 | #let exec-code(it, scope: (:), res: none, ..args) = _exec-code(it, res: eval-local(it, scope, res), ..args) 15 | /// - it (content): the body of code. 16 | #let code(it, scope: (:), res: none, ..args) = _code(it, res: eval-local(it, scope, res), ..args) 17 | 18 | #let frames(code, cjk-fonts: false, code-as: none, prelude: none) = { 19 | if code-as != none { 20 | code-as 21 | } else { 22 | code 23 | } 24 | 25 | if prelude != none { 26 | code-as = if code-as == none { 27 | code 28 | } 29 | code = prelude.text + "\n" + code.text 30 | } 31 | 32 | let fonts = if cjk-fonts { 33 | (..default-cjk-fonts(), ..default-fonts()) 34 | } 35 | 36 | grid(columns: (1fr, 1fr), ..svg-doc(code, fonts: fonts).pages.map(data => image(bytes(data))).map(rect)) 37 | } 38 | #let frames-cjk = frames.with(cjk-fonts: true) 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tutorial 2 | 3 | Typst 中文教程 4 | 5 | [![下载最新版本](https://custom-icon-badges.demolab.com/badge/-Download-blue?style=for-the-badge&logo=download&logoColor=white "下载最新版本")](https://nightly.link/typst-doc-cn/tutorial/workflows/build/main/ebook.zip) **(latest 版本)** 6 | 7 | [![下载最新版本](https://custom-icon-badges.demolab.com/badge/-Download-blue?style=for-the-badge&logo=download&logoColor=white "下载最新版本")](https://github.com/typst-doc-cn/tutorial/releases/download/v0.1.0/Typst.Tutorial.CN.v0.1.0.pdf) **(0.1.0 版本)** 8 | 9 | ## 安装字体 10 | 11 | ```bash 12 | git submodule update --init --recursive 13 | ``` 14 | 15 | ## 托管为静态网站 16 | 17 | ```bash 18 | shiroa serve --font-path ./assets/typst-fonts/ --font-path ./assets/fonts/ -w . ./src/ 19 | ``` 20 | 21 | ## 编译为静态网站 22 | 23 | ```bash 24 | shiroa build --font-path ./assets/typst-fonts/ --font-path ./assets/fonts/ -w . ./src/ 25 | ``` 26 | 27 | ## 编译电子书 28 | 29 | ```bash 30 | typst compile --root . --font-path ./assets/typst-fonts/ --font-path ./assets/fonts/ ./src/ebook.typ 31 | ``` 32 | 33 | ## 编译单独章节 34 | 35 | 选择一个章节文件,比如 `第一章.typ`,然后执行: 36 | 37 | ```bash 38 | typst compile --root . --font-path ./assets/typst-fonts/ --font-path ./assets/fonts/ 章节文件.typ 39 | ``` 40 | 41 | ## 复现 Artifacts 42 | 43 | 生成`typst-docs-v0.11.0.json`: 44 | 45 | ```bash 46 | cargo install --git https://github.com/typst/typst --locked typst-docs --features="cli" --tag v0.11.0 47 | typst-docs --out-file ./assets/artifacts/typst-docs-v0.11.0.json --assets-dir target/typst-docs/assets 48 | ``` 49 | -------------------------------------------------------------------------------- /src/tutorial/writing-chinese.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: "中文排版") 4 | 5 | 建议结合#link("https://typst-doc-cn.github.io/docs/chinese/")[《Typst中文文档:中文用户指南》]食用。 6 | 7 | == 中文排版 —— 字体 8 | 9 | == 设置中文字体 10 | 11 | 如果中文字体不符合 typst 要求,那么它不会选择你声明的字体,例如字体的变体数量不够,参考更详细的 issue。 12 | 13 | + typst fonts 查看系统字体,确保字体名字没有错误。 14 | + typst fonts --font-path path/to/your-fonts 指定字体目录。 15 | + typst fonts --variants 查看字体变体。 16 | + 检查中文字体是否已经完全安装。 17 | 18 | == 设置语言和区域 19 | 20 | 如果字体与 ```typc text(lang: .., region: ..)``` 不匹配,可能会导致连续标点的挤压。例如字体不是中国大陆的,标点压缩会出错;反之亦然。 21 | 22 | === 伪粗体 23 | 24 | === 伪斜体 25 | 26 | === 同时设置中西字体(以宋体和Times New Roman为例) 27 | 28 | == 中文排版 —— 间距 29 | 30 | === 首行缩进 31 | 32 | #let empty-par = par[#box()] 33 | #let fake-par = context empty-par + v(-measure(empty-par + empty-par).height) 34 | 35 | #let no-indent = context h(-par.first-line-indent) 36 | 37 | #let a = { 38 | 1 39 | } 40 | 41 | === 代码片段与中文文本之间的间距 42 | 43 | === 数学公式与中文文本之间的间距 44 | 45 | == 中文排版 —— 特殊排版 46 | 47 | === 使用中文编号 48 | 49 | === 为汉字和词组注音 50 | 51 | === 为汉字添加着重号 52 | 53 | === 竖排文本 54 | 55 | === 使用国标文献格式 56 | 57 | == Typst v0.10.0为止的已知问题及补丁 58 | 59 | === 源码换行导致linebreak 60 | 61 | === 标点字体fallback 62 | 63 | == 模板参考 64 | 65 | #link("https://github.com/typst-doc-cn/tutorial/blob/b452e6ec436aa150a6429becb8cf046d08360f63/typ/templates/page.typ")[本书各章节使用的模板] 66 | 67 | todo: 内嵌和超链接可直接食用的模板 68 | 69 | == 进一步阅读 70 | 71 | + 参考#link("https://typst-doc-cn.github.io/docs/chinese/")[《Typst中文文档:中文用户指南》],包含中文用户常见问题。 72 | + 参考#(refs.scripting-modules)[《模块、外部库与多文件文档》],在你的电脑上共享中文文档模板。 73 | + 推荐使用的外部库列表 74 | -------------------------------------------------------------------------------- /.github/workflows/gh_pages.yml: -------------------------------------------------------------------------------- 1 | name: tutorial::gh_pages 2 | on: 3 | push: 4 | branches: 5 | - main 6 | workflow_dispatch: 7 | 8 | permissions: 9 | pages: write 10 | id-token: write 11 | contents: read 12 | 13 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 14 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 15 | concurrency: 16 | group: 'pages' 17 | cancel-in-progress: false 18 | 19 | jobs: 20 | build-gh-pages: 21 | runs-on: ubuntu-latest 22 | environment: 23 | name: github-pages 24 | url: ${{ steps.deployment.outputs.page_url }} 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | - run: git submodule update --init --recursive 29 | - name: Download font assets 30 | # use fonts in stable releases 31 | run: | 32 | mkdir -p assets/fonts/ 33 | curl -L https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.1.2/font-assets.tar.gz | tar -xvz -C assets/fonts 34 | - name: Download & install shiroa 35 | run: | 36 | curl --proto '=https' --tlsv1.2 -LsSf https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.3.1-rc2/shiroa-installer.sh | sh 37 | - name: Build Book 38 | run: | 39 | shiroa build --font-path ./assets/typst-fonts/ --font-path ./assets/fonts/ --path-to-root /tutorial/ -w . src 40 | - name: Setup Pages 41 | uses: actions/configure-pages@v5 42 | - name: Upload artifact 43 | uses: actions/upload-pages-artifact@v3 44 | with: 45 | path: "./dist" 46 | - name: Deploy to GitHub Pages 47 | id: deployment 48 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace.package] 2 | description = "The Raindrop Book - Typst中文教程" 3 | authors = ["Myriad-Dreamin "] 4 | version = "0.1.0" 5 | edition = "2021" 6 | readme = "README.md" 7 | license = "Apache-2.0" 8 | homepage = "https://github.com/typst-doc-cn/tutorial" 9 | repository = "https://github.com/typst-doc-cn/tutorial" 10 | 11 | [workspace] 12 | resolver = "2" 13 | members = ["crates/*"] 14 | 15 | [workspace.dependencies] 16 | typst = "0.13.1" 17 | typst-svg = "0.13.1" 18 | 19 | serde_json = "1" 20 | 21 | reflexo-typst = { version = "0.6.0-rc1", default-features = false } 22 | 23 | [profile.release] 24 | # to satisfy stubber 25 | lto = false # Enable link-time optimization 26 | strip = true # Strip symbols from binary* 27 | opt-level = 3 # Optimize for speed 28 | codegen-units = 2 # Reduce number of codegen units to increase optimizations 29 | panic = 'abort' # Abort on panic 30 | 31 | [patch.crates-io] 32 | typst = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "typst.ts/v0.6.0-rc1" } 33 | typst-library = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "typst.ts/v0.6.0-rc1" } 34 | typst-syntax = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "typst.ts/v0.6.0-rc1" } 35 | typst-utils = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "typst.ts/v0.6.0-rc1" } 36 | typst-svg = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "typst.ts/v0.6.0-rc1" } 37 | # reflexo-typst = { git = "https://github.com/Myriad-Dreamin/typst.ts", tag = "v0.5.5-rc7" } 38 | # typst-ts-core = { path = "../../rust/typst.ts/core" } 39 | # typst-ts-compiler = { path = "../../rust/typst.ts/compiler" } 40 | 41 | [workspace.lints.rust] 42 | missing_docs = "warn" 43 | 44 | [workspace.lints.clippy] 45 | uninlined_format_args = "warn" 46 | missing_safety_doc = "warn" 47 | undocumented_unsafe_blocks = "warn" 48 | -------------------------------------------------------------------------------- /src/tutorial/stateful/s1.typ: -------------------------------------------------------------------------------- 1 | 2 | #let curr-heading = state("curr-heading", ()) 3 | 4 | #set text(size: 8pt) 5 | 6 | #let first-heading = state("first-heading", (:)) 7 | #let last-heading = state("last-heading", (:)) 8 | 9 | #let find-headings(headings, page-num) = if page-num > 0 { 10 | headings.at(str(page-num), default: find-headings(headings, page-num - 1)) 11 | } 12 | 13 | #let get-heading-at-page() = { 14 | let first-headings = first-heading.final() 15 | let last-headings = last-heading.at(here()) 16 | let page-num = here().page() 17 | 18 | first-headings.at(str(page-num), default: find-headings(last-headings, page-num)) 19 | } 20 | 21 | #let update-heading-at-page(h) = context { 22 | let k = str(here().page()) 23 | last-heading.update(it => { 24 | it.insert(k, h) 25 | it 26 | }) 27 | first-heading.update(it => { 28 | if k not in it { 29 | it.insert(k, h) 30 | } 31 | it 32 | }) 33 | } 34 | 35 | #let set-heading(content) = { 36 | show heading.where(level: 2): it => { 37 | it 38 | update-heading-at-page(it.body) 39 | } 40 | show heading.where(level: 3): it => { 41 | show regex("[\p{hani}\s]+"): underline 42 | it 43 | } 44 | show heading: it => { 45 | show regex("KiraKira"): box("★", baseline: -20%) 46 | show regex("FuwaFuwa"): box("✎", baseline: -20%) 47 | it 48 | } 49 | 50 | set page( 51 | header: context { 52 | set text(size: 5pt) 53 | emph(get-heading-at-page()) 54 | }, 55 | ) 56 | 57 | content 58 | } 59 | 60 | #let set-text(content) = { 61 | show regex("feat|refactor"): emph 62 | content 63 | } 64 | 65 | #show: set-heading 66 | #show: set-text 67 | 68 | #set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt)) 69 | 70 | == 雨滴书v0.1.2 71 | === KiraKira 样式改进 72 | feat: 改进了样式。 73 | === FuwaFuwa 脚本改进 74 | feat: 改进了脚本。 75 | 76 | == 雨滴书v0.1.1 77 | refactor: 移除了LaTeX。 78 | 79 | feat: 删除了一个多余的文件夹。 80 | 81 | == 雨滴书v0.1.0 82 | feat: 新建了两个文件夹。 83 | 84 | -------------------------------------------------------------------------------- /src/tutorial/stateful-v0.12.0/s1.typ: -------------------------------------------------------------------------------- 1 | 2 | #let curr-heading = state("curr-heading", ()) 3 | 4 | #set text(size: 8pt) 5 | 6 | #let first-heading = state("first-heading", (:)) 7 | #let last-heading = state("last-heading", (:)) 8 | 9 | #let find-headings(headings, page-num) = if page-num > 0 { 10 | headings.at(str(page-num), default: find-headings(headings, page-num - 1)) 11 | } 12 | 13 | #let get-heading-at-page() = { 14 | let first-headings = first-heading.final() 15 | let last-headings = last-heading.get() 16 | let page-num = here().page() 17 | 18 | first-headings.at(str(page-num), default: find-headings(last-headings, page-num)) 19 | } 20 | 21 | #let update-heading-at-page(h) = context { 22 | let k = str(here().page()) 23 | last-heading.update(it => { 24 | it.insert(k, h) 25 | it 26 | }) 27 | first-heading.update(it => { 28 | if k not in it { 29 | it.insert(k, h) 30 | } 31 | it 32 | }) 33 | } 34 | 35 | #let set-heading(content) = { 36 | show heading.where(level: 2): it => { 37 | it 38 | update-heading-at-page(it.body) 39 | } 40 | show heading.where(level: 3): it => { 41 | show regex("[\p{hani}\s]+"): underline 42 | it 43 | } 44 | show heading: it => { 45 | show regex("KiraKira"): box("★", baseline: -20%) 46 | show regex("FuwaFuwa"): box("✎", baseline: -20%) 47 | it 48 | } 49 | 50 | set page( 51 | header: context { 52 | set text(size: 5pt) 53 | emph(get-heading-at-page()) 54 | }, 55 | ) 56 | 57 | content 58 | } 59 | 60 | #let set-text(content) = { 61 | show regex("feat|refactor"): emph 62 | content 63 | } 64 | 65 | #show: set-heading 66 | #show: set-text 67 | 68 | #set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt)) 69 | 70 | == 雨滴书v0.1.2 71 | === KiraKira 样式改进 72 | feat: 改进了样式。 73 | === FuwaFuwa 脚本改进 74 | feat: 改进了脚本。 75 | 76 | == 雨滴书v0.1.1 77 | refactor: 移除了LaTeX。 78 | 79 | feat: 删除了一个多余的文件夹。 80 | 81 | == 雨滴书v0.1.0 82 | feat: 新建了两个文件夹。 83 | 84 | -------------------------------------------------------------------------------- /typ/embedded-typst/lib.typ: -------------------------------------------------------------------------------- 1 | 2 | #let separator = "\n\n\n\n\n\n\n\n" 3 | 4 | #let allocate-fonts(data) = (kind: "font", hash: none, data: data) 5 | 6 | #let create-font-ref(data) = { 7 | let data = read(data, encoding: none) 8 | 9 | (kind: "font", data: data) 10 | } 11 | 12 | #let default-fonts() = ( 13 | "/assets/typst-fonts/LinLibertine_R.ttf", 14 | "/assets/typst-fonts/LinLibertine_RB.ttf", 15 | "/assets/typst-fonts/LinLibertine_RBI.ttf", 16 | "/assets/typst-fonts/LinLibertine_RI.ttf", 17 | "/assets/typst-fonts/NewCMMath-Book.otf", 18 | "/assets/typst-fonts/NewCMMath-Regular.otf", 19 | "/assets/typst-fonts/NewCM10-Regular.otf", 20 | "/assets/typst-fonts/NewCM10-Bold.otf", 21 | "/assets/typst-fonts/NewCM10-Italic.otf", 22 | "/assets/typst-fonts/NewCM10-BoldItalic.otf", 23 | "/assets/typst-fonts/DejaVuSansMono.ttf", 24 | "/assets/typst-fonts/DejaVuSansMono-Bold.ttf", 25 | "/assets/typst-fonts/DejaVuSansMono-Oblique.ttf", 26 | "/assets/typst-fonts/DejaVuSansMono-BoldOblique.ttf", 27 | ) 28 | 29 | #let default-cjk-fonts() = ( 30 | "/assets/fonts/SourceHanSerifSC-Regular.otf", 31 | "/assets/fonts/SourceHanSerifSC-Bold.otf", 32 | ) 33 | 34 | #let create-world(fonts) = { 35 | let base = plugin("/assets/artifacts/embedded_typst.wasm") 36 | let with-fonts = fonts.fold( 37 | base, 38 | (pre, path) => { 39 | let data = read(path, encoding: none) 40 | plugin.transition(pre.allocate_data, bytes("font"), data) 41 | }, 42 | ) 43 | 44 | plugin.transition(with-fonts.resolve_world) 45 | } 46 | 47 | #let _svg-doc(code, fonts) = { 48 | let typst-with-fonts = create-world(fonts) 49 | let (header, ..pages) = str(typst-with-fonts.svg(code)).split(separator) 50 | (header, pages) 51 | } 52 | 53 | #let svg-doc(code, fonts: none) = { 54 | let code = bytes(if type(code) == str { 55 | code 56 | } else { 57 | code.text 58 | }) 59 | if fonts == none { 60 | fonts = default-fonts() 61 | } 62 | let (header, pages) = _svg-doc(code, fonts) 63 | (header: json(bytes(header)), pages: pages) 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/tutorial/stateful/q1.typ: -------------------------------------------------------------------------------- 1 | 2 | #set text(size: 8pt) 3 | 4 | #let calc-headings(headings) = { 5 | let max-page-num = calc.max(..headings.map(it => it.location().page())) 6 | let first-headings = (none,) * max-page-num 7 | let last-headings = (none,) * max-page-num 8 | 9 | for h in headings { 10 | if first-headings.at(h.location().page() - 1) == none { 11 | first-headings.at(h.location().page() - 1) = h 12 | } 13 | last-headings.at(h.location().page() - 1) = h 14 | } 15 | 16 | let res-headings = (none,) * max-page-num 17 | for i in range(res-headings.len()) { 18 | res-headings.at(i) = if first-headings.at(i) != none { 19 | first-headings.at(i) 20 | } else { 21 | last-headings.at(i) = last-headings.at( 22 | calc.max(0, i - 1), 23 | default: none, 24 | ) 25 | last-headings.at(i) 26 | } 27 | } 28 | 29 | ( 30 | res-headings, 31 | if max-page-num > 0 { 32 | last-headings.at(-1) 33 | }, 34 | ) 35 | } 36 | 37 | #let get-heading-at-page() = { 38 | let (headings, last-heading) = calc-headings(query(heading.where(level: 2))) 39 | headings.at(here().page() - 1, default: last-heading) 40 | } 41 | 42 | #let set-heading(content) = { 43 | show heading.where(level: 3): it => { 44 | show regex("[\p{hani}\s]+"): underline 45 | it 46 | } 47 | show heading: it => { 48 | show regex("KiraKira"): box("★", baseline: -20%) 49 | show regex("FuwaFuwa"): box("✎", baseline: -20%) 50 | it 51 | } 52 | 53 | set page( 54 | header: context { 55 | set text(size: 5pt) 56 | emph(get-heading-at-page()) 57 | }, 58 | ) 59 | 60 | content 61 | } 62 | 63 | #let set-text(content) = { 64 | show regex("feat|refactor"): emph 65 | content 66 | } 67 | 68 | #show: set-heading 69 | #show: set-text 70 | 71 | #set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt)) 72 | 73 | == 雨滴书v0.1.2 74 | === KiraKira 样式改进 75 | feat: 改进了样式。 76 | === FuwaFuwa 脚本改进 77 | feat: 改进了脚本。 78 | 79 | == 雨滴书v0.1.1 80 | refactor: 移除了LaTeX。 81 | 82 | feat: 删除了一个多余的文件夹。 83 | 84 | == 雨滴书v0.1.0 85 | feat: 新建了两个文件夹。 86 | -------------------------------------------------------------------------------- /src/tutorial/stateful-v0.12.0/q1.typ: -------------------------------------------------------------------------------- 1 | 2 | #set text(size: 8pt) 3 | 4 | #let calc-headings(headings) = { 5 | let max-page-num = calc.max(..headings.map(it => it.location().page())) 6 | let first-headings = (none,) * max-page-num 7 | let last-headings = (none,) * max-page-num 8 | 9 | for h in headings { 10 | if first-headings.at(h.location().page() - 1) == none { 11 | first-headings.at(h.location().page() - 1) = h 12 | } 13 | last-headings.at(h.location().page() - 1) = h 14 | } 15 | 16 | let res-headings = (none,) * max-page-num 17 | for i in range(res-headings.len()) { 18 | res-headings.at(i) = if first-headings.at(i) != none { 19 | first-headings.at(i) 20 | } else { 21 | last-headings.at(i) = last-headings.at( 22 | calc.max(0, i - 1), 23 | default: none, 24 | ) 25 | last-headings.at(i) 26 | } 27 | } 28 | 29 | ( 30 | res-headings, 31 | if max-page-num > 0 { 32 | last-headings.at(-1) 33 | }, 34 | ) 35 | } 36 | 37 | #let get-heading-at-page() = { 38 | let (headings, last-heading) = calc-headings(query(heading.where(level: 2))) 39 | headings.at(here().page() - 1, default: last-heading) 40 | } 41 | 42 | #let set-heading(content) = { 43 | show heading.where(level: 3): it => { 44 | show regex("[\p{hani}\s]+"): underline 45 | it 46 | } 47 | show heading: it => { 48 | show regex("KiraKira"): box("★", baseline: -20%) 49 | show regex("FuwaFuwa"): box("✎", baseline: -20%) 50 | it 51 | } 52 | 53 | // @typstyle off 54 | set page(header: context { 55 | set text(size: 5pt) 56 | emph(get-heading-at-page()) 57 | }) 58 | 59 | content 60 | } 61 | 62 | #let set-text(content) = { 63 | show regex("feat|refactor"): emph 64 | content 65 | } 66 | 67 | #show: set-heading 68 | #show: set-text 69 | 70 | #set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt)) 71 | 72 | == 雨滴书v0.1.2 73 | === KiraKira 样式改进 74 | feat: 改进了样式。 75 | === FuwaFuwa 脚本改进 76 | feat: 改进了脚本。 77 | 78 | == 雨滴书v0.1.1 79 | refactor: 移除了LaTeX。 80 | 81 | feat: 删除了一个多余的文件夹。 82 | 83 | == 雨滴书v0.1.0 84 | feat: 新建了两个文件夹。 85 | -------------------------------------------------------------------------------- /src/tutorial/reference-utils.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | #import "/typ/typst-meta/docs.typ": typst-v11 3 | #import "@preview/cmarker:0.1.0": render as md 4 | 5 | #show: book.page.with(title: [参考:函数表(字典序)]) 6 | 7 | #let table-lnk(name, ref, it, scope: (:), res: none, ..args) = ( 8 | align(center + horizon, link("todo", name)), 9 | it, 10 | align( 11 | horizon, 12 | { 13 | set heading(bookmarked: false, outlined: false) 14 | eval(it.text, mode: "markup", scope: scope) 15 | }, 16 | ), 17 | ) 18 | 19 | #let table-item(c, mp, featured) = { 20 | let item = mp.at(c) 21 | 22 | (typst-func(c), item.title, ..featured(item)) 23 | } 24 | 25 | #let table-items(mp, featured) = mp.keys().sorted().map(it => table-item(it, mp, featured)).flatten() 26 | 27 | #let featured-func(item) = { 28 | return (md(item.body.content.oneliner),) 29 | } 30 | 31 | #let featured-scope-item(item) = { 32 | return (md(item.oneliner),) 33 | } 34 | 35 | == 分类:函数 36 | 37 | #table( 38 | columns: (1fr, 1fr, 2fr), 39 | [函数], [名称], [描述], 40 | ..table-items(typst-v11.funcs, featured-func), 41 | ) 42 | 43 | == 分类:方法 44 | 45 | #table( 46 | columns: (1fr, 1fr, 2fr), 47 | [方法], [名称], [描述], 48 | ..table-items(typst-v11.scoped-items, featured-scope-item), 49 | ) 50 | 51 | #if false [ 52 | == `plain-text`,以及递归函数 53 | 54 | 如果我们想要实现一个函数`plain-text`,它将一段文本转换为字符串。它便可以在树上递归遍历: 55 | 56 | ```typ 57 | #let plain-text(it) = if it.has("text") { 58 | it.text 59 | } else if it.has("children") { 60 | ("", ..it.children.map(plain-text)).join() 61 | } else if it.has("child") { 62 | plain-text(it.child) 63 | } else { ... } 64 | ``` 65 | 66 | 所谓递归是一种特殊的函数实现技巧: 67 | - 递归总有一个不调用其自身的分支,称其为递归基。这里递归基就是返回`it.text`的分支。 68 | - 函数体中包含它自身的函数调用。例如,`plain-text(it.child)`便再度调用了自身。 69 | 70 | 这个函数充分利用了内容类型的特性实现了遍历。首先它使用了`has`函数检查内容的成员。 71 | 72 | 如果一个内容有孩子,那么对其每个孩子都继续调用`plain-text`函数并组合在一起: 73 | 74 | ```typ 75 | #if it.has("children") { ("", ..it.children.map(plain-text)).join() } 76 | #if it.has("child") { plain-text(it.child) } 77 | ``` 78 | 79 | 限于篇幅,我们没有提供`plain-text`的完整实现,你可以试着在课后完成。 80 | 81 | == 鸭子类型 82 | 83 | 这里值得注意的是,`it.text`具有多态行为。即便没有继承,这里通过一定动态特性,允许我们同时访问「代码片段」的`text`和「文本」的text。例如: 84 | 85 | #code(```typ 86 | #let plain-mini(it) = if it.has("text") { it.text } 87 | #repr(plain-mini(`代码片段中的text`)) \ 88 | #repr(plain-mini([文本中的text])) 89 | ```) 90 | 91 | 这也便是我们在「内容类型」小节所述的鸭子类型特性。如果「内容」长得像文本(鸭子),那么它就是文本。 92 | ] 93 | -------------------------------------------------------------------------------- /src/figures.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/fletcher:0.5.7" as fletcher: edge, node 2 | #import "/typ/templates/page.typ": is-light-theme, main-color, page-width 3 | #import "mod.typ": typst-func 4 | 5 | #let figure-typst-arch( 6 | stroke-color: main-color, 7 | light-theme: is-light-theme, 8 | ) = { 9 | let node = node.with(stroke: main-color + 0.5pt) 10 | let xd = align.with(center) 11 | 12 | fletcher.diagram( 13 | node-outset: 2pt, 14 | axes: (ltr, btt), 15 | // nodes 16 | node((0, 0), xd[文件解析\ (Parsing)]), 17 | node((1.5, 0), xd[表达式求值\ (Evaluation)]), 18 | node((3, 0), xd[内容排版\ (Typesetting)]), 19 | node((3, -1), xd[结构导出\ (Exporting)]), 20 | // edges 21 | edge((0, 0), (1.5, 0), "..}>", bend: 25deg), 22 | edge((1.5, 0), (0, 0), xd[`import`或\ `include`], "..}>", bend: 25deg), 23 | edge((1.5, 0), (3, 0), "..}>", bend: 25deg), 24 | edge((3, 0), (1.5, 0), xd[`styled`等], "..}>", bend: 25deg), 25 | edge((3, 0), (3, -1), "..}>", bend: 25deg), 26 | ) 27 | } 28 | 29 | #let figure-content-decoration( 30 | stroke-color: main-color, 31 | light-theme: is-light-theme, 32 | width: page-width, 33 | ) = { 34 | // let node = node.with(stroke: main-color + 0.5pt) 35 | let node-text = align.with(center) 36 | 37 | // todo: 消除重复 38 | if width < 500pt { 39 | let node-rect-text(content) = rect(node-text(content), width: 125pt) 40 | fletcher.diagram( 41 | node-outset: 2pt, 42 | axes: (ltr, btt), 43 | // nodes 44 | node((0, 0), node-rect-text[```typ 左#[一段文本]右```]), 45 | node((0, -1.5), node-rect-text(```typc text(blue)```)), 46 | node((0, -3), node-rect-text([左] + text(blue)[一段文本] + [右])), 47 | 48 | node((0, -0.5 + 0), node-text[选中内容]), 49 | node((0, -0.5 + -1.5), node-text[对内容块应用#typst-func("text")函数]), 50 | node((0, -0.5 + -3), node-text[最终效果]), 51 | 52 | // edges 53 | edge((0, -0.5 + 0), (0, -1.5), "..}>"), 54 | edge((0, -0.5 + -1.5), (0, -3), "..}>"), 55 | ) 56 | } else { 57 | fletcher.diagram( 58 | node-outset: 2pt, 59 | axes: (ltr, btt), 60 | // nodes 61 | node((0, 0), node-text[```typ 左#[一段文本]右```]), 62 | node((1.5, 0), node-text(```typc text(blue)```)), 63 | node((3, 0), node-text([左] + text(blue)[一段文本] + [右])), 64 | node((0, -0.5), node-text[选中内容]), 65 | node((1.5, -0.5), node-text[对内容块应用#typst-func("text")函数]), 66 | node((3, -0.5), node-text[最终效果]), 67 | // edges 68 | edge((0, 0), (1.5, 0), "..}>"), 69 | edge((1.5, 0), (3, 0), "..}>"), 70 | ) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/tutorial/reference-visualization.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.ref-page.with(title: [参考:图形与几何元素]) 4 | 5 | == 直线 6 | 7 | A line from one point to another. #ref-bookmark[`line`] 8 | 9 | #code(```typ 10 | #line(length: 100%) 11 | #line(end: (50%, 50%)) 12 | #line( 13 | length: 4cm, 14 | stroke: 2pt + maroon, 15 | ) 16 | ```) 17 | 18 | == 线条样式 19 | 20 | Defines how to draw a line. #ref-bookmark[`stroke`] 21 | 22 | A stroke has a paint (a solid color or gradient), a thickness, a line cap, a line join, a miter limit, and a dash pattern. All of these values are optional and have sensible defaults. 23 | 24 | #code(```typ 25 | #set line(length: 100%) 26 | #let rainbow = gradient.linear( 27 | ..color.map.rainbow) 28 | #stack( 29 | spacing: 1em, 30 | line(stroke: 2pt + red), 31 | line(stroke: (paint: blue, thickness: 4pt, cap: "round")), 32 | line(stroke: (paint: blue, thickness: 1pt, dash: "dashed")), 33 | line(stroke: 2pt + rainbow), 34 | ) 35 | ```) 36 | 37 | == 贝塞尔路径(曲线) 38 | 39 | A path through a list of points, connected by Bezier curves. #ref-bookmark[`path`] 40 | 41 | #code(```typ 42 | #path( 43 | fill: blue.lighten(80%), 44 | stroke: blue, 45 | closed: true, 46 | (0pt, 50pt), 47 | (100%, 50pt), 48 | ((50%, 0pt), (40pt, 0pt)), 49 | ) 50 | ```) 51 | 52 | == 圆形 53 | 54 | A circle with optional content. #ref-bookmark[`circle`] 55 | 56 | #code(```typ 57 | // Without content. 58 | #circle(radius: 25pt) 59 | 60 | // With content. 61 | #circle[ 62 | #set align(center + horizon) 63 | Automatically \ 64 | sized to fit. 65 | ] 66 | ```) 67 | 68 | == 椭圆 69 | 70 | An ellipse with optional content. #ref-bookmark[`ellipse`] 71 | 72 | #code(```typ 73 | // Without content. 74 | #ellipse(width: 35%, height: 30pt) 75 | 76 | // With content. 77 | #ellipse[ 78 | #set align(center) 79 | Automatically sized \ 80 | to fit the content. 81 | ] 82 | ```) 83 | 84 | == 正方形 85 | 86 | A square with optional content. #ref-bookmark[`square`] 87 | 88 | #code(```typ 89 | // Without content. 90 | #square(size: 40pt) 91 | 92 | // With content. 93 | #square[ 94 | Automatically \ 95 | sized to fit. 96 | ] 97 | ```) 98 | 99 | == 矩形 100 | 101 | A rectangle with optional content. #ref-bookmark[`rect`] 102 | 103 | #code(```typ 104 | // Without content. 105 | #rect(width: 35%, height: 30pt) 106 | 107 | // With content. 108 | #rect[ 109 | Automatically sized \ 110 | to fit the content. 111 | ] 112 | ```) 113 | 114 | == 多边形 115 | 116 | A closed polygon. #ref-bookmark[`ellipse`] 117 | 118 | The polygon is defined by its corner points and is closed automatically. 119 | 120 | #code(```typ 121 | #polygon( 122 | fill: blue.lighten(80%), 123 | stroke: blue, 124 | (20%, 0pt), 125 | (60%, 0pt), 126 | (80%, 2cm), 127 | (0%, 2cm), 128 | ) 129 | ```) 130 | 131 | == 图形库 132 | 133 | + typst-fletcher 134 | + typst-syntree 135 | + cetz 136 | -------------------------------------------------------------------------------- /src/graph/solid-geometry.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | #import "@preview/cetz:0.3.4": * 3 | 4 | #show: book.page.with(title: "立体几何") 5 | 6 | == 变换「坐标系」 7 | 8 | 在绘制立体图形(以及其他抽象图形)时,最重要的思路是变换「坐标系」(Viewport),以方便绘制。 9 | 10 | #code(```typ 11 | #import "@preview/cetz:0.3.4": * 12 | #canvas({ 13 | import draw: * 14 | let axis-line(p) = { 15 | line((0, 0), (x: 1.5), stroke: red) 16 | line((0, 0), (y: 1.5), stroke: blue) 17 | line((0, 0), (z: 1.5), stroke: green) 18 | circle((0, 0, 0), fill: black, radius: 0.05) 19 | content((-0.4, 0.1, -0.2), p) 20 | } 21 | set-viewport((0, 0, 0), (4, 4, -4), bounds: (4, 4, 4)) 22 | axis-line("A") 23 | set-viewport((4, 4, 4), (0, 0, 0), bounds: (4, 4, 4)) 24 | axis-line("B") 25 | }) 26 | ```) 27 | 28 | == 使用「空间变换」 29 | 30 | 由于「空间变换」(Transformation)的存在,你可以先绘制基本图形,再使用变换完成图形的绘制: 31 | 32 | ```typ 33 | #let 六面体 = { 34 | import draw: * 35 | let neg(u) = if u == 0 { 1 } else { -1 } 36 | for (p, c) in ( 37 | ((0, 0, 0), black), ((1, 1, 0), red), 38 | ((1, 0, 1), blue), ((0, 1, 1), green), 39 | ) { 40 | line(vector.add(p, (0, 0, neg(p.at(2)))), p, stroke: c) 41 | line(vector.add(p, (0, neg(p.at(1)), 0)), p, stroke: c) 42 | line(vector.add(p, (neg(p.at(0)), 0, 0)), p, stroke: c) 43 | } 44 | } 45 | ``` 46 | 47 | #let 六面体 = { 48 | import draw: * 49 | let neg(u) = if u == 0 { 50 | 1 51 | } else { 52 | -1 53 | } 54 | for (p, c) in ( 55 | ((0, 0, 0), black), 56 | ((1, 1, 0), red), 57 | ((1, 0, 1), blue), 58 | ((0, 1, 1), green), 59 | ) { 60 | line(vector.add(p, (0, 0, neg(p.at(2)))), p, stroke: c) 61 | line(vector.add(p, (0, neg(p.at(1)), 0)), p, stroke: c) 62 | line(vector.add(p, (neg(p.at(0)), 0, 0)), p, stroke: c) 63 | } 64 | } 65 | 66 | 运行以下代码你将获得: 67 | 68 | #code( 69 | ```typ 70 | #import "@preview/cetz:0.3.4": * 71 | #align(center, canvas(六面体)) 72 | ```, 73 | scope: (六面体: 六面体), 74 | ) 75 | 76 | #pagebreak(weak: true) 77 | 78 | == 六面体 79 | 80 | #code( 81 | ```typ 82 | #import "@preview/cetz:0.3.4": * 83 | #align(center, canvas({ 84 | import draw: * 85 | set-viewport((0, 0, 0), (4, 4, -4), bounds: (1, 1, 1)) 86 | group(name: "S", translate((0, 0, 0)) + { 87 | anchor("O", (0, 0, 0)) 88 | 六面体 89 | }) 90 | group(name: "T", translate((1.4, 0, 0)) + scale(x: 120%, y: 80%) + { 91 | line((0, 0, 0.5), (0.5, 0, 0), stroke: black) 92 | anchor("A", (0, 0, 0.5)) 93 | anchor("B", (0.5, 0, 0)) 94 | 六面体 95 | }) 96 | circle("S.O", fill: black, radius: 0.05 / 4) 97 | content((rel: (-0.08, 0.04, 0), to: "S.O"), [O]) 98 | circle("T.A", fill: black, radius: 0.05 / 4) 99 | content((rel: (0.02, -0.08, 0), to: "T.A"), [A]) 100 | circle("T.B", fill: black, radius: 0.05 / 4) 101 | content((rel: (0, 0.1, 0), to: "T.B"), [B]) 102 | })) 103 | ```, 104 | scope: (六面体: 六面体), 105 | ) 106 | -------------------------------------------------------------------------------- /src/tutorial/reference-color.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.ref-page.with(title: [参考:颜色、色彩渐变与模式]) 4 | 5 | == RGB 6 | 7 | Create an RGB(A) color. #ref-bookmark[`color.rgb`] 8 | 9 | The color is specified in the sRGB color space. 10 | 11 | An RGB(A) color is represented internally by an array of four components: 12 | 13 | #code(```typ 14 | #square(fill: rgb("#b1f2eb")) 15 | #square(fill: rgb(87, 127, 230)) 16 | #square(fill: rgb(25%, 13%, 65%)) 17 | ```) 18 | 19 | == HSL 20 | 21 | Create an HSL color. #ref-bookmark[`color.hsl`] 22 | 23 | This color space is useful for specifying colors by hue, saturation and lightness. It is also useful for color manipulation, such as saturating while keeping perceived hue. 24 | 25 | An HSL color is represented internally by an array of four components: 26 | - hue (angle) 27 | - saturation (ratio) 28 | - lightness (ratio) 29 | - alpha (ratio) 30 | 31 | These components are also available using the components method. 32 | 33 | #code(```typ 34 | #square( 35 | fill: color.hsl(30deg, 50%, 60%) 36 | ) 37 | ```) 38 | 39 | == CMYK 40 | 41 | Create a CMYK color. #ref-bookmark[`color.cmyk`] 42 | 43 | This is useful if you want to target a specific printer. The conversion to RGB for display preview might differ from how your printer reproduces the color. 44 | 45 | // todo: typo in reference 46 | An CMYK color is represented internally by an array of four components: 47 | - cyan (ratio) 48 | - magenta (ratio) 49 | - yellow (ratio) 50 | - key (ratio) 51 | 52 | These components are also available using the components method. 53 | 54 | #code(```typ 55 | #square( 56 | fill: cmyk(27%, 0%, 3%, 5%) 57 | ) 58 | ```) 59 | 60 | == Luma 61 | 62 | == Oklab 63 | 64 | == Oklch 65 | 66 | == Linear RGB 67 | 68 | == HSV 69 | 70 | == 色彩空间 71 | 72 | Returns the constructor function for this color's space. #ref-bookmark[`color.space`] 73 | 74 | == 十六进制表示(RGB) 75 | 76 | Returns the color's RGB(A) hex representation. #ref-bookmark[`color.to-hex`] 77 | 78 | Hex such as `#ffaa32` or `#020304fe`. The alpha component (last two digits in `#020304fe`) is omitted if it is equal to ff (255 / 100%). 79 | 80 | == 颜色计算 81 | 82 | === lighten 83 | 84 | Lightens a color by a given factor. #ref-bookmark[`color.lighten`] 85 | 86 | === darken 87 | 88 | Darkens a color by a given factor. #ref-bookmark[`color.darken`] 89 | 90 | === saturate 91 | 92 | Increases the saturation of a color by a given factor. #ref-bookmark[`color.saturate`] 93 | 94 | === negate 95 | 96 | Produces the negative of the color. #ref-bookmark[`color.negate`] 97 | 98 | === rotate 99 | 100 | Rotates the hue of the color by a given angle. #ref-bookmark[`color.rotate`] 101 | 102 | === mix 103 | 104 | Create a color by mixing two or more colors. #ref-bookmark[`color.mix`] 105 | 106 | == 色彩渐变 107 | 108 | A color gradient. #ref-bookmark[`gradient`] 109 | 110 | Typst supports linear gradients through the gradient.linear function, radial gradients through the gradient.radial function, and conic gradients through the gradient.conic function. 111 | 112 | A gradient can be used for the following purposes: 113 | 114 | - As a fill to paint the interior of a shape: ```typc rect(fill: gradient.linear(..))``` 115 | - As a stroke to paint the outline of a shape: ```typc rect(stroke: 1pt + gradient.linear(..))``` 116 | - As the fill of text: ```typc set text(fill: gradient.linear(..))``` 117 | - As a color map you can sample from: ```typc gradient.linear(..).sample(0.5)``` 118 | 119 | #code(```typ 120 | #stack( 121 | dir: ltr, 122 | spacing: 1fr, 123 | square(fill: gradient.linear( 124 | ..color.map.rainbow)), 125 | square(fill: gradient.radial( 126 | ..color.map.rainbow)), 127 | square(fill: gradient.conic( 128 | ..color.map.rainbow)), 129 | ) 130 | ```) 131 | 132 | == 填充模式 133 | 134 | A repeating pattern fill. #ref-bookmark[`pattern`] 135 | 136 | Typst supports the most common pattern type of tiled patterns, where a pattern is repeated in a grid-like fashion, covering the entire area of an element that is filled or stroked. The pattern is defined by a tile size and a body defining the content of each cell. You can also add horizontal or vertical spacing between the cells of the patterng. 137 | 138 | #code(```typ 139 | #let pat = pattern(size: (30pt, 30pt))[ 140 | #place(line(start: (0%, 0%), end: (100%, 100%))) 141 | #place(line(start: (0%, 100%), end: (100%, 0%))) 142 | ] 143 | 144 | #rect(fill: pat, width: 100%, height: 60pt, stroke: 1pt) 145 | ```) 146 | -------------------------------------------------------------------------------- /typ/templates/circuit-board.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/tutorial/reference-syntax-analysis.typ: -------------------------------------------------------------------------------- 1 | 2 | == variable identifier 3 | 4 | // `let`关键字后紧接着跟随一个#term("variable identifier")。标识符是变量的“名称”。「变量声明」后续的位置都可以通过标识符引用该变量。 5 | 6 | // + 标识符以Unicode字母、Unicode数字和#mark("_")开头。以下是示例的合法变量名: 7 | // ```typ 8 | // // 以英文字母开头,是Unicode字母 9 | // #let a; #let z; #let A; #let Z; 10 | // // 以汉字开头,是Unicode字母 11 | // #let 这; 12 | // // 以下划线开头 13 | // #let _; 14 | // ``` 15 | // + 标识符后接有限个Unicode字母、Unicode数字、#mark("hyphen")和#mark("_")。以下是示例的合法变量名: 16 | // ```typ 17 | // // 纯英文变量名,带连字号 18 | // #let alpha-test; #let alpha--test; 19 | // // 纯中文变量名 20 | // #let 这个变量; #let 这个_变量; #let 这个-变量; 21 | // // 连字号、下划线在多种位置 22 | // #let alpha-; // 连字号不能在变量名开头位置 23 | // #let _alpha; #let alpha_; 24 | // ``` 25 | // + 特殊规则:标识符仅为#mark("_")时,不允许在后续位置继续使用。 26 | // #code(```typ 27 | // #let _ = 1; 28 | // // 不能编译:#_ 29 | // ```) 30 | // 该标识符被称为#term("placeholder")。 31 | // + 特殊规则:标识符不允许为`let`、`set`、`show`等关键字。 32 | // #code(```typ 33 | // // 不能编译: 34 | // // #let let = 1; 35 | // ```) 36 | 37 | // 建议标识符简短且具有描述性。同时建议标识符中仅含英文与#mark("hyphen")。 38 | 39 | == let statement 40 | 41 | // 「变量声明」可以没有初始值表达式: 42 | 43 | // #code(```typ 44 | // #let x 45 | // #repr(x) 46 | // ```) 47 | 48 | // 事实上,它等价于将`x`初始化为`none`。 49 | 50 | // #code(```typ 51 | // #let x = none 52 | // #repr(x) 53 | // ```) 54 | 55 | // 尽管Typst允许你不写初始值表达式,本书还是建议你让所有的「变量声明」都具有初始值表达式。因为初始值表达式还告诉阅读你代码的人这个变量可能具有什么样的类型。 56 | 57 | == expression 58 | 59 | // === 逻辑比较表达式 60 | 61 | // 数字之间可以相互(算术逻辑)比较,并产生布尔类型的表达式: 62 | 63 | // - 小于: 64 | // #code(```typ 65 | // #(1 < 0), #(1 < 1), #(1 < 2) 66 | // ```) 67 | // - 小于等于: 68 | // #code(```typ 69 | // #(1 <= 0), #(1 <= 1), #(1 <= 2) 70 | // ```) 71 | // - 大于: 72 | // #code(```typ 73 | // #(1 > 0), #(1 > 1), #(1 > 2) 74 | // ```) 75 | // - 大于等于: 76 | // #code(```typ 77 | // #(1 >= 0), #(1 >= 1), #(1 >= 2) 78 | // ```) 79 | // - 等于: 80 | // #code(```typ 81 | // #(1 == 0), #(1 == 1), #(1 == 2) 82 | // ```) 83 | // - 不等于: 84 | // #code(```typ 85 | // #(1 != 0), #(1 != 1), #(1 != 2) 86 | // ```) 87 | 88 | // 不仅整数与整数之间、浮点数与浮点数之间可以做比较,而且整数与浮点数之间也可以做比较。当整数与浮点数相互比较时,整数会转换为浮点数再参与比较。 89 | // #code(```typ 90 | // #(1 != 0.), #(1 != 1.), #(1 != 2.) 91 | // ```) 92 | 93 | // === 算术运算表达式 94 | 95 | // 数字之间可以做算术运算,并产生数字结果的表达式: 96 | 97 | // - 取正运算: 98 | // #code(```typ 99 | // #(+1), #(+0), #(++1) 100 | // ```) 101 | // - 取负运算: 102 | // #code(```typ 103 | // #(-1), #(-0), #(--1), #(-+-1) 104 | // ```) 105 | // - 加运算: 106 | // #code(```typ 107 | // #(1 + 2), #(1 + 1), #(1 + -1), #(1 + -2) 108 | // ```) 109 | // - 减运算: 110 | // #code(```typ 111 | // #(1 - 2), #(1 - 1), #(1 - -1), #(1 - -2) 112 | // ```) 113 | // - 乘运算: 114 | // #code(```typ 115 | // #(1 * 2), #(2 * 2), #(2 * -2) 116 | // ```) 117 | // - 除运算: 118 | // #code(```typ 119 | // #(1 / 2), #(2 / 2), #(2 / -2) 120 | // ```) 121 | 122 | // 值得注意的是$-2^63$在Typst中是浮点数,这可能是Typst的bug: 123 | 124 | // #code(```typ 125 | // #type(-9223372036854775808) 126 | // ```) 127 | 128 | // 为了正常使用该整数值你需要强制转换: 129 | 130 | // #code(```typ 131 | // #int(-9223372036854775808), #type(int(-9223372036854775808)) 132 | // ```) 133 | 134 | // 在日常生活中,我们还常用整除,如下方法实现了整除: 135 | 136 | // #code(```typ 137 | // #let fdiv(x, y) = int(x / y) 138 | // #fdiv(3, 2), #fdiv(-12, 2), #fdiv(-12, 5) \ 139 | // // 或 140 | // #int(3 / 2), #int(-12 / 2), #int(-12 / 5) 141 | // ```) 142 | 143 | // #pro-tip[ 144 | // `calc.rem`函数帮你求解整除的余数: 145 | // #code(```typ 146 | // #let fdiv(x, y) = int(x / y) 147 | // $ 3 div & 2 = & #fdiv( 3, 2) & ...... & #calc.rem( 3, 2). \ 148 | // -12 div & 2 = & #fdiv(-12, 2) & ...... & #calc.rem(-12, 2). \ 149 | // -12 div & 5 = & #fdiv(-12, 5) & ...... & #calc.rem(-12, 5). $ 150 | // ```) 151 | // ] 152 | 153 | // === 赋值表达式 154 | 155 | // 变量可以被赋予一个表达式的值,所有的赋值表达式都产生`none`值而非返回变量的值。 156 | 157 | // - 赋值及先加(减、乘或除)后赋值: 158 | // #code(```typ 159 | // #let a = 1 160 | // #repr(a = 10), #a, #repr(a += 2), #a, #repr(a -= 2), #a, #repr(a *= 2), #a, #repr(a /= 2), #a 161 | // ```) 162 | 163 | // === 字符串相关的表达式 164 | 165 | // 字符串相加表示字符串的连接: 166 | 167 | // #code(```typ 168 | // #("a" + "b") 169 | // ```) 170 | 171 | // 字符串与数字相乘表示将该字符串重复$n$次后再连接: 172 | 173 | // #code(```typ 174 | // #("a" * 4), #(4 * "ab") 175 | // ```) 176 | 177 | // 字符串之间的比较遵从#link("https://en.wikipedia.org/wiki/Lexicographic_order")[#term("lexicographical order")]。 178 | 179 | // 等于和不等于的比较,比较每个字符是否相等: 180 | 181 | // #code(```typ 182 | // #("a" == "a"), #("a" != "a") \ 183 | // #("a" == "b"), #("a" != "b") \ 184 | // ```) 185 | 186 | // 大于和小于的比较,从第一个字符开始依次比较,比较每个字符是否相等。直到第一个不相等的字符时作以下判断,字符的Unicode值较小的字符串则x相应更“小”: 187 | 188 | // #code(```typ 189 | // #("a" < "b"), #("a" > "a"), \ 190 | // #("a" < "ba"), #("ac" < "ba"), #("aac" < "aba") 191 | // ```) 192 | 193 | // 若一直比到了其中一个字符串的尽头,则较短的字符串更“小”: 194 | 195 | // #code(```typ 196 | // #("a" < "ab") 197 | // ```) 198 | 199 | // 大于等于和小于等于的比较则将「相等性」纳入考虑: 200 | 201 | // #code(```typ 202 | // #("a" >= "a"), #("a" <= "a") \ 203 | // ```) 204 | -------------------------------------------------------------------------------- /typ/templates/ebook.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/shiroa:0.2.3": * 2 | #import "/typ/templates/page.typ": project, part-style, dash-color 3 | #import "/typ/templates/term.typ": reset-term-state 4 | 5 | #let _page-project = project 6 | 7 | /// Show a title page with a full page background 8 | #let cover(display-title) = { 9 | // #set text(fill: black, font: titleFont) 10 | // #if logo != none { 11 | // place(top + center, pad(top:1cm, image(logo, width: 3cm))) 12 | // } 13 | stack( 14 | 1fr, 15 | align( 16 | center + horizon, 17 | block( 18 | width: 100%, 19 | fill: dash-color.lighten(70%), 20 | height: 6.2cm, 21 | pad(x: 2cm, y: 1cm)[ 22 | // #text(size: 3em, weight: 900, project-meta.display-title) 23 | #text(size: 3em, weight: 900, [The Raindrop-Blue Book]) 24 | #v(1cm, weak: true) 25 | // #text(size: 3em, project-meta.at("subtitle", default: none)) 26 | #text(size: 2em, display-title) 27 | #v(1cm, weak: true) 28 | #text(size: 1em, weight: "bold", "Myriad-Dreamin等著") 29 | ], 30 | ), 31 | ), 32 | 2fr, 33 | ) 34 | } 35 | 36 | #let p = counter("book-part") 37 | #let p-num = numbering.with("1") 38 | #let default-styles = ( 39 | cover-image: "./rm175-noon-02.jpg", 40 | cover: cover, 41 | part: it => { 42 | //set image(width: 100%, height: 100%) 43 | page( 44 | margin: 0cm, 45 | header: none, 46 | background: [ 47 | #move(dy: 3%, scale(x: -130%, y: 130%, rotate(38.2deg, image("./rustacean-flat-gesture.svg", width: 130%)))) 48 | ], 49 | { 50 | p.step() 51 | stack( 52 | 1fr, 53 | align( 54 | right + bottom, 55 | block( 56 | width: 100%, 57 | fill: dash-color.lighten(70%), 58 | height: 6.2cm, 59 | pad(x: 1cm, y: 1cm)[ 60 | #set text(size: 32pt) 61 | #v(1em) 62 | #context { 63 | let loc = here() 64 | heading([Part.#p-num(..p.at(loc))#sym.space] + it) 65 | } 66 | ], 67 | ), 68 | ), 69 | 2fr, 70 | ) 71 | }, 72 | ) 73 | }, 74 | chapter: it => reset-term-state + it, 75 | ) 76 | 77 | #let project(title: "", display-title: none, authors: (), spec: "", content, styles: default-styles) = { 78 | let display-title = display-title 79 | if display-title == none { 80 | display-title = title 81 | } 82 | 83 | // inherit styles 84 | let styles = default-styles + styles 85 | 86 | // set document metadata early 87 | set document( 88 | author: authors, 89 | title: title, 90 | ) 91 | 92 | // set web/pdf page properties 93 | set page(numbering: "1") 94 | 95 | // todo: abstraction 96 | { 97 | // inherit from page setting 98 | show: _page-project.with(title: none, kind: none) 99 | 100 | //set image(width: 100%, height: 100%) 101 | set page( 102 | margin: 0cm, 103 | header: none, 104 | background: [ 105 | #place({ 106 | set block(spacing: -0.1em) 107 | image("./circuit-board.svg", width: 100%) 108 | image("./circuit-board.svg", width: 100%) 109 | }) 110 | #move(dy: 3%, scale(x: -130%, y: 130%, rotate(38.2deg, image("./rustacean-flat-gesture.svg", width: 130%)))) 111 | ], 112 | ) 113 | 114 | // place book meta 115 | external-book(spec: (styles.inc)(spec)) 116 | (styles.cover)(display-title) 117 | } 118 | 119 | context { 120 | let loc = here() 121 | let project-meta = (title: title, display-title: display-title, book: book-meta-state.final(), styles: styles) 122 | 123 | { 124 | // inherit from page setting 125 | show: _page-project.with(title: none, kind: none) 126 | 127 | // set web/pdf page properties 128 | set page(numbering: none) 129 | 130 | include "/src/prefaces/license.typ" 131 | include "/src/prefaces/acknowledgement.typ" 132 | 133 | let outline-numbering-base = numbering.with("1.") 134 | let outline-numbering(a0, ..args) = if a0 > 0 { 135 | h(1em * args.pos().len()) 136 | outline-numbering-base(a0, ..args) + [ ] 137 | } 138 | 139 | let outline-counter = counter("outline-counter") 140 | show outline.entry: it => { 141 | let has-part = if it.body().func() != none and "children" in it.body().fields() { 142 | for ch in it.body().children { 143 | if "text" in ch.fields() and ch.text.contains("Part") { 144 | ch.text 145 | } 146 | } 147 | } 148 | 149 | let numbering = if has-part == none { 150 | outline-counter.step(level: it.level + 1) 151 | context outline-counter.display(outline-numbering) 152 | } else { 153 | outline-counter.step(level: 1) 154 | } 155 | link(it.element.location(), text(black, it.indented(numbering + it.prefix(), it.inner()))) 156 | } 157 | 158 | set outline.entry(fill: repeat[.]) 159 | outline(depth: 1) 160 | } 161 | 162 | if project-meta.book != none { 163 | project-meta.book.summary.map(it => visit-summary(it, styles)).sum() 164 | } 165 | } 166 | 167 | content 168 | } 169 | -------------------------------------------------------------------------------- /src/book.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/shiroa:0.2.3": * 2 | 3 | #show: book 4 | 5 | #let book-info = json("/meta.json") 6 | 7 | #book-meta( 8 | title: "The Raindrop-Blue Book (Typst中文教程)", 9 | description: "Typst中文教程", 10 | repository: "https://github.com/typst-doc-cn/tutorial", 11 | repository-edit: "https://github.com/typst-doc-cn/tutorial/edit/main/src/{path}", 12 | authors: book-info.contributors, 13 | language: "zh-cn", 14 | summary: [ 15 | #prefix-chapter("introduction.typ")[导引] 16 | = 模式 17 | - #chapter("tutorial/writing-markup.typ")[标记模式] 18 | - #chapter("tutorial/writing-scripting.typ")[脚本模式] 19 | = 脚本 20 | - #chapter("tutorial/scripting-literal.typ")[基本类型] 21 | - #chapter("tutorial/scripting-variable.typ")[变量与函数] 22 | - #chapter("tutorial/scripting-composite.typ")[复合类型] 23 | - #chapter("tutorial/doc-modulize.typ")[模块化(多文件)] 24 | - #chapter("tutorial/scripting-block-and-expression.typ")[表达式] 25 | - #chapter("tutorial/scripting-control-flow.typ")[控制流] 26 | - #chapter("tutorial/doc-stateful.typ")[状态化] 27 | = 中文排版 28 | - #chapter("tutorial/scripting-main.typ")[编译流程] 29 | - #chapter("tutorial/writing-chinese.typ")[中文排版] 30 | - #chapter("tutorial/scripting-style.typ")[样式模型] 31 | = 科技排版 32 | - #chapter("tutorial/writing-math.typ")[数学排版] 33 | - #chapter("tutorial/scripting-shape.typ")[图形排版] 34 | - #chapter("tutorial/scripting-layout.typ")[布局模型] 35 | - #chapter("tutorial/scripting-content.typ")[文档模型] 36 | = 附录 37 | // - #chapter("tutorial/reference-grammar.typ")[语法示例检索表] 38 | - #chapter("tutorial/reference-utils.typ")[常用函数表] 39 | - #chapter("tutorial/reference-math-symbols.typ")[常用数学符号] 40 | - #chapter(none)[特殊Unicode字符] 41 | = 参考 42 | - #chapter("tutorial/reference-typebase.typ")[基本类型] 43 | - #chapter("tutorial/reference-type-builtin.typ")[内置类型] 44 | - #chapter("tutorial/reference-date.typ")[时间类型] 45 | // - #chapter("tutorial/reference-visualization.typ")[图形与几何元素] 46 | // - #chapter("tutorial/reference-color.typ")[颜色、色彩渐变与模式] 47 | - #chapter("tutorial/reference-data-process.typ")[数据读写] 48 | - #chapter("tutorial/reference-data-process.typ")[数据处理] 49 | - #chapter("tutorial/reference-calculation.typ")[数值计算] 50 | - #chapter("tutorial/reference-math-mode.typ")[数学模式] 51 | - #chapter("tutorial/reference-bibliography.typ")[参考文献] 52 | - #chapter("tutorial/reference-wasm-plugin.typ")[WASM插件] 53 | // = 进阶教程 — 排版Ⅳ 54 | // 6. 脚本Ⅱ 55 | // - IO与数据处理 56 | // - 内置类型 57 | // - 数值计算 58 | // - WASM插件 59 | // 7. 排版Ⅱ 60 | // - 代码高亮 61 | // - 参考文献 62 | // - 文档大纲 63 | // 8. 科技排版 64 | // - 数学模式 65 | // - 数学公式和定理 66 | // - 物理公式 67 | // - 化学方程式 68 | // 9. 图表 69 | // - 立体几何 70 | // - 统计图 71 | // - 状态机 72 | // - 电路图 73 | // 10. 模板 74 | // 11. 参考Ⅰ 75 | // - 基本类型 76 | // - 内置类型 77 | // - 时间 78 | // - 高级颜色 79 | // - 长度单位 80 | // 12. 参考Ⅱ 81 | // - 图形 82 | = 专题 83 | - #chapter("topics/writing-math.typ")[编写一篇数学文档] 84 | // https://github.com/PgBiel/typst-oxifmt/blob/main/oxifmt.typ 85 | // - #chapter("topics/format-lib.typ")[制作一个格式化库] 86 | // https://github.com/Pablo-Gonzalez-Calderon/showybox-package/blob/main/showy.typ 87 | - #chapter("topics/writing-component-lib.typ")[制作一个组件库] 88 | - #chapter("topics/writing-plugin-lib.typ")[制作一个外部插件] 89 | - #chapter("topics/call-externals.typ")[在Typst内执行Js、Python、Typst等] 90 | // https://github.com/frugal-10191/frugal-typst 91 | - #chapter("topics/template-book.typ")[制作一个书籍模板] 92 | // chicv 93 | - #chapter("topics/template-cv.typ")[制作一个CV模板] 94 | // official template 95 | - #chapter("topics/template-paper.typ")[制作一个IEEE模板] 96 | = 专题 — 公式和定理 97 | - #chapter("science/chemical.typ")[化学方程式] 98 | - #chapter("science/algorithm.typ")[伪算法] 99 | - #chapter("science/theorem.typ")[定理环境] 100 | = 专题 — 杂项 101 | - #chapter("misc/font-setting.typ")[字体设置] 102 | // - #chapter("misc/font-style.typ")[伪粗体、伪斜体] 103 | - #chapter("misc/code-syntax.typ")[自定义代码高亮规则] 104 | - #chapter("misc/code-theme.typ")[自定义代码主题] 105 | - #chapter("misc/text-processing.typ")[读取外部文件和文本处理] 106 | = 专题 — 绘制图表 107 | - #chapter("graph/table.typ")[制表] 108 | - #chapter("graph/solid-geometry.typ")[立体几何] 109 | - #chapter("graph/digraph.typ")[拓扑图] 110 | - #chapter("graph/statistics.typ")[统计图] 111 | - #chapter("graph/state-machine.typ")[状态机] 112 | - #chapter("graph/electronics.typ")[电路图] 113 | = 专题 — 附录Ⅱ 114 | - #chapter("tutorial/reference-grammar.typ")[语法示例检索表Ⅱ] 115 | - #chapter("template/slides.typ")[演示文稿(PPT)] 116 | - #chapter("template/paper.typ")[论文模板] 117 | - #chapter("template/book.typ")[书籍模板] 118 | = 专题参考 119 | - #chapter("tutorial/reference-counter-state.typ")[计数器和状态] 120 | - #chapter("tutorial/reference-length.typ")[长度单位] 121 | - #chapter("tutorial/reference-layout.typ")[布局函数] 122 | - #chapter("tutorial/reference-table.typ")[表格] 123 | - #chapter("tutorial/reference-outline.typ")[文档大纲] 124 | ], 125 | ) 126 | 127 | #build-meta(dest-dir: "../dist") 128 | 129 | // #get-book-meta() 130 | 131 | // re-export page template 132 | #import "/typ/templates/page.typ": heading-reference, project 133 | #let page = project 134 | #let ref-page = project.with(kind: "reference-page") 135 | #let cross-link = cross-link 136 | #let heading-reference = heading-reference 137 | -------------------------------------------------------------------------------- /src/tutorial/scripting-composite.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: "复合类型") 4 | 5 | 学会了数组和字典,我们就解锁了各种新技能。 6 | 7 | == 数组字面量 8 | 9 | 脚本模式中有两类核心复合字面量。一曰「数组」,一曰「字典」。 10 | 11 | 「数组」是按照顺序存储的一些「值」,你可以在「数组」中存放*任意内容*而不拘泥于类型。你可以使用圆括号与一个逗号分隔的列表创建一个*数组字面量*: 12 | 13 | #code(```typ 14 | #(1, "OvO", [一段内容]) 15 | ```) 16 | 17 | == 字典字面量 18 | 19 | 所谓「字典」即是「键值对」的集合,其每一项是由冒号分隔的「键值对」。如下例所示,冒号左侧,“neco-mimi”等「标识符」或「字符串」是字典的「键」,而冒号右侧分别是对应的「值」。 20 | 21 | #code(```typ 22 | #(neko-mimi: 2, "utterance": "喵喵喵") 23 | ```) 24 | 25 | == 数组和字典的典型构造 26 | 27 | 特别讲解一些关于数组与字典相关的典型构造: 28 | 29 | #code(```typ 30 | #() \ // 是空的数组 31 | #(:) \ // 是空的字典 32 | #(1) \ // 被括号包裹的整数1 33 | #(()) \ // 被括号包裹的空数组 34 | #((())) \ // 被括号包裹的被括号包裹的空数组 35 | #(1,) \ // 是含有一个元素的数组 36 | ```) 37 | 38 | `()`是空的数组,不含任何值。`(:)`是空的字典,不含任何键值对。 39 | 40 | 如果括号内含了一个值,例如`(1)`,那么它仅仅是被括号包裹的整数1,仍然是整数1本身。 41 | 42 | 类似的,`(())`是被括号包裹的空数组,`((()))`是被括号包裹的被括号包裹的空数组。 43 | 44 | 为了构建含有一个元素的数组,需要在列表末尾额外放置一个逗号以示区分,例如`(1,)`。 45 | 46 | // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Trailing_commas 47 | 这种逗号被称为尾后逗号。 48 | 49 | // todo: 改进 50 | 构造数组字面量时,允许尾随一个多余的逗号而不造成影响。 51 | 52 | #code(```typ 53 | #let x = (1, "OvO", [一段内容] , ); #x 54 | // 这里有一个多余的逗号^^^ 55 | ```) 56 | 57 | 构造字典字面量时,允许尾随一个多余的逗号。 58 | 59 | #code(```typ 60 | #let cat = (attribute: [kawaii\~], ); #cat 61 | // 这里有一个尾随的小逗号^^ 62 | ```) 63 | 64 | == 数组和字典的「解构赋值」 65 | 66 | 这是本节最难的内容,非程序员朋友想要灵活运用可能有点困难。 67 | 68 | 除了使用字面量「构造」元素,Typst还支持「构造」的反向操作:「解构赋值」。顾名思义,你可以在左侧用相似的语法从数组或字典中获取值并赋值到*对应*的变量上。 69 | 70 | #code(```typ 71 | #let (attr: a) = (attr: [kawaii\~]) 72 | #a 73 | ```) 74 | 75 | 「解构赋值」必须一一对应,但你也可以使用「占位符」(`_`)或「延展符」(`..`)以作*部分*解构: 76 | 77 | #code(```typ 78 | #let (first, ..) = (1, 2, 3) 79 | #let (.., second-last, _) = (7, 8, 9, 10) 80 | #first, #second-last 81 | ```) 82 | 83 | 数组的「解构赋值」有一个妙用,那就是重映射内容。 84 | 85 | #code(```typ 86 | #let (a, b, c) = (1, 2, 3) 87 | #let (b, c, a) = (a, b, c); #a, #b, #c 88 | ```) 89 | 90 | 特别地,如果两个变量相互重映射,这种操作被称为「交换」: 91 | 92 | #code(```typ 93 | #let (a, b) = (1, 2) 94 | #((a, b) = (b, a)); #a, #b 95 | ```) 96 | 97 | == 数组和字典的成员访问 98 | 99 | 为了访问数组,你可以使用`at`方法。“at”在中文里是“在”的意思,它表示对「数组」使用「索引」操作。`at(0)`索引到第1个值,`at(n)`索引到第 $n + 1$ 个值,以此类推。如下所示: 100 | 101 | #code(```typ 102 | #let x = (1, "OvO", [一段内容]) 103 | #x.at(0), #x.at(1), #x.at(2) 104 | ```) 105 | 106 | 至于「索引」从零开始的原因,这只是约定俗成。等你习惯了,你也会变成计数从零开始的好程序员。 107 | 108 | 为了访问字典,你可以使用`at`方法。但由于「键」都是字符串,你需要使用字符串作为字典的「索引」。 109 | 110 | #code(```typ 111 | #let cat = (attribute: [kawaii\~]) 112 | #cat.at("attribute") 113 | ```) 114 | 115 | 为了方便,Typst允许你直接通过成员方法访问字典对应「键」的值: 116 | 117 | #code(```typ 118 | #let cat = ("attribute": [kawaii\~]) 119 | #cat.attribute 120 | ```) 121 | 122 | == 数组和字典的「存在谓词」 123 | 124 | 为了访问数组,你可以使用`contains`方法。“contain”在中文里是“包含”的意思,如下所示: 125 | 126 | #code(```typ 127 | #let x = (1, "OvO", [一段内容]) 128 | #x.contains[一段内容] 129 | ```) 130 | 131 | 因为这太常用了,typst专门提供了`in`语法,表示判断`x`是否*存在于*某个数组中: 132 | 133 | #code(```typ 134 | #([一段内容] in (1, "OvO", [一段内容])) \ 135 | #([另一段内容] in (1, "OvO", [一段内容])) 136 | ```) 137 | 138 | 字典也可以使用此语法,表示判断`x`是否是字典的一个「键」。特别地,你还可以前置一个`not`判断`x`是否*不在*某个数组或字典中: 139 | 140 | #code(```typ 141 | #let cat = (neko-mimi: 2) 142 | #("neko-kiki" not in cat) 143 | ```) 144 | 145 | 注意:`x in (...)`与`"x" in (...)`是不同的。例如`neko-mimi in cat`将检查`neko-mimi`变量的内容是否是字典变量`cat`的一个「键」,而`"neko-mimi"`检查对应字符串是否在其中。 146 | 147 | // field access 148 | // - dictionary 149 | // - symbol 150 | // - module 151 | // - content 152 | 153 | == 位置参数声明 154 | 155 | typst通过数组和字典在执行时向函数传递参数。上一节我们学会了基本的函数声明,若进一步理解了数组和字典,可以定义更复杂的函数。接下来我们讲透Typst中如何声明复杂函数,以及如何调用函数。 156 | 157 | 其中最简单的是位置参数声明,我们在上节已经学过。 158 | 159 | #code(```typ 160 | #let f(a, b) = [两个值#(a)和#(b)偷偷混入了我们内容之中。] 161 | #f(1, 2) 162 | ```) 163 | 164 | 在函数调用的时候,位置参数必须按照声明的顺序传递。例子中,向`f`函数传递了两个位置参数`1`和`2`,于是`a`和`b`作为变量被赋值为了`1`和`2`,接着这个函数返回一个「内容块」,其中用到了`a`和`b`变量。 165 | 166 | == 具名参数声明 167 | 168 | 函数可以包含「具名参数」,其看起来就像字典的一项,冒号右侧则是参数的*默认值*: 169 | 170 | #code(```typ 171 | #let g(name: "?") = [你是#name] 172 | #g(/* 不传就是问号 */); #g(name: "OwO。") 173 | ```) 174 | 175 | == 变长参数 176 | 177 | 函数可以包含「变长参数」。 178 | 179 | #code(```typ 180 | #let g(..args) = [很多个值,#args.pos().join([、]),偷偷混入了我们内容之中。] 181 | #g([一个俩个], [仨个四个], [五六七八个]) 182 | ```) 183 | 184 | ```typc args```的类型是`arguments`。 185 | + 使用`args.pos()`得到传入的位置参数数组;使用`args.named()`得到传入的具名参数字典。 186 | + 使用`args.at(i)`访问索引为整数`i`的位置参数;使用`args.at(name)`访问名称为字符串`name`的具名参数。 187 | 188 | == 参数类型 189 | 190 | 什么是「参数类型」(argument type)?在typst中,你完全可以先把函数参数准备好,然后直接调用函数: 191 | 192 | #code(```typ 193 | #let sum(..args, init: 0) = init + args.pos().sum() 194 | #let args = arguments(1, 2) 195 | #sum(..args) // 3 196 | ```) 197 | 198 | `arguments`是一个类型,`arguments(1, 2)`是一个表达式,它返回一个`arguments`类型的值。 199 | 200 | 也可以预先传递参数,然后直接调用函数: 201 | 202 | #code(```typ 203 | #let sum(..args, init: 0) = init + args.pos().sum() 204 | #let args = arguments((1,), (2,), init: ()) 205 | #sum(..args) // (1, 2) 206 | ```) 207 | 208 | === 参数解构 209 | 210 | #todo-box[写完] 211 | todo参数解构。 212 | 213 | #todo-box[引言] 214 | -------------------------------------------------------------------------------- /src/introduction.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [导引]) 4 | 5 | 本书面向所有Typst用户,按三种方式讲解Typst语言。《教程》等章节循序渐进,讲述了Typst的排版方式和原理;《参考》等章节拓展广度;《专题》等则专注解决具体问题。本书希望缓解Typst相关资料严重缺失的问题,与官方文档相互补充,帮助大家入门和学习Typst。 6 | 7 | 其中,《教程》的主要定位是较低的阅读门槛。即使你没有编程语言基础,也能通过《教程》上手使用Typst,在日常生活中编写各式各样的文档。 8 | 9 | 《教程》的另一个作用是串联知识。有很多排版技巧和问题缺乏深度,不适合放在《教程》内。为了避免让章节冗长,这些知识会被单独列在《参考》或《专题》中,供《教程》引用。这样,擅于编程的同学也可以略读《教程》,重点阅读放在关联的章节上。 10 | 11 | 同时,本书也会不时总结在使用Typst时的疑难杂症。 12 | 13 | == 为什么学习Typst? 14 | 15 | 在开始之前,让我们考虑Typst的名称解释和用途。Typst首先是一种用于排版文档的标记语言,它旨在易于学习、快速且用途广泛。Typst还同时指代编译器本身。Typst编译器读取并解释带有标记的文本文件,产生适合不同终端阅读的PDF文档。Typst也支持导出其他格式,例如SVG和PNG。 16 | 17 | Typst的性能很好,是撰写长篇文本的极佳选择,例如书籍和报告。 并且,Typst 非常适合书写包含数学公式的文档,例如数学、物理和工程领域的论文。 由于其编程特性,它也适用于自动化生成一系列相同样式的文档(例如作业、丛书和发票)。 18 | 19 | == 阅读本教程前,您需要了解的知识 20 | 21 | 你不需要了解任何前置知识,所需的仅仅是安装Typst,并跟着本教程的在线示例一步步学习。 22 | 23 | == 其他参考资料 24 | 25 | - #link("https://typst-doc-cn.github.io/docs/tutorial/")[官方文档翻译 - 入门教程] 26 | - #link("https://typst-doc-cn.github.io/docs/chinese/")[非官方中文用户指南] 27 | - #link("https://typst-doc-cn.github.io/docs/reference/")[官方文档翻译 - 参考] 28 | - #link("https://sitandr.github.io/typst-examples-book/book/about.html")[Typst Example Books] 29 | 30 | = 配置Typst运行环境 31 | 32 | == 使用官方的webapp(推荐) 33 | 34 | 官方提供了*在线且免费*的多人协作编辑器。该编辑器会从远程下载WASM编译器,并在你的浏览器内运行编辑器,为你提供预览服务。你可以注册一个账户并开箱即用该编辑器。 35 | 36 | #figure(image("/assets/files/editor-webapp.png"), caption: [本书作者在网页中打开webapp的瞬间]) 37 | 38 | 该编辑器的速度相比许多LaTeX编辑器都有显著优势。这是因为: 39 | - 编辑文档后,即时预览基本不会被网络请求阻塞。 40 | - Typst脚本皆在本地浏览器中运行。 41 | - Typst编译器本身性能极其优越。 42 | 43 | *注意:如果遇到无法加载的问题,你需要检查你的网络环境,如有必要请使用科学上网工具。* 44 | 45 | == 使用VSCode编辑(推荐) 46 | 47 | 打开扩展界面,搜索并安装 Tinymist 插件。它会为你提供语法高亮,代码补全,代码格式化,即时预览等功能。 48 | 49 | #figure(image("/assets/files/editor-vscode.png"), caption: [本书作者在VSCode中预览并编辑本文的瞬间]) 50 | 51 | 该编辑器的性能不如webapp。这是因为: 52 | - 编译器与预览器是两个不同的进程,有通信开销。 53 | - 用户事件和文件IO有可能增加E2E延时。 54 | 55 | 但是: 56 | - 大部分时间下,你感受不到和webapp之间的性能差异。Typst真的非常快。 57 | - 你可以离线编辑Typst文档,无需任何网络连接,例如本书的部分章节是在飞机上完成的。 58 | - 你可以免费使用部分Typst Pro的功能。你可以在文件系统中管理你所有的源代码,实施包括但不限于使用git等源码管理软件等操作。 59 | 60 | == 使用其他编辑器编辑 61 | 62 | 你可以在这些编辑器中手动安装Tinymist LSP服务或Typst相关插件。自2024年初,Neovim和Emacs已经可以与VSCode一样,已经可以有很好的Typst编辑体验。 63 | 64 | == 使用typst-cli与PDF阅读器 65 | 66 | Typst 的 CLI 可从不同的来源获得。你可以在#link("https://github.com/typst/typst/releases/")[发布页面]获得最新版本的 Typst 的源代码和预构建的二进制文件。下载适合你平台的存档并将其放在“PATH”中的目录中。及时了解未来发布后,你只需运行```bash typst update```即可。 67 | 68 | 你可以通过不同的包管理器安装Typst。请注意,包管理器中的版本可能落后于最新版本。 69 | - Linux:查看#link("https://repology.org/project/typst/versions")[Typst on Repology。] 70 | - macOS:使用brew: 71 | ```bash 72 | brew install typst 73 | ``` 74 | - Windows:使用winget: 75 | ```bash 76 | winget install --id Typst.Typst 77 | ``` 78 | 79 | 你还可以使用#link("https://rustup.rs/")[Rust]、Nix或Docker安装Typst。更多信息请查看#link("https://github.com/typst/typst?tab=readme-ov-file#usage")[官方英文说明]。 80 | 81 | 安装好CLI之后,你就可以在命令行里运行Typst编译器了。以下命令将工作目录下的文件编译为`file.pdf`: 82 | 83 | ```bash 84 | typst compile file.typ 85 | ``` 86 | 87 | 为了增量编译和预览PDF,你需要让Typst编译器运行在监视模式(watch)下: 88 | 89 | ```bash 90 | typst watch file.typ 91 | ``` 92 | 93 | 当你启动Typst编译器后,你可以使用你喜欢的编辑器编辑Typst文档,并使用你喜欢的PDF阅读器打开编译好的PDF文件。PDF阅读器推荐使用: 94 | 95 | - 在Windows下使用#link("https://www.sumatrapdfreader.org/free-pdf-reader")[Sumatra PDF]。 96 | 97 | == 各Typst运行环境的比较 98 | 99 | #{ 100 | set align(center) 101 | table( 102 | columns: if get-page-width() < 500pt { 103 | (1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr) 104 | } else { 105 | (1fr, 1fr, 1fr, 5em, 5em, 5em, 10em) 106 | }, 107 | align: horizon + center, 108 | [名称], [编辑器], [编译器环境], [预览方案], [是否支持即时编译], [语言服务], [备注], 109 | [WebAPP], [Code Mirror], [wasm], [渲染图片], [是], [良好], align(left)[开箱即用], 110 | [VSCode], [VSCode], [native], [webview], [是], [优秀], align(left)[简单上手,定制性好], 111 | [neovim], [neovim], [native], [webview], [是], [良好], align(left)[不易安装,定制性强], 112 | [Emacs], [Emacs], [native], [webview], [是], [良好], align(left)[难以安装], 113 | [typst-cli], [任意编辑器], [native], [任意PDF阅读器], [否], [无], align(left)[简单上手,灵活组合], 114 | ) 115 | } 116 | 117 | = 如何阅读本书 118 | 119 | 本书主要分为三个部分: 120 | 121 | + 教程:介绍Typst的语法、原理和理念,以助你深度理解Typst。 122 | + 参考:完整介绍Typst中内置的函数和各种外部库,以助你广泛了解Typst的能力,用Typst实现各种排版需求。 123 | + 专题等:与参考等章节的区别是,每个专题都专注解决一类问题,而不会讲解函数的用法。 124 | 125 | 每个部分都包含大量例子,它们各有侧重: 126 | 127 | - 教程中的例子希望切中要点,而避免冗长的全面的展示特性。 128 | - 参考中的例子希望讲透元素和函数的使用,例子多选自过去的一年中大家常问的问题。 129 | - 专题中的例子往往有连贯性,从前往后带你完成一系列专门的问题。专题中的例子假设你已经掌握了相关知识,只以最专业的代码解决该问题。 130 | 131 | #pro-tip[ 132 | 本书会随时夹带一些“Pro Tip”。这些“Pro Tip”由蓝色框包裹。它们告诉你一些较难理解的知识点。 133 | 134 | 你可以选择在初次阅读时*跳过*这些框,而不影响对正文的理解。但建议你在阅读完整本书后回头观看所不理解的那些“Pro Tip”。 135 | ] 136 | 137 | 以下是推荐的阅读方法: 138 | 139 | + 首先阅读《基础教程》排版Ⅰ的两篇文章,即《初识标记模式》和《初识脚本模式》。 140 | 141 | 经过这一步,你应该可以像使用Markdown那样,编写一篇基本不设置样式的文档。同时,这一步的学习难度也与学习完整Markdown语法相当。 142 | 143 | + 接着,阅读《基础教程》脚本Ⅰ的三篇文章。 144 | 145 | 经过这一步,你应该已经基本入门了Typst的标记和脚本。此时,你和进阶用户的唯一区别是,你还不太会使用高级的样式配置。推荐: 146 | - 如果你熟练阅读英语,推荐主要参考#link("https://typst.app/docs/")[官方文档]的内容。 147 | - 否则,推荐继续阅读本书剩下的内容,并参考#link("https://typst-doc-cn.github.io/docs/")[非官方中文文档]的内容。 148 | 149 | 有什么问题? 150 | - 本书只有《基础教程》完成了校对和润色,后续部分还非常不完善。甚至《基础教程》部分还有待改进。 151 | - #link("https://typst-doc-cn.github.io/docs/")[非官方中文文档]是GPT机翻后润色的的结果,有可能错翻、漏翻,内容也可能有些许迟滞。 152 | 153 | + 接着,阅读《基础教程》脚本Ⅱ和排版Ⅲ的五篇文章。 154 | 155 | 这两部分分别介绍了如何使用Typst的模块机制与状态机制。模块允许你将文档拆分为多个文件。状态则类似于其他编程语言中的全局变量的概念,可用于收集和维护数据。 156 | 157 | + 最后,同时阅读《基础参考》和《进阶教程》。你可以根据你的需求挑选《基础参考》的部分章节阅读。即便不阅读任何《基础参考》中的内容,你也可以继续阅读《进阶教程》。 158 | 159 | 经过这一步,你应该已经完全学会了目前Typst的所有理念与内容。 160 | 161 | = 许可证 162 | 163 | *typst-tutorial-cn* 所有的源码和文档都在#link("https://www.apache.org/licenses/LICENSE-2.0")[Apache License v2.0]许可证下发布. 164 | -------------------------------------------------------------------------------- /src/tutorial/scripting-shape.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: "图形排版") 4 | 5 | #todo-box[本节处于校对阶段,所以可能存在不完整或错误。] 6 | 7 | == 颜色类型 8 | 9 | Typst只有一种颜色类型,其由两部分组成。 10 | 11 | #figure([ 12 | #block( 13 | width: 200pt, 14 | align(left)[ 15 | ```typ 16 | #color.rgb(87, 127, 230) 17 | --------- ------------ 18 | | +-- 色坐标 19 | +-- 色彩空间对应的构造函数 20 | ``` 21 | ], 22 | ) 23 | #text(todo-color)[这里有个图注解] 24 | ]) 25 | 26 | 27 | 「色彩空间」(color space)是人们主观确定的色彩模型。Typst为不同的色彩空间提供了专门的构造函数。 28 | 29 | 「色坐标」(chromaticity coordinate)是客观颜色在「色彩空间」中的坐标。给定一个色彩空间,如果某种颜色*在空间内*,那么颜色能分解到不同坐标分量上,并确定每个坐标分量上的数值。反之,选择一个构造函数,并提供坐标分量上的数值,就能构造出这个颜色。 30 | 31 | #todo-box[ 32 | chromaticity coordinate这个名词是对的吗,每种色彩空间中的坐标都是这个名字吗? 33 | ] 34 | 35 | 习惯上,颜色的坐标分量又称为颜色的「通道」。从物理角度,Typst使用`f32`存储颜色每通道的值,这允许你对颜色进行较复杂的计算,且计算结果仍然保证较好的误差。 36 | 37 | == 色彩空间 38 | 39 | RGB是我们使用最多的色彩空间,对应Typst的`color.rgb`函数或`rgb`函数: 40 | 41 | #code(```typ 42 | #box(square(fill: color.rgb("#b1f2eb"))) 43 | #box(square(fill: rgb(87, 127, 230))) 44 | #box(square(fill: rgb(25%, 13%, 65%))) 45 | ```) 46 | 47 | 除此之外,还支持HSL(`hsl`)、CMYK(`cmyk`)、Luma(`luma`)、Oklab(`oklab`)、Oklch(`oklch`)、Linear RGB(`color.linear-rgb`)、HSV(`color.hsv`)等色彩空间。感兴趣的可以自行搜索并使用。 48 | 49 | #pro-tip[ 50 | 尽管你可以随意使用这些构造器,但是可能会导致PDF阅读器或浏览器的兼容性问题。它们可能不支持某些色彩空间(或色彩模式)。 51 | ] 52 | 53 | == 预定义颜色 54 | 55 | 除此之外,你还可以使用一些预定义的颜色,详见#link("https://typst.app/docs/reference/visualize/color/#predefined-colors")[《Typst Docs: Predefined colors》。] 56 | 57 | #code(```typ 58 | #box(square(fill: red, size: 7pt)) 59 | #box(square(fill: blue, size: 7pt)) 60 | ```) 61 | 62 | == 颜色计算 63 | 64 | Typst较LaTeX的一个有趣的特色是内置了很多方法对颜色进行计算。这允许你基于某个颜色主题(Theme)配置更丰富的颜色方案。这里给出几个常用的函数: 65 | 66 | - `lighten/darken`:增减颜色的亮度,参数为绝对的百分比。 67 | - `saturate/desaturate`:增减颜色的饱和度,参数为绝对的百分比。 68 | - `mix`:参数为两个待混合的颜色。 69 | 70 | #code(```typ 71 | #show square: box 72 | #set square(size: 15pt) 73 | #square(fill: red.lighten(20%)) 74 | #square(fill: red.darken(20%)) \ 75 | #square(fill: red.saturate(20%)) 76 | #square(fill: red.desaturate(20%)) \ 77 | #square(fill: blue.mix(green)) 78 | ```) 79 | 80 | 还有一些其他不太常见的颜色计算,详见#link("https://typst.app/docs/reference/visualize/color/#definitions-lighten")[《Typst Docs: Color operations》]。 81 | 82 | == 渐变色 83 | 84 | 你可以以某种方式对Typst中的元素进行渐变填充。这有时候对科学作图很有帮助。 85 | 86 | 有三种渐变色的构造函数,可以分别构造出线性渐变(Linear Gradient),径向渐变(Radial Gradient),锥形渐变(Conic Gradient)。他们都接受一组颜色,对元素进行颜色填充。 87 | 88 | #code(```typ 89 | #let sqr(f) = square(fill: f( 90 | ..color.map.rainbow)) 91 | #stack( 92 | dir: ltr, spacing: 10pt, 93 | sqr(gradient.linear), 94 | sqr(gradient.radial), 95 | sqr(gradient.conic )) 96 | ```) 97 | 98 | 从字面意思理解`color.map.rainbow`是Typst为你预定义的一个颜色数组,按顺序给出了彩虹渐变的颜色。还有一些其他预定义的颜色数组,详见#link("https://typst.app/docs/reference/visualize/color/#predefined-color-maps")[《Typst Docs: Predefined color maps》]。 99 | 100 | == 填充模式 101 | 102 | Typst不仅支持颜色填充,还支持按照固定的模式将其他图形对元素进行填充。例如下面的pat定义了一个长为`61.8pt`,宽为`50pt`的图形。将其填充进一个矩形中,填充图形从左至右,从上至下布满矩形内容中。 103 | 104 | #code(```typ 105 | #let pat = pattern(size: (61.8pt, 50pt))[ 106 | #place(line(start: (0%, 0%), end: (100%, 100%))) 107 | #place(line(start: (0%, 100%), end: (100%, 0%))) 108 | ] 109 | 110 | #rect(fill: pat, width: 100%, height: 60pt, stroke: 1pt) 111 | ```) 112 | 113 | == 线条 114 | 115 | 我们学习的第一个图形元素是直线。 116 | 117 | `line`函数创建一个直线元素。这个函数接受一系列参数,包括线条的长度、起始点、终止点、颜色、粗细等。 118 | 119 | #code(```typ 120 | #line(length: 100%) 121 | #line(end: (50%, 50%)) 122 | #line( 123 | length: 30%, stroke: 2pt + maroon) 124 | ```) 125 | 126 | 除了直线,Typst还支持二次、三次贝塞尔曲线,以及它们和直线的组合。贝塞尔曲线是一种光滑的曲线,由一系列点和控制点组成。 127 | 128 | #code(```typ 129 | #curve( 130 | stroke: blue, 131 | curve.move((0pt, 20pt)), 132 | curve.quad((40%, 0pt), (100%, 0pt)), 133 | curve.line((100%, 20pt)), 134 | curve.close() 135 | ) 136 | ```) 137 | 138 | == 线条样式 139 | 140 | 与各类图形紧密联系的类型是「线条样式」(stroke)。线条样式可以是一个简单的「字典」,包含样式数据: 141 | 142 | #code(```typ 143 | #set line(length: 100%) 144 | #let rainbow = gradient.linear( 145 | ..color.map.rainbow) 146 | #stack( 147 | spacing: 1em, 148 | line(stroke: 2pt + red), 149 | line(stroke: (paint: blue, thickness: 4pt, cap: "round")), 150 | line(stroke: (paint: blue, thickness: 1pt, dash: "dashed")), 151 | line(stroke: 2pt + rainbow), 152 | ) 153 | ```) 154 | 155 | 你也可以使用`stroke`函数来设置线条样式: 156 | 157 | #code(```typ 158 | #set line(length: 100%) 159 | #let blue-line-style = stroke( 160 | paint: blue, thickness: 2pt) 161 | #line(stroke: blue-line-style) 162 | ```) 163 | 164 | == 填充样式 165 | 166 | 填充样式(fill)是另一个重要的图形属性。如果一个路径是闭合的,那么它可以被填充。 167 | 168 | #code(```typ 169 | #curve( 170 | fill: blue.lighten(80%), 171 | stroke: blue, 172 | curve.move((0pt, 50pt)), 173 | curve.line((100pt, 50pt)), 174 | curve.cubic(none, (90pt, 0pt), (50pt, 0pt)), 175 | curve.close(), 176 | ) 177 | ```) 178 | 179 | == 闭合图形 180 | 181 | Typst为你预定义了一些基于贝塞尔曲线的闭合图形元素。下例子中,`#circle`、`#ellipse`、`#square`、`#rect`、`#polygon`分别展示了圆、椭圆、正方形、矩形、多边形的构造方法。 182 | 183 | #code(```typ 184 | #box(circle(radius: 12.5pt, fill: blue)) 185 | #box(ellipse(width: 50pt, height: 25pt)) 186 | #box(square(size: 25pt, stroke: red)) 187 | #box(rect(width: 50pt, height: 25pt)) 188 | ```) 189 | 190 | 值得注意的是,这些图形元素都允许自适应一个内嵌的内容。例如矩形作为最常用的边框图形: 191 | 192 | #code(```typ 193 | #rect[ 194 | Automatically sized \ 195 | to fit the content. 196 | ] 197 | ```) 198 | 199 | == 多边形 200 | 201 | 多边形是仅使用直线组合而成的闭合图形。你可以使用`polygon`函数构造一个多边形。 202 | 203 | #code(```typ 204 | #polygon( 205 | fill: blue.lighten(80%), 206 | stroke: blue, 207 | (20%, 0pt), 208 | (60%, 0pt), 209 | (80%, 2cm), 210 | (0%, 2cm), 211 | ) 212 | ```) 213 | 214 | `polygon`不允许内嵌内容。 215 | 216 | == 外部图形库 217 | 218 | 很多时候我们只需要在文档中插入分割线(直线)和文本框(带文本的矩形)。但是,若有需求,一些外部图形库可以帮助你绘制更复杂的图形: 219 | 220 | + 树形图:#link("https://typst.app/universe/package/syntree")[typst-syntree] 221 | + 拓扑图:#link("https://typst.app/universe/package/fletcher")[typst-fletcher] 222 | + Canvas通用图形库:#link("https://typst.app/universe/package/cetz")[cetz] 223 | 224 | 这些库也是很好的参考资料,你可以通过查看源代码来学习如何绘制复杂的图形。 225 | -------------------------------------------------------------------------------- /src/tutorial/scripting-main.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: "编译流程") 4 | 5 | #todo-box[本节处于校对阶段,所以可能存在不完整或错误。] 6 | 7 | 经过几节稍显枯燥的脚本教程,我们继续回到排版本身。 8 | 9 | 在#(refs.writing-markup)[《初识标记模式》]中,我们学到了很多各式各样的内容。我们学到了段落、标题、代码片段...... 10 | 11 | 接着我们又花费了三节的篇幅,讲授了各式各样的脚本技巧。我们学到了字面量、变量、闭包...... 12 | 13 | 但是它们之间似乎隔有一层厚障壁,阻止了我们进行更高级的排版。是了,如果「内容」也是一种值,那么我们应该也可以更随心所欲地使用脚本操控它们。Typst以排版为核心,应当也对「内容类型」有着精心设计。 14 | 15 | 本节主要介绍如何使用脚本排版内容。这也是Typst的核心功能,并在语法上*与很多其他语言有着不同之处*。不用担心,在我们已经学了很多Typst语言的知识的基础上,本节也仅仅更进一步,教你如何真正以脚本视角看待一篇文档。 16 | 17 | 纵览Typst的编译流程,其大致分为4个阶段,解析、求值、排版和导出。 18 | 19 | // todo: 介绍Typst的多种概念 20 | 21 | // Source Code 22 | // Value 23 | // Type 24 | // Content 25 | 26 | // todo: 简化下面的的图片 27 | #import "../figures.typ": figure-typst-arch 28 | #align(center + horizon, figure-typst-arch()) 29 | 30 | // ,层层有缓存 31 | 32 | 为了方便排版,Typst首先使用了一个函数“解析和评估”你的代码。有趣地是,我们之前已经学过了这个函数。事实上,它就是#typst-func("eval")。 33 | 34 | #code(```typ 35 | #repr(eval("#[一段内容]", mode: "markup")) 36 | ```) 37 | 38 | 流程图展现了编译阶段间的关系,也包含了本节「块」与「表达式」两个概念之间的关系。 39 | 40 | - #typst-func("eval")输入:在文件解析阶段,*代码字符串*被解析成一个语法结构,即「表达式」。古人云,世界是一个巨大的表达式。作为世界的一部分,Typst文档本身也是一个巨大的表达式。事实上,它就是我们在上一章提及的「内容块」。文档的本身是一个内容块,其由一个个标记串联形成。 41 | 42 | - #typst-func("eval")输出:在内容排版阶段,排版引擎事实不作任何计算。用TeX黑话来说,文档被“解析和评估”完了之后,就成为了一个个「材料」(material)。排版引擎将材料。 43 | 44 | 在求值阶段。「表达式」被计算成一个方便排版引擎操作的值,即「材料」。一般来说,我们所谓的表达式是诸如`1+1`的算式,而对其求值则是做算数。 45 | 46 | #code(```typ 47 | #eval("1+1") 48 | ```) 49 | 50 | 显然,如果意图是让排版引擎输出计算结果,让排版引擎直接排版2要比排版“1+1”更简单。 51 | 52 | 但是对于整个文档,要如何理解对内容块的求值?这就引入了「可折叠」的值(Foldable)的概念。「可折叠」成为块作为表达式的基础。 53 | 54 | == Typst的主函数 55 | 56 | 在Typst的源代码中,有一个Rust函数直接对应整个编译流程,其内容非常简短,便是调用了两个阶段对应的函数。“求值”阶段(`eval`阶段)对应执行一个Rust函数,它的名称为`typst::eval`;“内容排版”阶段(`typeset`阶段)对应执行另一个Rust函数,它的名称为`typst::typeset`。 57 | 58 | ```rs 59 | pub fn compile(world: &dyn World) -> SourceResult { 60 | // Try to evaluate the source file into a module. 61 | let module = crate::eval::eval(world, &world.main())?; 62 | // Typeset the module's content, relayouting until convergence. 63 | typeset(world, &module.content()) 64 | } 65 | ``` 66 | 67 | 从代码逻辑上来看,它有明显的先后顺序,似乎与我们所展示的架构略有不同。其`typst::eval`的输出为一个文件模块`module`;其`typst::typeset`仅接受文件的内容`module.content()`并产生一个已经排版好的文档对象`typst::Document`。 68 | 69 | == 「`eval`阶段」与「`typeset`阶段」 70 | 71 | 现在我们介绍Typst的完整架构。 72 | 73 | 当Typst接受到一个编译请求时,他会使用「解析器」(Parser)从`main`文件开始解析整个项目;对于每个文件,Typst使用「评估器」(Evaluator)执行脚本并得到「内容」;对于每个「内容」,Typst使用「排版引擎」(Typesetting Engine)计算布局与合成样式。 74 | 75 | 当一切布局与样式都计算好后,Typst将最终结果导出为各种格式的文件,例如PDF格式。 76 | 77 | 我们回忆上一节讲过的内容,Typst大致上分为四个执行阶段。这四个执行阶段并不完全相互独立,但有明显的先后顺序: 78 | 79 | #import "../figures.typ": figure-typst-arch 80 | #align(center + horizon, figure-typst-arch()) 81 | 82 | 我们在上一节着重讲解了前两个阶段。这里,我们着重讲解“表达式求值”阶段与“内容排版”阶段。 83 | 84 | 事实上,Typst直接在脚本中提供了对应“求值”阶段的函数,它就是我们之前已经介绍过的函数`eval`。你可以使用`eval`函数,将一个字符串对象「评估」为「内容」: 85 | 86 | #code(```typ 87 | 以代码模式评估:#eval("repr(str(1 + 1))") \ 88 | 以标记模式评估:#eval("repr(str(1 + 1))", mode: "markup") \ 89 | 以标记模式评估2:#eval("#show: it => [c] + it + [t];a", mode: "markup") 90 | ```) 91 | 92 | 由于技术原因,Typst并不提供对应“内容排版”阶段的函数,如果有的话这个函数的名称应该为`typeset`。已经有很多地方介绍了潜在的`typeset`函数: 93 | + #link("https://github.com/andreasKroepelin/polylux")[Polylux], #link("https://github.com/touying-typ/touying")[Touying]等演示文档(PPT)框架需要将一部分内容固定为特定结果的能力。 94 | + Typst的作者在其博客中提及#link("https://laurmaedje.github.io/posts/frozen-state/")[Frozen State 95 | ]的可能性。 96 | + 他提及数学公式的编号在演示文档框架。 97 | + 即便不涉及用户需求,Typst的排版引擎已经自然存在Frozen State的需求。 98 | + 本文档也需要`typeset`的能力为你展示特定页面的最终结果而不影响全局状态。 99 | 100 | == 延迟执行 101 | 102 | 架构图中还有两个关键的反向箭头,疑问顿生:这两个反向箭头是如何产生的? 103 | 104 | 我们首先关注与本节直接相关的「样式化」内容。当`eval`阶段结束时,「`show`」语法将会对应产生一个`styled`元素,其包含了被设置样式的内容,以及设置样式的「回调」: 105 | 106 | #code(```typ 107 | 内容是:#repr({show: set text(fill: blue); [abc]}) \ 108 | 样式无法描述,但它在这里:#repr({show: set text(fill: blue); [abc]}.styles) 109 | ```) 110 | 111 | 也就是说`eval`并不具备任何排版能力,它只能为排版准备好各种“素材”,并把素材交给排版引擎完成排版。 112 | 113 | 这里的「回调」术语很关键:它是一个计算机术语。所谓「回调函数」就是一个临时的函数,它会在后续执行过程的合适时机“回过头来被调用”。例如,我们写了一个这样的「`show`」规则: 114 | 115 | #code(```typ 116 | #repr({ 117 | show raw: content => layout(parent => if parent.width > 100pt { 118 | set text(fill: red); content 119 | } else { 120 | content 121 | }) 122 | `a` 123 | }) 124 | ```) 125 | 126 | 这里`parent.width > 100pt`是说当且仅当父元素的宽度大于`100pt`时,才为该代码片段设置红色字体样式。其中,`parent.width`与排版相关。那么,自然`eval`也不知道该如何评估该条件的真正结果。*计算因此被停滞*。 127 | 128 | 于是,`eval`干脆将整个`show`右侧的函数都作为“素材”交给了排版引擎。当排版引擎计算好了相关内容,才回到评估阶段,执行这一小部分“素材”函数中的脚本,得到为正确的内容。我们可以看出,`show`右侧的函数*被延后执行*可。 129 | 130 | 这种被延后执行零次、一次或多次的函数便被称为「回调函数」。相关的计算方法也有对应的术语,被称为「延迟执行」。 131 | 132 | 我们对每个术语咬文嚼字一番,它们都很准确: 133 | 134 | 1. *「表达式求值」*阶段仅仅“评估”出*「内容排版」*阶段所需的素材.*「评估器」*并不具备排版能力。 135 | 2. 对于依赖排版产生的内容,「表达式求值」产生包含*「回调函数」*的内容,让「排版引擎」在合适的时机“回过头来调用”。 136 | 3. 相关的计算方法又被称为*「延迟执行」*。因为现在不具备执行条件,所以延迟到条件满足时才继续执行。 137 | 138 | 现在我们可以理解两个反向箭头是如何产生的了。它们是下一阶段的回调,用于完成阶段之间复杂的协作。评估阶段可能会`import`或`include`文件,这时候会重新让解析器解析文件的字符串内容。排版阶段也可能会继续根据`styled`等元素产生复杂的内容,这时候依靠评估器执行脚本并产生或改变内容。 139 | 140 | == 模拟Typst的执行 141 | 142 | 我们来模拟一遍上述示例的执行,以加深理解: 143 | 144 | #code(```typ 145 | #show raw: content => layout(parent => if parent.width < 100pt { 146 | set text(fill: red); content 147 | } else { 148 | content 149 | }) 150 | #box(width: 50pt, `a`) 151 | `b` 152 | ```) 153 | 154 | 首先进行表达式求值得到: 155 | 156 | ```typ 157 | #styled((box(width: 50pt, `a`), `b`), styles: content => ..) 158 | ``` 159 | 160 | 排版引擎遇到``` `a` ```。由于``` `a` ```是`raw`元素,它「回调」了对应`show`规则右侧的函数。待执行的代码如下: 161 | 162 | ```typc 163 | layout(parent => if parent.width < 100pt { 164 | set text(fill: red); `a` 165 | } else { 166 | `a` 167 | }) 168 | ``` 169 | 170 | 此时`parent`即为`box(width: 50pt)`。排版引擎将这个`parent`的具体内容交给「评估器」,待执行的代码如下: 171 | 172 | ```typc 173 | if box(width: 50pt).width < 100pt { 174 | set text(fill: red); `a` 175 | } else { 176 | `a` 177 | } 178 | ``` 179 | 180 | 由于此时父元素(`box`元素)宽度只有`50pt`,评估器进入了`then`分支,其为代码片段设置了红色样式。内容变为: 181 | 182 | ```typ 183 | #(box(width: 50pt, {set text(fill: red); `a`}), styled((`b`, ), styles: content => ..)) 184 | ``` 185 | 186 | 待执行的代码如下: 187 | 188 | ```typc 189 | set text(fill: red); text("a", font: "monospace") 190 | ``` 191 | 192 | 排版引擎遇到``` `a` ```中的`text`元素。由于其是`text`元素,「回调」了`text`元素的「`show`」规则。记得我们之前说过`set`是一种特殊的`show`,于是排版器执行了`set text(fill: red)`。 193 | 194 | ```typ 195 | #(box(width: 50pt, text(fill: red, "a", ..)), styled((`b`, ), styles: content => ..)) 196 | ``` 197 | 198 | 排版引擎离开了`show`规则右侧的函数,该函数调用由``` `a` ```触发。同时`set text(fill: red)`规则也被解除,因为离开了相关作用域。 199 | 200 | 回到文档顶层,待执行的代码如下: 201 | 202 | ```typc 203 | #show raw: ... 204 | `b` 205 | ``` 206 | 207 | 排版引擎遇到``` `b` ```,再度「回调」了对应`show`规则右侧的函数。由于此时父元素(`page`元素,即整个页面)宽度有`500pt`,我们没有为代码片段设置样式。 208 | 209 | ```typ 210 | #(box(width: 50pt, text(fill: red, "a", ..)), text("b", ..)) 211 | ``` 212 | 213 | 至此,文档的内容已经准备好「导出」(Export)了。 214 | 215 | #pro-tip[ 216 | 有时候`show`规则会原地执行,这属于一种细节上的优化,例如: 217 | 218 | #code(```typ 219 | #repr({ show: it => it; [a] }) \ 220 | #repr({ show: it => [c] + it + [d]; [a] }) 221 | ```) 222 | 223 | 这个时候`show`规则不会对应一个`styled`元素。 224 | 225 | 这种优化告诉你前面手动描述的过程仅作理解。一旦涉及更复杂的环境,Typst的实际执行过程就会产生诸多变化。因此,你不应该依赖以上某步中排版引擎的瞬间状态。这些瞬间状态将产生「未注明特性」(undocumented details),并随时有可能在未来被打破。 226 | ] 227 | -------------------------------------------------------------------------------- /src/tutorial/reference-math-symbols.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [参考:常用数学符号]) 4 | 5 | #set table( 6 | stroke: none, 7 | align: horizon + left, 8 | row-gutter: 0.45em, 9 | ) 10 | 11 | 推荐阅读: 12 | + #link("https://github.com/johanvx/typst-undergradmath/releases/download/v1.4.0/undergradmath.pdf")[本科生Typst数学英文版,适用于Typst 0.11.1] 13 | + #link("https://github.com/brynne8/typst-undergradmath-zh/blob/6a1028bc13c525f29f6445e92fc6af3246b3c3cb/undergradmath.pdf")[本科生Typst数学中文版,适用于Typst 0.9.0] 14 | 15 | 以下表格列出了*数学模式*中的常用符号。 16 | 17 | #figure( 18 | table( 19 | columns: 8, 20 | [$hat(a)$], [`hat(a)`], [$caron(a)$], [`caron(a)`], [$tilde(a)$], [`tilde(a)`], [$grave(a)$], [`grave(a)`], 21 | [$dot(a)$], 22 | [`dot(a)`], 23 | [$dot.double(a)$], 24 | [dot.double(a)], 25 | [$macron(a)$], 26 | [`macron(a)`], 27 | [$arrow(a)$], 28 | [`arrow(a)`], 29 | 30 | [$acute(a)$], [`acute(a)`], [$breve(a)$], [`breve(a)`], 31 | ), 32 | caption: [数学模式重音符号], 33 | ) 34 | 35 | #figure( 36 | table( 37 | columns: 8, 38 | [$alpha$], [`alpha`], [$theta$], [`theta`], [$o$], [`o`], [$upsilon$], [`upsilon`], 39 | [$beta$], [`beta`], [$theta.alt$], [`theta.alt`], [$pi$], [`pi`], [$phi$], [`phi`], 40 | [$gamma$], [`gamma`], [$iota$], [`iota`], [$pi.alt$], [`pi.alt`], [$phi$], [`phi`], 41 | [$delta$], [`delta`], [$kappa$], [`kappa`], [$rho$], [`rho`], [$chi$], [`chi`], 42 | [$epsilon.alt$], [`epsilon.alt`], [$lambda$], [`lambda`], [$rho.alt$], [`rho.alt`], [$psi$], [`psi`], 43 | [$epsilon$], [`epsilon`], [$mu$], [`mu`], [$sigma$], [`sigma`], [$omega$], [`omega`], 44 | [$zeta$], [`zeta`], [$nu$], [`nu`], [$sigma.alt$], [`sigma.alt`], [$$], [``], 45 | [$eta$], [`eta`], [$xi$], [`xi`], [$tau$], [`tau`], [$$], [``], 46 | [$Gamma$], [`Gamma`], [$Lambda$], [`Lambda`], [$Sigma$], [`Sigma`], [$Psi$], [`Psi`], 47 | [$Delta$], [`Delta`], [$Xi$], [`Xi`], [$Upsilon$], [`Upsilon`], [$Omega$], [`Omega`], 48 | [$Theta$], [`Theta`], [$Pi$], [`Pi`], [$Phi$], [`Phi`], 49 | ), 50 | caption: [希腊字母], 51 | ) 52 | 53 | #figure( 54 | table( 55 | columns: 6, 56 | [$<$], [`<, lt`], [$>$], [`>, gt`], [$=$], [`=`], 57 | [$<=$], [`<=, lt.eq`], [$>=$], [`>=, gt.eq`], [$equiv$], [`equiv`], 58 | [$<<$], [`<<, lt.double`], [$>>$], [`>>, gt.double`], [$$], [``], 59 | [$prec$], [`prec`], [$succ$], [`succ`], [$tilde$], [`tilde`], 60 | [$prec.eq$], [`prec.eq`], [$succ.eq$], [`succ.eq`], [$tilde.eq$], [`tilde.eq`], 61 | [$subset$], [`subset`], [$supset$], [`supset`], [$approx$], [`approx`], 62 | [$subset.eq$], [`subset.eq`], [$supset.eq$], [`supset.eq`], [$tilde.equiv$], [`tilde.equiv`], 63 | [$subset.sq$], [`subset.sq`], [$supset.sq$], [`supset.sq`], [$join$], [`join`], 64 | [$subset.eq.sq$], [`subset.eq.sq`], [$supset.eq.sq$], [`supset.eq.sq`], [$$], [``], 65 | [$in$], [`in`], [$in.rev$], [`in.rev`], [$prop$], [`prop`], 66 | [$tack.r$], [`tack.r`], [$tack.l$], [`tack.l`], [$tack.r.double$], [`tack.r.double`], 67 | [$divides$], [`divides`], [$parallel$], [`parallel`], [$tack.t$], [`tack.t`], 68 | [$:$], [`:`], [$in.not$], [`in.not`], [$!=$], [`!=, eq.not`], 69 | ), 70 | caption: [二元关系], 71 | ) 72 | 73 | #figure( 74 | table( 75 | columns: 6, 76 | [$+$], [`+, plus`], [$-$], [`-, minus`], [$$], [``], 77 | [$plus.minus$], [`plus.minus`], [$minus.plus$], [`minus.plus`], [$lt.tri$], [`lt.tri`], 78 | [$dot$], [`dot`], [$div$], [`div`], [$gt.tri$], [`gt.tri`], 79 | [$times$], [`times`], [$without$], [`without`], [$star$], [`star`], 80 | [$union$], [`union`], [$inter$], [`inter`], [$*$], [`*`], 81 | [$union.sq$], [`union.sq`], [$inter.sq$], [`inter.sq`], [$circle.stroked.tiny$], [`circle.stroked.tiny`], 82 | [$or$], [`or`], [$and$], [`and`], [$bullet$], [`bullet`], 83 | [$xor$], [`xor`], [$minus.circle$], [`minus.circle`], [$dot.circle$], [`dot.circle`], 84 | [$union.plus$], [`union.plus`], [$times.circle$], [`times.circle`], [$circle.big$], [`circle.big`], 85 | [$product.co$], 86 | [`product.co`], 87 | [$triangle.stroked.t$], 88 | [`triangle.stroked.t`], 89 | [$triangle.stroked.b$], 90 | [`triangle.stroked.b`], 91 | 92 | [$dagger$], [`dagger`], [$lt.tri$], [`lt.tri`], [$gt.tri$], [`gt.tri`], 93 | [$dagger.double$], [`dagger.double`], [$lt.tri.eq$], [`lt.tri.eq`], [$gt.tri.eq$], [`gt.tri.eq`], 94 | [$wreath$], [`wreath`], 95 | ), 96 | caption: [二元运算符], 97 | ) 98 | 99 | #figure( 100 | table( 101 | columns: 6, 102 | [$sum$], [`sum`], [$union.big$], [`union.big`], [$or.big$], [`or.big`], 103 | [$product$], [`product`], [$inter.big$], [`inter.big`], [$and.big$], [`and.big`], 104 | [$product.co$], [`product.co`], [$union.sq.big$], [`union.sq.big`], [$union.plus.big$], [`union.plus.big`], 105 | [$integral$], [`integral`], [$integral.cont$], [`integral.cont`], [$dot.circle.big$], [`dot.circle.big`], 106 | [$plus.circle.big$], [`plus.circle.big`], [$times.circle.big$], [`times.circle.big`], 107 | ), 108 | caption: [「大」运算符], 109 | ) 110 | 111 | #figure( 112 | table( 113 | columns: 4, 114 | [$arrow.l$], [`arrow.l`], [$arrow.l.long$], [`arrow.l.long`], 115 | [$arrow.r$], [`arrow.r`], [$arrow.r.long$], [`arrow.r.long`], 116 | [$arrow.l.r$], [`arrow.l.r`], [$arrow.l.r.long$], [`arrow.l.r.long`], 117 | [$arrow.l.double$], [`arrow.l.double`], [$arrow.l.double.long$], [`arrow.l.double.long`], 118 | [$arrow.r.double$], [`arrow.r.double`], [$arrow.r.double.long$], [`arrow.r.double.long`], 119 | [$arrow.l.r.double$], [`arrow.l.r.double`], [$arrow.l.r.double.long$], [`arrow.l.r.double.long`], 120 | [$arrow.r.bar$], [`arrow.r.bar`], [$arrow.r.long.bar$], [`arrow.r.long.bar`], 121 | [$arrow.l.hook$], [`arrow.l.hook`], [$arrow.r.hook$], [`arrow.r.hook`], 122 | [$harpoon.lt$], [`harpoon.lt`], [$harpoon.rt$], [`harpoon.rt`], 123 | [$harpoon.lb$], [`harpoon.lb`], [$harpoon.rb$], [`harpoon.rb`], 124 | [$harpoons.ltrb$], [`harpoons.ltrb`], [$arrow.t$], [`arrow.t`], 125 | [$arrow.b$], [`arrow.b`], [$arrow.t.b$], [`arrow.t.b`], 126 | [$arrow.t.double$], [`arrow.t.double`], [$arrow.b.double$], [`arrow.b.double`], 127 | [$arrow.t.b.double$], [`arrow.t.b.double`], [$arrow.tr$], [`arrow.tr`], 128 | [$arrow.br$], [`arrow.br`], [$arrow.bl$], [`arrow.bl`], 129 | [$arrow.tl$], [`arrow.tl`], [$arrow.r.squiggly$], [`arrow.r.squiggly`], 130 | ), 131 | caption: [箭头], 132 | ) 133 | 134 | #figure( 135 | table( 136 | columns: 6, 137 | [$dots$], [`dots`], [$dots.c$], [`dots.c`], [$dots.v$], [`dots.v`], 138 | [$dots.down$], [`dots.down`], [$planck.reduce$], [`planck.reduce`], [$dotless.i$], [`dotless.i`], 139 | [$dotless.j$], [`dotless.j`], [$ell$], [`ell`], [$Re$], [`Re`], 140 | [$Im$], [`Im`], [$aleph$], [`aleph`], [$forall$], [`forall`], 141 | [$exists$], [`exists`], [$Omega.inv$], [`Omega.inv`], [$partial$], [`partial`], 142 | [$prime$], [`', prime`], [$emptyset$], [`emptyset`], [$infinity$], [`infinity`], 143 | [$nabla$], [`nabla`], [$triangle.stroked.t$], [`triangle.stroked.t`], [$square.stroked$], [`square.stroked`], 144 | [$diamond.stroked$], [`diamond.stroked`], [$bot$], [`bot`], [$top$], [`top`], 145 | [$angle$], [`angle`], [$suit.club$], [`suit.club`], [$suit.spade$], [`suit.spade`], 146 | [$not$], [`not`], 147 | ), 148 | caption: [其他符号], 149 | ) 150 | -------------------------------------------------------------------------------- /typ/templates/rustacean-flat-gesture.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/tutorial/scripting-block-and-expression.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: "表达式") 4 | 5 | Typst中绝大部分语法结构都可作表达式,可以说学习完了所有表达式,则学会了Typst所有语法。当然,其吸取了一定历史经验,仅有少量的语句不是表达式。 6 | 7 | #pro-tip[ 8 | Typst借鉴了Rust,遵从「面向表达式编程」(expression-oriented programming)的哲学。它将所有的语句都根据可折叠规则(见后文)设计为表达式。 9 | + 如果一个语句能产生值,那么该语句的结果是按*控制流*顺序所产生所有值的折叠。 10 | + 否则,如果一个语句不能产生值,那么该语句的结果是```typc none```。 11 | + 特别地,任意类型 $T$ 的值 $v$ 与```typc none```折叠仍然是值本身。 12 | ] 13 | 14 | #pro-tip[ 15 | `show`语句和`set`语句不是表达式。 16 | ] 17 | 18 | 根据大的分类,可用的表达式可以分为5类(它们并非严格术语,而是为了方便教学而分类),他们是: 19 | + 代数运算表达式。 20 | + 逻辑比较表达式。 21 | + 逻辑运算表达式。 22 | + 赋值表达式。 23 | + 项。 24 | 25 | == 代数运算表达式 26 | 27 | Typst支持对数字的算数运算,其中浮点运算遵守IEEE-754标准。整数和浮点数之间可以混合运算: 28 | 29 | #code(```typ 30 | //加 减 乘 除 31 | #(1 + 2, 1.0 - 2, 1 * 2, 1 / 2 + 1) 32 | ```) 33 | 34 | 从上可以看出,整数和整数运算尽可能*保持*整数,但是整数和浮点数运算则结果变为浮点数。除法运算的结果是浮点数。算数之间遵守四则运算规则。 35 | 36 | #pro-tip[ 37 | 更高级的运算,例如数字的位运算和数值计算隐藏在类型的方法和`calc`标准库中: 38 | 39 | #code(```typ 40 | #(0o755.bit-and(0o644)), // 8进制位运算 41 | #(calc.pow(9, 4)) // 9的4次方 42 | ```) 43 | 44 | 请参考《》。 45 | ] 46 | 47 | 除了数字运算,字符串、数组等还支持加法和乘法运算。它们的加法实际上是连接操作,它们的乘法则是重复元素的累加。 48 | 49 | #code(```typ 50 | //乘 加 51 | #("1" * 2, "4" + "5", ) \ 52 | #((1,) * 2, (4,) + (5,),) 53 | ```) 54 | 55 | 字典只支持加法操作。若有重复键值对,则右侧列表的键值对会覆盖左侧列表的键值对。 56 | 57 | #code(```typ 58 | #((a: 1) + (b: 2), 59 | (a: 1) + (b: 3, a: 2) + (a: 4 , c: 5)) 60 | ```) 61 | 62 | 63 | == 浮点数陷阱 64 | 65 | 浮点数陷阱是指由于浮点转换或浮点精度问题导致的一系列逻辑问题。 66 | 67 | // todo: 把基本字面量的运算和表达式挪过来 68 | 69 | + 类型比较陷阱。在运行期切记同时考虑到`float`和`int`两种类型: 70 | 71 | #code(```typ 72 | #type(2), #type(2.), #(type(2.) == int) 73 | ```) 74 | 75 | 可见```typc 2.```与```typc 2```类型并不相同。```typc 2.```在数学上是整数,但以浮点数存储。 76 | 77 | + 大数类型陷阱。当数字过大时,其会被隐式转换为浮点数存储: 78 | 79 | #code(```typ 80 | #type(9000000000000000000000000000000) 81 | ```) 82 | 83 | + 整数运算陷阱。整数相除时会被转换为浮点数: 84 | 85 | #code(```typ 86 | #(10 / 4), #type(10 / 4) \ 87 | #(12 / 4), #type(12 / 4) \ 88 | ```) 89 | 90 | + 浮点误差陷阱。哪怕你的运算在数学上理论是可逆的,由于浮点数精度有限,也会误判结果: 91 | 92 | #code(```typ 93 | #(1000000 / 9e21 * 9e21), #((1000000 / 9e21 * 9e21) == 1000000) 94 | ```) 95 | 96 | 这提示我们,正确的浮点比较需要考虑误差。例如以上两数在允许`1e-6`误差前提下是相等的: 97 | 98 | #code(```typ 99 | #(calc.abs((1000000 / 9e21 * 9e21) - 1000000) < 1e-6) 100 | ```) 101 | 102 | + 整数转换陷阱。为了转换类型,可以使用`int`,但有可能产生精度损失(就近取整): 103 | 104 | #code(```typ 105 | #int(10 / 4), 106 | #int(12 / 4) 107 | ```) 108 | 109 | 或有可能产生「截断」。(todo: typst v0.12.0已经不适用) 110 | 111 | // #code(```typ 112 | // #int(9000000000000000000000000000000) 113 | // ```) 114 | 115 | 这些都是编程语言中的共通问题。凡是令数字保持有效精度,都会产生如上问题。 116 | 117 | == 逻辑比较表达式 118 | 119 | 有三大简单比较关系: 120 | 121 | #code(```typ 122 | //大于 小于 等于 123 | #(1 > 2, 1 < 2, 1 == 2) 124 | ```) 125 | 126 | 基于此,可以延申出三个方便脚本编辑的比较关系。 127 | 128 | #code(```typ 129 | //大于或等于 小于或等于 不等于 130 | #(1 >= 2.0, 1 <= 1, 1 != 2) 131 | ```) 132 | 133 | 从数学角度,真正基本的关系另有其系,其中“小于或等于”是数学中的偏序关系,“等于”是数学中的等价关系。这两个关系可以衍生出其他四种关系,但是不太好理解。举例来讲,大于其实就是“小于等于”的否定;而小于则是“小于等于”但是不能“等于”。 134 | 135 | 注意:不推荐将整数与浮点数相互比较。具体请参考上一节所提及的浮点数陷阱。 136 | 137 | 字符串、数组和字典之间也是可以比较的,理解他们则必须从偏序关系和等价关系入手。 138 | 139 | 三种项的等价关系比较容易理解,说两字符串/数组/字典相等,则是在说二者有一样多的子项,且每个子项都一一相等。 140 | 141 | #code(```typ 142 | #((1, 1) == (1, 1)), 143 | #((a: 1, c: (1, )) == (a: 1, c: (1, ))) 144 | ```) 145 | 146 | 字典之间没有偏序关系,便只剩字符串和数组。偏序关系则需要指定一种排序规则。如果两字符串/数组不相等,则首先考虑它们的长度关系,长度小的一方是更小的: 147 | 148 | #code(```typ 149 | #("1" <= "23"), 150 | #((1, ) <= (2, 3, )) 151 | ```) 152 | 153 | 否则从前往后依次比较每一个子项,直到找到*第一个*不相等的子项,进而确定顺序。见下三例: 154 | 155 | #code(```typ 156 | #("1" <= "2", "113" <= "121", "2" <= "12") 157 | ```) 158 | 159 | 对于单个字符,我们依照字典序比较,其中容易理解地是数字字符顺序按字面递增。对于前两者,我们可以看到`"1"`比`"2"`小;进由此,故"113"比"121"小;尽管如此,优先判断长度关系,故"2"比"12"小。 160 | 161 | 从两种基础比较关系,其他四种比较关系也能被良好地定义,并在脚本中使用: 162 | 163 | #code(```typ 164 | #("1" > "2", "1" != "2", 165 | "1" < "2", "1" >= "2") 166 | ```) 167 | 168 | == 逻辑运算表达式 169 | 170 | 布尔值之间可以做“且”、“或”和“非”三种逻辑运算,并产生布尔类型的表达式: 171 | 172 | #code(```typ 173 | #(not false), #(false or false), #(true and false) 174 | ```) 175 | 176 | 真值表如下: 177 | 178 | #{ 179 | set align(center) 180 | table( 181 | columns: (33pt * 0.6,) * 2 + (33pt,) * 3, 182 | stroke: 0.5pt, 183 | $p$, $q$, $not p$, $p or q$, $p and q$, 184 | $0$, $0$, $1$, $0$, $0$, 185 | $0$, $1$, $1$, $1$, $0$, 186 | $1$, $0$, $0$, $1$, $0$, 187 | $1$, $1$, $0$, $1$, $1$, 188 | ) 189 | } 190 | 191 | 逻辑运算使用起来很简单,建议入门的同学找一些专题阅读,例如#link("https://zhuanlan.zhihu.com/p/82986019")[数理逻辑(1)——命题逻辑的基本概念]。但一旦涉及到对复杂事物的逻辑讨论,你就可能陷入了知识的海洋。关于逻辑运算已经形成一门学科,如有兴趣建议后续找一些书籍阅读,例如#link("https://www.xuetangx.com/course/THU12011001060/19316572")[逻辑学概论]。 192 | 193 | 本书自然不负责教你逻辑学。 194 | 195 | == 赋值表达式 196 | 197 | 变量可以被赋予一个表达式的值。事实上,`let`表达式后的语法结构就是赋值表达式。 198 | 199 | #code(```typ 200 | #let a = 1; #a, 201 | #(a = 10); #a 202 | ```) 203 | 204 | 除此之外,还有先加(减、乘或除)后赋值的变形。所有这些赋值语句都产生`none`值而非返回变量的值。 205 | 206 | #code(```typ 207 | #let a = 1; #a, 208 | #repr(a += 2), #a, #repr(a -= 2), #a, #repr(a *= 2), #a, #repr(a /= 2), #a 209 | ```) 210 | 211 | == 代码块 212 | 213 | 除了字面量,由于「面向表达式编程」,「代码块」也可以作为表达式的「项」。在Typst中,代码块和内容块是等同的。将「内容块」与「代码块」作比较: 214 | 215 | - 代码块:按顺序包含一系列语句,内部为#term("code mode")。 216 | - 内容块:按顺序包含一系列内容,内部为#term("markup mode")。 217 | 218 | 内容块(标记模式)内部没有语句的概念,一个个内容或元素按顺序排列。相比,代码块内部则有语句概念。每个语句可以是换行分隔,也可以是#mark(";")分隔。 219 | 220 | #code(```typ 221 | #{ 222 | "a" 223 | "b" 224 | } \ // 与下表达式等同: 225 | #{ "a"; "b" } 226 | ```) 227 | 228 | #pro-tip[ 229 | 公式块只是一种特殊的内容块,只不过其内容处于「数学模式」。对比: 230 | #code(```typ 231 | 文本优先:#[ab] v.s. $#[a]#[b]$ \ 232 | 数学优先:#[$lambda$$(x)$] v.s. $lambda(x)$ 233 | ```) 234 | ] 235 | 236 | 「代码块」和值(表达式)的关系与「控制流」息息相关。我们将会在下一节(todo:添加链接)详细介绍「控制流」。 237 | 238 | // 整数转浮点数: 239 | 240 | // #code(```typ 241 | // #float(1), #(type(float(1))) 242 | // ```) 243 | 244 | // 布尔值转整数: 245 | 246 | // #code(```typ 247 | // #int(false), #(type(int(false))) \ 248 | // #int(true), #(type(int(true))) 249 | // ```) 250 | 251 | // 浮点数转整数: 252 | 253 | // #code(```typ 254 | // #int(1), #(type(int(1))) 255 | // ```) 256 | 257 | // 数字转字符串: 258 | 259 | // #code(```typ 260 | // #repr(str(1)), #(type(str(1))) 261 | // #repr(str(.5)), #(type(str(.5))) 262 | // ```) 263 | 264 | // 布尔值转字符串: 265 | 266 | // #code(```typ 267 | // #repr(false), #(type(repr(false))) 268 | // ```) 269 | 270 | // 数字转布尔值: 271 | 272 | // #code(```typ 273 | // #let to-bool(x) = x != 0 274 | // #repr(to-bool(0)), #(type(to-bool(0))) \ 275 | // #repr(to-bool(1)), #(type(to-bool(1))) 276 | // ```) 277 | 278 | // 表达式从感性地理解上就是检验执行一段代码是否对应产生一个值。精确来说,表达式是以下对象的集合: 279 | // + 前述的各种「字面量」是表达式: 280 | // #code(```typ 281 | // // 这些是表达式 282 | // #none, #true, #1, #.2, #"s" 283 | // ```) 284 | // + 「变量」是表达式: 285 | // #code(```typ 286 | // #let a = 1; 287 | // // 这是表达式 288 | // #a 289 | // ```) 290 | // + 「括号表达式」(parenthesized expression)是表达式: 291 | // #code(```typ 292 | // // 这些是表达式 293 | // #(0), #(1+2), #((((((((1)))))))) 294 | // ```) 295 | // + 「函数调用」是表达式: 296 | // #code(```typ 297 | // #let add(a, b) = a + b 298 | // #let a = 1; 299 | // // 这是表达式 300 | // #add(a, 2) 301 | // ```) 302 | // + 特别地,Typst中的「代码块」和「内容块」是表达式: 303 | // #code(```typ 304 | // // 这些是表达式 305 | // #repr({ 1 }), #repr([ 1 ]), 306 | // ```) 307 | // + 特别地,Typst中的「if语句」、「for语句」和「while语句」等都是表达式: 308 | // #code(```typ 309 | // // 这些是表达式 310 | // #repr(if false [啊?]), 311 | // #repr(for _ in range(0) {}), 312 | // #repr(while false {}), 313 | // #repr(let _ = 1), 314 | // ```) 315 | // 这些将在后文中介绍。 316 | // + 情形1至情形7的所有对象作为项,任意项之间的「运算」也是表达式。 317 | 318 | // 其中,情形1至情形3被称为「初始表达式」(primary expression),它们就像是表达式的种子,由其他操作继续组合生成更多表达式。 319 | 320 | // 情形4至情形6本身都是经典意义上的「语句」(statement),它们由一个或多个子表达式组成,形成一个有新含义的表达式。在Typst中它们都被赋予了「值」的语义,因此它们也都是表达式。我们将会在后续文章中继续学习。 321 | 322 | // 本节主要讲解情形7。由于情形1至情形6都可以作为情形7的项,不失一般性,我们仍然可以仅以「字面量」作为项讲解所有情形7的情况。 323 | -------------------------------------------------------------------------------- /src/tutorial/scripting-variable.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: "变量与函数") 4 | 5 | == 变量声明 6 | 7 | 变量是存储“字面量”的一个个容器。它相当于为一个个字面量取名,以方便在脚本中使用。 8 | 9 | 如下语法,「变量声明」表示使得`x`的内容与`"Hello world!!"`相等。我们对语法一一翻译: 10 | 11 | #code(```typ 12 | #let x = "Hello world!!" 13 | // ^^^^ ^ ^ ^^^^^^^^^^^^^^^^ 14 | // 令 变量名 为 初始值表达式 15 | ```) 16 | 17 | #pro-tip[ 18 | 同时我们看见输出的文档为空,这是因为「变量声明」本身的值是`none`。 19 | ] 20 | 21 | 「变量声明」一共分为3个有效部分。 22 | 23 | + `let`关键字:告诉Typst接下来即将开启一段「声明」。在英语中let是"令"的意思。 24 | + #term("variable identifier")、#mark("="):标识符是变量的“名称”。「变量声明」后续的位置都可以通过标识符引用该变量。 25 | + #term("initialization expression"):#mark("=")告诉Typst变量初始等于一个表达式的值。该表达式在编译领域有一个专业术语,称为#term("initialization expression")。这里,#term("initialization expression")可以为任意表达式,请参阅 26 | 27 | 建议标识符简短且具有描述性。尽管标识符中可以包含中文等unicode字符,但仍建议标识符中仅含英文与#mark("hyphen")。 28 | 29 | // #link()[表达式小节]。 30 | 31 | // 「变量声明」可以没有初始值表达式: 32 | 33 | // #code(```typ 34 | // #let x 35 | // #repr(x) 36 | // ```) 37 | 38 | // 事实上,它等价于将`x`初始化为`none`。 39 | 40 | // #code(```typ 41 | // #let x = none 42 | // #repr(x) 43 | // ```) 44 | 45 | // 尽管Typst允许你不写初始值表达式,本书还是建议你让所有的「变量声明」都具有初始值表达式。因为初始值表达式还告诉阅读你代码的人这个变量可能具有什么样的类型。 46 | 47 | 「变量声明」后续的位置都可以继续使用该变量,取决于「作用域」。 48 | // + 标识符以Unicode字母、Unicode数字和#mark("_")开头。以下是示例的合法变量名: 49 | // ```typ 50 | // // 以英文字母开头,是Unicode字母 51 | // #let a; #let z; #let A; #let Z; 52 | // // 以汉字开头,是Unicode字母 53 | // #let 这; 54 | // // 以下划线开头 55 | // #let _; 56 | // ``` 57 | // + 标识符后接有限个Unicode字母、Unicode数字、#mark("hyphen")和#mark("_")。以下是示例的合法变量名: 58 | // ```typ 59 | // // 纯英文变量名,带连字号 60 | // #let alpha-test; #let alpha--test; 61 | // // 纯中文变量名 62 | // #let 这个变量; #let 这个_变量; #let 这个-变量; 63 | // // 连字号、下划线在多种位置 64 | // #let alpha-; // 连字号不能在变量名开头位置 65 | // #let _alpha; #let alpha_; 66 | // ``` 67 | // + 特殊规则:标识符仅为#mark("_")时,不允许在后续位置继续使用。 68 | // #code(```typ 69 | // #let _ = 1; 70 | // // 不能编译:#_ 71 | // ```) 72 | // 该标识符被称为#term("placeholder")。 73 | // + 特殊规则:标识符不允许为`let`、`set`、`show`等关键字。 74 | // #code(```typ 75 | // // 不能编译: 76 | // // #let let = 1; 77 | // ```) 78 | 79 | #pro-tip[ 80 | 关于作用域,你可以参考#(refs.content-scope-style)[《内容、作用域与样式》]。 81 | ] 82 | 83 | 变量可以重复输出到文档中: 84 | 85 | #code(```typ 86 | #let x = "阿吧" 87 | #x#x,#x#x 88 | ```) 89 | 90 | 任意时刻都可以将任意类型的值赋给一个变量。上一节所提到的「内容块」也可以赋值给一个变量。 91 | 92 | #code(```typ 93 | #let y = [一段文本]; #y \ 94 | #(y = 1) /* 重新赋值为一个整数 */ #y \ 95 | ```) 96 | 97 | 任意时刻都可以重复定义相同变量名的变量,但是之前被定义的变量将无法再被使用: 98 | 99 | #code(```typ 100 | #let y = [一段文本]; #y \ 101 | #let y = 1; /* 重新声明为一个整数 */ #y \ 102 | ```) 103 | 104 | == 成员 105 | 106 | // ,它们是: 107 | // + #term("array literal", postfix: "。") 108 | // + #term("dictionary literal", postfix: "。") 109 | 110 | Typst提供了一系列「成员」和「方法」访问字面量、变量与函数中存储的“信息”。 111 | 112 | // 其实在上一节(甚至是第二节),你就已经见过了「成员」语法。你可以通过「点号」获得代码块的“text”(文本内容): 113 | 114 | // #code(```typ 115 | // #repr(`OvO`.text), #type(`OvO`), #type(`OvO`.text) 116 | // ```) 117 | 118 | 每个类型有哪些「成员」是由Typst决定的。你需要逐渐积累经验以知晓这些「成员」的分布,才能更快地通过访问成员快速编写出收集和处理信息的脚本。(todo: 建议阅读《参考:XXX》) 119 | 120 | 当然,为防你不知道,大家不都是死记硬背的:有软件手段帮助你使用这些「成员」。许多编辑器都支持LSP(Language Server Protocol,语言服务),例如VSCode安装Tinymist LSP。当你对某个对象后接一个点号时,编辑器会自动为你做代码补全。 121 | 122 | #figure(image("./IDE-autocomplete.png", width: 120pt), caption: [作者使用编辑器作代码补全的精彩瞬间。]) 123 | 124 | 从图中可以看出来,该代码片段「对象」上有七个「成员」。特别是“text”成员赫然立于其中,就是它了。除了「成员」列表,编辑器还会告诉你每个「成员」的作用,以及如何使用。这时候只需要选择一个「成员」作为补全结果即可。 125 | 126 | === 函数声明 127 | 128 | 「函数声明」也由`let`关键字开始。如果你仔细对比,可以发现它们在语法上是一致的。 129 | 130 | 如下语法,「函数声明」表示使得`f(x, y)`的内容与右侧表达式的值相等。我们对语法一一翻译: 131 | 132 | ```typ 133 | #let f(x, y) = [两个值#(x)和#(y)偷偷混入了我们内容之中。] 134 | // ^^^^ ^^^^^^^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 135 | // 令 函数名(参数列表) 为 一段内容 136 | ``` 137 | 138 | 「函数声明」一共分为4个部分,相较于「变量声明」,多了一个参数列表。 139 | 140 | 参数列表中参数的个数可以有零个,一个,至任意多个,由逗号分隔。每个参数都是一个单独的#term("parameter identifier")。 141 | 142 | ```typ 143 | // 零个参数,一个参数,两个参数,.. 144 | #f(), #f(x), #f(x, y) 145 | ``` 146 | 147 | #term("parameter identifier")的规则与功能与#term("variable identifier")相似。为参数取名是为其能在函数体中使用。 148 | 149 | 视角挪到等号右侧。#term("function body expression")的规则与功能与#term("initialization expression")相似。#term("function body expression")可以任意使用参数列表中的“变量”,并组合出一个表达式。组合出的表达式的值就是函数调用的结果。 150 | 151 | 结合例子理解函数的作用。将对应参数应用于函数可以取得对应的结果: 152 | 153 | #code(```typ 154 | #let f(x, y) = [两个值#(x)和#(y)偷偷混入了我们内容之中。] 155 | 156 | #let x = "Hello world!!" 157 | #let y = [一段文本] 158 | #f(repr(x), y) 159 | ```) 160 | 161 | 其中```typ #f(repr(x), y)```的执行过程是这样的: 162 | 163 | ```typ 164 | #f(repr(x), y) // 转变为 165 | #f([\"Hello world!!\"], [一段文本]) 166 | ``` 167 | 168 | 注意,此时我们进入右侧表达式。 169 | 170 | ```typ 171 | #[两个值#(x)和#(y)偷偷混入了我们内容之中。] // 转变为 172 | #[两个值#([\"Hello world!!\"])和#([一段文本])偷偷混入了我们内容之中。] 173 | ``` 174 | 175 | 最后整理式子得到,也即是我们看到的输出: 176 | 177 | ```typ 178 | 两个值\"Hello world!!\"和一段文本偷偷混入了我们内容之中。 179 | ``` 180 | 181 | 这里仅作较为基础的理解。在下一节,等我们学会了数组和字典,我们可以定义更复杂的函数。 182 | 183 | // 请体会我们在介绍「变量声明」时的特殊措辞,并与「函数声明」的语法描述相对应。 184 | 185 | // - 以下「变量声明」表示使得`x`的内容与`"Hello world!!"`相等。 186 | 187 | // ```typ 188 | // #let x = "Hello world!!" 189 | // ``` 190 | 191 | // - 以下「函数声明」表示使得`f(x, y)`的内容与经过计算后的`[两个值#(x)和#(y)偷偷混入了我们内容之中。]`相等。 192 | 193 | // ```typ 194 | // #let f(x, y) = [两个值#(x)和#(y)偷偷混入了我们内容之中。] 195 | // ``` 196 | 197 | == 函数闭包 198 | 199 | 「闭包」是一类特殊的函数,又称为匿名函数。一个简单的闭包如下: 200 | 201 | #code(```typ 202 | #let f = (x, y) => [#(x)和#(y)。] 203 | #f("我", "你") 204 | ```) 205 | 206 | 我们可以看到其以箭头为体,两侧为参数列表和函数体。不像「函数声明」,它不需要取名。 207 | 208 | 闭包以其匿名特征,很适合就地使用,让我们不必为一个暂时使用的函数起名,例如作为「回调」函数。我们将会在下一章经常使用「回调」函数。例如,Typst提供了「`show`」语法,其可以接收一个选择器和一个函数,并将其作用范围内被选择器选中的内容都使用给定函数处理: 209 | 210 | #code(```typ 211 | #let style(body) = text(blue, underline(body)) 212 | #show heading.where(level: 3): style 213 | === 标题 214 | ```) 215 | 216 | 但是,等价地,此时「函数声明」不如「闭包声明」优美: 217 | 218 | #code(```typ 219 | #show heading.where(level: 3): it => text(blue, underline(it)) 220 | === 标题 221 | ```) 222 | 223 | == 方法 224 | 225 | 「方法」是一种特殊的「成员」。准确来说,如果一个「成员」是一个对象的函数,那么它就被称为该对象的「方法」。 226 | 227 | 来看以下代码,它们输出了相同的内容,事实上,它们是*同一*「函数调用」的不同写法: 228 | 229 | #code(```typ 230 | #let x = "Hello World" 231 | #let str-split = str.split 232 | #str-split(x, " ") \ 233 | #str.split(x, " ") \ 234 | #x.split(" ") 235 | ```) 236 | 237 | 第三行脚本含义对照如下。之前已经学过,这正是「函数调用」的语法: 238 | 239 | ```typ 240 | #( str-split( x, " " )) 241 | // 调用 字符串拆分函数,参数为 变量x和空格 242 | ``` 243 | 244 | 与第三行脚本相比,第四行脚本仍然是在做「函数调用」,只不过在语法上更为紧凑。 245 | 246 | 第五行脚本则更加简明,此即「方法调用」。约定`str.split(x, y)`可以简写为`x.split(y)`,如果: 247 | + 对象`x`是`str`类型,且方法`split`是`str`类型的「成员」。 248 | + 对象`x`用作`str.split`调用的第一个参数。 249 | 250 | 「方法调用」即一种特殊的「函数调用」规则(语法糖),在各编程语言中广泛存在。其大大简化了脚本。但你也可以选择不用,毕竟「函数调用」一样可以完成所有任务。 251 | 252 | #pro-tip[ 253 | 这里有一个问题:为什么Typst要引入「方法」的概念呢?主要有以下几点考量。 254 | 255 | 其一,为了引入「方法调用」的语法,这种语法相对要更为方便和易读。对比以下两行,它们都完成了获取`"Hello World"`字符串的第二个单词的第一个字母的功能: 256 | 257 | #code( 258 | ```typ 259 | #"Hello World".split(" ").at(1).split("").at(1) 260 | #array.at(str.split(array.at(str.split("Hello World", " "), 1), ""), 1) 261 | ```, 262 | al: top, 263 | ) 264 | 265 | 可以明显看见,第二行语句的参数已经散落在括号的里里外外,很难理解到底做了什么事情。 266 | 267 | 其二,相比「函数调用」,「方法调用」更有利于现代IDE补全脚本。你可以通过`.split`很快定位到“字符串拆分”这个函数。 268 | 269 | 其三,方便用户管理相似功能的函数。不仅仅是字符串可以拆分,似乎内容及其他许多类型也可以拆分。如果一一为它们取不同的名字,那可就太头疼了。相比,`str.split`就简单多了。要知道,很多程序员都非常头痛为不同的变量和函数取名。 270 | ] 271 | 272 | == 总结 273 | 274 | // Typst如何保证一个简单函数甚至是一个闭包是“纯函数”? 275 | 276 | // 答:1. 禁止修改外部变量,则捕获的变量的值是“纯的”或不可变的;2. 折叠的对象是纯的,且「折叠」操作是纯的。 277 | 278 | // Typst的多文件特性从何而来? 279 | 280 | // 答:1. import函数产生一个模块对象,而模块其实是文件顶层的scope。2. include函数即执行该文件,获得该文件对应的内容块。 281 | 282 | // 基于以上两个特性,Typst为什么快? 283 | 284 | // + Typst支持增量解析文件。 285 | // + Typst所有由*用户声明*的函数都是纯的,在其上的调用都是纯的。例如Typst天生支持快速计算递归实现的fibnacci函数: 286 | 287 | // #code(```typ 288 | // #let fib(n) = if n <= 1 { n } else { fib(n - 1) + fib(n - 2) } 289 | // #fib(42) 290 | // ```) 291 | // + Typst使用`include`导入其他文件的顶层「内容块」。当其他文件内容未改变时,内容块一定不变,而所有使用到对应内容块的函数的结果也一定不会因此改变。 292 | 293 | // 这意味着,如果你发现了Typst中与一般语言的不同之处,可以思考以上种种优势对用户脚本的增强或限制。 294 | 295 | == 总结 296 | 297 | #todo-box[总结] 298 | 299 | == 习题 300 | 301 | #todo-box[习题] 302 | -------------------------------------------------------------------------------- /src/tutorial/scripting-control-flow.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: "控制流") 4 | 5 | == `none`类型和`if`语句 6 | 7 | 默认情况下,在逻辑上,Typst按照顺序执行执行你的代码,即先执行前面的语句,再执行后面的语句。开发者如果想要控制程序执行的流程,就必须使用流程控制的语法结构,主要是条件执行和循环执行。 8 | 9 | `if`语句用于条件判断,满足条件时,就执行指定的语句。 10 | 11 | ```typ 12 | #if expression { then-block } else { else-block } 13 | #if expression { then-block } 14 | ``` 15 | 16 | 上面式子中,表达式`expression`为真(值为布尔值`true`)时,就执行`then-block`代码块,否则执行`else-block`代码块。特别地,`else`可以省略。 17 | 18 | 如下所示: 19 | 20 | #code(```typ 21 | #if (1 < 2) { "确实" } else { "啊?" } 22 | ```) 23 | 24 | 因为`1 < 2`表达式为真,所以脚本仅执行了`then-block`代码块,于是最后文档的内容为“确实”。 25 | 26 | `if`语句还可以无限串联下去,你可以自行类比推理更长的`if`语句的语义: 27 | 28 | ```typ 29 | #if expression { .. } else if expression { .. } else { .. } 30 | #if expression { .. } else if expression { .. } 31 | #if expression { .. } else if expression { .. } else if .. 32 | ``` 33 | 如果只写了`then`代码块,而没写`else`代码块,但偏偏表达式不为真,最终脚本会报错吗?请看: 34 | 35 | #code(```typ 36 | #repr(if (1 > 2) { "啊?" }) 37 | ```) 38 | 39 | 当`if`表达式没写`else`代码块而条件为假时,结果为`none`。“none”在中文里意思是“无”,表示“什么都没有”。同时再次强调`none`在「可折叠」的值中很重要的一个性质:`none`在折叠过程中被忽略。 40 | 41 | 见下程序,其根据数组所包含的值输出特定字符串: 42 | 43 | #code(```typ 44 | #let 查成分(成分数组) = { 45 | "是个" 46 | if "A" in 成分数组 or "C" in 成分数组 or "G" in 成分数组 { "萌萌" } 47 | if "T" in 成分数组 { "工具" } 48 | "人" 49 | } 50 | 51 | #查成分(()) \ 52 | #查成分(("A","T",)) \ 53 | ```) 54 | 55 | 由于`if`也是表达式,你可以直接将`if`作为函数体,例如fibnacci函数的递归可以非常简单: 56 | 57 | #code(```typ 58 | #let fib(n) = if n <= 1 { n } else { 59 | fib(n - 1) + fib(n - 2) 60 | } 61 | #fib(46) 62 | ```) 63 | 64 | == `while`语句 65 | 66 | // if condition {..} 67 | // if condition [..] 68 | // if condition [..] else {..} 69 | // if condition [..] else if condition {..} else [..] 70 | 71 | `while`语句用于循环结构,满足条件时,不断执行循环体。 72 | 73 | ```typ 74 | #while expression { cont-block } 75 | ``` 76 | 77 | 上面代码中,如果表达式`expression`为真,就会执行`cont-block`代码块,然后再次判断`expression`是否为假;如果`expression`为假就跳出循环,不再执行循环体。 78 | 79 | #code(```typ 80 | #let i = 0; 81 | #while i < 10 { (i * 2, ); i += 1 } 82 | ```) 83 | 84 | 上面代码中,循环体会执行`10`次,每次将`i`增加`1`,直到等于`10`才退出循环。 85 | 86 | == `for`语句 87 | 88 | `for`语句也是常用的循环结构,它迭代访问某个对象的每一项。 89 | 90 | ```typ 91 | #for X in A { cont-block } 92 | ``` 93 | 94 | 上面代码中,对于`A`的每一项,都执行`cont-block`代码块。在执行`cont-block`时,项的内容是`X`。例如以下代码做了与之前循环相同的事情: 95 | 96 | #code(```typ 97 | #for i in range(10) { (i * 2, ) } 98 | ```) 99 | 100 | 其中`range(10)`创建了一个内容为`(0, 1, 2, .., 9)`一共10个值的数组。 101 | 102 | == 使用内容块替代代码块 103 | 104 | 所有可以使用代码块的地方都可以使用内容块作为替代。 105 | 106 | #code(```typ 107 | #for i in range(4) [阿巴]...... 108 | ```) 109 | 110 | == 使用`for`遍历字典 111 | 112 | 与数组相同,同理所有字典也都可以使用`for`遍历。此时,在执行`cont-block`时,Typst将每个键值对以数组的形式交给你。键值对数组的第0项是键,键值对数组的第1项是对应的值。 113 | 114 | #code(```typ 115 | #let cat = (neko-mimi: 2, "utterance": "喵喵喵", attribute: [kawaii\~]) 116 | #for i in cat { 117 | [猫猫的 #i.at(0) 是 #i.at(1)\ ] 118 | } 119 | ```) 120 | 121 | 你可以同时使用「解构赋值」让代码变得更容易阅读: 122 | 123 | ```typ 124 | #let cat = (neko-mimi: 2, "utterance": "喵喵喵", attribute: [kawaii\~]) 125 | #for (特色, 这个) in cat [猫猫的 #特色 是 #这个\ ] 126 | ``` 127 | 128 | == `break`语句和`continue`语句 129 | 130 | 无论是`while`还是`for`,都可以使用`break`跳出循环,或`continue`直接进入下一次执行。 131 | 132 | 基于以下`for`循环,我们探索`break`和`continue`语句的作用。 133 | 134 | #code(```typ 135 | #for i in range(10) { (i, ) } 136 | ```) 137 | 138 | 在第一次执行时,如果我们直接使用`break`跳出循环,但是在break之前就已经产生了一些值,那么`for`的结果是`break`前的那些值的「折叠」。 139 | 140 | #code(```typ 141 | #for i in range(10) { (i, ); (i + 1926, ); break } 142 | ```) 143 | 144 | 特别地,如果我们直接使用`break`跳出循环,那么`for`的结果是*`none`*。 145 | 146 | #code(```typ 147 | #for i in range(10) { break } 148 | ```) 149 | 150 | 在`break`之后的那些值将会被忽略: 151 | 152 | #code(```typ 153 | #for i in range(10) { break; (i, ); (i + 1926, ); } 154 | ```) 155 | 156 | 以下代码将收集迭代的所有结果,直到`i >= 5`: 157 | #code(```typ 158 | #for i in range(10) { 159 | if i >= 5 { break } 160 | (i, ) 161 | } 162 | ```) 163 | 164 | // #for 方位 in ("东", "南", "西", "北", "中", "间", "东北", "西北", "东南", "西南") [鱼戏莲叶#方位,] 165 | 166 | `continue`有相似的规则,便不再赘述。我们举一个例子,以下程序输出在`range(10)`中不是偶数的数字: 167 | 168 | #code(```typ 169 | #let 是偶数(i) = calc.even(i) 170 | #for i in range(10) { 171 | if 是偶数(i) { continue } 172 | (i, ) 173 | } 174 | ```) 175 | 176 | 事实上`break`语句和`continue`语句还可以在参数列表中使用,但本书非常不推荐这些写法,因此也不多做介绍: 177 | 178 | #code(```typ 179 | #let add(a, b, c) = a + b + c 180 | #while true { add(1, break, 2) } 181 | ```) 182 | 183 | == 控制函数返回值 184 | 185 | 你可以通过多种方法控制函数返回值。 186 | 187 | === 占位符 188 | 189 | 早在上节我们就学习过了占位符,这在编写函数体表达式的时候尤为有用。你可以通过占位符忽略不需要的函数返回值。 190 | 191 | 以下函数获取数组的倒数第二个元素: 192 | 193 | #code(```typ 194 | #let last-two(t) = { 195 | let _ = t.pop() 196 | t.pop() 197 | } 198 | #last-two((1, 2, 3, 4)) 199 | ```) 200 | 201 | === `return`语句 202 | 203 | 你可以通过`return`语句忽略表达式其余*所有语句*的结果,而使用`return`语句返回特定的值。 204 | 205 | 以下函数获取数组的倒数第二个元素: 206 | 207 | #code(```typ 208 | #let last-two(t) = { 209 | t.pop() 210 | return t.pop() 211 | } 212 | #last-two((1, 2, 3, 4)) 213 | ```) 214 | 215 | 216 | == 「作用域」 217 | 218 | // 内容块与代码块没有什么不同。 219 | 220 | 「作用域」是一个非常抽象的概念。但是理解他也并不困难。我们需要记住一件事,那就是每个「代码块」创建了一个单独的「作用域」: 221 | 222 | #code(```typ 223 | 两只#{ 224 | [兔] 225 | set text(rgb("#ffd1dc").darken(15%)) 226 | { [兔白]; set text(orange); [又白] } 227 | [,真可爱] 228 | } 229 | ```) 230 | 231 | 从上面的染色结果来看,粉色规则可以染色到`[兔白]`、`[又白]`和`[真可爱]`,橘色规则可以染色到`[又白]`但不能染色到[,真可爱]。以内容的视角来看: 232 | 1. `[兔]`不是相对粉色规则的后续内容,更不是相对橘色规则的后续内容,所以它默认是黑色。 233 | 2. `[兔白]`是相对粉色规则的后续内容,所以它是粉色。 234 | 3. `[又白]`同时被两个规则影响,但是根据「执行顺序」,橘色规则被优先使用。 235 | 4. `[真可爱]`虽然从代码先后顺序来看在橘色规则后面,但不在橘色规则所在作用域内,不满足「`set`」影响范围的设定。 236 | 237 | 我们说「`set`」的影响范围是其所在「作用域」内的后续内容,意思是:对于每个「代码块」,「`set`」规则只影响到从它自身语句开始,到该「代码块」的结束位置。 238 | 239 | 接下来,我们回忆:「内容块」和「代码块」没有什么不同。上述例子还可以以「内容块」的语法改写成: 240 | 241 | #code(```typ 242 | 两只#[兔#set text(fill: rgb("#ffd1dc").darken(15%)) 243 | #[兔白#set text(fill: orange) 244 | 又白],真可爱 245 | ] 246 | ```) 247 | 248 | 由于断行问题,这不方便阅读,但从结果来看,它们确实是等价的。 249 | 250 | 最后我们再回忆:文件本身是一个「内容块」。 251 | 252 | #code(```typ 253 | 两小只,#set text(fill: orange) 254 | 真可爱 255 | ```) 256 | 257 | 针对文件,我们仍重申一遍「`set`」的影响范围。其影响等价于:对于文件本身,*顶层*「`set`」规则影响到该文件的结束位置。 258 | 259 | #pro-tip[ 260 | 也就是说,`include`文件内部的样式不会影响到外部的样式。 261 | ] 262 | 263 | == 变量的可变性 264 | 265 | 理解「作用域」对理解变量的可变性有帮助。这原本是上一节的内容,但是前置知识包含「作用域」,故在此介绍。 266 | 267 | 话说Typst对内置实现的所有函数都有良好的自我管理,但总免不了用户打算写一些逆天的函数。为了保证缓存计算仍较为有效,Typst强制要求用户编写的*所有函数*都是纯函数。这允许Typst有效地缓存计算,在相当一部分文档的编译速度上,快过LaTeX等语言上百倍。 268 | 269 | 你可能不知道所谓的纯函数是为何物,本书也不打算讲解什么是纯函数。关键点是,涉及函数的*纯性*,就涉及到变量的可变性。 270 | 271 | 所谓变量的可变性是指,你可以任意改变一个变量的内容,也就是说一个变量默认是可变的: 272 | 273 | #code(```typ 274 | #let a = 1; #let b = 2; 275 | #((a, b) = (b, a)); #a, #b \ 276 | #for i in range(10) { a += i }; #a, #b 277 | ```) 278 | 279 | 但是,一个函数的函数体表达式不允许涉及到函数体外的变量修改: 280 | 281 | #code( 282 | ```typ 283 | #let a = 1; 284 | #let f() = (a += 1); 285 | #f() 286 | ```, 287 | res: [#text(red, [error]): variables from outside the function are read-only and cannot be modified], 288 | ) 289 | 290 | 这是因为纯函数不允许产生带有副作用的操作。 291 | 292 | 同时,传递进函数的数组和字典参数都会被拷贝。这将导致对参数数组或参数字典的修改不会影响外部变量的内容: 293 | 294 | #code(```typ 295 | #let a = (1, ); #a \ // 初始值 296 | #let add-array(a) = (a += (2, )); 297 | #add-array(a); #a \ // 函数调用无法修改变量 298 | #(a += (2, )); #a \ // 实际期望的效果 299 | ```) 300 | 301 | #pro-tip[ 302 | 准确地来说,数组和字典参数会被写时拷贝。所谓写时拷贝,即只有当你期望修改数组和字典参数时,拷贝才会随即发生。 303 | ] 304 | 305 | 为了“修改”外部变量,你必须将修改过的变量设法传出函数,并在外部更新外部变量。 306 | 307 | #code(```typ 308 | #let a = (1, ); #a \ // 初始值 309 | #let add-array(a) = { a.push(2); a }; 310 | #(a = add-array(a)); #a \ // 返回值更新数组 311 | ```) 312 | 313 | #pro-tip[ 314 | 一个函数是纯的,如果: 315 | + 对于所有相同参数,返回相同的结果。 316 | + 函数没有副作用,即局部静态变量、非局部变量、可变引用参数或输入/输出流等状态不会发生变化。 317 | 318 | 本节所讲述的内容是对第二点要求的体现。 319 | ] 320 | 321 | == 「`set if`」语法 322 | 323 | 回到「set」语法的话题。假设我们脚本中设置了当前文档是否处于暗黑主题,并希望使用「`set`」规则感知这个设定,你可能会写: 324 | 325 | #code(```typ 326 | #let is-dark-theme = true 327 | #if is-dark-theme { 328 | set rect(fill: black) 329 | set text(fill: white) 330 | } 331 | 332 | #rect([wink!]) 333 | ```) 334 | 335 | 根据我们的知识,这应该不起作用,因为`if`后的代码块创建了一个新的作用域,而「`set`」规则只能影响到该代码块内后续的代码。但是`if`的`then`和`else`一定需要创建一个新的作用域,这有点难办了。 336 | 337 | `set if`语法出手了,它允许你在当前作用域设置规则。 338 | 339 | #code(```typ 340 | #let is-dark-theme = true 341 | #set rect(fill: black) if is-dark-theme 342 | #set text(fill: white) if is-dark-theme 343 | #rect([wink!]) 344 | ```) 345 | 346 | 解读`#set rect(fill: black) if is-dark-theme`。它的意思是,如果满足`is-dark-theme`条件,那么设置相关规则。这其实与下面代码“感觉”一样。 347 | 348 | #code(```typ 349 | #let is-dark-theme = true 350 | #if is-dark-theme { 351 | set rect(fill: black) 352 | } 353 | #rect([wink!]) 354 | ```) 355 | 356 | 区别仅仅在`set if`语法确实从语法上没有新建一个作用域。这就好像一个“规则怪谈”:如果你想要让「`set`」规则影响到对应的内容,就想方设法满足「`set`」影响范围的要求。 357 | -------------------------------------------------------------------------------- /src/tutorial/scripting-literal.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: "基本类型") 4 | 5 | Typst很快,并非因为它的#term("parser")和#term("interpreter")具有惊世的执行性能,而是语言特性本身适合缓存优化。 6 | 7 | 自本节起,本教程将进入第二阶段,希望不仅让你了解如何使用脚本,还一并讲解Typst的执行原理。当然,我们不会陷入Typst的细节。所有在教程中出现的原理都是为了更好地了解语言本身。 8 | 9 | // 什么是自省 10 | // 程序不会自己自省,这是给你看的 11 | 12 | == 代码表示的自省函数 13 | 14 | 在开始学习之前,先学习几个与排版无关但非常实用的函数。 15 | 16 | #typst-func("repr")是一个#term("introspection function"),可以帮你获得任意值的代码表示,很适合用来在调试代码的时候输出内容。 17 | 18 | #code(```typ 19 | #[ 一段文本 ] 20 | 21 | #repr([ 一段文本 ]) 22 | ```) 23 | 24 | #pro-tip[ 25 | #term("introspection function")是指那些为你获取解释器内部状态的函数。它们往往接受一些语言对象,而返回存储在解释器内部的相关信息。在这里,#typst-func("repr")接受任意值,而返回对应的代码表示。 26 | ] 27 | 28 | == `type`函数 29 | 30 | 与#typst-func("repr")类似,一个特殊的函数#typst-func("type")可以获得任意值的#term("type")。所谓#term("type"),就是这个值归属的分类。例如: 31 | - `1`是整数数字,类型就对应于整数类型(integer)。 32 | #code(```typ 33 | #type(1) 34 | ```) 35 | - `一段内容`是文本内容,类型就对应于内容类型(content)。 36 | #code(```typ 37 | #type[一段内容] 38 | ```) 39 | 40 | === 类型 41 | 42 | 一个值只会属于一种类型,因此类型是可以比较的: 43 | 44 | #code(```typ 45 | #(str == str) \ 46 | #(type("X") == type("Y")) \ 47 | #(type("X") == str) \ 48 | #([一段内容] == str) 49 | ```) 50 | 51 | 类型的类型是类型(它自身): 52 | 53 | #code(```typ 54 | #(type(str)) \ 55 | #(type(type(str))) \ 56 | #(type(type(str)) == type) \ 57 | ```) 58 | 59 | == `eval`函数 60 | 61 | `eval`函数接受一个字符串,把字符串当作代码执行并求出结果: 62 | 63 | #code(```typ 64 | #eval("1"), #type(eval("1")) \ 65 | #eval("[一段内容]"), #type(eval("[一段内容]")) 66 | ```) 67 | 68 | 从```typc eval("[一段内容]")```的中括号被解释为「内容块」可以得知,`eval`默认以#term("code mode")解释你的代码。 69 | 70 | 你可以使用`mode`参数修改`eval`的「解释模式」。`code`对应为#term("code mode"),`markup`对应为#term("markup mode"): 71 | 72 | #code(```typ 73 | 代码模式eval:#eval("[一段内容]", mode: "code") \ 74 | 标记模式eval:#eval("[一段内容]", mode: "markup") 75 | ```) 76 | 77 | // #let x = 1; 78 | 79 | // #let f() = x 80 | 81 | // #f() 82 | // #(x = 2) 83 | // #f() 84 | 85 | == 基本字面量 86 | 87 | 我们将介绍所有基本字面量,这是脚本的“一加一”。其实在上一节,我们已经见过了一部分字面量,但皆凭直觉使用:```typc 1```不就是数字吗,那么在Typst中,它就是数字。与之相对,TeX底层没有数字和字符串的概念。 88 | 89 | 如果你学过Python等语言,那么这将对你来说不是问题。在Typst中,常用的字面量并不多,它们是: 90 | + #term("none literal")。 91 | + #term("boolean literal")。 92 | + #term("integer literal")。 93 | + #term("floating-point literal")。 94 | + #term("string literal")。 95 | 96 | === 空字面量 97 | 98 | 就像是数学中的零与负数,空字面量自然产生于运算过程中。 99 | 100 | #code(```typ 101 | #repr((0, 1).find((_) => false)), 102 | #repr(if false [啊?]) 103 | ```) 104 | 105 | 上例第一行,当在「数组」中查找一个不存在的元素时,“没有”就是```typc none```。第二行,当条件不满足,且没有`false`分支时,“没有内容”就是```typc none```。 106 | 107 | 空字面量是纯粹抽象的概念,这意味着你在现实中很难找到对应的实体。 108 | 109 | // #pro-tip[ 110 | // 空字面量自然产生于运算过程中。除上所述,以下是其他会产生```typc none```的自然场景: 111 | // - 当变量未初始化时。 112 | // #code(```typ 113 | // #let x; #type(x) 114 | // ```) 115 | // - 当`for`语句、`while`语句、函数没有产生任何值时,函数返回值为```typc none```。 116 | // #code(```typ 117 | // #let f() = {}; #type(f()) 118 | // ```) 119 | // - 当函数显式`return`且未写明返回值时,函数返回值为```typc none```。 120 | // #code(```typ 121 | // #let f() = return; #type(f()) 122 | // ```) 123 | // ] 124 | 125 | `none`值不会对输出文档有任何影响: 126 | 127 | #code(```typ 128 | #none 129 | ```) 130 | 131 | `none`的类型是`none`类型。`none`值不等于`none`类型,因为一个是值而另一个是类型: 132 | 133 | #code(```typ 134 | #type(none), #(type(none) == none), #type(type(none)) 135 | ```) 136 | 137 | === 布尔字面量 138 | 139 | 一个布尔字面量表示逻辑的确否。它要么为```typc false```(真)要么为```typc true```(假)。 140 | 141 | #code(```typ 142 | 假设 #false 那么一切为 #true。 143 | ```) 144 | 145 | 一般来说,我们不直接使用布尔值。当代码做逻辑判断的时候,会自然产生布尔值。 146 | 147 | #code(```typ 148 | $1 < 2$的结果为:#(1 < 2) 149 | ```) 150 | 151 | === 整数字面量 152 | 153 | 一个整数字面量代表一个整数。Typst中的整数默认为十进制: 154 | 155 | #code(```typ 156 | 三个值 #(-1)、#0 和 #1 偷偷混入了我们内容之中。 157 | ```) 158 | 159 | #pro-tip[ 160 | 有的时候Typst不支持在#mark("#")后直接跟一个值。例如,Typst无法处理#mark("#")后直接跟随一个#mark("hyphen")的情况。这个时候无论多么复杂,都可以用括号包裹值: 161 | 162 | #code(```typ 163 | #(-1), #(0), #(1) 164 | ```) 165 | ] 166 | 167 | 有些数字使用其他进制表示更为方便。你可以分别使用`0x`、`0o`和`0b`前缀加上进制内容表示十六进制数、八进制数和二进制数: 168 | 169 | #code(```typ 170 | #(0xbeef)、#(0o755)、#(-0b1001) 171 | ```) 172 | 173 | 整数的有效取值范围是$[-2^63,2^63)$,其中$2^63=9223372036854775808$。 174 | 175 | === 浮点数字面量 176 | 177 | 浮点数与整数非常类似。最常见的浮点数由至少一个整数部分或小数部分组成: 178 | 179 | #code(```typ 180 | 浮点数#(1.001)、小数部分#(.1) 和整数部分#(2.)。 181 | ```) 182 | 183 | 有些数字使用#term("exponential notation")更为方便: 184 | 185 | #code(```typ 186 | #(1e2)、#(1.926e3)、#(-1e-3) 187 | ```) 188 | 189 | Typst还为你内置了一些特殊的数值,它们都是浮点数: 190 | 191 | #code(```typ 192 | $inf$=#calc.inf, 193 | $pi$=#calc.round(calc.pi, digits: 5), 194 | $tau$=#calc.round(calc.tau, digits: 5) 195 | ```) 196 | // NaN=#calc.nan \ 197 | 198 | === 字符串字面量 199 | 200 | Typst中所有字符串都是`utf-8`编码的,因此使用时不存在编码转换问题。字符串由一对「英文双引号」定界: 201 | 202 | #code(```typ 203 | #"Hello world!!" 204 | ```) 205 | 206 | 有些字符无法置于双引号之内,例如双引号本身。与「标记模式」中的转义序列语法类似,这时候你需要嵌入字符的转义序列: 207 | 208 | #code(```typ 209 | #"Hello \"world\"!!" 210 | ```) 211 | 212 | 字符串中的转义序列与「标记模式」中的转义序列语法相同,但有效转义的字符集不同。字符串中如下转义序列是有效的: 213 | 214 | #{ 215 | set align(center) 216 | table( 217 | columns: 7, 218 | [代码], [`\\`], [`\"`], [`\n`], [`\r`], [`\t`], [`\u{2665}`], 219 | [效果], "\\", "\"", [(换行)], [(回车)], [(制表)], "\u{2665}", 220 | ) 221 | } 222 | 223 | 你同样可以使用`\u{unicode}`格式直接嵌入Unicode字符。 224 | 225 | #code(```typ 226 | #"香辣牛肉粉好吃\u{2665}" 227 | ```) 228 | 229 | 除了使用简单字面量构造,可以使用以下方法从代码块获得字符串: 230 | 231 | #code(```typ 232 | #repr(`包含换行符和双引号的 233 | 234 | "内容"`.text) 235 | ```) 236 | 237 | == 类型转换 238 | 239 | 类型本身也是函数,例如`int`类型可以接受字符串,转换成整数。 240 | 241 | #code(```typ 242 | #int("11"), #int("-23") 243 | ```) 244 | 245 | 从转换的角度,`eval`可以将代码字符串转换成值。例如,你可以转换16进制数字: 246 | 247 | #code(```typ 248 | #eval("0x3f") 249 | ```) 250 | 251 | == 总结 252 | 253 | // Typst如何保证一个简单函数甚至是一个闭包是“纯函数”? 254 | 255 | // 答:1. 禁止修改外部变量,则捕获的变量的值是“纯的”或不可变的;2. 折叠的对象是纯的,且「折叠」操作是纯的。 256 | 257 | // Typst的多文件特性从何而来? 258 | 259 | // 答:1. import函数产生一个模块对象,而模块其实是文件顶层的scope。2. include函数即执行该文件,获得该文件对应的内容块。 260 | 261 | // 基于以上两个特性,Typst为什么快? 262 | 263 | // + Typst支持增量解析文件。 264 | // + Typst所有由*用户声明*的函数都是纯的,在其上的调用都是纯的。例如Typst天生支持快速计算递归实现的fibnacci函数: 265 | 266 | // #code(```typ 267 | // #let fib(n) = if n <= 1 { n } else { fib(n - 1) + fib(n - 2) } 268 | // #fib(42) 269 | // ```) 270 | // + Typst使用`include`导入其他文件的顶层「内容块」。当其他文件内容未改变时,内容块一定不变,而所有使用到对应内容块的函数的结果也一定不会因此改变。 271 | 272 | // 这意味着,如果你发现了Typst中与一般语言的不同之处,可以思考以上种种优势对用户脚本的增强或限制。 273 | 274 | #todo-box[重写总结] 275 | 276 | 基于《字面量、变量和函数》掌握的知识你应该可以: 277 | + 查看#(refs.ref-type-builtin)[《参考:内置类型》],以掌握内置类型的使用方法。 278 | + 查看#(refs.ref-visualization)[《参考:图形与几何元素》],以掌握图形和几何元素的使用方法。 279 | + 查看#(refs.ref-wasm-plugin)[《参考:WASM插件》],以掌握在Typst中使用Rust、JavaScript、Python等语言编写插件库。 280 | + 阅读#(refs.ref-grammar)[《参考:语法示例检索表》],以检查自己的语法掌握程度。 281 | + 查看#(refs.ref-typebase)[《参考:基本类型》],以掌握基本类型的使用方法。 282 | + 查看#(refs.ref-color)[《参考:颜色、色彩渐变与模式》],以掌握色彩的高级管理方法。 283 | + 查看#(refs.ref-data-process)[《参考:数据读写与数据处理》],以助你从外部读取数据或将文档数据输出到文件。 284 | + 查看#(refs.ref-bibliography)[《参考:导入和使用参考文献》],以助你导入和使用参考文献。 285 | + 阅读基本参考部分中的所有内容。 286 | 287 | == 习题 288 | 289 | #let q1 = ````typ 290 | #let a0 = 2 291 | #let a1 = a0 * a0 292 | #let a2 = a1 * a1 293 | #let a3 = a2 * a2 294 | #let a4 = a3 * a3 295 | #let a5 = a4 * a4 296 | #a5 297 | ```` 298 | 299 | #exercise[ 300 | 使用本节所讲述的语法,计算$2^32$的值:#rect(width: 100%, eval(q1.text, mode: "markup")) 301 | ][ 302 | #q1 303 | ] 304 | 305 | #let q1 = ````typ 306 | #let a = [一] 307 | #let b = [渔] 308 | #let c = [江] 309 | #let f(x, y) = a + x + a + y 310 | #let g(x, y, z, u, v) = [#f(x, y + a + z),#f(u, v)。] 311 | #g([帆], [桨], [#(b)舟], [个#(b)翁], [钓钩]) \ 312 | #g([俯], [仰], [场笑], [#(c)明月], [#(c)秋]) 313 | ```` 314 | 315 | #exercise[ 316 | 输出下面的诗句,但你的代码中至多只能出现17个汉字:#rect(width: 100%, eval(q1.text, mode: "markup")) 317 | ][ 318 | #q1 319 | ] 320 | 321 | #let q1 = ````typ 322 | #let calc-fib() = { 323 | let res = range(2).map(float) 324 | for i in range(2, 201) { 325 | res.push(res.at(i - 1) + res.at(i - 2)) 326 | } 327 | 328 | res 329 | } 330 | #let fib(n) = calc-fib().at(n) 331 | 332 | #fib(75) 333 | ```` 334 | 335 | #exercise[ 336 | 已知斐波那契数列的递推式为$F_n = F_(n-1) + F_(n-2)$,且$F_0 = 0, F_1 = 1$。使用本节所讲述的语法,计算$F_75$的值:#rect(width: 100%, eval(q1.text, mode: "markup")) 337 | ][ 338 | #q1 339 | ] 340 | 341 | #let q1 = ````typ 342 | #set align(center) 343 | #let matrix-fmt(..args) = table( 344 | columns: args.at(0).len(), 345 | ..args.pos().flatten().flatten().map(str) 346 | ) 347 | #matrix-fmt( 348 | (1, 2, 3), 349 | (4, 5, 6), 350 | (7, 8, 9), 351 | ) 352 | ```` 353 | 354 | #exercise[ 355 | 编写函数,使用`table`(表格)元素打印任意$N times M$矩阵,例如: 356 | 357 | ```typ 358 | #matrix-fmt( 359 | (1, 2, 3), 360 | (4, 5, 6), 361 | (7, 8, 9), 362 | ) 363 | ``` 364 | 365 | #rect(width: 100%, eval(q1.text, mode: "markup")) 366 | ][ 367 | #q1 368 | ] 369 | 370 | -------------------------------------------------------------------------------- /typ/templates/page.typ: -------------------------------------------------------------------------------- 1 | 2 | // This is important for typst-book to produce a responsive layout 3 | // and multiple targets. 4 | #import "@preview/shiroa:0.2.3": get-page-width, is-pdf-target, is-web-target, plain-text, target, templates 5 | #import templates: * 6 | #import "template-link.typ": * 7 | #import "/typ/templates/side-notes.typ": side-attrs 8 | 9 | // Metadata 10 | #let page-width = get-page-width() 11 | #let is-pdf-target = is-pdf-target() 12 | #let is-web-target = is-web-target() 13 | 14 | // Theme (Colors) 15 | #let ( 16 | style: theme-style, 17 | is-dark: is-dark-theme, 18 | is-light: is-light-theme, 19 | main-color: main-color, 20 | dash-color: dash-color, 21 | code-extra-colors: code-extra-colors, 22 | ) = book-theme-from(toml("theme-style.toml"), xml: it => xml(it)) 23 | 24 | // Sizes 25 | #let main-size = if is-web-target { 26 | 16pt 27 | } else { 28 | 10.5pt 29 | } 30 | 31 | #let heading-sizes = if is-web-target { 32 | (36pt, 26pt, 22pt, 18pt, main-size) 33 | } else { 34 | (26pt, 22pt, 14pt, 12pt, main-size) 35 | } 36 | #assert( 37 | heading-sizes.at(-1) < heading-sizes.at(-2), 38 | message: "The second smallest heading size should be larger than the paragraph size (main-size).", 39 | ) 40 | 41 | #let list-indent = 0.5em 42 | #let par-leading = if is-web-target { 43 | 0.8em 44 | } else { 45 | // typst's default 46 | 0.65em 47 | } 48 | // 1.2, 1.5 * this parameter 49 | #let block-spacing = if is-web-target { 50 | par-leading * 1.5 51 | } else { 52 | 0.7em 53 | } 54 | #let heading-below = block-spacing * 1. 55 | #let heading-spacing = heading-below * 1.5 56 | 57 | // Fonts 58 | #let use-fandol-fonts = false 59 | #let main-font-cn = { 60 | if is-web-target { 61 | ("Noto Sans CJK SC",) 62 | } else if use-fandol-fonts { 63 | ("FandolSong",) 64 | } 65 | ("Source Han Serif SC",) 66 | } 67 | 68 | #let code-font-cn = ("Noto Sans CJK SC",) 69 | 70 | #let main-font = if use-fandol-fonts { 71 | ("New Computer Modern", ..main-font-cn) 72 | } else { 73 | ( 74 | // "Charter", 75 | // typst-book's embedded font 76 | "Libertinus Serif", 77 | ..main-font-cn, 78 | ) 79 | } 80 | 81 | #let code-font = ( 82 | "BlexMono Nerd Font Mono", 83 | // typst-book's embedded font 84 | "DejaVu Sans Mono", 85 | ..code-font-cn, 86 | ) 87 | 88 | // todo: move code theme parser to another lib file 89 | #let code-theme-file = theme-style.at("code-theme") 90 | 91 | #let code-extra-colors = if code-theme-file.len() > 0 { 92 | let data = xml(theme-style.at("code-theme")).first() 93 | 94 | let find-child(elem, tag) = { 95 | elem.children.find(e => "tag" in e and e.tag == tag) 96 | } 97 | 98 | let find-kv(elem, key, tag) = { 99 | let idx = elem.children.position(e => "tag" in e and e.tag == "key" and e.children.first() == key) 100 | elem.children.slice(idx).find(e => "tag" in e and e.tag == tag) 101 | } 102 | 103 | let plist-dict = find-child(data, "dict") 104 | let plist-array = find-child(plist-dict, "array") 105 | let theme-setting = find-child(plist-array, "dict") 106 | let theme-setting-items = find-kv(theme-setting, "settings", "dict") 107 | let background-setting = find-kv(theme-setting-items, "background", "string") 108 | let foreground-setting = find-kv(theme-setting-items, "foreground", "string") 109 | (bg: rgb(background-setting.children.first()), fg: rgb(foreground-setting.children.first())) 110 | } else { 111 | (bg: rgb(239, 241, 243), fg: none) 112 | } 113 | 114 | #let make-unique-label(it, disambiguator: 1) = label({ 115 | let k = plain-text(it).trim() 116 | if disambiguator > 1 { 117 | k + "_d" + str(disambiguator) 118 | } else { 119 | k 120 | } 121 | }) 122 | 123 | #let heading-reference(it, d: 1) = make-unique-label(it.body, disambiguator: d) 124 | 125 | // The project function defines how your document looks. 126 | // It takes your content and some metadata and formats it. 127 | // Go ahead and customize it to your liking! 128 | #let project(title: "Typst中文教程", authors: (), kind: "page", body) = { 129 | let is-ref-page = kind == "reference-page" 130 | let is-page = kind == "page" 131 | 132 | // set basic document metadata 133 | set document( 134 | author: authors, 135 | title: title, 136 | ) if not is-pdf-target 137 | 138 | // set web/pdf page properties 139 | set page( 140 | numbering: if is-pdf-target { 141 | "1" 142 | }, 143 | ) 144 | 145 | // set web/pdf page properties 146 | set page( 147 | number-align: center, 148 | width: page-width, 149 | ) 150 | 151 | // remove margins for web target 152 | set page( 153 | margin: ( 154 | // reserved beautiful top margin 155 | top: 20pt, 156 | // reserved for our heading style. 157 | // If you apply a different heading style, you may remove it. 158 | left: 20pt, 159 | // Typst is setting the page's bottom to the baseline of the last line of text. So bad :(. 160 | bottom: 0.5em, 161 | // remove rest margins. 162 | rest: 0pt, 163 | ), 164 | height: auto, 165 | ) if is-web-target 166 | 167 | // set text style 168 | set text( 169 | font: main-font, 170 | size: main-size, 171 | fill: main-color, 172 | lang: "zh", 173 | region: "cn", 174 | ) 175 | set block(spacing: block-spacing) 176 | 177 | let ld = state("label-disambiguator", (:)) 178 | let update-ld(k) = ld.update(it => { 179 | it.insert(k, it.at(k, default: 0) + 1) 180 | it 181 | }) 182 | let get-ld(loc, k) = make-unique-label(k, disambiguator: ld.at(loc).at(k)) 183 | 184 | // show regex("[A-Za-z]+"): set text(font: main-font-en) 185 | let cjk-markers = regex("[“”‘’.,。、?!:;(){}[]〔〕〖〗《》〈〉「」【】『』─—_·…\u{30FC}]+") 186 | show cjk-markers: set text(font: main-font-cn) 187 | show raw: it => { 188 | show cjk-markers: set text(font: code-font-cn) 189 | it 190 | } 191 | // show regex("[a-zA-Z\s\#\[\]]+"): set text(baseline: -0.05em) 192 | // show regex("[“”]+"): set text(font: main-font-cn) 193 | 194 | // Set text, spacing for headings 195 | // Render a hash to hint headings instead of bolding it as well if it's for web. 196 | show heading: set text(weight: "regular") if is-web-target 197 | show heading: it => { 198 | let it = { 199 | set text(size: heading-sizes.at(it.level)) 200 | if is-web-target { 201 | heading-hash(it, hash-color: dash-color) 202 | } 203 | it 204 | } 205 | 206 | block( 207 | spacing: heading-spacing, 208 | below: heading-below, 209 | it, 210 | ) 211 | } 212 | 213 | // link setting 214 | show link: set text(fill: dash-color) 215 | 216 | // math setting 217 | show math.equation: set text(weight: 400) 218 | 219 | // code block setting 220 | show raw: it => { 221 | if "block" in it.fields() and it.block { 222 | rect( 223 | width: 100%, 224 | inset: (x: 4pt, y: 5pt), 225 | radius: 2pt, 226 | stroke: none, 227 | fill: code-extra-colors.at("bg"), 228 | [ 229 | #set text(font: code-font) 230 | #set text(fill: code-extra-colors.at("fg")) if code-extra-colors.at("fg") != none 231 | #set par(justify: false) 232 | #it 233 | ], 234 | ) 235 | } else { 236 | set text( 237 | font: code-font, 238 | baseline: -0.08em, 239 | ) 240 | it 241 | } 242 | } 243 | 244 | // figure setting 245 | set rect(stroke: main-color) 246 | set table(stroke: main-color) 247 | 248 | show : it => { 249 | let children = it.lines.at(0).body.children 250 | let rb = children.position(e => e.at("text", default: none) == "(") 251 | children.slice(0, rb).join() 252 | } 253 | 254 | 255 | if title != none { 256 | if is-web-target { 257 | // [= #title] 258 | } else { 259 | v(0.5em) 260 | align(center, [= #title]) 261 | v(1em) 262 | } 263 | } 264 | 265 | // Main body. 266 | set par(leading: par-leading, justify: true) 267 | 268 | if is-ref-page { 269 | let side-space = 4 * main-size 270 | let side-overflow = 2 * main-size 271 | let gutter = 1 * main-size 272 | grid( 273 | columns: (side-space, 100% - side-space - gutter), 274 | column-gutter: gutter, 275 | place( 276 | dx: -side-overflow, 277 | block( 278 | breakable: true, 279 | width: side-space + side-overflow, 280 | context { 281 | let loc = here() 282 | side-attrs.update(it => { 283 | it.insert("left", loc.position().x) 284 | it.insert("width", side-space + side-overflow) 285 | it.insert("gutter", gutter) 286 | it 287 | }) 288 | }, 289 | ), 290 | ), 291 | body, 292 | ) 293 | } else if is-page { 294 | side-attrs.update(it => { 295 | it.insert("left", 0pt) 296 | it.insert("width", 0pt) 297 | it.insert("gutter", 0pt) 298 | it 299 | }) 300 | body 301 | } else { 302 | body 303 | } 304 | } 305 | 306 | #let part-style = heading 307 | -------------------------------------------------------------------------------- /src/tutorial/scripting-content.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: "文档树") 4 | 5 | #todo-box[本节处于校对阶段,所以可能存在不完整或错误。] 6 | 7 | == 「可折叠」的值(Foldable) 8 | 9 | 先来看代码块。代码块其实就是一个脚本。既然是脚本,Typst就可以按照语句顺序依次执行「语句」。 10 | 11 | #pro-tip[ 12 | 准确地来说,按照控制流顺序。 13 | ] 14 | 15 | Typst按控制流顺序执行代码,将所有结果*折叠*成一个值。所谓折叠,就是将所有数值“连接”在一起。这样讲还是太抽象了,来看一些具体的例子。 16 | 17 | === 字符串折叠 18 | 19 | Typst实际上不限制代码块的每个语句将会产生什么结果,只要是结果之间可以*折叠*即可。 20 | 21 | 我们说字符串是可以折叠的: 22 | 23 | #code(```typ 24 | #{"Hello"; " "; "World"} 25 | ```) 26 | 27 | 实际上折叠操作基本就是#mark("+")操作。那么字符串的折叠就是在做字符串连接操作: 28 | 29 | #code(```typ 30 | #("Hello" + " " + "World") 31 | ```) 32 | 33 | 再看一个例子: 34 | 35 | #code(```typ 36 | #{ 37 | let hello = "Hello"; 38 | let space = " "; 39 | let world = "World"; 40 | hello; space; world; 41 | let destroy = ", Destroy" 42 | destroy; space; world; "." 43 | } 44 | ```) 45 | 46 | 如何理解将「变量声明」与表达式混写? 47 | 48 | 回忆前文。对了,「变量声明」表达式的结果为```typc none```。 49 | #code(```typ 50 | #type(let hello = "Hello") 51 | ```) 52 | 53 | 并且还有一个重点是,字符串与`none`相加是字符串本身,`none`加`none`还是`none`: 54 | 55 | #code(```typ 56 | #("Hello" + none), #(none + "Hello"), #repr(none + none) 57 | ```) 58 | 59 | 现在可以重新体会这句话了:Typst按控制流顺序执行代码,将所有结果*折叠*成一个值。对于上例,每句话的执行结果分别是: 60 | 61 | ```typc 62 | #{ 63 | none; // let hello = "Hello"; 64 | none; // let space = " "; 65 | none; // let world = "World"; 66 | "Hello"; " "; "World"; // hello; space; world; 67 | none; // let destroy = ", Destroy" 68 | ", Destroy"; " "; "World"; "." // destroy; space; world; "." 69 | } 70 | ``` 71 | 72 | 将结果收集并“折叠”,得到结果: 73 | 74 | #code(```typc 75 | #(none + none + none + "Hello" + " " + "World" + none + ", Destroy" + " " + "World" + ".") 76 | ```) 77 | 78 | #pro-tip[ 79 | 还有其他可以折叠的值,例如,数组与字典也是可以折叠的: 80 | 81 | #code(```typ 82 | #for i in range(1, 5) { (i, i * 10) } 83 | ```) 84 | 85 | #code(```typ 86 | #for i in range(1, 5) { let d = (:); d.insert(str(i), i * 10); d } 87 | ```) 88 | ] 89 | 90 | === 其他基本类型的情况 91 | 92 | 那么为什么说折叠操作基本就是#mark("+")操作。那么就是说有的“#mark("+")操作”并非是折叠操作。 93 | 94 | 布尔值、整数和浮点数都不能相互折叠: 95 | 96 | ```typ 97 | // 不能编译 98 | #{ false; true }; #{ 1; 2 }; #{ 1.; 2. } 99 | ``` 100 | 101 | 那么是否说布尔值、整数和浮点数都不能折叠呢。答案又是否认的,它们都可以与```typc none```折叠(把下面的加号看成折叠操作): 102 | 103 | #code(```typ 104 | #(1 + none) 105 | ```) 106 | 107 | 所以你可以保证一个代码块中只有一个「语句」产生布尔值、整数或浮点数结果,这样的代码块就又是能编译的了。让我们利用`let _ = `来实现这一点: 108 | 109 | #code(```typ 110 | #{ let _ = 1; true }, 111 | #{ let _ = false; 2. } 112 | ```) 113 | 114 | 回忆之前所讲的特殊规则:#term("placeholder")用作标识符的作用是“忽略不必要的语句结果”。 115 | 116 | === 内容折叠 117 | 118 | Typst脚本的核心重点就在本段。 119 | 120 | 内容也可以作为代码块的语句结果,这时候内容块的结果是每个语句内容的“折叠”。 121 | 122 | #code(```typ 123 | #{ 124 | [= 生活在Content树上] 125 | [现代社会以海德格尔的一句“一切实践传统都已经瓦解完了”为嚆矢。] 126 | [滥觞于家庭与社会传统的期望正失去它们的借鉴意义。] 127 | [但面对看似无垠的未来天空,我想循卡尔维诺“树上的男爵”的生活好过过早地振翮。] 128 | } 129 | ```) 130 | 131 | 是不是感觉很熟悉?实际上内容块就是上述代码块的“糖”。所谓糖就是同一事物更方便书写的语法。上述代码块与下述内容块等价: 132 | 133 | ```typ 134 | #[ 135 | = 生活在Content树上 136 | 现代社会以海德格尔的一句“一切实践传统都已经瓦解完了”为嚆矢。滥觞于家庭与社会传统的期望正失去它们的借鉴意义。但面对看似无垠的未来天空,我想循卡尔维诺“树上的男爵”的生活好过过早地振翮。 137 | ] 138 | ``` 139 | 140 | 由于Typst默认以「标记模式」开始解释你的文档,这又与省略`#[]`的写法等价: 141 | 142 | ```typ 143 | = 生活在Content树上 144 | 现代社会以海德格尔的一句“一切实践传统都已经瓦解完了”为嚆矢。滥觞于家庭与社会传统的期望正失去它们的借鉴意义。但面对看似无垠的未来天空,我想循卡尔维诺“树上的男爵”的生活好过过早地振翮。 145 | ``` 146 | 147 | #pro-tip[ 148 | 实际上有区别,由于多两个换行和缩进,前后各多一个Space Element。 149 | ] 150 | 151 | // == Hello World程序 152 | 153 | // 有的时候,我们想要访问字面量、变量与函数中存储的“信息”。例如,给定一个字符串```typc "Hello World"```,我们想要截取其中的第二个单词。 154 | 155 | // 单词`World`就在那里,但仅凭我们有限的脚本知识,却没有方法得到它。这是因为字符串本身是一个整体,虽然它具备单词信息,我们却缺乏了*访问*信息的方法。 156 | 157 | // Typst为我们提供了「成员」和「方法」两种概念访问这些信息。使用「方法」,可以使用以下脚本完成目标: 158 | 159 | // #code(```typ 160 | // #"Hello World".split(" ").at(1) 161 | // ```) 162 | 163 | // 为了方便讲解,我们改写出6行脚本。除了第二行,每一行都输出一段内容: 164 | 165 | // #code(```typ 166 | // #let x = "Hello World"; #x \ 167 | // #let split = str.split 168 | // #split(x, " ") \ 169 | // #str.split(x, " ") \ 170 | // #x.split(" ") \ 171 | // #x.split(" ").at(1) 172 | // ```) 173 | 174 | // 从```typ #x.split(" ").at(1)```的输出可以看出,这一行帮助我们实现了“截取其中的第二个单词”的目标。我们虽然隐隐约约能揣测出其中的意思: 175 | 176 | // ```typ 177 | // #( x .split(" ") .at(1) ) 178 | // // 将字符串 根据字符串拆分 取出其中的第2个单词(字符串) 179 | // ``` 180 | 181 | // 但至少我们对#mark(".")仍是一无所知。 182 | 183 | // 本节我们就来讲解Typst中较为高级的脚本语法。这些脚本语法与大部分编程语言的语法相同,但是我们假设你并不知道这些语法。 184 | 185 | == 「内容」是一棵树(Cont.) 186 | 187 | #pro-tip[ 188 | 利用「内容」与「树」的特性,我们可以在Typst中设计出更多优雅的脚本功能。 189 | ] 190 | 191 | === CeTZ的「树」 192 | 193 | CeTZ利用内容树制作“内嵌的DSL”。CeTZ的`canvas`函数接收的不完全是内容,而是内容与其IR的混合。 194 | 195 | 例如它的`line`函数的返回值,就完全不是一个内容,而是一个无法窥视的函数。 196 | 197 | #code(```typ 198 | #import "@preview/cetz:0.3.4" 199 | #repr(cetz.draw.line((0, 0), (1, 1), fill: blue)) 200 | ```) 201 | 202 | 当你产生一个“混合”的内容并将其传递给`cetz.canvas`,CeTZ就会像`plain-text`一样遍历你的混合内容,并加以区分和处理。如果遇到了他自己特定的IR,例如`cetz.draw.line`,便将其以特殊的方式转换为真正的「内容」。 203 | 204 | 使用混合语言,在Typst中可以很优雅地画多面体: 205 | 206 | #code.with(al: top)(```typ 207 | #import "@preview/cetz:0.3.4" 208 | #align(center, cetz.canvas({ 209 | // 导入cetz的draw方言 210 | import cetz.draw: *; import cetz.vector: add 211 | let neg(u) = if u == 0 { 1 } else { -1 } 212 | for (p, c) in ( 213 | ((0, 0, 0), black), ((1, 1, 0), red), ((1, 0, 1), blue), ((0, 1, 1), green), 214 | ) { 215 | line(add(p, (0, 0, neg(p.at(2)))), p, stroke: c) 216 | line(add(p, (0, neg(p.at(1)), 0)), p, stroke: c) 217 | line(add(p, (neg(p.at(0)), 0, 0)), p, stroke: c) 218 | } 219 | })) 220 | ```) 221 | 222 | === curryst的「树」 223 | 224 | 我们知道「内容块」与「代码块」没有什么本质区别。 225 | 226 | 如果我们可以基于「代码块」描述一棵「内容」的树,那么逻辑推理的过程也可以被描述为条件、规则、结论的树。 227 | 228 | #link("https://typst.app/universe/package/curryst/")[curryst]包提供了接收条件、规则、结论参数的`rule`函数,其返回一个包含传入信息的`dict`,并且允许把`rule`函数返回的`dict`作为`rule`的部分参数。于是我们可以通过嵌套`rule`函数建立描述推理过程的树,并通过该包提供的`prooftree`函数把包含推理过程的`dict`树画出来: 229 | 230 | #code(```typ 231 | #import "@preview/curryst:0.5.0": rule, prooftree 232 | #let tree-dict = rule( 233 | name: $R$, 234 | $C_1 or C_2 or C_3$, 235 | rule( 236 | name: $A$, 237 | $C_1 or C_2 or L$, 238 | rule( 239 | $C_1 or L$, 240 | $Pi_1$, 241 | ), 242 | ), 243 | rule( 244 | $C_2 or overline(L)$, 245 | $Pi_2$, 246 | ), 247 | ) 248 | `tree-dict`的类型:#type(tree-dict) \ 249 | `tree-dict`代表的树:#prooftree(tree-dict) 250 | ```) 251 | 252 | == 内容类型 253 | 254 | 我们已经学过很多元素:段落、标题、代码片段等。这些元素在被创建后都会被包装成为一种被称为「内容」的值。这些值所具有的类型便被称为「内容类型」。同时「内容类型」提供了一组公共方法访问元素本身。 255 | 256 | 乍一听,内容就像是一个“容器”将元素包裹。但内容又不太像是之前所学过的数组或字典那样的复合字面量,或者说这样不方便理解。事实上,每个元素都有各自的特点,但仅仅为了保持动态性,所有的元素都被硬凑在一起,共享一种类型。有两种理解这种类型的视角:从表象论,「内容类型」是一种鸭子类型;从原理论,「内容类型」提供了操控内容的公共方法,即它是一种接口,或称特征(Trait)。 257 | 258 | === 特性一:元素包装于「内容」 259 | 260 | 我们知道所有的元素语法都可以等价使用相应的函数构造。例如标题: 261 | 262 | #code(```typ 263 | #repr([= 123]) \ // 语法构造 264 | #repr(heading(depth: 1)[123]) // 函数构造 265 | 266 | ```) 267 | 268 | 一个常见的误区是误认为元素继承自「内容类型」,进而使用以下方法判断一个内容是否为标题元素: 269 | 270 | #code(```typ 271 | 标题是heading类型(伪)?#(type([= 123]) == heading) 272 | ```) 273 | 274 | 但两者类型并不一样。事实上,元素是「函数类型」,元素函数的返回值为「内容类型」。 275 | 276 | #code(```typ 277 | 标题函数的类型:#(type(heading)) \ 278 | 标题的类型:#type([= 123]) 279 | ```) 280 | 281 | 这引出了一个重要的理念,Typst中一切皆组合。Typst中目前没有继承概念,一切功能都是组合出来的,这类似于Rust语言的概念。你可能没有学过Rust语言,但这里有一个冷知识: 282 | 283 | #align(center, [Typst $<=>$ Typ(setting Ru)st $<=>$ Typesetting Rust]) 284 | 285 | 即Typst是以Rust语言特性为基础设计出的一个排版(Typesetting)语言。 286 | 287 | 当各式各样的元素函数接受参数时,它们会构造出「元素」,然后将元素包装成一个共同的类型:「内容类型」。`heading`是函数而不是类型。与其他语言不同,没有一个`heading`类型继承`content`。因此不能使用`type([= 123]) == heading`判断一个内容是否为标题元素。 288 | 289 | === 特性二:内容类型的`func`方法 290 | 291 | 所有内容都允许使用`func`得到构造这个内容所使用的函数。因此,可以使用以下方法判断一个内容是否为标题元素: 292 | 293 | #code(```typ 294 | 标题所使用的构造函数:#([= 123]).func() 295 | 296 | 标题的构造函数是`heading`?#(([= 123]).func() == heading) 297 | ```) 298 | 299 | // 这一段不要了 300 | // === 特性二点五:内容类型的`func`方法可以直接拿来用 301 | 302 | // `func`方法返回的就是函数本身,自然也可以拿来使用: 303 | 304 | // #code(```typ 305 | // 重新构造标题:#(([= 123]).func())([456]) 306 | // ```) 307 | 308 | // 这一般没什么用,但是有的时候可以用于得到一些Typst没有暴露出来的内容函数,例如`styled`。 309 | 310 | // #code(```typ 311 | // #let type_styled = text(fill: red, "").func() 312 | // #let st = text(fill: blue, "").styles 313 | // #text([abc], st) 314 | // ```) 315 | 316 | === 特性三:内容类型的`fields`方法 317 | 318 | Typst中一切皆组合,它将所有内容打包成「内容类型」的值以完成类型上的统一,而非类型继承。 319 | 320 | 但是这也有坏处,坏处是无法“透明”访问内部内容。例如,我们可能希望知道`heading`的级别。如果不提供任何方法访问标题的级别,那么我们就无法编程完成与之相关的排版。 321 | 322 | 为了解决这个问题,Typst提供一个`fields`方法提供一个content的部分信息: 323 | 324 | #code(```typ 325 | #([= 123]).fields() 326 | ```) 327 | 328 | `fields()`将部分信息组成字典并返回。如上图所示,我们可以通过这个字典对象进一步访问标题的内容和级别。 329 | 330 | #code(```typ 331 | #([= 123]).fields().at("depth") 332 | ```) 333 | 334 | #pro-tip[ 335 | 这里的“部分信息”描述稍显模糊。具体来说,Typst只允许你直接访问元素中不受样式影响的信息,至少包含语法属性,而不允许你*直接*访问元素的样式。 336 | 337 | // 如下: 338 | 339 | // #code.with(al: top)(````typ 340 | // #let x = [= 123] 341 | // #rect([#x ]) 342 | // #x.fields() \ 343 | // #locate(loc => query(, loc)) 344 | // ````) 345 | ] 346 | 347 | === 特性四:内容类型与`fields`相关的糖 348 | 349 | 由于我们经常需要与`fields`交互,Typst提供了`has`方法帮助我们判断一个内容的`fields`是否有相关的「键」。 350 | 351 | #code(```typ 352 | 使用`... in x.fields()`判断:#("text" in `x`.fields()) \ 353 | 等同于使用`has`方法判断:#(`x`.has("text")) 354 | ```) 355 | 356 | Typst提供了`at`方法帮助我们访问一个内容的`fields`中键对应的值。 357 | 358 | #code(```typ 359 | 使用`x.fields().at()`获取值:#(`www`.fields().at("text")) \ 360 | 等同于使用`at`方法:#(`www`.at("text")) 361 | ```) 362 | 363 | 特别地,内容的成员包含`fields`的键,我们可以直接通过成员访问相关信息: 364 | 365 | #code(```typ 366 | 使用`at`方法:#(`www`.at("text")) \ 367 | 等同于访问`text`成员:#(`www`.text) 368 | ```) 369 | -------------------------------------------------------------------------------- /src/tutorial/writing-markup.typ: -------------------------------------------------------------------------------- 1 | #import "mod.typ": * 2 | 3 | #show: book.page.with(title: [标记模式]) 4 | 5 | Typst是一门简明但强大的现代排版语言,你可以使用简洁直观的语法排版出好看的文档。 6 | 7 | Typst希望你总是尽可能少的配置样式,就获得一个排版精良的文档。多数情况下,你只需要专心撰写文档,而不需要在文档内部对排版做任何更复杂的调整。 8 | 9 | 得益于此设计目标,为了使你可以用Typst编写一篇基本文档,本节仍只需涉及最基本的语法。哪怕只依靠这些语法,你已经可以编写满足很多场合需求的文档。 10 | 11 | == 段落 12 | 13 | 普通文本默认组成一个个段落。 14 | 15 | #code(```typ 16 | 我是一段文本 17 | ```) 18 | 19 | 另起一行文本不会产生新的段落。为了创建新的段落,你需要空至少一行。 20 | 21 | #code(```typ 22 | 轻轻的我走了, 23 | 正如我轻轻的来; 24 | 25 | 我轻轻的招手, 26 | 作别西天的云彩。 27 | ```) 28 | 29 | 缩进并不会产生新的空格: 30 | 31 | #code(```typ 32 | 轻轻的我走了, 33 | 正如我轻轻的来; 34 | 35 | 我轻轻的招手, 36 | 作别西天的云彩。 37 | ```) 38 | 39 | 注意:如下图的蓝框高亮所示,另起一行会引入一个小的空格。该问题会在未来修复。 40 | 41 | #code( 42 | ```typ 43 | 轻轻的我走了,#box(fill: blue, outset: (right: 0.2em), sym.space) 44 | 正如我轻轻的来; 45 | 46 | 轻轻的我走了,正如我轻轻的来; 47 | ```, 48 | code-as: ```typ 49 | 轻轻的我走了, 50 | 正如我轻轻的来; 51 | 52 | 轻轻的我走了,正如我轻轻的来; 53 | ```, 54 | ) 55 | 56 | == 标题 57 | 58 | 你可以使用一个或多个*连续*的#mark("=")开启一个标题。 59 | 60 | #code(```typ 61 | = 一级标题 62 | 我走了。 63 | == 二级标题 64 | 我来了。 65 | === 三级标题 66 | 我走了又来了。 67 | ```) 68 | 69 | 等于号的数量恰好对应了标题的级别。一级标题由一个#mark("=")开启,二级标题由两个#mark("=")开启,以此类推。 70 | 71 | 注意:正如你所见,标题会强制划分新的段落。 72 | 73 | #pro-tip[ 74 | 使用show规则可以改变“标题会强制划分新的段落”这个默认规则。 75 | 76 | #code(```typ 77 | #show heading.where(level: 3): box 78 | = 一级标题 79 | 我走了。 80 | 81 | === 三级标题 82 | 我走了又来了。 83 | ```) 84 | ] 85 | 86 | == 着重和强调语义 87 | 88 | 有许多与#mark("=")类似的语法标记。当你以相应的语法标记文本内容时,相应的文本就被赋予了特别的语义和样式。 89 | 90 | #pro-tip[ 91 | 与HTML一样,Typst总是希望语义先行。所谓语义先行,就是在编写文档时总是首先考虑标记语义。所有样式都是附加到语义上的。 92 | 93 | 例如在英文排版中,#typst-func("strong")的样式是加粗,#typst-func("emph")的样式是倾斜。你完全可以在中文排版中为它们更换样式。 94 | 95 | #if is-web-target { 96 | code(```typ 97 | #show strong: content => { 98 | show regex("\p{Hani}"): it => box(place(text("·", size: 0.8em), dx: 0.1em, dy: 0.75em) + it) 99 | content.body 100 | } 101 | *中文排版的着重语义用加点表示。* 102 | ```) 103 | } else { 104 | code(```typ 105 | #show strong: content => { 106 | show regex("\p{Hani}"): it => box(place(text("·", size: 1.3em), dx: 0.3em, dy: 0.5em) + it) 107 | content.body 108 | } 109 | *中文排版的着重语义用加点表示。* 110 | ```) 111 | } 112 | ] 113 | 114 | 与许多标记语言相同,Typst中使用一系列#term("delimiter")规则确定一段语义的开始和结束。为赋予语义,需要将一个#term("delimiter")置于文本*之前*,表示某语义的开始;同时将另一个#term("delimiter")置于文本*之后*,表示该语义的结束。 115 | 116 | 例如,#mark("*")作为定界符赋予所包裹的一段文本以#term("strong semantics")。 117 | 118 | #code(```typ 119 | 着重语义:这里有一个*重点!* 120 | ```) 121 | 122 | 与#term("strong semantics")类似,#mark("_")作为定界符将赋予#term("emphasis semantics"): 123 | 124 | #code(```typ 125 | 强调语义:_emphasis_ 126 | ```) 127 | 128 | 着重语义一般比强调语义语气更重。着重和强调语义可以相互嵌套: 129 | 130 | #code(```typ 131 | 着重且强调:*_strong emph_* 或 _*strong emph*_ 132 | ```) 133 | 134 | 注意:中文排版一般不使用斜体表示着重或强调。 135 | 136 | == (计算机)代码片段 137 | 138 | Typst的#term("raw block")标记语法与Markdown完全相同。 139 | 140 | 配对的#mark("`")包裹一段内容,表示内容为#term("raw block")。 141 | 142 | #code(````typ 143 | 短代码片段:`code` 144 | ````) 145 | 146 | 有时候你希望允许代码内容包含换行或#mark("`")。这时候,你需要使用*至少连续*三个#mark("`")组成定界符标记#term("raw block"): 147 | 148 | #code(`````typ 149 | 使用三个反引号包裹:``` ` ``` 150 | 151 | 使用四个反引号包裹:```` ``` ```` 152 | `````) 153 | 154 | 对于长代码片段,你还可以在起始定界符后*紧接着*指定该代码的语言类别,以便Typst据此完成语法高亮。 155 | 156 | #code(`````typ 157 | 一段有高亮的代码片段:```javascript function uninstallLaTeX {}``` 158 | 159 | 另一段有高亮的代码片段:````typst 包含反引号的长代码片段:``` ` ``` ```` 160 | `````) 161 | 162 | 除了定界符的长短,代码片段还有是否成块的区分。如果代码片段符合以下两点,那么它就是一个#term("blocky raw block"): 163 | + 使用*至少连续*三个#mark("`"),即其需为长代码片段。 164 | + 内容包含至少一个#term("line break")。 165 | 166 | #code(`````typ 167 | 非块代码片段:```rust trait World``` 168 | 169 | 块代码片段:```js 170 | function fibnacci(n) { 171 | return n <= 1 ?: `...`; 172 | } 173 | ``` 174 | `````) 175 | 176 | // typ 177 | // typc 178 | 179 | == 列表 180 | 181 | Typst的列表语法与Markdown非常类似,但*不完全相同*。 182 | 183 | 一行以#mark("-")开头即开启一个无编号列表项: 184 | 185 | #code(```typ 186 | - 一级列表项1 187 | ```) 188 | 189 | 与之相对,#mark("+")开启一个有编号列表项。 190 | 191 | #code(```typ 192 | + 一级列表项1 193 | ```) 194 | 195 | 利用缩进控制列表项等级: 196 | 197 | #code(```typ 198 | - 一级列表项1 199 | - 二级列表项1.1 200 | - 三级列表项1.1.1 201 | - 二级列表项1.2 202 | - 一级列表项2 203 | - 二级列表项2.1 204 | ```) 205 | 206 | 有编号列表项可以与无编号列表项相混合。 207 | 208 | #code(```typ 209 | + 一级列表项1 210 | - 二级列表项1.1 211 | + 三级列表项1.1.1 212 | - 二级列表项1.2 213 | + 一级列表项2 214 | - 二级列表项2.1 215 | ```) 216 | 217 | 和Markdown相同,Typst同样允许使用显式的编号`1.`开启列表。这方便对列表继续编号。 218 | 219 | #code(```typ 220 | 1. 列表项1 221 | 1. 列表项2 222 | ```) 223 | 224 | #code(```typ 225 | 1. 列表项1 226 | + 列表项2 227 | 228 | 列表间插入一段描述。 229 | 230 | 3. 列表项3 231 | + 列表项4 232 | + 列表项5 233 | ```) 234 | 235 | == 转义序列 236 | 237 | 你有时希望直接展示标记符号本身。例如,你可能想直接展示一个#mark("_"),而非使用强调语义。这时你需要利用#term("escape sequences")语法: 238 | 239 | #code(````typ 240 | 在段落中直接使用下划线 >\_<! 241 | ````) 242 | 243 | 遵从许多编程语言的习惯,Typst使用#mark("\\")转义特殊标记。下表给出了部分可以转义的字符: 244 | 245 | #let escaped-sequences = ( 246 | (`\\`, [\\]), 247 | (`\/`, [\/]), 248 | (`\[`, [\[]), 249 | (`\]`, [\]]), 250 | (`\{`, [\{]), 251 | (`\}`, [\}]), 252 | (`\<`, [\<]), 253 | (`\>`, [\>]), 254 | (`\(`, [\(]), 255 | (`\)`, [\)]), 256 | (`\#`, [\#]), 257 | (`\*`, [\*]), 258 | (`\_`, [\_]), 259 | (`\+`, [\+]), 260 | (`\=`, [\=]), 261 | (`\~`, [\~]), 262 | // @typstyle off 263 | (```\` ```, [\`]), 264 | (`\$`, [\$]), 265 | (`\"`, [\"]), 266 | (`\'`, [\']), 267 | (`\@`, [\@]), 268 | (`\a`, [\a]), 269 | (`\A`, [\A]), 270 | ) 271 | 272 | #let mk-tab(seq) = { 273 | set align(center) 274 | let u = it => raw(it.at(0).text, lang: "typ") 275 | let ovo = it => it.at(1) 276 | let w = 8 277 | table( 278 | columns: w + 2, 279 | [代码], 280 | ..seq.slice(w * 0, w * 1).map(u), 281 | u((`\u{cccc}`, [\u{cccc}])), 282 | [效果], 283 | ..seq.slice(w * 0, w * 1).map(ovo), 284 | [\u{cccc}], 285 | [代码], 286 | ..seq.slice(w * 1, w * 2).map(u), 287 | u((`\u{cCCc}`, [\u{cCCc}])), 288 | [效果], 289 | ..seq.slice(w * 1, w * 2).map(ovo), 290 | [\u{cCCc}], 291 | [代码], 292 | ..seq.slice(w * 2).map(u), 293 | [], 294 | u((`\u{2665}`, [\u{2665}])), 295 | [效果], 296 | ..seq.slice(w * 2).map(ovo), 297 | [], 298 | [\u{2665}], 299 | ) 300 | } 301 | 302 | #mk-tab(escaped-sequences) 303 | 304 | 以上大部分#term("escape sequences")都紧跟单个字符,除了表中的最后一列。 305 | 306 | 表中的最后一列所展示的`\u{unicode}`语法被称为Unicode转义序列,也常见于各种语言。你可以通过将`unicode`替换为#link("https://zh.wikipedia.org/zh-cn/%E7%A0%81%E4%BD%8D")[Unicode码位]的值,以输出该特定字符,而无需*输入法支持*。例如,你可以这样输出一句话: 307 | 308 | #code( 309 | ````typ 310 | \u{9999}\u{8FA3}\u{725B}\u{8089}\u{7C89}\u{597D}\u{5403}\u{2665} 311 | ````, 312 | code-as: ````typ 313 | \u{9999}\u{8FA3}\u{725B}\u{8089}\u{7C89} 314 | \u{597D}\u{5403}\u{2665} 315 | ````, 316 | ) 317 | 318 | 诸多#term("escape sequences")无需死记硬背,你只需要记住: 319 | + 如果其在Typst中已经被赋予含义,请尝试在字符前添加一个#mark("\\")。 320 | + 如果其不可见或难以使用输入法获得,请考虑使用`\u{unicode}`。 321 | 322 | == 输出换行符 323 | 324 | 输出换行符是一种特殊的#term("escape sequences"),它使得文档输出换行。 325 | 326 | #mark("\\")后紧接一个任意#term("whitespace"),表示在此处主动插入一个段落内的换行符: 327 | 328 | #code(````typ 329 | 转义空格可以换行 \ 转义回车也可以换行 \ 330 | 换行! 331 | ````) 332 | 333 | 空白字符可以取短空格(`U+0020`)、长空格(`U+3000`)、回车(`U+000D`)等。 334 | 335 | == 速记符号 336 | 337 | 在#term("markup mode")下,一些符号需要用特殊的符号组合打出,这种符号组合被称为#term("shorthand")。它们是: 338 | 339 | 空格(`U+0020`)的#term("shorthand")是#mark("~"): 340 | 341 | #code(```typ 342 | AB v.s. A~B 343 | ```) 344 | 345 | 连接号(en dash, `U+2013`)的#term("shorthand")由两个连续的#mark("hyphen")组成: 346 | 347 | #code(```typ 348 | 北京--上海路线的列车正在到站。 349 | ```) 350 | 351 | // 破折号的#term("shorthand")由三个连续的#mark("hyphen")组成(有问题,毋用,请直接使用em dash,`—`): 352 | 353 | // #code(```typ 354 | // 你的生日------四月十八日------每年我总记得。\ 355 | // 你的生日——四月十八日——每年我总记得。 356 | // ```) 357 | 358 | 省略号的#term("shorthand")由三个连续的#mark(".")组成: 359 | 360 | #code(```typ 361 | 真的假的...... 362 | ```) 363 | 364 | // - minusi 365 | // -? soft-hyphen 366 | 367 | 完整的速记符号列表参考#link("https://typst.app/docs/reference/symbols/")[Typst Symbols]。 368 | 369 | == 注释 370 | 371 | Typst的#term("comment")直接采用C语言风格的注释语法,有两种表示方法。 372 | 373 | 第一种写法是将注释内容放在两个连续的#mark("/")后面,从双斜杠到行尾都属于#term("comment")。 374 | 375 | #code(````typ 376 | // 这是一行注释 377 | 一行文本 // 这也是注释 378 | ````) 379 | 380 | 与代码片段的情形类似,Typst也提供了另外一种可以跨行的#term("comment"),形如`/*...*/`。 381 | 382 | #code(````typ 383 | 你没有看见/* 混入其中 */注释 384 | ````) 385 | 386 | 值得注意的是,Typst会将#term("comment")从源码中剔除后再解释你的文档,因此它们对文档没有影响。 387 | 388 | 以下两个段落等价: 389 | 390 | #code(````typ 391 | 注释不会 392 | // 这是一行注释 // 注释内的注释还是注释 393 | 插入换行 // 这也是注释 394 | 395 | 注释不会 396 | 插入换行 397 | ````) 398 | 399 | 以下三个段落等价: 400 | 401 | #code(````typ 402 | 注释不会/* 混入其中 */插入空格 403 | 404 | 注释不会/* 405 | 混入其中 406 | */插入空格 407 | 408 | 注释不会插入空格 409 | ````) 410 | 411 | == 总结 412 | 413 | 基于《初识标记模式》掌握的知识,你应该编写一些非常简单的文档。 414 | 415 | == 习题 416 | 417 | #let q1 = ````typ 418 | 欲穷千里目,/* 419 | */更上一层楼。 420 | ```` 421 | 422 | #exercise[ 423 | 使源码至少有两行,第一行包含“欲穷千里目,”,第二行包含“更上一层楼。”,但是输出不换行不空格:#rect(width: 100%, eval(q1.text, mode: "markup")) 424 | ][ 425 | #q1 426 | ] 427 | 428 | #exercise[ 429 | 输出一个#mark("*"),期望的输出:#rect(width: 100%)[\*] 430 | ][ 431 | ```typ 432 | \* 433 | ``` 434 | ] 435 | 436 | #let q1 = ````typ 437 | ``` 438 | ` 439 | ``` 440 | ```` 441 | 442 | #exercise[ 443 | 插入代码片段使其包含一个反引号,期望的输出:#rect(width: 100%, eval(q1.text, mode: "markup")) 444 | ][ 445 | #q1 446 | ] 447 | 448 | #let q1 = ```typ 449 | 450 | ``` 451 | 452 | #let q1 = `````typ 453 | ```` 454 | ``` 455 | ```` 456 | ````` 457 | 458 | #exercise[ 459 | 插入代码片段使其包含三个反引号,期望的输出:#rect(width: 100%, eval(q1.text, mode: "markup")) 460 | ][ 461 | #q1 462 | ] 463 | 464 | #let q1 = ````typ 465 | 你可以在Typst内通过插件 ```typc plugin("typst.wasm")``` 调用Typst编译器。 466 | ```` 467 | 468 | #exercise[ 469 | 插入行内的“typc”代码,期望的输出:#rect(width: 100%, eval(q1.text, mode: "markup")) 470 | ][ 471 | #q1 472 | ] 473 | 474 | #let q2 = ```typ 475 | 约法五章。 476 | 1. 其一。 477 | + 其二。 478 | 479 | 前两条不算。 480 | 481 | 3. 其三。 482 | + 其四。 483 | + 其五。 484 | ``` 485 | 486 | #exercise[ 487 | 在有序列表间插入描述,期望的输出:#rect(width: 100%, eval(q2.text, mode: "markup")) 488 | ][ 489 | #q2 490 | ] 491 | -------------------------------------------------------------------------------- /src/mod.typ: -------------------------------------------------------------------------------- 1 | #import "/src/book.typ" 2 | #import "/typ/templates/page.typ" 3 | #import "/typ/templates/term.typ": _term 4 | #import "/typ/templates/side-notes.typ": side-note, side-attrs 5 | #import "/typ/templates/page.typ": main-color, get-page-width, plain-text 6 | #import "/typ/templates/template-link.typ": enable-heading-hash 7 | 8 | #import "/typ/typst-meta/docs.typ": typst-v11 9 | 10 | #let refs = { 11 | let cl = book.cross-link 12 | ( 13 | writing-markup: cl.with("/basic/writing-markup.typ"), 14 | writing-scripting: cl.with("/basic/writing-scripting.typ"), 15 | scripting-base: cl.with("/basic/scripting-literal-and-variable.typ"), 16 | scripting-complex: cl.with("/basic/scripting-block-and-expression.typ"), 17 | scripting-modules: cl.with("/intermediate/scripting-modules.typ"), 18 | content-scope-style: cl.with("/intermediate/content-scope-and-style.typ"), 19 | content-stateful: cl.with("/intermediate/content-stateful.typ"), 20 | ref-typebase: cl.with("/basic/reference-typebase.typ"), 21 | ref-type-builtin: cl.with("/basic/reference-type-builtin.typ"), 22 | ref-color: cl.with("/basic/reference-color.typ"), 23 | ref-visualization: cl.with("/basic/reference-visualization.typ"), 24 | ref-bibliography: cl.with("/basic/reference-bibliography.typ"), 25 | ref-datetime: cl.with("/basic/reference-date.typ"), 26 | ref-math-mode: cl.with("/basic/reference-math-mode.typ"), 27 | ref-math-symbols: cl.with("/basic/reference-math-symbols.typ"), 28 | ref-data-process: cl.with("/basic/reference-data-process.typ"), 29 | ref-wasm-plugin: cl.with("/basic/reference-wasm-plugin.typ"), 30 | ref-grammar: cl.with("/basic/reference-grammar.typ"), 31 | ref-layout: cl.with("/intermediate/reference-layout.typ"), 32 | ref-length: cl.with("/intermediate/reference-length.typ"), 33 | misc-font-setting: cl.with("/misc/font-setting.typ"), 34 | ) 35 | } 36 | 37 | #let term-list = ( 38 | "array literal": "数组字面量", 39 | "blocky raw block": "块代码片段", 40 | "boolean literal": "布尔字面量", 41 | "code mode": "脚本模式", 42 | "codepoint": "码位", 43 | "codepoint width": "码位宽度", 44 | "character": "字符", 45 | "comment": "注释", 46 | "content": "内容", 47 | "consistency": "一致性", 48 | "content block": "内容块", 49 | "delimiter": "定界符", 50 | "dictionary literal": "字典字面量", 51 | "floating-point literal": "浮点数字面量", 52 | "function identifier": "函数名标识符", 53 | "emphasis semantics": "强调语义", 54 | "escape sequences": "转义序列", 55 | "exponential notation": "指数表示法", 56 | "expression": "表达式", 57 | "field": "域成员", 58 | "field access": "访问域成员", 59 | "function body expression": "函数体表达式", 60 | // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String 61 | "grapheme cluster": "字素簇", 62 | "grapheme cluster width": "字素簇宽度", 63 | "initialization expression": "初始值表达式", 64 | "integer literal": "整数字面量", 65 | "interpret": "解释", 66 | "interpreter": "解释器", 67 | "interpreting mode": "解释模式", 68 | "introspection function": "自省函数", 69 | "lexicographical order": "字典序", 70 | "line break": "换行符", 71 | "literal": "字面量", 72 | "markup mode": "标记模式", 73 | "math mode": "数学模式", 74 | "none literal": "空字面量", 75 | "nearest": "最近", 76 | "raw block": "代码片段", 77 | "representation": "表示法", 78 | "parameter identifier": "参数标识符", 79 | "parser": "解析器", 80 | "pattern": "模式串", 81 | "placeholder": "占位符", 82 | "shorthand": "速记符号", 83 | "scripting": "脚本", 84 | "string literal": "字符串字面量", 85 | "strong semantics": "着重语义", 86 | "syntactic predecessor": "语法前驱", 87 | "tilde diacritical marks": "波浪变音符号", 88 | "type": "类型", 89 | "utf-8 encoding": "UTF-8编码", 90 | "value": "值", 91 | "variable identifier": "变量名标识符", 92 | "whitespace": "空白字符", 93 | ) 94 | 95 | #let mark-list = ( 96 | "=": "等于号", 97 | "*": "星号", 98 | "#": "井号", 99 | "_": "下划线", 100 | "`": "反引号", 101 | "hyphen": "连字号", 102 | "-": "减号", 103 | "+": "加号", 104 | "\\": "反斜杠", 105 | "/": "斜杠", 106 | "~": "波浪号", 107 | ".": "点号", 108 | ";": "分号", 109 | ) 110 | 111 | #let exec-code(cc, res: none, scope: (:), eval: eval) = { 112 | rect( 113 | width: 100%, 114 | inset: 10pt, 115 | context { 116 | // Don't corrupt normal headings 117 | set heading(outlined: false) 118 | 119 | let prev = enable-heading-hash.get() 120 | enable-heading-hash.update(false) 121 | 122 | if res != none { 123 | res 124 | } else { 125 | eval(cc.text, mode: "markup", scope: scope) 126 | } 127 | 128 | enable-heading-hash.update(prev) 129 | }, 130 | ) 131 | } 132 | 133 | #let _code-al = if get-page-width() >= 500pt { 134 | left 135 | } else { 136 | top 137 | } 138 | 139 | // al: alignment 140 | #let code(cc, code-as: none, res: none, scope: (:), eval: eval, exec-code: exec-code, al: _code-al) = { 141 | let code-as = if code-as == none { 142 | cc 143 | } else { 144 | code-as 145 | } 146 | 147 | let vv = exec-code(cc, res: res, scope: scope, eval: eval) 148 | if al == left { 149 | layout(lw => context { 150 | let width = lw.width * 0.5 - 0.5em 151 | let u = box(width: width, code-as) 152 | let v = box(width: width, vv) 153 | 154 | let u-box = measure(u) 155 | let v-box = measure(v) 156 | 157 | let height = calc.max(u-box.height, v-box.height) 158 | stack( 159 | dir: ltr, 160 | { 161 | set rect(height: height) 162 | u 163 | }, 164 | 1em, 165 | { 166 | rect(height: height, width: width, inset: 10pt, vv.body) 167 | }, 168 | ) 169 | }) 170 | } else { 171 | code-as 172 | vv 173 | } 174 | } 175 | 176 | #let fg-blue = main-color.mix(rgb("#0074d9")) 177 | #let pro-tip(content) = context { 178 | let attr = side-attrs.get() 179 | let ext = attr.width + attr.gutter 180 | move( 181 | dx: -ext, 182 | block( 183 | width: 100% + ext, 184 | breakable: false, 185 | inset: (x: 0.65em, y: 0.65em, left: 0.65em * 0.6), 186 | radius: 4pt, 187 | fill: rgb("#0074d920"), 188 | { 189 | set text(fill: fg-blue) 190 | stack( 191 | dir: ltr, 192 | move(dy: 0.1em, image("/assets/files/info-icon.svg", width: 1em)), 193 | 0.2em, 194 | box(width: 100% - 1.2em, v(0.2em) + content), 195 | ) 196 | }, 197 | ), 198 | ) 199 | } 200 | 201 | #let fg-red = main-color.mix(red) 202 | #let todo-color = fg-red 203 | #let todo-box(content) = context { 204 | let attr = side-attrs.get() 205 | let ext = attr.width + attr.gutter 206 | move( 207 | dx: -ext, 208 | block( 209 | width: 100% + ext, 210 | breakable: false, 211 | inset: (x: 0.65em, y: 0.65em, left: 0.65em * 0.6), 212 | radius: 4pt, 213 | fill: fg-red.transparentize(80%), 214 | { 215 | set text(fill: fg-red) 216 | stack( 217 | dir: ltr, 218 | move(dy: 0.4em, text(size: 0.5em)[todo]), 219 | 0.2em, 220 | box(width: 100% - 1.2em, v(0.2em) + content), 221 | ) 222 | }, 223 | ), 224 | ) 225 | } 226 | 227 | /// This function is to render a text string in monospace style and function 228 | /// color in your defining themes. 229 | /// 230 | /// ## Examples 231 | /// 232 | /// ```typc 233 | /// typst-func("list.item") 234 | /// ``` 235 | /// 236 | /// Note: it doesn't check whether input is a valid function identifier or path. 237 | #let typst-func(it) = [ 238 | #raw(plain-text(it) + "()", lang: "typc") 239 | ] 240 | 241 | #let show-answer = false 242 | // #let show-answer = true 243 | 244 | /// Make an exercise item. 245 | /// 246 | /// - question (content): The question to ask. 247 | /// - answer (content): The answer to the question. 248 | /// -> content 249 | #let exercise(question, answer) = { 250 | enum.item( 251 | question 252 | + if show-answer { 253 | parbreak() + [答:] + answer 254 | }, 255 | ) 256 | } 257 | 258 | /// Make a term item. 259 | /// 260 | /// - term (string): The name of the term. 261 | /// - postfix (string): The postfix to conform typst bug. 262 | /// - en (bool): Whether to show the English name. 263 | /// -> content 264 | #let term(term, en: none) = _term(term-list, term, en: en) 265 | 266 | #let mark(mark) = _term( 267 | mark-list, 268 | mark, 269 | en: if mark == "hyphen" { 270 | raw("-") 271 | } else { 272 | raw(mark) 273 | }, 274 | ) 275 | 276 | #let ref-bookmark = side-note 277 | 278 | #let highlighter(it, k) = { 279 | if k == "method" { 280 | set text(fill: rgb("4b69c6")) 281 | raw(it) 282 | } else if k == "keyword" { 283 | set text(fill: rgb("8b41b1")) 284 | raw(it) 285 | } else if k == "var" { 286 | set text(fill: blue.mix(main-color)) 287 | raw(it) 288 | } else { 289 | raw(it) 290 | } 291 | } 292 | 293 | #let darkify(clr) = clr.mix(main-color.negate()).saturate(30%) 294 | 295 | #let ref-ty-locs = ( 296 | "int": refs.ref-typebase, 297 | "bool": refs.ref-typebase, 298 | "float": refs.ref-typebase, 299 | "str": refs.ref-typebase, 300 | "array": refs.ref-typebase, 301 | "dict": refs.ref-typebase, 302 | "none": refs.ref-typebase, 303 | "ratio": refs.ref-length, 304 | "alignment": refs.ref-layout, 305 | "version": refs.ref-type-builtin, 306 | "any": refs.ref-type-builtin, 307 | "bytes": refs.ref-type-builtin, 308 | "label": refs.ref-type-builtin, 309 | "type": refs.ref-type-builtin, 310 | "regex": refs.ref-type-builtin, 311 | ) 312 | 313 | #let show-type(ty) = { 314 | h(3pt) 315 | ref-ty-locs.at(ty)( 316 | reference: label("reference-type-" + ty), 317 | box(fill: darkify(rgb("eff0f3")), outset: 2pt, radius: 2pt, raw(ty)), 318 | ) 319 | h(3pt) 320 | } 321 | 322 | #let ref-signature(name, kind: "scope") = { 323 | let fn = if kind == "scope" { 324 | typst-v11.scoped-items.at(name) 325 | } else if kind == "cons" { 326 | let ty = typst-v11.types.at(name) 327 | ty.body.content.constructor 328 | } else { 329 | typst-v11.funcs.at(name) 330 | } 331 | 332 | context { 333 | let attr = side-attrs.get() 334 | let ext = attr.width + attr.gutter 335 | 336 | move( 337 | dx: -ext, 338 | block( 339 | fill: rgb("#add5a220"), 340 | radius: 2pt, 341 | width: 100% + ext, 342 | inset: (x: 1pt, y: 5pt), 343 | { 344 | set par(justify: false) 345 | set text(fill: main-color.mix(rgb("eff0f3").negate())) 346 | highlighter("fn", "keyword") 347 | raw(" ") 348 | highlighter(name, "method") 349 | raw("(") 350 | fn 351 | .params 352 | .map(param => { 353 | highlighter(param.name, "var") 354 | ": " 355 | param.types.map(show-type).join() 356 | }) 357 | .join(raw(", ")) 358 | raw(")") 359 | if fn.returns.len() > 0 { 360 | raw(" ") 361 | box(raw("->")) 362 | raw(" ") 363 | fn.returns.map(show-type).join() 364 | } 365 | }, 366 | ), 367 | ) 368 | } 369 | } 370 | #let ref-func-signature = ref-signature.with(kind: "func") 371 | #let ref-cons-signature = ref-signature.with(kind: "cons") 372 | #let ref-method-signature = ref-signature 373 | 374 | #let f() = { } 375 | -------------------------------------------------------------------------------- /src/tutorial/figure-time-travel.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/cetz:0.3.4" 2 | #import "/typ/templates/page.typ": main-color, is-light-theme 3 | 4 | #let std-scale = scale 5 | 6 | #let figure-time-travel( 7 | stroke-color: main-color, 8 | light-theme: is-light-theme, 9 | ) = { 10 | import cetz.draw: * 11 | 12 | let stroke-factor = if light-theme { 13 | 1 14 | } else { 15 | 0.8 16 | } 17 | 18 | let line-width = 0.5pt * stroke-factor 19 | let rect = rect.with(stroke: line-width + stroke-color) 20 | let circle = circle.with(stroke: line-width + stroke-color) 21 | let line = line.with(stroke: line-width + stroke-color) 22 | let light-line = line.with(stroke: (0.3pt * stroke-factor) + stroke-color) 23 | let exlight-line = line.with(stroke: (0.2pt * stroke-factor) + stroke-color) 24 | 25 | let preview-img-fill = if light-theme { 26 | green.lighten(80%) 27 | } else { 28 | green.darken(20%) 29 | } 30 | 31 | let preview-img-fill2 = if light-theme { 32 | green.lighten(80%) 33 | } else { 34 | green.darken(20%) 35 | } 36 | 37 | let state-background-fill = rgb("#2983bb80") 38 | let preview-background-fill = rgb("#baccd980") 39 | 40 | let preview-content0 = { 41 | translate((0.05, 1 - 0.2)) 42 | // title 43 | light-line((0.15, 0), (0.55, 0)) 44 | translate((0, -0.08)) 45 | exlight-line((0.05, 0), (0.7, 0)) 46 | translate((0, -0.03)) 47 | exlight-line((0.00, 0), (0.7, 0)) 48 | translate((0, -0.03)) 49 | exlight-line((0.00, 0), (0.7, 0)) 50 | translate((0, -0.03)) 51 | exlight-line((0.00, 0), (0.4, 0)) 52 | } 53 | 54 | let preview-content1 = { 55 | translate((0, -0.06)) 56 | rect((0.05, 0), (0.65, -0.24), stroke: 0pt, fill: preview-img-fill, name: "picture") 57 | content("picture", std-scale(20%)[$lambda x = integral.double x dif x$]) 58 | translate((0, -0.24)) 59 | } 60 | let doc-state(prev-name, name, checkpoint: 0) = { 61 | let arrow-line(pos) = { 62 | line((rel: (-0.01, 0.025), to: pos), pos) 63 | line((rel: (0.01, 0.025), to: pos), pos) 64 | } 65 | 66 | rect((0, 0), (0.2, 1), stroke: 0pt, fill: state-background-fill) 67 | 68 | if checkpoint == 0 { 69 | line(prev-name + "-S2", name + "-S1", stroke: line-width + red) 70 | } 71 | if checkpoint == 1 { 72 | line(prev-name + "-S3", name + "-S2", stroke: line-width + red) 73 | } 74 | if checkpoint == 2 { 75 | line(prev-name + "-S4", name + "-S3", stroke: line-width + red) 76 | } 77 | 78 | circle(name + "-S1", fill: blue, stroke: 0pt, radius: 0.01) 79 | content((rel: (-0.04, 0), to: name + "-S1"), std-scale(40%, [S1])) 80 | if checkpoint <= 0 { 81 | return 82 | } 83 | circle(name + "-S2", fill: blue, stroke: 0pt, radius: 0.01) 84 | content((rel: (-0.04, -0.03), to: name + "-S2"), std-scale(40%, [S2])) 85 | line(name + "-S1", name + "-S2") 86 | arrow-line(name + "-S2") 87 | if checkpoint <= 1 { 88 | return 89 | } 90 | circle(name + "-S3", fill: blue, stroke: 0pt, radius: 0.01) 91 | content((rel: (-0.04, -0.02), to: name + "-S3"), std-scale(40%, [S3])) 92 | line(name + "-S2", name + "-S3") 93 | arrow-line(name + "-S3") 94 | 95 | if checkpoint == 2 { 96 | circle(name + "-R1", fill: blue, stroke: 0pt, radius: 0.01) 97 | arc-through(name + "-R1", (rel: (0.02, -0.03), to: name + "-R1"), name + "-S3", stroke: line-width + blue) 98 | line((rel: (50deg, 0.02), to: name + "-S3"), name + "-S3", stroke: line-width + blue) 99 | line((rel: (10deg, 0.02), to: name + "-S3"), name + "-S3", stroke: line-width + blue) 100 | } 101 | 102 | if checkpoint <= 2 { 103 | return 104 | } 105 | 106 | 107 | circle(name + "-S4", fill: blue, stroke: 0pt, radius: 0.01) 108 | content((rel: (-0.04, -0.01), to: name + "-S4"), std-scale(40%, [S4])) 109 | line(name + "-S3", name + "-S4") 110 | if checkpoint == 3 { 111 | circle(name + "-R2", fill: blue, stroke: 0pt, radius: 0.01) 112 | } 113 | arrow-line(name + "-S4") 114 | 115 | 116 | if checkpoint == 3 { 117 | arc-through(name + "-R2", (rel: (0.05, 0.33, 0), to: name + "-R2"), name + "-S2", stroke: line-width + blue) 118 | line((rel: (-110deg, 0.02), to: name + "-S2"), name + "-S2", stroke: line-width + blue) 119 | line((rel: (-20deg, 0.02), to: name + "-S2"), name + "-S2", stroke: line-width + blue) 120 | } 121 | } 122 | let doc(name, checkpoint: 0) = { 123 | group( 124 | name: name, 125 | { 126 | // line((0, 0), (0.707, 0)) 127 | // line((0.707, 0), (0.707, 1)) 128 | // line((0.707, 1), (0, 1)) 129 | // line((0, 0), (0, 1)) 130 | rect((0, 0), (0.707, 1), stroke: 0pt, fill: preview-background-fill) 131 | 132 | let margin = 0.05 133 | 134 | translate((margin, 1 - 0.1)) 135 | scale(x: (0.707 - margin * 2)) 136 | let lm = (-margin) / (0.707 - margin * 2) 137 | 138 | anchor("title", (0.5, 0)) 139 | content("title", std-scale(30%)[关于吃睡玩自动机的形式化研究]) 140 | // content("title", std-scale(30%)[#align(center, [A formal study on eat-sleep-\ play automating cats])]) 141 | 142 | translate((0, -0.07)) 143 | light-line((0.1, 0), (0.95, 0)) 144 | translate((0, -0.025)) 145 | light-line((0.05, 0), (0.95, 0)) 146 | 147 | translate((0, -0.025)) 148 | if checkpoint <= 0 { 149 | light-line((0.05, 0), (0.45, 0)) 150 | anchor("P", (0.45, 0)) 151 | anchor("Q", (lm, 0)) 152 | return 153 | } 154 | 155 | light-line((0.05, 0), (0.95, 0)) 156 | translate((0, -0.025)) 157 | light-line((0.05, 0), (0.75, 0)) 158 | 159 | translate((0, -0.03)) 160 | anchor("P", (0.59, -0.072)) 161 | anchor("Q", (lm, -0.072)) 162 | rect((0.1, 0), (0.9, -0.10), stroke: 0pt, fill: preview-img-fill, name: "picture") 163 | content("picture", std-scale(30%)[$lambda x = integral.double x dif x$]) 164 | if checkpoint <= 1 { 165 | return 166 | } 167 | 168 | translate((0, -0.03 - 0.10)) 169 | 170 | light-line((0.1, 0), (0.95, 0)) 171 | translate((0, -0.025)) 172 | light-line((0.05, 0), (0.95, 0)) 173 | translate((0, -0.025)) 174 | light-line((0.05, 0), (0.85, 0)) 175 | 176 | translate((0, -0.03)) 177 | 178 | light-line((0.1, 0), (0.95, 0)) 179 | translate((0, -0.025)) 180 | light-line((0.05, 0), (0.95, 0)) 181 | translate((0, -0.025)) 182 | if checkpoint == 2 { 183 | anchor("R", (0.75, 0)) 184 | anchor("R2", (lm, 0)) 185 | } 186 | light-line((0.05, 0), (0.95, 0)) 187 | translate((0, -0.025)) 188 | light-line((0.05, 0), (0.95, 0)) 189 | translate((0, -0.025)) 190 | light-line((0.05, 0), (0.95, 0)) 191 | anchor("P", (0.85, 0)) 192 | anchor("Q", (lm, 0)) 193 | translate((0, -0.025)) 194 | light-line((0.05, 0), (0.35, 0)) 195 | 196 | if checkpoint <= 2 { 197 | return 198 | } 199 | 200 | translate((0, -0.03)) 201 | 202 | light-line((0.1, 0), (0.95, 0)) 203 | translate((0, -0.025)) 204 | light-line((0.05, 0), (0.95, 0)) 205 | translate((0, -0.025)) 206 | light-line((0.05, 0), (0.95, 0)) 207 | translate((0, -0.025)) 208 | light-line((0.05, 0), (0.95, 0)) 209 | translate((0, -0.025)) 210 | light-line((0.05, 0), (0.95, 0)) 211 | translate((0, -0.025)) 212 | light-line((0.05, 0), (0.35, 0)) 213 | 214 | translate((0, -0.03)) 215 | 216 | light-line((0.1, 0), (0.95, 0)) 217 | translate((0, -0.025)) 218 | light-line((0.05, 0), (0.95, 0)) 219 | translate((0, -0.025)) 220 | light-line((0.05, 0), (0.95, 0)) 221 | translate((0, -0.025)) 222 | light-line((0.05, 0), (0.95, 0)) 223 | if checkpoint == 3 { 224 | anchor("R", (0.75, 0)) 225 | anchor("R2", (lm, 0)) 226 | } 227 | translate((0, -0.025)) 228 | light-line((0.05, 0), (0.95, 0)) 229 | anchor("P", (0.6, 0)) 230 | anchor("Q", (lm, 0)) 231 | translate((0, -0.025)) 232 | light-line((0.05, 0), (0.35, 0)) 233 | }, 234 | ) 235 | } 236 | 237 | cetz.canvas({ 238 | // 导入cetz的draw方言 239 | import cetz.draw: * 240 | set-viewport((0, 0, 0), (4, 4, -4), bounds: (1, 1, 1)) 241 | 242 | group( 243 | name: "doc", 244 | { 245 | let x = 0.25 246 | let z = 0.22 247 | translate((x, 0, -z)) 248 | translate((x, 0, -z)) 249 | translate((x, 0, -z)) 250 | doc("D1", checkpoint: 3) 251 | circle("D1.P", fill: red, stroke: 0pt, radius: 0.01) 252 | translate((-x, 0, z)) 253 | doc("D2", checkpoint: 2) 254 | circle("D2.P", fill: red, stroke: 0pt, radius: 0.01) 255 | translate((-x, 0, z)) 256 | doc("D3", checkpoint: 1) 257 | circle("D3.P", fill: red, stroke: 0pt, radius: 0.01) 258 | translate((-x, 0, z)) 259 | doc("D4", checkpoint: 0) 260 | circle("D4.P", fill: red, stroke: 0pt, radius: 0.01) 261 | 262 | circle("D2.R", fill: blue, stroke: 0pt, radius: 0.01) 263 | circle("D1.R", fill: blue, stroke: 0pt, radius: 0.01) 264 | 265 | let blue-light-line = line.with(stroke: (0.3pt * stroke-factor) + blue) 266 | let red-light-line = line.with(stroke: (0.3pt * stroke-factor) + red) 267 | red-light-line("D4.P", "D3.P") 268 | red-light-line("D3.P", "D2.P") 269 | red-light-line("D2.P", "D1.P") 270 | 271 | blue-light-line("D2.R", "D2.P") 272 | blue-light-line("D1.R", "D3.P") 273 | 274 | let round-z-axis = ((x, y, z)) => (x, y, calc.round(z, digits: 10)) 275 | anchor("S1", (round-z-axis, (rel: (-x * 0, 0, z * 0), to: "D4.Q"))) 276 | anchor("S2", (round-z-axis, (rel: (-x * 1, 0, z * 1), to: "D3.Q"))) 277 | anchor("S3", (round-z-axis, (rel: (-x * 2, 0, z * 2), to: "D2.Q"))) 278 | anchor("S4", (round-z-axis, (rel: (-x * 3, 0, z * 3), to: "D1.Q"))) 279 | anchor("R1", (round-z-axis, (rel: (-x * 2, 0, z * 2), to: "D2.R2"))) 280 | anchor("R2", (round-z-axis, (rel: (-x * 3, 0, z * 3), to: "D1.R2"))) 281 | }, 282 | ) 283 | 284 | let anchor-s(prefix, p) = { 285 | anchor(prefix + "-S1", (rel: p, to: "doc.S1")) 286 | anchor(prefix + "-S2", (rel: p, to: "doc.S2")) 287 | anchor(prefix + "-S3", (rel: p, to: "doc.S3")) 288 | anchor(prefix + "-S4", (rel: p, to: "doc.S4")) 289 | anchor(prefix + "-R1", (rel: p, to: "doc.R1")) 290 | anchor(prefix + "-R2", (rel: p, to: "doc.R2")) 291 | } 292 | 293 | translate((-1, 0, 0)) 294 | 295 | group({ 296 | let x = 0.12 297 | let z = 0.22 298 | translate((x, 0, -z)) 299 | translate((x, 0, -z)) 300 | translate((x, 0, -z)) 301 | anchor-s("T1", (-1 + 0.2 / 2 + x * 3, 0, -z * 3)) 302 | doc-state("T0", "T1", checkpoint: 3) 303 | translate((-x, 0, z)) 304 | anchor-s("T2", (-1 + 0.2 / 2 + x * 2, 0, -z * 2)) 305 | doc-state("T1", "T2", checkpoint: 2) 306 | translate((-x, 0, z)) 307 | anchor-s("T3", (-1 + 0.2 / 2 + x * 1, 0, -z * 1)) 308 | doc-state("T2", "T3", checkpoint: 1) 309 | translate((-x, 0, z)) 310 | anchor-s("T4", (-1 + 0.2 / 2 + x * 0, 0, -z * 0)) 311 | doc-state("T3", "T4", checkpoint: 0) 312 | }) 313 | 314 | translate((-0.7, 0, 0)) 315 | line((0, 0), (0, 1), stroke: 0pt, fill: none) 316 | }) 317 | } 318 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------