├── LICENSE ├── README.md ├── assets ├── homework.png └── template.png ├── docs ├── .gitignore ├── font-setting.pdf ├── font-setting.typ ├── homework-template.pdf ├── homework-template.typ ├── question-frame.pdf ├── question-frame.typ ├── template.pdf └── template.typ ├── src ├── .gitignore ├── bundles │ └── homework.typ ├── lib.typ ├── styles │ ├── basic.typ │ └── homework.typ └── utils │ ├── font.typ │ └── question.typ └── typst.toml /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Fr4nk1inCs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Typreset (W.I.P.) 2 | 3 | A collection of [Typst](https://typst.app) presets to provide a starting point for your writing. 4 | 5 | Available presets: 6 | 7 | - `set-font()`: Chinese & English Font support. 8 | - `typesetting`: A typesetting preset. 9 | - `template`: A template for writing a document. 10 | - `homework`: Homework template and `simple-question` and `complex-question` frame to write your homework. 11 | 12 | There are some manuals/docs in the `docs` folder. 13 | 14 | ## Usage 15 | 16 | Typrest supports Typst 0.12.0 and newer. 17 | 18 | At this time there is no release of this project. You need to manually configure your local Typst packages. 19 | 20 | - Clone this project to `{data-dir}/typst/packages/local/typreset/0.1.0`, where `data-dir` [depends on your systems](https://github.com/typst/packages#local-packages). For example, in Linux, it is `~/.local/share/typst/packages/local/typreset/0.1.0`: 21 | 22 | ```bash 23 | mkdir -p ~/.local/share/typst/packages/local/typreset/ 24 | git clone https://github.com/Fr4nk1inCs/typreset.git ~/.local/share/typst/packages/local/typreset/0.2.0 25 | ``` 26 | 27 | Then you can use the presets in your Typst project: 28 | 29 | ```typ 30 | #import "@local/typreset:0.2.0": * 31 | ``` 32 | 33 | ## Examples 34 | 35 | ### Basic Template 36 | 37 | ```typ 38 | #import "@local/typreset:0.2.0": template 39 | 40 | #set page(height: auto) 41 | #show: template.with( 42 | title: ("Example of typreset.template", [Example of `typreset.template`]), 43 | author-infos: "Author" 44 | ) 45 | 46 | #lorem(100) 47 | ``` 48 | 49 | ![template.png](./assets/template.png) 50 | 51 | For more examples, please see [`docs/template.pdf`](./docs/template.pdf). 52 | 53 | ### Homework 54 | 55 | ```typ 56 | #import "@local/typreset:0.2.0": homework 57 | 58 | #set page(height: auto) 59 | #show: homework.template.with( 60 | course: "Course Name", 61 | number: 1, 62 | student-infos: ((name: "Student", id: "ID"),), 63 | ) 64 | 65 | #homework.simple-question[ 66 | A `simple-question` frame. 67 | ] 68 | 69 | #homework.complex-question[ 70 | A `complex-question` frame. 71 | ] 72 | ``` 73 | 74 | ![homework.png](./assets/homework.png) 75 | 76 | For more examples, please see [`docs/homework-template.pdf`](./docs/homework-template.pdf) and [`docs/question-frame.pdf`](./docs/question-frame.pdf). 77 | -------------------------------------------------------------------------------- /assets/homework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fr4nk1inCs/typreset/7e0874dacf455b9a5798f37430cfa6d444dd0400/assets/homework.png -------------------------------------------------------------------------------- /assets/template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fr4nk1inCs/typreset/7e0874dacf455b9a5798f37430cfa6d444dd0400/assets/template.png -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | !*.pdf 2 | -------------------------------------------------------------------------------- /docs/font-setting.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fr4nk1inCs/typreset/7e0874dacf455b9a5798f37430cfa6d444dd0400/docs/font-setting.pdf -------------------------------------------------------------------------------- /docs/font-setting.typ: -------------------------------------------------------------------------------- 1 | #import "../src/lib.typ": set-font, template 2 | 3 | #show: template.with( 4 | title: "Font Setting", 5 | author-infos: ("Fr4nk1in",), 6 | ) 7 | #show raw.where(block: true): block.with( 8 | width: 100%, 9 | fill: luma(93%), 10 | inset: 5pt, 11 | spacing: 0.7em, 12 | breakable: false, 13 | radius: 0.35em, 14 | ) 15 | #set raw(lang: "typc") 16 | 17 | Usage: 18 | 19 | ```typ 20 | #import "@local/typreset:0.2.0": set-font 21 | 22 | #show: set-font.with(lang: "en") 23 | ``` 24 | 25 | All the templates in `Typreset` uses the following language-specific fonts, where `base` is for English and `zh` is for Chinese. 26 | 27 | ``` 28 | #let fonts = ( 29 | base: ( 30 | math: ( 31 | "Libertinus Math", 32 | ), 33 | serif: ( 34 | "Libertinus Serif", 35 | ), 36 | sans-serif: ( 37 | "Libertinus Sans", 38 | ), 39 | italic: ( 40 | "Libertinus Serif", 41 | ), 42 | ), 43 | zh: ( 44 | serif: ( 45 | "SimSun", // Windows 46 | "Songti SC", // MacOS 47 | "FandolSong", // Linux 48 | // fallback 49 | "Source Han Serif SC", 50 | "Noto Serif CJK SC", 51 | ), 52 | sans-serif: ( 53 | "SimHei", // Windows 54 | "Heiti SC", // MacOS 55 | "FandolHei", // Linux 56 | // fallback 57 | "Source Han Sans SC", 58 | "Noto Sans CJK SC", 59 | ), 60 | italic: ( 61 | "SimKai SC", // Windows 62 | "Kaiti SC", // MacOS 63 | "FandolKai", // Linux 64 | // fallback 65 | "Source Han Serif SC", 66 | "Noto Serif CJK SC", 67 | ), 68 | ), 69 | ) 70 | ``` 71 | 72 | - Due to the complexity of the font setting for Chinese, we adopt the same pattern that CTeX uses, which is to use different fonts on different systems. This is achieved by setting all the fonts together. 73 | 74 | = Chinese Example 75 | 76 | This document is built under MacOS, so the font used is `Songti SC` and `Kaiti SC`. 77 | 78 | Basic style (serif): 79 | #block( 80 | breakable: true, 81 | stroke: 1pt, 82 | inset: 5pt, 83 | set-font(lang: "zh")[ 84 | 豫章故郡,洪都新府。星分翼轸,地接衡庐。襟三江而带五湖,控蛮荆而引瓯越。物华天宝,龙光射牛斗之墟;人杰地灵,徐孺下陈蕃之榻。雄州雾列,俊彩星驰。台隍枕夷夏之交,宾主尽东南之美。都督阎公之雅望,棨戟遥临;宇文新州之懿范,襜帷暂驻。十旬休暇,胜友如云。千里逢迎,高朋满座。腾蛟起凤,孟学士之词宗;紫电青霜,王将军之武库。家君作宰,路出名区。童子何知?躬逢胜饯。 85 | 86 | 87 | 时维九月,序属三秋。潦水尽而寒潭清,烟光凝而暮山紫。俨骖𬴂于上路,访风景于崇阿。临帝子之长洲,得仙人之旧馆。层台耸翠,上出重霄;飞阁流丹,下临无地。鹤汀凫渚,穷岛屿之萦回;桂殿兰宫,即冈峦之体势。 88 | 89 | 披绣闼,俯雕甍。山原旷其盈视,川泽纡其骇瞩。闾阎扑地,钟鸣鼎食之家;舸舰弥津,青雀黄龙之舳。云销雨霁,彩彻区明。落霞与孤鹜齐飞,秋水共长天一色。渔舟唱晚,响穷彭蠡之滨;雁阵惊寒,声断衡阳之浦。 90 | 91 | 遥襟甫畅,逸兴遄飞。爽籁发而清风生,纤歌凝而白云遏。睢园绿竹,气凌彭泽之樽;邺水朱华,光照临川之笔。四美具,二难并。穷睇眄于中天,极娱游于暇日。天高地迥,觉宇宙之无穷;兴尽悲来,识盈虚之有数。望长安于日下,目吴会于云间。地势极而南溟深,天柱高而北辰远。关山难越,谁悲失路之人;萍水相逢,尽是他乡之客。怀帝阍而不见,奉宣室以何年? 92 | 93 | 嗟乎!时运不齐,命途多舛。冯唐易老,李广难封。屈贾谊于长沙,非无圣主;窜梁鸿于海曲,岂乏明时?所赖君子安贫,达人知命。老当益壮,宁移白首之心;穷且益坚,不坠青云之志。酌贪泉而觉爽,处涸辙以犹懽。北海虽赊,扶摇可接;东隅已逝,桑榆非晚。孟尝高洁,空馀报国之心;阮籍猖狂,岂效穷途之哭? 94 | 95 | 勃,三尺微命,一介书生,无路请缨,等终军之弱冠;有怀投笔,慕宗悫之长风。舍簪笏于百龄,奉晨昏于万里。非谢家之宝树,接孟氏之芳邻。他日趋庭,叨陪鲤对;今兹捧袂,喜托龙门。杨意不逢,抚凌云而自惜;锺期既遇,奏流水以何惭? 96 | 97 | 呜呼!胜地不常,盛筵难再。兰亭已矣,梓泽丘墟。临别赠言,幸承恩于伟饯;登高作赋,是所望于群公。敢竭鄙诚,恭疏短引。一言均赋,四韵俱成。请洒潘江,各倾陆海云尔。 98 | ], 99 | ) 100 | 101 | Italic style: 102 | #block( 103 | breakable: true, 104 | stroke: 1pt, 105 | inset: 5pt, 106 | set-font(lang: "zh")[ 107 | #show: emph 108 | 109 | 豫章故郡,洪都新府。星分翼轸,地接衡庐。襟三江而带五湖,控蛮荆而引瓯越。物华天宝,龙光射牛斗之墟;人杰地灵,徐孺下陈蕃之榻。雄州雾列,俊彩星驰。台隍枕夷夏之交,宾主尽东南之美。都督阎公之雅望,棨戟遥临;宇文新州之懿范,襜帷暂驻。十旬休暇,胜友如云。千里逢迎,高朋满座。腾蛟起凤,孟学士之词宗;紫电青霜,王将军之武库。家君作宰,路出名区。童子何知?躬逢胜饯。 110 | 111 | 112 | 时维九月,序属三秋。潦水尽而寒潭清,烟光凝而暮山紫。俨骖𬴂于上路,访风景于崇阿。临帝子之长洲,得仙人之旧馆。层台耸翠,上出重霄;飞阁流丹,下临无地。鹤汀凫渚,穷岛屿之萦回;桂殿兰宫,即冈峦之体势。 113 | 114 | 披绣闼,俯雕甍。山原旷其盈视,川泽纡其骇瞩。闾阎扑地,钟鸣鼎食之家;舸舰弥津,青雀黄龙之舳。云销雨霁,彩彻区明。落霞与孤鹜齐飞,秋水共长天一色。渔舟唱晚,响穷彭蠡之滨;雁阵惊寒,声断衡阳之浦。 115 | 116 | 遥襟甫畅,逸兴遄飞。爽籁发而清风生,纤歌凝而白云遏。睢园绿竹,气凌彭泽之樽;邺水朱华,光照临川之笔。四美具,二难并。穷睇眄于中天,极娱游于暇日。天高地迥,觉宇宙之无穷;兴尽悲来,识盈虚之有数。望长安于日下,目吴会于云间。地势极而南溟深,天柱高而北辰远。关山难越,谁悲失路之人;萍水相逢,尽是他乡之客。怀帝阍而不见,奉宣室以何年? 117 | 118 | 嗟乎!时运不齐,命途多舛。冯唐易老,李广难封。屈贾谊于长沙,非无圣主;窜梁鸿于海曲,岂乏明时?所赖君子安贫,达人知命。老当益壮,宁移白首之心;穷且益坚,不坠青云之志。酌贪泉而觉爽,处涸辙以犹懽。北海虽赊,扶摇可接;东隅已逝,桑榆非晚。孟尝高洁,空馀报国之心;阮籍猖狂,岂效穷途之哭? 119 | 120 | 勃,三尺微命,一介书生,无路请缨,等终军之弱冠;有怀投笔,慕宗悫之长风。舍簪笏于百龄,奉晨昏于万里。非谢家之宝树,接孟氏之芳邻。他日趋庭,叨陪鲤对;今兹捧袂,喜托龙门。杨意不逢,抚凌云而自惜;锺期既遇,奏流水以何惭? 121 | 122 | 呜呼!胜地不常,盛筵难再。兰亭已矣,梓泽丘墟。临别赠言,幸承恩于伟饯;登高作赋,是所望于群公。敢竭鄙诚,恭疏短引。一言均赋,四韵俱成。请洒潘江,各倾陆海云尔。 123 | ], 124 | ) 125 | 126 | Bold style: 127 | #block( 128 | breakable: true, 129 | stroke: 1pt, 130 | inset: 5pt, 131 | set-font(lang: "zh")[ 132 | #show: strong 133 | 134 | 豫章故郡,洪都新府。星分翼轸,地接衡庐。襟三江而带五湖,控蛮荆而引瓯越。物华天宝,龙光射牛斗之墟;人杰地灵,徐孺下陈蕃之榻。雄州雾列,俊彩星驰。台隍枕夷夏之交,宾主尽东南之美。都督阎公之雅望,棨戟遥临;宇文新州之懿范,襜帷暂驻。十旬休暇,胜友如云。千里逢迎,高朋满座。腾蛟起凤,孟学士之词宗;紫电青霜,王将军之武库。家君作宰,路出名区。童子何知?躬逢胜饯。 135 | 136 | 137 | 时维九月,序属三秋。潦水尽而寒潭清,烟光凝而暮山紫。俨骖𬴂于上路,访风景于崇阿。临帝子之长洲,得仙人之旧馆。层台耸翠,上出重霄;飞阁流丹,下临无地。鹤汀凫渚,穷岛屿之萦回;桂殿兰宫,即冈峦之体势。 138 | 139 | 披绣闼,俯雕甍。山原旷其盈视,川泽纡其骇瞩。闾阎扑地,钟鸣鼎食之家;舸舰弥津,青雀黄龙之舳。云销雨霁,彩彻区明。落霞与孤鹜齐飞,秋水共长天一色。渔舟唱晚,响穷彭蠡之滨;雁阵惊寒,声断衡阳之浦。 140 | 141 | 遥襟甫畅,逸兴遄飞。爽籁发而清风生,纤歌凝而白云遏。睢园绿竹,气凌彭泽之樽;邺水朱华,光照临川之笔。四美具,二难并。穷睇眄于中天,极娱游于暇日。天高地迥,觉宇宙之无穷;兴尽悲来,识盈虚之有数。望长安于日下,目吴会于云间。地势极而南溟深,天柱高而北辰远。关山难越,谁悲失路之人;萍水相逢,尽是他乡之客。怀帝阍而不见,奉宣室以何年? 142 | 143 | 嗟乎!时运不齐,命途多舛。冯唐易老,李广难封。屈贾谊于长沙,非无圣主;窜梁鸿于海曲,岂乏明时?所赖君子安贫,达人知命。老当益壮,宁移白首之心;穷且益坚,不坠青云之志。酌贪泉而觉爽,处涸辙以犹懽。北海虽赊,扶摇可接;东隅已逝,桑榆非晚。孟尝高洁,空馀报国之心;阮籍猖狂,岂效穷途之哭? 144 | 145 | 勃,三尺微命,一介书生,无路请缨,等终军之弱冠;有怀投笔,慕宗悫之长风。舍簪笏于百龄,奉晨昏于万里。非谢家之宝树,接孟氏之芳邻。他日趋庭,叨陪鲤对;今兹捧袂,喜托龙门。杨意不逢,抚凌云而自惜;锺期既遇,奏流水以何惭? 146 | 147 | 呜呼!胜地不常,盛筵难再。兰亭已矣,梓泽丘墟。临别赠言,幸承恩于伟饯;登高作赋,是所望于群公。敢竭鄙诚,恭疏短引。一言均赋,四韵俱成。请洒潘江,各倾陆海云尔。 148 | ], 149 | ) 150 | 151 | Bold italic style: 152 | #block( 153 | breakable: true, 154 | stroke: 1pt, 155 | inset: 5pt, 156 | set-font(lang: "zh")[ 157 | #show: strong 158 | #show: emph 159 | 160 | 豫章故郡,洪都新府。星分翼轸,地接衡庐。襟三江而带五湖,控蛮荆而引瓯越。物华天宝,龙光射牛斗之墟;人杰地灵,徐孺下陈蕃之榻。雄州雾列,俊彩星驰。台隍枕夷夏之交,宾主尽东南之美。都督阎公之雅望,棨戟遥临;宇文新州之懿范,襜帷暂驻。十旬休暇,胜友如云。千里逢迎,高朋满座。腾蛟起凤,孟学士之词宗;紫电青霜,王将军之武库。家君作宰,路出名区。童子何知?躬逢胜饯。 161 | 162 | 163 | 时维九月,序属三秋。潦水尽而寒潭清,烟光凝而暮山紫。俨骖𬴂于上路,访风景于崇阿。临帝子之长洲,得仙人之旧馆。层台耸翠,上出重霄;飞阁流丹,下临无地。鹤汀凫渚,穷岛屿之萦回;桂殿兰宫,即冈峦之体势。 164 | 165 | 披绣闼,俯雕甍。山原旷其盈视,川泽纡其骇瞩。闾阎扑地,钟鸣鼎食之家;舸舰弥津,青雀黄龙之舳。云销雨霁,彩彻区明。落霞与孤鹜齐飞,秋水共长天一色。渔舟唱晚,响穷彭蠡之滨;雁阵惊寒,声断衡阳之浦。 166 | 167 | 遥襟甫畅,逸兴遄飞。爽籁发而清风生,纤歌凝而白云遏。睢园绿竹,气凌彭泽之樽;邺水朱华,光照临川之笔。四美具,二难并。穷睇眄于中天,极娱游于暇日。天高地迥,觉宇宙之无穷;兴尽悲来,识盈虚之有数。望长安于日下,目吴会于云间。地势极而南溟深,天柱高而北辰远。关山难越,谁悲失路之人;萍水相逢,尽是他乡之客。怀帝阍而不见,奉宣室以何年? 168 | 169 | 嗟乎!时运不齐,命途多舛。冯唐易老,李广难封。屈贾谊于长沙,非无圣主;窜梁鸿于海曲,岂乏明时?所赖君子安贫,达人知命。老当益壮,宁移白首之心;穷且益坚,不坠青云之志。酌贪泉而觉爽,处涸辙以犹懽。北海虽赊,扶摇可接;东隅已逝,桑榆非晚。孟尝高洁,空馀报国之心;阮籍猖狂,岂效穷途之哭? 170 | 171 | 勃,三尺微命,一介书生,无路请缨,等终军之弱冠;有怀投笔,慕宗悫之长风。舍簪笏于百龄,奉晨昏于万里。非谢家之宝树,接孟氏之芳邻。他日趋庭,叨陪鲤对;今兹捧袂,喜托龙门。杨意不逢,抚凌云而自惜;锺期既遇,奏流水以何惭? 172 | 173 | 呜呼!胜地不常,盛筵难再。兰亭已矣,梓泽丘墟。临别赠言,幸承恩于伟饯;登高作赋,是所望于群公。敢竭鄙诚,恭疏短引。一言均赋,四韵俱成。请洒潘江,各倾陆海云尔。 174 | ], 175 | ) 176 | -------------------------------------------------------------------------------- /docs/homework-template.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fr4nk1inCs/typreset/7e0874dacf455b9a5798f37430cfa6d444dd0400/docs/homework-template.pdf -------------------------------------------------------------------------------- /docs/homework-template.typ: -------------------------------------------------------------------------------- 1 | #import "../src/lib.typ": template, homework 2 | 3 | #show raw.where(block: true): block.with( 4 | width: 100%, 5 | fill: luma(93%), 6 | inset: 5pt, 7 | spacing: 0.7em, 8 | breakable: false, 9 | radius: 0.35em, 10 | ) 11 | #set raw(lang: "typc") 12 | 13 | #set page(height: auto) 14 | #show: template.with(title: "Homework Template Examples", author-infos: "Fr4nk1in") 15 | 16 | `homework.template` is built upon the basic `template` and is designed for homework. It sacrifices some flexibility for a more specialized and convenient interface. For more information about the basic `template`, please refer to its examples. 17 | 18 | ```typc 19 | let template( 20 | lang: "en", 21 | use-patch: true, 22 | course: "Course Name", 23 | number: 1, 24 | transform-title: auto, 25 | student-infos: ((name: "Student Name", id: "Student ID"),), 26 | team-name: none, 27 | body, 28 | ) = content 29 | ``` 30 | 31 | - `lang`: `str`, language identifier used in `set text(lang: lang)` 32 | - `use-patch`: `bool` | `(list?: bool, enum?: bool)` \ 33 | whether to use the patch for list and/or enum, default enabled when ignored 34 | - `course`: `str`, course name 35 | - `number`: `int`, homework number 36 | - `transform-title`: `auto` | `(course: str, number: int) => str`: 37 | title transformation function, default to `auto`, which generates the 38 | title as `"{course}{hw-literal} {number}"` where `hw-literal` is 39 | " Homework" in English and "作业" in Chinese 40 | - `student-infos`: `array[(name: str, id: str)]`, list of student names and ids 41 | - `team-name`: `str` | `none`: team name. \ 42 | When set to a string, the team name will be displayed in the header. This is useful when there are multiple students, since the header only handles the case that there is only one student. 43 | 44 | In the following, each page is an example of the template with different configurations. 45 | 46 | #show: homework.template.with( 47 | course: "Math", 48 | number: 1, 49 | student-infos: ((name: "Student", id: "1234567890"),), 50 | ) 51 | 52 | ```typ 53 | #show: homework.template.with( 54 | course: "Math", 55 | number: 1, 56 | student-infos: ((name: "Student", id: "1234567890"),), 57 | ) 58 | ``` 59 | 60 | 61 | #show: homework.template.with( 62 | course: "Math", 63 | number: 1, 64 | student-infos: ((name: "Student", id: "1234567890"),), 65 | team-name: "Team Name", 66 | ) 67 | 68 | ```typ 69 | #import "@local/typreset:0.2.0": homework 70 | 71 | #show: homework.template.with( 72 | course: "Math", 73 | number: 1, 74 | student-infos: ((name: "Student", id: "1234567890"),), 75 | team-name: "Team Name", 76 | ) 77 | ``` 78 | 79 | 80 | #show: homework.template.with( 81 | course: "Math", 82 | number: 1, 83 | student-infos: ( 84 | (name: "Student 1", id: "1234567890"), 85 | (name: "Student 2", id: "2345678901"), 86 | ), 87 | ) 88 | 89 | ```typ 90 | #import "@local/typreset:0.2.0": homework 91 | 92 | #show: homework.template.with( 93 | course: "Math", 94 | number: 1, 95 | student-infos: ( 96 | (name: "Student 1", id: "1234567890"), 97 | (name: "Student 2", id: "2345678901"), 98 | ), 99 | ) 100 | ``` 101 | 102 | 103 | #show: homework.template.with( 104 | course: "Math", 105 | number: 1, 106 | student-infos: ( 107 | (name: "Student 1", id: "1234567890"), 108 | (name: "Student 2", id: "2345678901"), 109 | ), 110 | team-name: "Team Name", 111 | ) 112 | 113 | ```typ 114 | #import "@local/typreset:0.2.0": homework 115 | 116 | #show: homework.template.with( 117 | course: "Math", 118 | number: 1, 119 | student-infos: ( 120 | (name: "Student 1", id: "1234567890"), 121 | (name: "Student 2", id: "2345678901"), 122 | ), 123 | team-name: "Team Name", 124 | ) 125 | ``` 126 | 127 | 128 | #show: homework.template.with( 129 | course: "Math", 130 | number: 1, 131 | student-infos: ((name: "Student", id: "1234567890"),), 132 | transform-title: (course, number) => "Homework " + str(number) + " for " + course, 133 | ) 134 | 135 | ```typ 136 | #show: homework.template.with( 137 | course: "Math", 138 | number: 1, 139 | student-infos: ((name: "Student", id: "1234567890"),), 140 | transform-title: (course, number) => "Homework " + str(number) + " for " + course, 141 | ) 142 | ``` 143 | -------------------------------------------------------------------------------- /docs/question-frame.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fr4nk1inCs/typreset/7e0874dacf455b9a5798f37430cfa6d444dd0400/docs/question-frame.pdf -------------------------------------------------------------------------------- /docs/question-frame.typ: -------------------------------------------------------------------------------- 1 | #import "../src/lib.typ": template, homework.simple-question, homework.complex-question 2 | 3 | #show: template.with( 4 | title: "Question Frame Manual", 5 | author-infos: ("Fr4nk1in",), 6 | ) 7 | #show raw.where(block: true): block.with( 8 | width: 100%, 9 | fill: luma(93%), 10 | inset: 5pt, 11 | spacing: 0.7em, 12 | breakable: false, 13 | radius: 0.35em, 14 | ) 15 | #set raw(lang: "typc") 16 | 17 | #set heading(numbering: "1.") 18 | 19 | = The `question` function 20 | 21 | ```typc 22 | let question( 23 | heading-counter: false, 24 | number: auto, 25 | display-number: none, 26 | display-desc: none, 27 | display-frame: none, 28 | desc, 29 | ) = content 30 | ``` 31 | 32 | - `heading-counter`: `boolean` \ 33 | Whether to show heading counter or not. 34 | - `number`: `auto` | `int` | `str` | `content` \ 35 | Question number, defaults to `auto`. 36 | - If `auto`, it would be the number of the questions (whose `number` are `auto`) up to this point. 37 | - Both `auto` and `int` works with `heading-counter`, while `str` and `content` do not and fail the compilation. 38 | - Both `auto` and `int` are treated as the `..numbers` in the `display-number` function. `str` and `content` are treated as the `override`. 39 | - `display-number` : `(override: str | content, ..numbers: int) => content` \ 40 | How to display the question number. 41 | - `display-desc`: `(content) => content` \ 42 | How to display the question description 43 | - `display-frame`: `(content) => content` \ 44 | How to display the question frame 45 | 46 | 47 | To enable the heading counter argument `heading-counter`, you need to set the heading numbering rule first. For example: 48 | ```typ 49 | #set heading(numbering: "1.") 50 | ``` 51 | 52 | = Presets and Examples 53 | 54 | We also provide two presets for the `question` function: `simple-question` and `complex-question`. 55 | 56 | == The `simple-question` preset 57 | 58 | #grid( 59 | columns: (1fr, 1fr), 60 | column-gutter: 1em, 61 | row-gutter: 1em, 62 | align: start + top, 63 | ```typc 64 | simple-question[Default Configuration] 65 | ```, 66 | simple-question[Default Configuration], 67 | 68 | ```typc 69 | simple-question( 70 | number: 1024, 71 | )[Custom Numbering] 72 | ```, 73 | simple-question(number: 1024)[Custom Integer Numbering], 74 | 75 | ```typc 76 | simple-question( 77 | number: "Number." 78 | )[Custom Numbering] 79 | ```, 80 | simple-question(number: "Number.")[Custom Numbering], 81 | 82 | ```typc 83 | simple-question( 84 | number: $gamma$ 85 | )[Custom Numbering] 86 | ```, 87 | simple-question(number: $gamma$)[Custom Numbering], 88 | 89 | ```typc 90 | simple-question[ 91 | Second Default Configuration 92 | ] 93 | ```, 94 | simple-question[ 95 | Second Default Configuration 96 | ], 97 | 98 | ```typc 99 | simple-question( 100 | heading-counter: true 101 | )[With Heading Counter] 102 | ```, 103 | simple-question(heading-counter: true)[With Heading Counter], 104 | 105 | ```typc 106 | simple-question( 107 | heading-counter: true, 108 | number: 1024, 109 | )[Custom Numbering] 110 | ```, 111 | { 112 | simple-question(heading-counter: true, number: 1024)[ 113 | Custom Integer Numbering 114 | ] 115 | 116 | [Works with `heading-counter`] 117 | }, 118 | 119 | ```typc 120 | simple-question( 121 | display-number: ( 122 | override: none, 123 | ..numbers, 124 | ) => { 125 | strong(numbering("I.", ..numbers)) 126 | }, 127 | )[Custom Numbering Rule], 128 | ```, 129 | simple-question( 130 | display-number: ( 131 | override: none, 132 | ..numbers, 133 | ) => { 134 | strong(numbering("I.", ..numbers)) 135 | }, 136 | )[Custom Numbering Rule], 137 | 138 | ```typc 139 | simple-question( 140 | heading-counter: true, 141 | display-number: ( 142 | override: none, 143 | ..numbers, 144 | ) => { 145 | strong(numbering("I.", ..numbers)) 146 | }, 147 | )[Custom Numbering Rule], 148 | ```, 149 | simple-question( 150 | heading-counter: true, 151 | display-number: ( 152 | override: none, 153 | ..numbers, 154 | ) => { 155 | strong(numbering("I.", ..numbers)) 156 | }, 157 | )[Custom Numbering Rule], 158 | 159 | ```typc 160 | simple-question( 161 | heading-counter: true, 162 | number: 1024, 163 | display-number: ( 164 | override: none, 165 | ..numbers, 166 | ) => { 167 | strong(numbering("I.", ..numbers)) 168 | }, 169 | )[Custom Integer Numbering] 170 | ```, 171 | { 172 | simple-question( 173 | heading-counter: true, 174 | number: 1024, 175 | display-number: ( 176 | override: none, 177 | ..numbers, 178 | ) => { 179 | strong(numbering("I.", ..numbers)) 180 | }, 181 | )[ 182 | Custom Integer Numbering 183 | ] 184 | 185 | [Works with `display-number`] 186 | }, 187 | 188 | ```typc 189 | simple-question( 190 | display-desc: it => strong(emph(it)) 191 | )[Custom Description Style], 192 | ```, 193 | simple-question(display-desc: it => strong(emph(it)))[Custom Description Style], 194 | 195 | ```typc 196 | simple-question( 197 | display-frame: it => { 198 | it 199 | v(-0.9em) 200 | line(length: 100%, stroke: red) 201 | v(-0.6em) 202 | }, 203 | )[Custom Frame Style], 204 | ```, 205 | simple-question( 206 | display-frame: it => { 207 | it 208 | v(-0.9em) 209 | line(length: 100%, stroke: red) 210 | v(-0.6em) 211 | }, 212 | )[Custom Frame Style], 213 | ) 214 | 215 | == The `complex-question` preset 216 | 217 | #complex-question[ 218 | Default Configuration 219 | 220 | ```typc 221 | complex-question[Default Configuration] 222 | ``` 223 | ] 224 | 225 | #complex-question(number: 1024)[ 226 | Custom Integer Numbering 227 | 228 | ```typc 229 | complex-question(number: 1024)[Custom Integer Numbering] 230 | ``` 231 | ] 232 | 233 | #complex-question(number: "Number.")[ 234 | Custom Numbering 235 | 236 | ```typc 237 | complex-question(number: "Number.")[Custom Numbering] 238 | ``` 239 | ] 240 | 241 | #complex-question(number: $gamma$)[ 242 | Custom Numbering 243 | 244 | ```typc 245 | complex-question(number: $gamma$)[Custom Numbering] 246 | ``` 247 | ] 248 | 249 | #complex-question[ 250 | Second Default Configuration 251 | 252 | ```typc 253 | complex-question[Second Default Configuration] 254 | ``` 255 | ] 256 | 257 | #complex-question(heading-counter: true)[ 258 | With Heading Counter 259 | 260 | ```typc 261 | complex-question(heading-counter: true)[With Heading Counter] 262 | ``` 263 | ] 264 | 265 | #complex-question(heading-counter: true, number: 1024)[ 266 | Custom Integer Numbering with Heading 267 | 268 | ```typc 269 | complex-question(heading-counter: true, number: 1024)[Custom Integer Numbering] 270 | ``` 271 | ] 272 | 273 | #complex-question( 274 | display-number: (override: none, ..numbers) => { 275 | strong(numbering("I.", ..numbers)) 276 | }, 277 | )[ 278 | Custom Numbering Rule 279 | 280 | ```typc 281 | complex-question( 282 | display-number: (override: none, ..numbers) => { 283 | strong(numbering("I.", ..numbers)) 284 | }, 285 | )[Custom Numbering Rule] 286 | ``` 287 | ] 288 | 289 | #complex-question( 290 | heading-counter: true, 291 | display-number: (override: none, ..numbers) => { 292 | strong(numbering("I.", ..numbers)) 293 | }, 294 | )[ 295 | Custom Numbering Rule with Heading 296 | 297 | ```typc 298 | complex-question( 299 | heading-counter: true, 300 | display-number: (override: none, ..numbers) => { 301 | strong(numbering("I.", ..numbers)) 302 | }, 303 | )[Custom Numbering Rule with Heading] 304 | ``` 305 | ] 306 | 307 | #complex-question( 308 | heading-counter: true, 309 | number: 1024, 310 | display-number: (override: none, ..numbers) => { 311 | strong(numbering("I.", ..numbers)) 312 | }, 313 | )[ 314 | Custom Integer Numbering with Custom Numbering Rule 315 | 316 | ```typc 317 | complex-question( 318 | heading-counter: true, 319 | number: 1024, 320 | display-number: (override: none, ..numbers) => { 321 | strong(numbering("I.", ..numbers)) 322 | }, 323 | )[Custom Integer Numbering with Custom Numbering Rule] 324 | ``` 325 | ] 326 | 327 | #complex-question(display-desc: emph)[ 328 | Custom Description Style 329 | 330 | ```typc 331 | complex-question(display-desc: emph)[Custom Description Style] 332 | ``` 333 | ] 334 | 335 | #complex-question(display-frame: rect.with(width: 100%, radius: 5pt, stroke: red))[ 336 | Custom Frame Style 337 | 338 | ```typc 339 | complex-question( 340 | display-frame: rect.with(width: 100%, radius: 5pt, stroke: red), 341 | )[Custom Frame Style] 342 | ``` 343 | ] 344 | -------------------------------------------------------------------------------- /docs/template.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fr4nk1inCs/typreset/7e0874dacf455b9a5798f37430cfa6d444dd0400/docs/template.pdf -------------------------------------------------------------------------------- /docs/template.typ: -------------------------------------------------------------------------------- 1 | #show raw.where(block: true): block.with( 2 | width: 100%, 3 | fill: luma(93%), 4 | inset: 5pt, 5 | spacing: 0.7em, 6 | breakable: false, 7 | radius: 0.35em, 8 | ) 9 | #set raw(lang: "typc") 10 | 11 | #import "../src/lib.typ": template, typesetting 12 | #import "../src/styles/basic.typ": patch-list-item, patch-enum-item 13 | 14 | #set page(height: auto) 15 | #show: template.with(use-patch: false, title: "Template Examples", author-infos: "Fr4nk1in") 16 | 17 | In the following examples, the header of each page is exactly what the template on that page specifies. In practice, the header only displays on the page whose number is greater than one, so the header and the title wouldn't appear on the same page. 18 | 19 | We've made the patches for typst's bullet list and numbered list discussed in #link("https://github.com/typst/typst/issues/1204#issuecomment-2447947433", quote[_List and enum markers are not aligned with the baseline of the item's contents_ (\#1204)]) a built-in feature for all the templates in `typreset`. To disable it, pass `use-patch: false` as a template's named argument. If you want to enable only one patch, use `use-patch: (list: false)` or `use-patch: (enum: false)`. 20 | 21 | Example: 22 | 23 | ```typ 24 | - This is a very tall equation: $2^2^2^2^2^2^2^2$. 25 | 26 | 1. This is a very tall equation: $2^2^2^2^2^2^2^2$. 27 | 28 | + This is a very tall equation: $2^2^2^2^2^2^2^2$. 29 | ``` 30 | 31 | Without patches: 32 | 33 | - This is a very tall equation: $2^2^2^2^2^2^2^2$. 34 | 35 | 1. This is a very tall equation: $2^2^2^2^2^2^2^2$. 36 | 37 | + This is a very tall equation: $2^2^2^2^2^2^2^2$. 38 | 39 | With patches: 40 | 41 | #show list.item: patch-list-item 42 | #show enum.item: patch-enum-item 43 | 44 | - This is a very tall equation: $2^2^2^2^2^2^2^2$. 45 | 46 | 1. This is a very tall equation: $2^2^2^2^2^2^2^2$. 47 | 48 | + This is a very tall equation: $2^2^2^2^2^2^2^2$. 49 | 50 | In the following, each page is an example of the template with different configurations. 51 | 52 | #show: template.with( 53 | title: ( 54 | "Documentation of template", 55 | [Documentation of `template`], 56 | ), 57 | ) 58 | 59 | ```typ 60 | #import "@local/typreset:0.2.0": template 61 | 62 | #show: template.with( 63 | title: ( 64 | "Documentation of template", 65 | [Documentation of `template`], 66 | ), 67 | ) 68 | ``` 69 | 70 | ```typc 71 | let template( 72 | lang: "en", 73 | use-patch: true, 74 | title: ("Document Title", [Document Title]), 75 | author-infos: "Author", 76 | display-author-block: true, 77 | display-author-header: true, 78 | make-title: true, 79 | make-header: true, 80 | body, 81 | ) = content 82 | ``` 83 | 84 | - `lang`: `str`, Language identifier used in `set text(lang: lang)` 85 | - `use-patch`: `bool` | `(list?: bool, enum?: bool)` \ 86 | Whether to use the patch for list and/or enum, default enabled when ignored. 87 | - `title`: `str` | `(str, content)`: 88 | document title and (optional) style-free content how the title displays 89 | - If the content is provided, better not setting the font size and font weight in the content, as this content will be used both in the title and the header. The template would handle it for consistency 90 | - If the content is not provided, the title will be just a text block 91 | - `author-infos`: `str` | `array[str]` | `array[dict[str, any]]` \ 92 | List of authors, together with their information 93 | - If a single string is provided, it is treated as the name of the only author 94 | - If an array of strings is provided, it is treated as the names of 95 | authors 96 | - If an array of dictionaries is provided, each dictionary should contain the "name" key 97 | - `display-author-block`: `bool` | `dict[str, any] => content` \ 98 | Whether or How to display an author block for a single author, default to a simple text block displaying the name. The author blocks are placed under the title as a up-to-3-column grid 99 | - `display-author-header`: `bool` | `array[dict[str, any]] => content` \ 100 | Whether or How to display the author information in the header. For single author, the default is to display the name of the author. For multiple authors, the default is to not display the author information. 101 | - For multiple authors, only when this is set to a valid function, the author information will be displayed in the header, otherwise (even if set to `true`), the author information will not be displayed in the header 102 | - Better not setting the font size in the content, the template would handle the font size 103 | - Better control the height of the content the same as the title content with the same font size 104 | - `make-title`: `bool`, whether to make the title, default to true 105 | - `make-header`: `bool`, whether to make the header, default to true 106 | 107 | 108 | #show: template.with( 109 | title: ("single string author-infos", [single string `author-info`]), 110 | author-infos: "Author", 111 | ) 112 | 113 | ```typ 114 | #import "@local/typreset:0.2.0": template 115 | 116 | #show: template.with( 117 | title: ("single string author-info", [single string `author-info`]), 118 | author-infos: "Author", 119 | ) 120 | ``` 121 | 122 | 123 | #show: template.with( 124 | title: ( 125 | "multiple author-infos and display-author-block", 126 | [multiple `author-infos` and `display-author-block`], 127 | ), 128 | author-infos: range(1, 6).map(i => ( 129 | name: "Author " + str(i), 130 | id: "ID " + str(i), 131 | )), 132 | display-author-block: info => { 133 | info.name 134 | linebreak() 135 | info.id 136 | }, 137 | ) 138 | 139 | ```typ 140 | #import "@local/typreset:0.2.0": template 141 | 142 | #show: template.with( 143 | title: ( 144 | "multiple author-infos and display-author-block", 145 | [multiple `author-infos` and `display-author-block`], 146 | ), 147 | author-infos: range(1, 6).map(i => ( 148 | name: "Author " + str(i), 149 | id: "ID " + str(i), 150 | )), 151 | display-author-block: info => { 152 | info.name 153 | linebreak() 154 | info.id 155 | }, 156 | ) 157 | ``` 158 | 159 | 160 | #show: template.with( 161 | title: "set the header with multiple authors", 162 | author-infos: range(1, 6).map(i => "Author " + str(i)), 163 | display-author-header: _ => [ Author Team ], 164 | ) 165 | 166 | ```typ 167 | #import "@local/typreset:0.2.0": template 168 | 169 | #show: template.with( 170 | title: "set the header with multiple authors", 171 | author-infos: range(1, 6).map(i => "Author " + str(i)), 172 | display-author-header: _ => [ Author Team ], 173 | ) 174 | ``` 175 | 176 | 177 | #show: template.with( 178 | title: "disable author header", 179 | author-infos: "Author", 180 | display-author-header: false, 181 | ) 182 | 183 | ```typ 184 | #import "@local/typreset:0.2.0": template 185 | 186 | #show: template.with( 187 | title: "disable author header", 188 | author-infos: "Author", 189 | display-author-header: false, 190 | ) 191 | ``` 192 | 193 | 194 | #show: template.with( 195 | title: "disable header", 196 | author-infos: "Author", 197 | make-header: false, 198 | ) 199 | 200 | ```typ 201 | #import "@local/typreset:0.2.0": template 202 | 203 | #show: template.with( 204 | title: "disable header", 205 | author-infos: "Author", 206 | make-header: false, 207 | ) 208 | ``` 209 | 210 | 211 | #show: template.with( 212 | title: "disable author blocks", 213 | author-infos: "Author", 214 | display-author-block: false, 215 | ) 216 | 217 | ```typ 218 | #import "@local/typreset:0.2.0": template 219 | 220 | #show: template.with( 221 | title: "disable author blocks", 222 | author-infos: "Author", 223 | display-author-block: false, 224 | ) 225 | ``` 226 | 227 | 228 | #show: template.with( 229 | title: "disable title", 230 | make-title: false, 231 | ) 232 | 233 | Disable title 234 | 235 | ```typ 236 | #import "@local/typreset:0.2.0": template 237 | 238 | #show: template.with( 239 | title: "disable title", 240 | make-title: false, 241 | ) 242 | ``` 243 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.pdf -------------------------------------------------------------------------------- /src/bundles/homework.typ: -------------------------------------------------------------------------------- 1 | #import "../styles/homework.typ": template 2 | #import "../utils/question.typ": question, simple-question, complex-question 3 | -------------------------------------------------------------------------------- /src/lib.typ: -------------------------------------------------------------------------------- 1 | #let version = "0.2.0" 2 | 3 | #import "bundles/homework.typ" 4 | #import "utils/font.typ": set-font, fonts 5 | #import "styles/basic.typ": typesetting, template 6 | -------------------------------------------------------------------------------- /src/styles/basic.typ: -------------------------------------------------------------------------------- 1 | #import "../utils/font.typ": set-font 2 | 3 | /// Patch typst's bullet list 4 | /// Usage: 5 | /// ```typ 6 | /// show list.item: patch-list-item 7 | /// ``` 8 | /// 9 | /// - it (content): list.item 10 | /// -> content 11 | #let patch-list-item(it) = { 12 | // The generated terms is not tight 13 | // So setting `par.spacing` is to set the result lists' spacing 14 | let spacing = if list.spacing != auto { 15 | list.spacing 16 | } else if enum.tight { 17 | par.leading 18 | } else { 19 | par.spacing 20 | } 21 | set par(spacing: spacing) 22 | 23 | let current-marker = if type(list.marker) == array { 24 | list.marker.at(0) 25 | } else { 26 | list.marker 27 | } 28 | 29 | context { 30 | let hanging-indent = measure(current-marker).width + terms.separator.amount 31 | set terms(hanging-indent: hanging-indent) 32 | if type(list.marker) == array { 33 | terms.item( 34 | current-marker, 35 | { 36 | // set the value of list.marker in a loop 37 | set list(marker: list.marker.slice(1) + (list.marker.at(0),)) 38 | it.body 39 | }, 40 | ) 41 | } else { 42 | terms.item(current-marker, it.body) 43 | } 44 | } 45 | } 46 | 47 | 48 | #let counting-symbols = "1aAiI一壹あいアイא가ㄱ*" 49 | #let consume-regex = regex("[^" + counting-symbols + "]*[" + counting-symbols + "][^" + counting-symbols + "]*") 50 | 51 | /// Patch typst's numbered list 52 | /// Usage: 53 | /// ```typ 54 | /// show enum.item: patch-enum-item 55 | /// ``` 56 | /// 57 | /// - it (content): list.item 58 | /// -> content 59 | #let patch-enum-item(it) = { 60 | if it.number == none { 61 | return it 62 | } 63 | // The generated terms is not tight 64 | // So setting `par.spacing` is to set the result enums' spacing 65 | let spacing = if enum.spacing != auto { 66 | enum.spacing 67 | } else if enum.tight { 68 | par.leading 69 | } else { 70 | par.spacing 71 | } 72 | set par(spacing: spacing) 73 | 74 | 75 | let new-numbering = if type(enum.numbering) == function or enum.full { 76 | numbering.with(enum.numbering, it.number) 77 | } else { 78 | enum.numbering.trim(consume-regex, at: start, repeat: false) 79 | } 80 | let current-number = numbering(enum.numbering, it.number) 81 | context { 82 | let hanging-indent = measure(current-number).width + terms.separator.amount 83 | 84 | set terms(hanging-indent: hanging-indent) 85 | 86 | terms.item( 87 | strong(delta: -300, numbering(enum.numbering, it.number)), 88 | { 89 | if new-numbering != "" { 90 | set enum(numbering: new-numbering) 91 | it.body 92 | } else { 93 | it.body 94 | } 95 | }, 96 | ) 97 | } 98 | } 99 | 100 | #let identical-mapping = it => it 101 | 102 | /// Basic typesetting 103 | /// 104 | /// - title (str): title 105 | /// - authors (str | array): list of authors 106 | /// - lang (str): language identifier used in `set text(lang: lang)` 107 | /// - use-patch (bool | (list?: bool, enum?: bool)): 108 | /// whether to use the patch for list and/or enum, default enabled when ignored 109 | /// - body (content): content 110 | /// -> content 111 | #let typesetting( 112 | title: "Document Title", 113 | authors: "Fr4nk1in", 114 | lang: "en", 115 | use-patch: true, 116 | body, 117 | ) = { 118 | // document 119 | set document(title: title, author: authors) 120 | // font 121 | show: set-font.with(lang: lang) 122 | // paragraph 123 | set par(linebreaks: "optimized", justify: true) 124 | show raw.where(block: true): set par(linebreaks: "simple", justify: false) 125 | // heading 126 | show heading: strong 127 | // page 128 | set page( 129 | numbering: "1", 130 | number-align: center, 131 | ) 132 | 133 | let patches = ( 134 | list: identical-mapping, 135 | enum: identical-mapping, 136 | ) 137 | 138 | if type(use-patch) == bool { 139 | if use-patch { 140 | patches = ( 141 | list: patch-list-item, 142 | enum: patch-enum-item, 143 | ) 144 | } 145 | } else if type(use-patch) == dictionary { 146 | patches = ( 147 | list: if use-patch.at("list", true) { patch-list-item } else { identical-mapping }, 148 | enum: if use-patch.at("enum", true) { patch-enum-item } else { identical-mapping }, 149 | ) 150 | } else { 151 | assert( 152 | false, 153 | message: "Invalid value for `use-patch`, expected bool or (list?: bool, enum?: bool)", 154 | ) 155 | } 156 | 157 | show list.item: patches.list 158 | show enum.item: patches.enum 159 | 160 | body 161 | } 162 | 163 | 164 | /// Template 165 | /// 166 | /// - lang (str): language identifier used in `set text(lang: lang)` 167 | /// - use-patch (bool | (list?: bool, enum?: bool)): 168 | /// whether to use the patch for list and/or enum, default enabled when 169 | /// ignored 170 | /// - title (str | (str, content)): 171 | /// document title and (optional) style-free content how the title displays 172 | /// - If the content is provided, better not setting the font size and font 173 | /// weight in the content, as this content will be used both in the title 174 | /// and the header. The template would handle it for consistency 175 | /// - If the content is not provided, the title will be just a text block 176 | /// - author-infos (str | array[str] | array[dict[str, any]]): 177 | /// list of authors, together with their information 178 | /// - If a single string is provided, it is treated as the name of the only 179 | /// author 180 | /// - If an array of strings is provided, it is treated as the names of 181 | /// authors 182 | /// - If an array of dictionaries is provided, each dictionary should contain 183 | /// the "name" key 184 | /// - display-author-block (bool | function(dict[str, any]) -> content): 185 | /// Whether or How to display an author block for a single author, default to 186 | /// a simple text block displaying the name. The author blocks are placed 187 | /// under the title as a up-to-3-column grid 188 | /// - display-author-header (bool | function(array[dict[str, any]]) -> content): 189 | /// Whether or How to display the author information in the header. For single 190 | /// author, the default is to display the name of the author. For multiple 191 | /// authors, the default is to not display the author information. 192 | /// - For multiple authors, only when this is set to a valid function, the 193 | /// author information will be displayed in the header, otherwise (even if 194 | /// set to `true`), the author information will not be displayed in the 195 | /// header 196 | /// - Better not setting the font size in the content, the template would 197 | /// handle the font size 198 | /// - Better control the height of the content the same as the title content 199 | /// with the same font size 200 | /// - make-title (bool): whether to make the title, default to true 201 | /// - make-header (bool): whether to make the header, default to true 202 | /// - body (content): content 203 | /// -> content 204 | #let template( 205 | lang: "en", 206 | use-patch: true, 207 | title: ("Document Title", [Document Title]), 208 | author-infos: "Author", 209 | display-author-block: true, 210 | display-author-header: true, 211 | make-title: true, 212 | make-header: true, 213 | body, 214 | ) = { 215 | let title-str = if type(title) == str { title } else { title.at(0) } 216 | let title-content = if type(title) == str { text(title) } else { title.at(1) } 217 | 218 | let author-infos = if type(author-infos) == str { 219 | ((name: author-infos),) 220 | } else if type(author-infos) == array { 221 | if type(author-infos.at(0)) == str { 222 | author-infos.map(name => (name: name)) 223 | } else { 224 | author-infos 225 | } 226 | } 227 | let authors = author-infos.map(info => info.name) 228 | 229 | let header = if type(display-author-header) == function { 230 | grid( 231 | columns: (auto, auto), 232 | column-gutter: 1fr, 233 | align: center + top, 234 | emph(title-content), display-author-header(author-infos), 235 | ) 236 | } else if author-infos.len() > 1 or not display-author-header { 237 | align(center, emph(title-content)) 238 | } else if display-author-header { 239 | grid( 240 | columns: (auto, auto), 241 | column-gutter: 1fr, 242 | align: center + top, 243 | emph(title-content), text(author-infos.at(0).name), 244 | ) 245 | } 246 | 247 | show: typesetting.with( 248 | title: title-str, 249 | authors: authors, 250 | lang: lang, 251 | use-patch: use-patch, 252 | ) 253 | 254 | set page( 255 | header-ascent: 14pt, 256 | header: { 257 | context if make-header and counter(page).get().at(0) != 1 { 258 | set text(size: 8pt) 259 | header 260 | } 261 | }, 262 | ) 263 | 264 | // title 265 | if make-title { 266 | align( 267 | center, 268 | { 269 | strong(text(size: 1.75em, title-content)) 270 | 271 | let display-author-block = if ( 272 | type(display-author-block) == bool and display-author-block 273 | ) { 274 | info => text(info.name) 275 | } else { 276 | display-author-block 277 | } 278 | 279 | let reminder = calc.rem(author-infos.len(), 3) 280 | let fill = author-infos.len() - reminder 281 | 282 | if type(display-author-block) == function { 283 | if (fill > 0) { 284 | grid( 285 | columns: 3, 286 | column-gutter: 1em, 287 | row-gutter: 1em, 288 | align: center + bottom, 289 | ..author-infos.slice(0, count: fill).map(display-author-block), 290 | ) 291 | } 292 | if (reminder > 0) { 293 | grid( 294 | columns: reminder, 295 | column-gutter: 1em, 296 | row-gutter: 1em, 297 | align: center + bottom, 298 | ..author-infos.slice(fill).map(display-author-block), 299 | ) 300 | } 301 | } 302 | }, 303 | ) 304 | } 305 | 306 | body 307 | } 308 | -------------------------------------------------------------------------------- /src/styles/homework.typ: -------------------------------------------------------------------------------- 1 | #import "./basic.typ": template as base-template 2 | 3 | /// Template for homework 4 | /// 5 | /// - lang (str): language identifier used in `set text(lang: lang)` 6 | /// - use-patch (bool | (list?: bool, enum?: bool)): 7 | /// whether to use the patch for list and/or enum, default enabled when 8 | /// ignored 9 | /// - course (str): course name 10 | /// - number (int): homework number 11 | /// - transform-title (auto | (course: str, number: int) -> str): 12 | /// title transformation function, default to `auto`, which generates the 13 | /// title as `"{course}{hw-literal} {number}"` where `hw-literal` is 14 | /// " Homework" in English and "作业" in Chinese 15 | /// - student-infos (array[(name: str, id: str)]): list of student names and ids 16 | /// - team-name (str | none): team name. When set to a string, the team name 17 | // will be displayed in the header. This is useful when there are multiple 18 | // students, since the header only handles the case that there is only one 19 | // student 20 | /// - body (content): content 21 | /// -> content 22 | #let template( 23 | lang: "en", 24 | use-patch: true, 25 | course: "Course Name", 26 | number: 1, 27 | transform-title: auto, 28 | student-infos: ((name: "Student Name", id: "Student ID"),), 29 | team-name: none, 30 | body, 31 | ) = { 32 | let title = if transform-title == auto { 33 | ( 34 | course 35 | + ( 36 | en: " Homework", 37 | zh: "作业", 38 | ).at(lang) 39 | + " " 40 | + str(number) 41 | ) 42 | } else { transform-title(course, number) } 43 | 44 | show: base-template.with( 45 | lang: lang, 46 | use-patch: use-patch, 47 | title: title, 48 | author-infos: student-infos, 49 | display-author-block: info => { 50 | info.name 51 | linebreak() 52 | info.id 53 | }, 54 | display-author-header: if team-name == none { 55 | true 56 | } else { _ => text(team-name) }, 57 | ) 58 | 59 | body 60 | } 61 | -------------------------------------------------------------------------------- /src/utils/font.typ: -------------------------------------------------------------------------------- 1 | /* Font settings for all presets */ 2 | 3 | #let fonts = ( 4 | base: ( 5 | math: ( 6 | "Libertinus Math", 7 | ), 8 | serif: ( 9 | "Libertinus Serif", 10 | ), 11 | sans-serif: ( 12 | "Libertinus Sans", 13 | ), 14 | italic: ( 15 | "Libertinus Serif", 16 | ), 17 | ), 18 | zh: ( 19 | serif: ( 20 | "SimSun", // Windows 21 | "Songti SC", // MacOS 22 | "FandolSong", // Linux 23 | // fallback 24 | "Source Han Serif SC", 25 | "Noto Serif CJK SC", 26 | ), 27 | sans-serif: ( 28 | "SimHei", // Windows 29 | "Heiti SC", // MacOS 30 | "FandolHei", // Linux 31 | // fallback 32 | "Source Han Sans SC", 33 | "Noto Sans CJK SC", 34 | ), 35 | italic: ( 36 | "SimKai SC", // Windows 37 | "Kaiti SC", // MacOS 38 | "FandolKai", // Linux 39 | "Source Han Serif SC", 40 | "Noto Serif CJK SC", 41 | ), 42 | ), 43 | ) 44 | 45 | #let set-font(lang: "en", body) = { 46 | assert(lang == "en" or lang in fonts.keys(), message: "Font for language " + lang + " not supported") 47 | let keys = if lang == "en" { ("base",) } else { ("base", lang) } 48 | let serif-fonts = keys.map(key => fonts.at(key).serif).flatten() 49 | let sans-serif-fonts = keys.map(key => fonts.at(key).sans-serif).flatten() 50 | let italic-fonts = keys.map(key => fonts.at(key).italic).flatten() 51 | 52 | show math.equation: set text(font: (..fonts.base.math, ..serif-fonts)) 53 | set text(font: serif-fonts, lang: lang) 54 | show emph: set text(font: italic-fonts) 55 | 56 | body 57 | } 58 | -------------------------------------------------------------------------------- /src/utils/question.typ: -------------------------------------------------------------------------------- 1 | #let question-counter = counter("question") 2 | 3 | /// Base question frame 4 | /// 5 | /// - heading-counter (bool): whether to show heading counter 6 | /// - number (auto | int | str | content): question number, defaults to `auto`. 7 | /// When using `auto`, the number will be auto-incremented and it only 8 | /// increments with the `auto` value. 9 | /// - display-number (function(override: str | content, ..int) -> content): how 10 | /// to display the question number 11 | /// - display-desc (function(content) -> content): how to display the 12 | /// question description 13 | /// - display-frame (function(content) -> content): how to display the 14 | /// question frame 15 | /// - desc (content): question description 16 | /// -> content 17 | /// 18 | #let question( 19 | heading-counter: false, 20 | number: auto, 21 | display-number: none, 22 | display-desc: none, 23 | display-frame: none, 24 | desc, 25 | ) = { 26 | assert( 27 | type(display-number) == function and type(display-frame) == function and type(display-desc) == function, 28 | message: "display-number, display-frame, and display-desc must be functions", 29 | ) 30 | 31 | // heading-counter doesn't work when number is a string 32 | assert( 33 | (type(number) != str and type(number) != content) or not heading-counter, 34 | message: "heading-counter doesn't work when `number` is a string or content", 35 | ) 36 | 37 | if number == auto { 38 | question-counter.step() 39 | } 40 | 41 | context { 42 | let heading-number = counter(heading).get() 43 | let current-number = if number == auto { 44 | (..question-counter.get(),) 45 | } else if type(number) == int { 46 | (number,) 47 | } else { 48 | () 49 | } 50 | 51 | let numbers = if heading-counter { 52 | (..heading-number, ..current-number) 53 | } else { 54 | current-number 55 | } 56 | 57 | display-frame({ 58 | if numbers.len() == 0 { 59 | display-number(override: number) 60 | } else { 61 | display-number(..numbers) 62 | } 63 | 64 | h(5pt) 65 | 66 | display-desc(desc) 67 | }) 68 | } 69 | } 70 | 71 | /// Preset: simple question 72 | /// 73 | /// - heading-counter (bool): whether to show heading counter 74 | /// - number (auto | int | str | content): 75 | /// question number, defaults to `auto` (auto-incremented) 76 | /// - display-number (function(override: str | content, ..int) -> content): 77 | /// how to display the question number, defaults "1.1." with bold style 78 | /// - display-desc (function(content) -> content): 79 | /// how to display the question description, defaults to bold style 80 | /// - display-frame (function(content) -> content): 81 | /// how to display the question frame, defaults to number and description over a line 82 | /// - desc (content): question description 83 | /// -> content 84 | /// 85 | #let simple-question = question.with( 86 | display-number: (override: none, ..numbers) => { 87 | if type(override) == str { 88 | strong(override) 89 | } else if type(override) == content { 90 | strong(override) 91 | } else { 92 | strong(numbering("1.1.", ..numbers)) 93 | } 94 | }, 95 | display-desc: it => strong(it), 96 | display-frame: it => { 97 | it 98 | v(-0.9em) 99 | line(length: 100%) 100 | v(-0.6em) 101 | }, 102 | ) 103 | 104 | /// Preset: complex question 105 | /// 106 | /// - heading-counter (bool): whether to show heading counter 107 | /// - number (auto | int | str | content): 108 | /// question number, defaults to `auto` (auto-incremented) 109 | /// - display-number (auto | function(override: str | content, ..int) -> content): 110 | /// how to display the question number, defaults "1.1." with bold style 111 | /// - display-desc (function(content) -> content): 112 | /// how to display the question description, defaults to no extra style 113 | /// - display-frame (function(content) -> content): 114 | /// how to display the question frame, defaults to 100% width rectangle with 5pt radius 115 | /// - desc (content): question description 116 | /// -> content 117 | /// 118 | #let complex-question = question.with( 119 | display-number: (override: none, ..numbers) => { 120 | if type(override) == str { 121 | strong(override) 122 | } else if type(override) == content { 123 | strong(override) 124 | } else { 125 | strong(numbering("1.1.", ..numbers)) 126 | } 127 | }, 128 | display-desc: it => it, 129 | display-frame: it => rect(width: 100%, radius: 5pt)[#it], 130 | ) 131 | 132 | #set heading(numbering: "1.") 133 | -------------------------------------------------------------------------------- /typst.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "typreset" 3 | version = "0.2.1" 4 | entrypoint = "src/lib.typ" 5 | authors = ["Fr4nk1inCs"] 6 | license = "MIT" 7 | description = "A collection of typst presets for my daily writing." 8 | --------------------------------------------------------------------------------