From 9b72ee4d221d1e9e8031e53631aaccd06841ff04 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Mon, 17 Jul 2023 22:43:33 +0800 Subject: [PATCH] Load theme for raw code (#1675) --- Cargo.lock | 41 +- NOTICE | 4 + assets/files/halcyon.tmTheme | 865 +++++++++++++++++++++++++++ crates/typst-library/Cargo.toml | 2 +- crates/typst-library/src/text/raw.rs | 91 ++- tests/ref/text/raw-theme.png | Bin 0 -> 5380 bytes tests/typ/text/raw-theme.typ | 24 + 7 files changed, 1012 insertions(+), 15 deletions(-) create mode 100644 assets/files/halcyon.tmTheme create mode 100644 tests/ref/text/raw-theme.png create mode 100644 tests/typ/text/raw-theme.typ diff --git a/Cargo.lock b/Cargo.lock index 8ed0897ac..1d5ecb602 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -942,7 +942,7 @@ dependencies = [ "log", "num-format", "once_cell", - "quick-xml", + "quick-xml 0.26.0", "rgb", "str_stack", ] @@ -1122,6 +1122,15 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +[[package]] +name = "line-wrap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" +dependencies = [ + "safemem", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1450,6 +1459,20 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "plist" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" +dependencies = [ + "base64", + "indexmap", + "line-wrap", + "quick-xml 0.28.2", + "serde", + "time", +] + [[package]] name = "png" version = "0.17.8" @@ -1518,6 +1541,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.27" @@ -1783,6 +1815,12 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + [[package]] name = "same-file" version = "1.0.6" @@ -2053,6 +2091,7 @@ dependencies = [ "fnv", "lazy_static", "once_cell", + "plist", "regex-syntax 0.6.29", "serde", "serde_derive", diff --git a/NOTICE b/NOTICE index 93a412b45..8cfa6b61a 100644 --- a/NOTICE +++ b/NOTICE @@ -25,6 +25,10 @@ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ================================================================================ The MIT License applies to: +* The tmTheme in `assets/files/halcyon.tmTheme` + Copyright (c) 2018 Brittany Chiang + in the Sublime Text package `Halcyon` (https://github.com/bchiang7/Halcyon) + * The default color set defined in `crates/typst/src/geom/color.rs` which is adapted from the colors.css project (https://clrs.cc/) diff --git a/assets/files/halcyon.tmTheme b/assets/files/halcyon.tmTheme new file mode 100644 index 000000000..36f2d66a1 --- /dev/null +++ b/assets/files/halcyon.tmTheme @@ -0,0 +1,865 @@ + + + + + author + Brittany Chiang + colorSpaceName + sRGB + name + ayu + semanticClass + halcyon + settings + + + settings + + background + #1d2433 + foreground + #a2aabc + caret + #FFCC66 + findHighlight + #8695b7 + findHighlightForeground + #d7dce2 + guide + #2f3b54 + activeGuide + #2f3b54 + stackGuide + #2f3b54 + gutter + #1d2433 + gutterForeground + #8695b755 + inactiveBackground + #1d2433 + inactiveSelection + #2f3b54 + invisibles + #6679a4 + lineHighlight + #2f3b54 + popupCss + + html, body { + background-color: #1d2433; + font-size: 12px; + color: #a2aabc; + padding: 0; + } + body { + padding: 5px; + } + div { + padding-bottom: -3px; + } + b, strong { + font-weight: normal; + } + a { + color: rgba(92, 207, 230, .7); + line-height: 16px; + } + .type { + color: #ef6b73; + } + .name { + color: #ffd580; + } + .param { + color: #FFD580; + } + .current { + text-decoration: underline; + } + + selection + #2f3b54 + selectionBorder + #a2aabc35 + shadow + #00000010 + + + + name + Comments + scope + comment, punctuation.definition.comment + settings + + fontStyle + italic + foreground + #8695b799 + + + + name + Variable + scope + variable + settings + + foreground + #a2aabc + + + + name + Keyword + scope + keyword, keyword.operator + settings + + foreground + #FFAE57 + + + + name + Storage + scope + storage.type, storage.modifier + settings + + foreground + #c3a6ff + + + + name + Operator, Misc + scope + constant.other.color, meta.tag, punctuation.separator.inheritance.php, punctuation.section.embedded, keyword.other.substitution + settings + + foreground + #5ccfe6 + + + + name + Tag + scope + entity.name.tag, meta.tag.sgml + settings + + foreground + #5ccfe6 + + + + name + Git Gutter Deleted + scope + markup.deleted.git_gutter + settings + + foreground + #ef6b73 + + + + name + Function, Special Method, Block Level + scope + entity.name, entity.name.class, entity.other.inherited-class, variable.function, support.function, keyword.other.special-method, meta.block-level + settings + + foreground + #FFD580 + + + + name + Other Variable, String Link + scope + support.other.variable, string.other.link + settings + + foreground + #ef6b73 + + + + name + Number, Constant, Function Argument, Tag Attribute, Embedded + scope + constant.numeric, constant.language, constant.character, keyword.other.unit + settings + + foreground + #c3a6ff + + + + name + Number, Constant, Function Argument, Tag Attribute, Embedded + scope + support.constant, meta.jsx.js, punctuation.section, string.unquoted.label + settings + + foreground + #a2aabc + + + + name + String, Symbols, Inherited Class, Markup Heading + scope + string, keyword.other.template, constant.other.symbol, constant.other.key, entity.other.inherited-class, markup.heading, markup.inserted.git_gutter, meta.group.braces.curly + settings + + fontStyle + normal + foreground + #bae67e + + + + name + Class, Support + scope + entity.name.type.class, support.type, support.class, support.orther.namespace.use.php, meta.use.php, support.other.namespace.php, markup.changed.git_gutter + settings + + foreground + #5ccfe6 + + + + name + Sub-methods + scope + entity.name.module.js, variable.import.parameter.js, variable.other.class.js + settings + + foreground + #5ccfe6 + + + + name + Language methods + scope + variable.language + settings + + fontStyle + italic + foreground + #5ccfe6 + + + + name + Invalid + scope + invalid, invalid.illegal + settings + + foreground + #ef6b73 + + + + name + Deprecated + scope + invalid.deprecated + settings + + background + #FFAE57 + foreground + #d7dce2 + + + + name + Html punctuations tags + scope + punctuation.definition.tag.end, punctuation.definition.tag.begin, punctuation.definition.tag, meta.group.braces.curly.js, meta.property-value, meta.jsx.js + settings + + foreground + #a2aabc + + + + name + Attributes + scope + entity.other.attribute-name, meta.attribute-with-value.style, constant.other.color.rgb-value, meta.at-rule.media, support.constant.mathematical-symbols, + punctuation.separator.key-value + settings + + foreground + #FFAE57 + + + + name + Inserted + scope + markup.inserted + settings + + foreground + #bae67e + + + + name + Deleted + scope + markup.deleted + settings + + foreground + #5ccfe6 + + + + name + Changed + scope + markup.changed + settings + + foreground + #FFAE57 + + + + name + Regular Expressions and Escape Characters + scope + string.regexp, constant.character.escape + settings + + foreground + #95E6CB + + + + name + URL + scope + *url*, *link*, *uri* + settings + + fontStyle + underline + + + + name + Search Results Nums + scope + constant.numeric.line-number.find-in-files - match + settings + + foreground + #8695b7 + + + + name + Search Results Lines + scope + entity.name.filename.find-in-files + settings + + foreground + #bae67e + + + + name + Decorators + scope + tag.decorator.js entity.name.tag.js, tag.decorator.js punctuation.definition.tag.js + settings + + fontStyle + italic + foreground + #ffd580 + + + + name + ES7 Bind Operator + scope + constant.other.object.key + settings + + foreground + #5ccfe6 + + + + name + entity.name.method + scope + entity.name.method + settings + + fontStyle + italic + foreground + #ffd580 + + + + name + meta.method.js + scope + entity.name.function, variable.function.constructor + settings + + foreground + #ffd580 + + + + name + Markup - Italic + scope + markup.italic + settings + + fontStyle + italic + foreground + #ef6b73 + + + + name + Markup - Bold + scope + markup.bold + settings + + fontStyle + bold + foreground + #ef6b73 + + + + name + Markup - Underline + scope + markup.underline + settings + + fontStyle + underline + foreground + #c3a6ff + + + + name + Markup - Strike + scope + markup.strike + settings + + fontStyle + strike + foreground + #ffd580 + + + + name + Markup - Quote + scope + markup.quote + settings + + fontStyle + italic + foreground + #80D4FF + + + + name + Markup - Raw Block + scope + markup.raw.block + settings + + foreground + #FFAE57 + + + + name + Markup - Table + scope + markup.table + settings + + background + #1d2433aa + foreground + #5ccfe6 + + + + name + Markdown - Plain + scope + text.html.markdown, punctuation.definition.list_item.markdown + settings + + foreground + #a2aabc + + + + name + Markdown - Markup Raw Inline + scope + text.html.markdown markup.raw.inline + settings + + foreground + #5ccfe6 + + + + name + Markdown - Line Break + scope + text.html.markdown meta.dummy.line-break + settings + + foreground + #8695b7 + + + + name + Markdown - Heading + scope + markdown.heading, markup.heading | markup.heading entity.name, markup.heading.markdown punctuation.definition.heading.markdown + settings + + foreground + #bae67e + + + + name + Markdown - Blockquote + scope + markup.quote, punctuation.definition.blockquote.markdown + settings + + fontStyle + italic + foreground + #80D4FF + + + + name + Markdown - Link + scope + string.other.link.title.markdown + settings + + fontStyle + underline + foreground + #ffd580 + + + + name + Markdown - Raw Block Fenced + scope + markup.raw.block.fenced.markdown + settings + + background + #d7dce210 + foreground + #a2aabc + + + + name + Markdown - Fenced Bode Block + scope + punctuation.definition.fenced.markdown, variable.language.fenced.markdown + settings + + background + #d7dce210 + foreground + #8695b7 + + + + name + Markdown - Fenced Language + scope + variable.language.fenced.markdown + settings + + fontStyle + + foreground + #8695b7 + + + + name + Markdown - Separator + scope + meta.separator + settings + + background + #d7dce210 + fontStyle + bold + foreground + #8695b7 + + + + name + JSON Key - Level 0 + scope + source.json meta.structure.dictionary.json string.quoted.double.json - meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta.structure.dictionary.json punctuation.definition.string - meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #5ccfe6 + + + + name + JSON Key - Level 1 + scope + source.json meta meta.structure.dictionary.json string.quoted.double.json - meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta.structure.dictionary.json punctuation.definition.string - meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #5ccfe6 + + + + name + JSON Key - Level 2 + scope + source.json meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #ffae57 + + + + name + JSON Key - Level 3 + scope + source.json meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #5ccfe6 + + + + name + JSON Key - Level 4 + scope + source.json meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #ffae57 + + + + name + JSON Key - Level 5 + scope + source.json meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #5ccfe6 + + + + name + JSON Key - Level 6 + scope + source.json meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #ffae57 + + + + name + JSON Key - Level 7 + scope + source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #5ccfe6 + + + + name + JSON Key - Level 8 + scope + source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #ffae57 + + + + name + AceJump Label - Blue + scope + acejump.label.blue + settings + + background + #5ccfe6 + foreground + #d7dce2 + + + + name + AceJump Label - Green + scope + acejump.label.green + settings + + background + #bae67e + foreground + #d7dce2 + + + + name + AceJump Label - Orange + scope + acejump.label.orange + settings + + background + #FFAE57 + foreground + #d7dce2 + + + + name + AceJump Label - Purple + scope + acejump.label.purple + settings + + background + #ef6b73 + foreground + #d7dce2 + + + + name + SublimeLinter Warning + scope + sublimelinter.mark.warning + settings + + foreground + #5ccfe6 + + + + name + SublimeLinter Gutter Mark + scope + sublimelinter.gutter-mark + settings + + foreground + #d7dce2 + + + + name + SublimeLinter Error + scope + sublimelinter.mark.error + settings + + foreground + #ef6b73 + + + + name + GitGutter Ignored + scope + markup.ignored.git_gutter + settings + + foreground + #8695b7 + + + + name + GitGutter Untracked + scope + markup.untracked.git_gutter + settings + + foreground + #8695b7 + + + + name + GutterColor + scope + gutter_color + settings + + foreground + #d7dce2 + + + + uuid + 0e709986-46a0-40a0-b3bf-c8dfe525c455 + + diff --git a/crates/typst-library/Cargo.toml b/crates/typst-library/Cargo.toml index 86c3ab5aa..da0fae5fa 100644 --- a/crates/typst-library/Cargo.toml +++ b/crates/typst-library/Cargo.toml @@ -39,7 +39,7 @@ rustybuzz = "0.7" serde_json = "1" serde_yaml = "0.8" smallvec = "1.10" -syntect = { version = "5", default-features = false, features = ["parsing", "regex-fancy", "yaml-load"] } +syntect = { version = "5", default-features = false, features = ["parsing", "regex-fancy", "plist-load", "yaml-load"] } time = { version = "0.3.20", features = ["formatting"] } toml = { version = "0.7.3", default-features = false, features = ["parse"] } tracing = "0.1.37" diff --git a/crates/typst-library/src/text/raw.rs b/crates/typst-library/src/text/raw.rs index 4d4cb7102..af1607ab3 100644 --- a/crates/typst-library/src/text/raw.rs +++ b/crates/typst-library/src/text/raw.rs @@ -149,17 +149,39 @@ pub struct RawElem { /// ``` /// ```` #[parse( - let (syntaxes, data) = parse_syntaxes(vm, args)?; + let (syntaxes, syntaxes_data) = parse_syntaxes(vm, args)?; syntaxes )] #[fold] pub syntaxes: SyntaxPaths, - /// The raw file buffers. + /// The raw file buffers of syntax definition files. #[internal] - #[parse(data)] + #[parse(syntaxes_data)] #[fold] - pub data: Vec, + pub syntaxes_data: Vec, + + /// The theme to use for syntax highlighting. Theme files should be in the in the + /// `tmTheme` file format. + /// + /// ````example + /// #set raw(theme: "halcyon.tmTheme") + /// + /// ```typ + /// = Chapter 1 + /// #let hi = "Hello World" + /// ``` + /// ```` + #[parse( + let (theme_path, theme_data) = parse_theme(vm, args)?; + theme_path.map(Some) + )] + pub theme: Option, + + /// The raw file buffer of syntax theme file. + #[internal] + #[parse(theme_data.map(Some))] + pub theme_data: Option, } impl RawElem { @@ -190,16 +212,28 @@ impl Show for RawElem { #[tracing::instrument(name = "RawElem::show", skip_all)] fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { let text = self.text(); - let lang = self.lang(styles).as_ref().map(|s| s.to_lowercase()); - let foreground = THEME + let lang = self + .lang(styles) + .as_ref() + .map(|s| s.to_lowercase()) + .or(Some("txt".into())); + + let extra_syntaxes = UnsyncLazy::new(|| { + load_syntaxes(&self.syntaxes(styles), &self.syntaxes_data(styles)).unwrap() + }); + + let theme = self.theme(styles).map(|theme_path| { + load_theme(theme_path, self.theme_data(styles).unwrap()).unwrap() + }); + + let theme = theme.as_deref().unwrap_or(&THEME); + + let foreground = theme .settings .foreground .map(to_typst) .map_or(Color::BLACK, Color::from); - let extra_syntaxes = - UnsyncLazy::new(|| load(&self.syntaxes(styles), &self.data(styles)).unwrap()); - let mut realized = if matches!(lang.as_deref(), Some("typ" | "typst" | "typc")) { let root = match lang.as_deref() { Some("typc") => syntax::parse_code(&text), @@ -207,7 +241,7 @@ impl Show for RawElem { }; let mut seq = vec![]; - let highlighter = synt::Highlighter::new(&THEME); + let highlighter = synt::Highlighter::new(theme); highlight_themed( &LinkedNode::new(&root), vec![], @@ -229,7 +263,7 @@ impl Show for RawElem { }) }) { let mut seq = vec![]; - let mut highlighter = syntect::easy::HighlightLines::new(syntax, &THEME); + let mut highlighter = syntect::easy::HighlightLines::new(syntax, theme); for (i, line) in text.lines().enumerate() { if i != 0 { seq.push(LinebreakElem::new().pack()); @@ -385,7 +419,7 @@ impl Fold for SyntaxPaths { /// Load a syntax set from a list of syntax file paths. #[comemo::memoize] -fn load(paths: &SyntaxPaths, bytes: &[Bytes]) -> StrResult> { +fn load_syntaxes(paths: &SyntaxPaths, bytes: &[Bytes]) -> StrResult> { let mut out = SyntaxSetBuilder::new(); // We might have multiple sublime-syntax/yaml files @@ -423,11 +457,42 @@ fn parse_syntaxes( .collect::>>()?; // Check that parsing works. - let _ = load(&paths, &data).at(span)?; + let _ = load_syntaxes(&paths, &data).at(span)?; Ok((Some(paths), Some(data))) } +#[comemo::memoize] +fn load_theme(path: EcoString, bytes: Bytes) -> StrResult> { + let mut cursor = std::io::Cursor::new(bytes.as_slice()); + + synt::ThemeSet::load_from_reader(&mut cursor) + .map(Arc::new) + .map_err(|e| eco_format!("failed to parse theme file `{path}`: {e}")) +} + +/// Function to parse the theme argument. +/// Much nicer than having it be part of the `element` macro. +fn parse_theme( + vm: &mut Vm, + args: &mut Args, +) -> SourceResult<(Option, Option)> { + let Some(Spanned { v: path, span }) = + args.named::>("theme")? + else { + return Ok((None, None)); + }; + + // Load theme file. + let id = vm.location().join(&path).at(span)?; + let data = vm.world().file(id).at(span)?; + + // Check that parsing works. + let _ = load_theme(path.clone(), data.clone()).at(span)?; + + Ok((Some(path), Some(data))) +} + /// The syntect syntax definitions. /// /// Code for syntax set generation is below. The `syntaxes` directory is from diff --git a/tests/ref/text/raw-theme.png b/tests/ref/text/raw-theme.png new file mode 100644 index 0000000000000000000000000000000000000000..0ce17760591a68c94c938fe87da28daf176c564e GIT binary patch literal 5380 zcmb7IXHXMNw+=P*-XZj=6p4B30C-`yJedUl z)+#EQW&i*TBid?@9tSOK&+X~bKLZTzU%!%KU!G|KCSQ0{cS>VZz~>D4q@}p^bjq6FBAgRP?8#mcq#|Avb-XxTo%y|Q)aA_#sG?eC#hs~ zf7iJBdN={?bAy}zZl!^;tPJtoo$nhuqoL-lziBhj{F*9|03F#%Mw@@fbbK87P5ylR zct>qLUJetKJ;J;#zi_np8kI`KHsXJXby6QHwUGh2$qF;^puUJ=&Tm-qa6l;4wNp%cX3)ThdoC6`s~@bz?Irzxw?0(>i!HZrUe zUwTKnZiLG=zY%?NUv78cCSa^$Nxi6dyL-`ZZO|Y;((@{;?s_d7TupNBbXYaG{Embb zq^}fm0HZxvJgr{d&M`NzG@}{p);45o`Y@HSPbMo%y!XSyVcWyu6Vmw8Knv2`)6>M? z9ADm!G2tWNxveXifT51oU0xZ>cQlPg76m0rkKIdjOfC$M=@Hqg-Zan>;T4`wT9MXQ zF1rfr8@RHp))U90`kcE}^v1KPWQgstxGQp~2ARd9gHRg265mrtvWJ-HD6!%bo`&cJ zmo;trpgL3HuqYj&m2hmM%--F*d0j+hOQJyI3D@$S1{$2`4I5INLg@oVVGP(Y1L`u4 z9AqbyV#_Mkt?co*S`!FRVC<+7&;&Kx1@VRa zZu?7#bV{-I1tC!W(~Goud?fU=%*1hjE+$09KosFTYYBemVyoik-BlX=Azj5~8-A5U zL;{+!i=h(IZ49}DhPkgTtnO($I~=Y%kaFDnzG4^Hd84I*3NS9z5_rcE4&P=o1MX0K z(;FH^u;55cCZZ&)iYLtHPE>0NF|6I)F{xQXW5u4|(#tJSx>>3kd%K_F<5hqQlCe~q znDS90xiC9Kp!2hhnR#qs4ffHW;tASe;d`04+p*{1-y^w`d3QO;_6b4flelpf^1M}% z+@WU5Pqn2{X|2ho_#ok64m@Mk7I~kF+}z$T43Daagg*5FA#dq>Fu(3vsg2Y@?QIg$n}!I=Mb z(!_OC9Ms@IlSuB1Cv8jU>qch@5RBs!trS7d-_wg6(gRwE2~=Yg8wTlY&;;c$^9)v) zB$OPu3 zcLq;;DIq-|aPXD_rR$XBWy_M#FDt(MDHgxtLEdXrGff;uVGeVC;A_IB>$7g*WyK4m zC>gWNsgC)lE2*f8JjuF`c{980>(lBN*S%}MUh7q~UfB;?427|b1I|SYxV~*#RhWVNWM>T8Mc$lgO9?1Q@CkmJ^Sk|= zy4Pz)vz}8{h<>IR{iX?38yzfwNP`K~_jcnU?zglVu68_Ft}>bqXcVt+B^usPn7mqa z6IGapZ5*vLucRUkN%9C5;Y7+BD1F7FIx(9(aywCkDIYWgLOkI8P3`iI*1?`8-hBf6 zp2ccQ_(B2Td-BX@`hs^xUj)Z9db3tL1C9@OU?r{C*{euQRGZ*JvsaPD)vjxS_kcYG zTg98md70+S^5}cW8=*BXiOo&-P^D1i-I|@6-*JyU<@82GU&~nb9l-2Oi|yU`V>JSd z!pc#x#bvz1Dk1UW)DxkGG{o%(Wl8xtD=4lH%1LQ%Pv+-lo?-ji zem$tv4gVP%gi0XMa1m1NyxixCA4Bk}Q6#j-iaILtD*(8ne0 z($j-*i4tN1**jqos>Q~Pl;by;v7P;19T)VE>!?>-?Ak)JyXNStqB~9oniILM#SZSS z>eEUc`$jMxMlM(7=_Txl8ewt0wG?Pr|GoOf>-M~jY|oq%H-GNDp?I0E{OtTM8z)>N zmqbCR$@=S-!8MGDsEb`>1{7WxXdm*7V52+=%c|iAkFuFDpD7|E?USJ!BAxv_eYzn& zj3$4zpsJ^)0$zlH(=vv3YqeHVrF=!Y!Z6_*2&-&)r^@N^xQDYQ4_Elp4Rl8Os>&1G)qU#fAxRrP&MMk6CPsJNa;lqyp2 zyHoNTr*CiLkcgL5fLkxY{}O--SPKaFZ{M{npg0Br0BisN6OX_EE#Q96Hm=A_NCuoey-*_D@pne&fD>E`}?pTw65@0}ez9&CGlkn~TM z($z|<>`m97C3hW07j!^7-y_n`$FGJ4Z1_gT zb2!dBTYbjatvh1U6BnWIO(Dzh&J(XaEVD7NPm;f*k_q6`Oz^$ zY1o7$l7{E1wa@`7_gki=zvU0+9EpSup}3~>meYmYCDoDKOFg?31{GqwI!q#FNb}2@ zOb40-6H^l%i;D1J_XK$gvlpgSp21ATMq2Q`JUtZEgg`DZ%#C$S2!T^4%L~idgx2nC zpwkCcPb31t&+!L*a6eeM1#vUnjASiSdt#{~4OKlAH4x?K3=e)Oh>PP*mfll;H+>g% zRcehlYD0QWxz{W2XkSS&ds_r^Kv{fE)7%Go-v_^GoH%>GnNEZF@S;)V`4Tu22`!L% zx20uNxh#939{0EwO^$dht1KTab&3|nQ~NBjDPDKJIHA)%fsz2OijnScF#@;%HqLPb z9XRD52D{aC3gdgm6Jj`+b+U&u#!rnM|EezEma8 zlL|m(d{(FKeqg1_vt0CQWL;2e?3j3$wW-Gx<05PRo^-!fq_*u+h~~Xj85S(h%C@)7 zo=*v`lZ53PgFdQ@L9h9U5f(xnjfx)?shLS}w#r-9#Ez;@u-7R^ttw^SnoWx?>NGA> zRB)158}+PoOotqGCg5vszo`!;(L&SegSx;2ZZ?RW&n(2q>h~S6UX39 z4OHs$Tv}%fO@4ds&hicZmsWuZKLcHuBB?j?pYI4mc1dT-DLs0Ny3~d61g~Dvx`@&Zx zw35rI?U;7j98ESlBaMWhrNsB&330L6=AF`?Is~P zKp#cL8Jp1Zp`Xu}w_VO{q=orW3pKV%d@CaBNA9IF-$c1^+)oo55w^~pcd72*YM3zE z5`KF5aeTmWAS8#rx$+N&te%Dje8d}<0=ctj;*7xkv^?~m98@GnPy=^B@J_8TLzBWn zzwYsw^bDGik0~0|15RnII2thn(W)(`>fEvKp|o#7t^PU0n8_X+CUJ@;TSxc{usXuv zu_d()8@z}w2e-C|U>)}>DwiFM&`TQec4kq)FolIEkW5C@d;nF{U z*nRNq)n2n?P3=}aQevhrVSV9ZBHTrzCl@k+BgAuw|JL=WtYWW$D{KmUWTh+JU8XuI zU8st~{Kx3yXu#6{G4!{D9=yj1W*7Y;;~}60R! zdS))nxt#5Raii@}m9XK<%Rcgx#KeBJgaDV-2h8yHjuxDkk|Yk&<)=v^64`b0Kr-lN z^!2)jf8mo7n8O%f5Gv)#-K(p9cX8ruOt1s47{{onOS!xgxLYM1-ac!dS<(Na2YlEW zvA6?gKOw_$R7NTsxDH+0IlezD@!KXU4j@XNQ~GY7@I>qpV0C0{T~=U>B+s}vPA)b5 z!FjPp#bXD43%Z9gMh9n}zvDXl@p;=V!MAZ;4K z;(?Eld@8JhE`hPe*AbJ{Q(8dIH{>x8;=3DWCA)+cC^$UQp%jlAe~x@z6)lKZBg-rI zuiJrtf09A!IB!~PHrOvx{Bck>qRye?7D>CP!*~M?~X88DD$r=QscZ#$862*Iu&UNh-0|H<7&~ zYR%jx(y;Boy5z0g8*zAf zH({;nOl5t_1o2jk(uGoHxHI?0Z-C+-OSMwI(%V~Kcu0)FQ$r^! zgoZzNxwJk@SJUlp(c@JOh&f%*+uN8b_8>%DKUn$vMGZMY!N>}i4JuJ)C8Y|h5Mw?6 z>Jb8V6LC%#BYCMQYRQ&jbE+z-9Y#dM6{8Oz zm!C@Y2qich{@I*%Gb$aU1q%f*1?84vN|7n@y$GDEG#jCCEkOR;BoD$BvYuQR1E)C1 z4yvFyPhojvl~JlcF=XJ`(C~Z9IhKlwlY|APPu37cScZO4+_1J~;Y)WfdhE9C*MIf% zEaz4*GRu9P`&+vYrfHI~a)0*17{2tB7@-zLz_IN<0@;lb!7U*9$^$nEpq6QN8n_?7 za0lF1!4Xv=gn4OSnhx5$30IhYv$rXnF__)HIhUcn-vd_V%sgfX`~E$-6J-!eoU=gk z&T1bC{9&TINAVKjk?nZ`ot{V%y~L^R%mCk~*Zv(BQZ z3?-tj@3ZP7!N}qx$z=JjLOws0cI}UQOx(Qi_PMR>D%LQs5J4xmF;e%lp8xcB*~4+c zf|wL)78HTnUaR!7;Mz}Y%NRW*Uoq(*FF_wu5W86@xgmy>jn@1HbXm8 zVH3+r*uJpxeTxUFW$Gt=T;aE^ilEwj{4`?nD7wMQ!TKdeZ{su6=a&XD04 z7E7gW#PD}{i>Z!yOrdZhFXBJQW~K!*s2lxN{KX+J1XU!Q_*ZcYcevVZzWQIqUyLP@ rfD(&@Zx!V?insZ { + set text(fill: rgb("a2aabc")) + rect( + width: 100%, + inset: (x: 4pt, y: 5pt), + radius: 4pt, + fill: rgb("1d2433"), + place(right, text(luma(240), it.lang)) + it, + ) +} + +```typ += Chapter 1 +#lorem(100) + +#let hi = "Hello World" +#show heading: emph +```