From b44f0b9f92074e577e9dfccbb70ec41fba20bbcb Mon Sep 17 00:00:00 2001 From: 8LWXpg <105704427+8LWXpg@users.noreply.github.com> Date: Sun, 10 Dec 2023 18:45:28 +0800 Subject: [PATCH 1/3] ansi-render:0.6.0 (#282) --- .../preview/ansi-render/0.6.0/CHANGELOG.md | 76 +++ packages/preview/ansi-render/0.6.0/LICENSE | 21 + packages/preview/ansi-render/0.6.0/README.md | 120 ++++ .../preview/ansi-render/0.6.0/ansi-render.typ | 547 ++++++++++++++++++ packages/preview/ansi-render/0.6.0/typst.toml | 10 + 5 files changed, 774 insertions(+) create mode 100644 packages/preview/ansi-render/0.6.0/CHANGELOG.md create mode 100644 packages/preview/ansi-render/0.6.0/LICENSE create mode 100644 packages/preview/ansi-render/0.6.0/README.md create mode 100644 packages/preview/ansi-render/0.6.0/ansi-render.typ create mode 100644 packages/preview/ansi-render/0.6.0/typst.toml diff --git a/packages/preview/ansi-render/0.6.0/CHANGELOG.md b/packages/preview/ansi-render/0.6.0/CHANGELOG.md new file mode 100644 index 000000000..fd713e635 --- /dev/null +++ b/packages/preview/ansi-render/0.6.0/CHANGELOG.md @@ -0,0 +1,76 @@ +# Changelog 📝 + +## [0.6.0] - 2023-12-06 + +### Fixed + +* Removed workaround for a bug in `raw` that fixed in Typst 0.10.0 + +## [0.5.1] - 2023-10-21 + +### Fixed + +* Fixed height with empty newline + +## [0.5.0] - 2023-09-29 + +### Added + +* Added `bold-is-bright` option #2 + +### Changed + +* Allow setting font to none #3 +* Changed default font size to `1em` +* Use `raw` to render content now + +### Fixed + +* Fixed 8-bit colors 8-15 use the wrong colors #4 + +## [0.4.2] - 2023-09-24 + +### Added + +* Added gruvbox themes #1 + +## [0.4.1] - 2023-09-22 + +### Changed + +* Changed default font size to `9pt` +* Prevent `set` affects box layout from outside of the function + +## [0.4.0] - 2023-09-13 + +### Added + +* Added most options from [`block`]([https://](https://typst.app/docs/reference/layout/block/)) function with the same names and default values +* Added `vscode-light` theme + +### Changed + +* Changed outmost layout from `rect` to `block` +* Changed default theme to `vscode-light` + +## [0.3.0] - 2023-09-09 + +### Added + +* Added `radius` option, default is `3pt` + +### Changed + +* Changed default font size to `10pt` +* Changed default font to `Cascadia Code` +* Changed default theme to `solarized-light` + +## [0.2.0] 2023-08-05 + +### Changed + +* Changed coding style to kebab-case and two spaces + +## [0.1.0] 2023-07-02 + +first release diff --git a/packages/preview/ansi-render/0.6.0/LICENSE b/packages/preview/ansi-render/0.6.0/LICENSE new file mode 100644 index 000000000..a05c753ce --- /dev/null +++ b/packages/preview/ansi-render/0.6.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 8LWXpg + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/preview/ansi-render/0.6.0/README.md b/packages/preview/ansi-render/0.6.0/README.md new file mode 100644 index 000000000..a4d46f056 --- /dev/null +++ b/packages/preview/ansi-render/0.6.0/README.md @@ -0,0 +1,120 @@ +# ANSI Escape Sequence Renderer + + + GitHub manifest version (path) + + + GitHub Repo stars + + + GitHub + + + typst package + + +This script provides a simple way to render text with ANSI escape sequences. Package `ansi-render` provides a function `ansi-render`, and a dictionary of themes `terminal-themes`. + +contribution is welcomed! + +## Usage + +```typst +#import "@preview/ansi-render:0.6.0": * + +#ansi-render( + string, + font: string or none, + size: length, + width: auto or relative length, + height: auto or relative length, + breakable: boolean, + radius: relative length or dictionary, + inset: relative length or dictionary, + outset: relative length or dictionary, + spacing: relative length or fraction, + above: relative length or fraction, + below: relative length or fraction, + clip: boolean, + bold-is-bright: boolean, + theme: terminal-themes.theme, +) +``` + +### Parameters + +- `string` - string with ANSI escape sequences +- `font` - font name or none, default is `Cascadia Code`, set to `none` to use the same font as `raw` +- `size` - font size, default is `1em` +- `bold-is-bright` - boolean, whether bold text is rendered with bright colors, default is `false` +- `theme` - theme, default is `vscode-light` +- parameters from [`block`](https://typst.app/docs/reference/layout/block/) function with the same default value, only affects outmost block layout: + - `width` + - `height` + - `breakable` + - `radius` + - `inset` + - `outset` + - `spacing` + - `above` + - `below` + - `clip` + +## Themes + +see [themes](https://github.com/8LWXpg/typst-ansi-render/blob/master/test/themes.pdf) + +## Demo + +see [demo.typ](https://github.com/8LWXpg/typst-ansi-render/blob/master/test/demo.typ) [demo.pdf](https://github.com/8LWXpg/typst-ansi-render/blob/master/test/demo.pdf) + +```typst +#ansi-render( +"\u{1b}[38;2;255;0;0mThis text is red.\u{1b}[0m +\u{1b}[48;2;0;255;0mThis background is green.\u{1b}[0m +\u{1b}[38;2;255;255;255m\u{1b}[48;2;0;0;255mThis text is white on a blue background.\u{1b}[0m +\u{1b}[1mThis text is bold.\u{1b}[0m +\u{1b}[4mThis text is underlined.\u{1b}[0m +\u{1b}[38;2;255;165;0m\u{1b}[48;2;255;255;0mThis text is orange on a yellow background.\u{1b}[0m", +inset: 5pt, radius: 3pt, +theme: terminal-themes.vscode +) +``` + +![1.png](https://github.com/8LWXpg/typst-ansi-render/blob/master/img/1.png) + +```typst +#ansi-render( +"\u{1b}[38;5;196mRed text\u{1b}[0m +\u{1b}[48;5;27mBlue background\u{1b}[0m +\u{1b}[38;5;226;48;5;18mYellow text on blue background\u{1b}[0m +\u{1b}[7mInverted text\u{1b}[0m +\u{1b}[38;5;208;48;5;237mOrange text on gray background\u{1b}[0m +\u{1b}[38;5;39;48;5;208mBlue text on orange background\u{1b}[0m +\u{1b}[38;5;255;48;5;0mWhite text on black background\u{1b}[0m", +inset: 5pt, radius: 3pt, +theme: terminal-themes.vscode +) +``` + +![2.png](https://github.com/8LWXpg/typst-ansi-render/blob/master/img/2.png) + +```typst +#ansi-render( +"\u{1b}[31;1mHello \u{1b}[7mWorld\u{1b}[0m + +\u{1b}[53;4;36mOver and \u{1b}[35m Under! +\u{1b}[7;90mreverse\u{1b}[101m and \u{1b}[94;27mreverse", +inset: 5pt, radius: 3pt, +theme: terminal-themes.vscode +) +``` + +![3.png](https://github.com/8LWXpg/typst-ansi-render/blob/master/img/3.png) + +```typst +// uses the font that supports ligatures +#ansi-render(read("test.txt"), inset: 5pt, radius: 3pt, font: "Cascadia Code", theme: terminal-themes.putty) +``` + +![4.png](https://github.com/8LWXpg/typst-ansi-render/blob/master/img/4.png) diff --git a/packages/preview/ansi-render/0.6.0/ansi-render.typ b/packages/preview/ansi-render/0.6.0/ansi-render.typ new file mode 100644 index 000000000..95a26cbbb --- /dev/null +++ b/packages/preview/ansi-render/0.6.0/ansi-render.typ @@ -0,0 +1,547 @@ +// add your theme here! +#let terminal-themes = ( + // vscode terminal theme + vscode: ( + black: rgb(0, 0, 0), + red: rgb(205, 49, 49), + green: rgb(13, 188, 121), + yellow: rgb(229, 229, 16), + blue: rgb(36, 114, 200), + magenta: rgb(188, 63, 188), + cyan: rgb(17, 168, 205), + white: rgb(229, 229, 229), + gray: rgb(102, 102, 102), + bright-red: rgb(214, 76, 76), + bright-green: rgb(35, 209, 139), + bright-yellow: rgb(245, 245, 67), + bright-blue: rgb(59, 142, 234), + bright-magenta: rgb(214, 112, 214), + bright-cyan: rgb(41, 184, 219), + bright-white: rgb(229, 229, 229), + default-text: rgb(229, 229, 229), // white + default-bg: rgb(0, 0, 0), // black + ), + // vscode light theme + vscode-light: ( + black: rgb("#F8F8F8"), + red: rgb("#CD3131"), + green: rgb("#00BC00"), + yellow: rgb("#949800"), + blue: rgb("#0451A5"), + magenta: rgb("#BC05BC"), + cyan: rgb("#0598BC"), + white: rgb("#555555"), + gray: rgb("#666666"), + bright-red: rgb("#CD3131"), + bright-green: rgb("#14CE14"), + bright-yellow: rgb("#B5BA00"), + bright-blue: rgb("#0451A5"), + bright-magenta: rgb("#BC05BC"), + bright-cyan: rgb("#0598BC"), + bright-white: rgb("#A5A5A5"), + default-text: rgb("#A5A5A5"), // white + default-bg: rgb("#F8F8F8"), // black + ), + // putty terminal theme + putty: ( + black: rgb(0, 0, 0), + red: rgb(187, 0, 0), + green: rgb(0, 187, 0), + yellow: rgb(187, 187, 0), + blue: rgb(0, 0, 187), + magenta: rgb(187, 0, 187), + cyan: rgb(0, 187, 187), + white: rgb(187, 187, 187), + gray: rgb(85, 85, 85), + bright-red: rgb(255, 0, 0), + bright-green: rgb(0, 255, 0), + bright-yellow: rgb(255, 255, 0), + bright-blue: rgb(0, 0, 255), + bright-magenta: rgb(255, 0, 255), + bright-cyan: rgb(0, 255, 255), + bright-white: rgb(255, 255, 255), + default-text: rgb(187, 187, 187), // white + default-bg: rgb(0, 0, 0), // black + ), + // themes from Windows Terminal + campbell: ( + black: rgb("#0C0C0C"), + red: rgb("#C50F1F"), + green: rgb("#13A10E"), + yellow: rgb("#C19C00"), + blue: rgb("#0037DA"), + magenta: rgb("#881798"), + cyan: rgb("#3A96DD"), + white: rgb("#CCCCCC"), + gray: rgb("#767676"), + bright-red: rgb("#E74856"), + bright-green: rgb("#16C60C"), + bright-yellow: rgb("#F9F1A5"), + bright-blue: rgb("#3B78FF"), + bright-magenta: rgb("#B4009E"), + bright-cyan: rgb("#61D6D6"), + bright-white: rgb("#F2F2F2"), + default-text: rgb("#CCCCCC"), + default-bg: rgb("#0C0C0C"), + ), + campbell-powershell: ( + black: rgb("#0C0C0C"), + red: rgb("#C50F1F"), + green: rgb("#13A10E"), + yellow: rgb("#C19C00"), + blue: rgb("#0037DA"), + magenta: rgb("#881798"), + cyan: rgb("#3A96DD"), + white: rgb("#CCCCCC"), + gray: rgb("#767676"), + bright-red: rgb("#E74856"), + bright-green: rgb("#16C60C"), + bright-yellow: rgb("#F9F1A5"), + bright-blue: rgb("#3B78FF"), + bright-magenta: rgb("#B4009E"), + bright-cyan: rgb("#61D6D6"), + bright-white: rgb("#F2F2F2"), + default-text: rgb("#CCCCCC"), + default-bg: rgb("#012456"), + ), + vintage: ( + black: rgb("#000000"), + red: rgb("#800000"), + green: rgb("#008000"), + yellow: rgb("#808000"), + blue: rgb("#000080"), + magenta: rgb("#800080"), + cyan: rgb("#008080"), + white: rgb("#C0C0C0"), + gray: rgb("#808080"), + bright-red: rgb("#FF0000"), + bright-green: rgb("#00FF00"), + bright-yellow: rgb("#FFFF00"), + bright-blue: rgb("#0000FF"), + bright-magenta: rgb("#FF00FF"), + bright-cyan: rgb("#00FFFF"), + bright-white: rgb("#FFFFFF"), + default-text: rgb("#C0C0C0"), + default-bg: rgb("#000000"), + ), + one-half-dark: ( + black: rgb("#282C34"), + red: rgb("#E06C75"), + green: rgb("#98C379"), + yellow: rgb("#E5C07B"), + blue: rgb("#61AFEF"), + magenta: rgb("#C678DD"), + cyan: rgb("#56B6C2"), + white: rgb("#DCDFE4"), + gray: rgb("#5A6374"), + bright-red: rgb("#E06C75"), + bright-green: rgb("#98C379"), + bright-yellow: rgb("#E5C07B"), + bright-blue: rgb("#61AFEF"), + bright-magenta: rgb("#C678DD"), + bright-cyan: rgb("#56B6C2"), + bright-white: rgb("#DCDFE4"), + default-text: rgb("#DCDFE4"), + default-bg: rgb("#282C34"), + ), + one-half-light: ( + black: rgb("#383A42"), + red: rgb("#E45649"), + green: rgb("#50A14F"), + yellow: rgb("#C18301"), + blue: rgb("#0184BC"), + magenta: rgb("#A626A4"), + cyan: rgb("#0997B3"), + white: rgb("#FAFAFA"), + gray: rgb("#4F525D"), + bright-red: rgb("#DF6C75"), + bright-green: rgb("#98C379"), + bright-yellow: rgb("#E4C07A"), + bright-blue: rgb("#61AFEF"), + bright-magenta: rgb("#C577DD"), + bright-cyan: rgb("#56B5C1"), + bright-white: rgb("#FFFFFF"), + default-text: rgb("#383A42"), + default-bg: rgb("#FAFAFA"), + ), + solarized-dark: ( + black: rgb("#002B36"), + red: rgb("#DC322F"), + green: rgb("#859900"), + yellow: rgb("#B58900"), + blue: rgb("#268BD2"), + magenta: rgb("#D33682"), + cyan: rgb("#2AA198"), + white: rgb("#EEE8D5"), + gray: rgb("#073642"), + bright-red: rgb("#CB4B16"), + bright-green: rgb("#586E75"), + bright-yellow: rgb("#657B83"), + bright-blue: rgb("#839496"), + bright-magenta: rgb("#6C71C4"), + bright-cyan: rgb("#93A1A1"), + bright-white: rgb("#FDF6E3"), + default-text: rgb("#839496"), + default-bg: rgb("#002B36"), + ), + solarized-light: ( + black: rgb("#002B36"), + red: rgb("#DC322F"), + green: rgb("#859900"), + yellow: rgb("#B58900"), + blue: rgb("#268BD2"), + magenta: rgb("#D33682"), + cyan: rgb("#2AA198"), + white: rgb("#EEE8D5"), + gray: rgb("#073642"), + bright-red: rgb("#CB4B16"), + bright-green: rgb("#586E75"), + bright-yellow: rgb("#657B83"), + bright-blue: rgb("#839496"), + bright-magenta: rgb("#6C71C4"), + bright-cyan: rgb("#93A1A1"), + bright-white: rgb("#FDF6E3"), + default-text: rgb("#657B83"), + default-bg: rgb("#FDF6E3"), + ), + tango-dark: ( + black: rgb("#000000"), + red: rgb("#CC0000"), + green: rgb("#4E9A06"), + yellow: rgb("#C4A000"), + blue: rgb("#3465A4"), + magenta: rgb("#75507B"), + cyan: rgb("#06989A"), + white: rgb("#D3D7CF"), + gray: rgb("#555753"), + bright-red: rgb("#EF2929"), + bright-green: rgb("#8AE234"), + bright-yellow: rgb("#FCE94F"), + bright-blue: rgb("#729FCF"), + bright-magenta: rgb("#AD7FA8"), + bright-cyan: rgb("#34E2E2"), + bright-white: rgb("#EEEEEC"), + default-text: rgb("#D3D7CF"), + default-bg: rgb("#000000"), + ), + tango-light: ( + black: rgb("#000000"), + red: rgb("#CC0000"), + green: rgb("#4E9A06"), + yellow: rgb("#C4A000"), + blue: rgb("#3465A4"), + magenta: rgb("#75507B"), + cyan: rgb("#06989A"), + white: rgb("#D3D7CF"), + gray: rgb("#555753"), + bright-red: rgb("#EF2929"), + bright-green: rgb("#8AE234"), + bright-yellow: rgb("#FCE94F"), + bright-blue: rgb("#729FCF"), + bright-magenta: rgb("#AD7FA8"), + bright-cyan: rgb("#34E2E2"), + bright-white: rgb("#EEEEEC"), + default-text: rgb("#555753"), + default-bg: rgb("#FFFFFF"), + ), + gruvbox-dark: ( + black: rgb("#282828"), + red: rgb("#cc241d"), + green: rgb("#98971a"), + yellow: rgb("#d79921"), + blue: rgb("#458588"), + magenta: rgb("#b16286"), + cyan: rgb("#689d6a"), + white: rgb("#ebdbb2"), + gray: rgb("#928374"), + bright-red: rgb("#fb4934"), + bright-green: rgb("#b8bb26"), + bright-yellow: rgb("#fabd2f"), + bright-blue: rgb("#83a598"), + bright-magenta: rgb("#d3869b"), + bright-cyan: rgb("#8ec07c"), + bright-white: rgb("#ebdbb2"), + default-text: rgb("#ebdbb2"), + default-bg: rgb("#282828"), + ), + gruvbox-light: ( + black: rgb("#3c3836"), + red: rgb("#cc241d"), + green: rgb("#98971a"), + yellow: rgb("#d79921"), + blue: rgb("#458588"), + magenta: rgb("#b16286"), + cyan: rgb("#689d6a"), + white: rgb("#fbf1c7"), + gray: rgb("#7c6f64"), + bright-red: rgb("#9d0006"), + bright-green: rgb("#79740e"), + bright-yellow: rgb("#b57614"), + bright-blue: rgb("#076678"), + bright-magenta: rgb("#8f3f71"), + bright-cyan: rgb("#427b58"), + bright-white: rgb("#fbf1c7"), + default-text: rgb("#3c3836"), + default-bg: rgb("#fbf1c7"), + ), +) + +// ansi rendering function +#let ansi-render( + body, + font: "Cascadia Code", + size: 1em, + width: auto, + height: auto, + breakable: true, + radius: 0pt, + inset: 0pt, + outset: 0pt, + spacing: 1.2em, + above: 1.2em, + below: 1.2em, + clip: false, + bold-is-bright: false, + theme: terminal-themes.vscode-light, +) = { + // dict with text style + let match-text = ( + "1": (weight: "bold"), + "3": (style: "italic"), + "23": (style: "normal"), + "30": (fill: theme.black), + "31": (fill: theme.red), + "32": (fill: theme.green), + "33": (fill: theme.yellow), + "34": (fill: theme.blue), + "35": (fill: theme.magenta), + "36": (fill: theme.cyan), + "37": (fill: theme.white), + "39": (fill: theme.default-text), + "90": (fill: theme.gray), + "91": (fill: theme.bright-red), + "92": (fill: theme.bright-green), + "93": (fill: theme.bright-yellow), + "94": (fill: theme.bright-blue), + "95": (fill: theme.bright-magenta), + "96": (fill: theme.bright-cyan), + "97": (fill: theme.bright-white), + "default": (weight: "regular", style: "normal", fill: theme.default-text), + ) + // dict with background style + let match-bg = ( + "40": (fill: theme.black), + "41": (fill: theme.red), + "42": (fill: theme.green), + "43": (fill: theme.yellow), + "44": (fill: theme.blue), + "45": (fill: theme.magenta), + "46": (fill: theme.cyan), + "47": (fill: theme.white), + "49": (fill: theme.default-bg), + "100": (fill: theme.gray), + "101": (fill: theme.bright-red), + "102": (fill: theme.bright-green), + "103": (fill: theme.bright-yellow), + "104": (fill: theme.bright-blue), + "105": (fill: theme.bright-magenta), + "106": (fill: theme.bright-cyan), + "107": (fill: theme.bright-white), + "default": (fill: theme.default-bg), + ) + + // match for regex parsed options + // input: array of options in string inside escape sequence + // return: a dict with text and background style + let match-options(opt) = { + // parse 38;5 48;5 + let parse-8bit-color(num) = { + num = int(num) + let colors = (0, 95, 135, 175, 215, 255) + if num <= 7 { match-text.at(str(num + 30)) } + else if num <= 15 { match-text.at(str(num + 82)) } + else if num <= 231 { + num -= 16 + (fill: rgb( + colors.at(int(num / 36)), + colors.at(calc.rem(int(num / 6), 6)), + colors.at(calc.rem(num, 6)), + )) + } else { + num -= 232 + (fill: rgb(8 + 10 * num, 8 + 10 * num, 8 + 10 * num)) + } + } + + let (opt-text, opt-bg) = ((:), (:)) + let (ul, ol, rev, last) = (none, none, none, none) + let count = 0 + let color = (0, 0, 0) + + // match options + for i in opt { + if last == "382" or last == "482" { + color.at(count) = int(i) + count += 1 + if count == 3 { + if last == "382" { opt-text += (fill: rgb(..color)) } + else { opt-bg += (fill: rgb(..color)) } + count = 0 + last = none + } + continue + } + else if last == "385" { + opt-text += parse-8bit-color(i) + last = none + continue + } + else if last == "485" { + opt-bg += parse-8bit-color(i) + last = none + continue + } + else if i == "0" { + opt-text += match-text.default + opt-bg += match-bg.default + ul = false + ol = false + rev = false + } + else if i in match-bg.keys() { opt-bg += match-bg.at(i) } + else if i in match-text.keys() { opt-text += match-text.at(i) } + else if i == "4" { ul = true } + else if i == "24" { ul = false } + else if i == "53" { ol = true } + else if i == "55" { ol = false } + else if i == "7" { rev = true } + else if i == "27" { rev = false } + else if i == "38" or i == "48" { + last = i + continue + } + else if i == "2" or i == "5" { + if last == "38" or last == "48" { + last += i + count = 0 + continue + } + } + last = none + } + (text: opt-text, bg: opt-bg, ul: ul, ol: ol, rev: rev) + } + + // parse escape sequence + // return: array of (str, options) + // str is split by newline + let parse-option(body) = { + let arr = () + let cur = 0 + for map in body.matches(regex("\x1b\[([0-9;]*)m([^\x1b]*)")) { + // loop through all matches + let str = map.captures.at(1) + // split the string by newline and preserve newline + let split = str.split("\n") + for (k, v) in split.enumerate() { + if k != split.len() - 1 { + v = v + "\n" + } + let temp = (v, ()) + for option in map.captures.at(0).split(";") { + temp.at(1).push(option) + } + arr.push(temp) + } + cur += 1 + } + arr + } + + // prevent set from outside of the function + set box( + width: auto, + height: auto, + baseline: 0pt, + fill: none, + stroke: none, + radius: 0pt, + inset: 0pt, + outset: 0pt, + clip: false, + ) + + // settings + show raw: if font == none { + text.with(top-edge: "ascender", bottom-edge: "descender") + } else { + text.with(font: font, top-edge: "ascender", bottom-edge: "descender") + } + set text(..(match-text.default), size: size) + set par(leading: 0em) + show: block.with( + ..(match-bg.default), + width: width, + height: height, + breakable: breakable, + radius: radius, + inset: inset, + outset: outset, + spacing: spacing, + above: above, + below: below, + clip: clip, + ) + + // current option + let option = ( + text: match-text.default, + bg: match-bg.default, + ul: false, + ol: false, + rev: false, + ) + // work around for rendering first line without escape sequence + body = "\u{1b}[0m" + body + // work around for one trailing newline consumed by raw + if body.ends-with("\n") { + body = body + "\n" + } + for (str, opt) in parse-option(body) { + let m = match-options(opt) + option.text += m.text + option.bg += m.bg + if m.rev != none { option.rev = m.rev } + if option.rev { (option.text.fill, option.bg.fill) = (option.bg.fill, option.text.fill) } + if option.text.weight == "bold" and bold-is-bright { + option.text.fill = if option.text.fill == theme.black { theme.gray } + else if option.text.fill == theme.red { theme.bright-red } + else if option.text.fill == theme.green { theme.bright-green } + else if option.text.fill == theme.yellow { theme.bright-yellow } + else if option.text.fill == theme.blue { theme.bright-blue } + else if option.text.fill == theme.magenta { theme.bright-magenta } + else if option.text.fill == theme.cyan { theme.bright-cyan } + else if option.text.fill == theme.white { theme.bright-white } + else { option.text.fill } + } + if m.ul != none { option.ul = m.ul } + if m.ol != none { option.ol = m.ol } + + // work around for trailing whitespace with under/overline + str = str.replace(regex("([ \t]+)$"), m => m.captures.at(0) + "\u{200b}") + { + show: box.with(..option.bg) + set text(..option.text) + show: c => if option.ul { underline(c) } else { c } + show: c => if option.ol { overline(c) } else { c } + raw(str) + } + // fill trailing newlines + let s = str.find(regex("\n+$")) + if s != none { + for i in s { + linebreak() + } + } + } +} diff --git a/packages/preview/ansi-render/0.6.0/typst.toml b/packages/preview/ansi-render/0.6.0/typst.toml new file mode 100644 index 000000000..e6c5f33ba --- /dev/null +++ b/packages/preview/ansi-render/0.6.0/typst.toml @@ -0,0 +1,10 @@ +[package] +name = "ansi-render" +version = "0.6.0" +entrypoint = "ansi-render.typ" +authors = ["8LWXpg"] +license = "MIT" +description = "provides a simple way to render text with ANSI escape sequences." +repository = "https://github.com/8LWXpg/typst-ansi-render" +keywords = ["ansi", "escape", "terminal"] +compiler = "0.10.0" From f90de1e9ae014af021b7f7cff8e596c0c0028a30 Mon Sep 17 00:00:00 2001 From: gavales <35015294+gavales@users.noreply.github.com> Date: Sun, 10 Dec 2023 02:51:32 -0800 Subject: [PATCH 2/3] Fix link to Amazon in `cartao` README.md (#288) --- packages/preview/cartao/0.1.0/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/preview/cartao/0.1.0/README.md b/packages/preview/cartao/0.1.0/README.md index 96f1b0a9c..b3ff10807 100644 --- a/packages/preview/cartao/0.1.0/README.md +++ b/packages/preview/cartao/0.1.0/README.md @@ -83,7 +83,7 @@ Defines a card by updating the below `counter` and `state`(s), and dropping a Date: Sun, 10 Dec 2023 11:55:19 +0100 Subject: [PATCH 3/3] droplet:0.1.0 (#290) --- packages/preview/droplet/0.1.0/LICENSE | 22 + packages/preview/droplet/0.1.0/README.md | 72 ++ .../0.1.0/assets/example-transform.svg | 239 +++++++ .../0.1.0/assets/example-transform.typ | 35 + .../preview/droplet/0.1.0/assets/example.svg | 676 ++++++++++++++++++ .../preview/droplet/0.1.0/assets/example.typ | 31 + .../preview/droplet/0.1.0/src/droplet.typ | 282 ++++++++ packages/preview/droplet/0.1.0/src/lib.typ | 1 + packages/preview/droplet/0.1.0/typst.toml | 13 + 9 files changed, 1371 insertions(+) create mode 100644 packages/preview/droplet/0.1.0/LICENSE create mode 100644 packages/preview/droplet/0.1.0/README.md create mode 100644 packages/preview/droplet/0.1.0/assets/example-transform.svg create mode 100644 packages/preview/droplet/0.1.0/assets/example-transform.typ create mode 100644 packages/preview/droplet/0.1.0/assets/example.svg create mode 100644 packages/preview/droplet/0.1.0/assets/example.typ create mode 100644 packages/preview/droplet/0.1.0/src/droplet.typ create mode 100644 packages/preview/droplet/0.1.0/src/lib.typ create mode 100644 packages/preview/droplet/0.1.0/typst.toml diff --git a/packages/preview/droplet/0.1.0/LICENSE b/packages/preview/droplet/0.1.0/LICENSE new file mode 100644 index 000000000..05bc588e4 --- /dev/null +++ b/packages/preview/droplet/0.1.0/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2023 Eric Biedert + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/preview/droplet/0.1.0/README.md b/packages/preview/droplet/0.1.0/README.md new file mode 100644 index 000000000..9db9c7df4 --- /dev/null +++ b/packages/preview/droplet/0.1.0/README.md @@ -0,0 +1,72 @@ +# droplet +A package for creating dropped capitals in typst. + +## Usage +The package comes with a single `dropcap` function that takes content and a few optional parameters. The first letter of the content will be shown as a dropped capital, while the rest of the content will be wrapped around it. The parameters are as follows: + +| Parameter | Description | Default | +|------------------|----------------------------------------------------------|---------| +| `height` | The height of the dropped capital in lines or as length. | `2` | +| `justify` | Whether the text should be justified. | `false` | +| `gap` | The space between the first letter and the text. | `0pt` | +| `hanging-indent` | The indent of lines after the first. | `0pt` | +| `transform` | A function to be applied to the first letter. | `none` | +| `..text-args` | Arguments to be passed to the text function. | `(:)` | + +> [!NOTE] +> Show and set rules applied inside the content passed to the `dropcap` function do not work! + +```typ +#import "@preview/droplet:0.1.0": dropcap + +#dropcap( + height: 3, + justify: true, + gap: 4pt, + hanging-indent: 1em, + font: "Curlz MT", +)[ + *Typst* is a new markup-based typesetting system that is designed to be as + _powerful_ as LaTeX while being _much easier_ to learn and use. Typst has: + + - Built-in markup for the most common formatting tasks + - Flexible functions for everything else + - A tightly integrated scripting system + - Math typesetting, bibliography management, and more + - Fast compile times thanks to incremental compilation + - Friendly error messages in case something goes wrong +] +``` + +![Result of example code.](assets/example.svg) + +## Extended Customization +To further customize the appearance of the dropped capital, you can apply a `transform` function, which takes the first letter as a string and returns the content to be shown. The font size of the letter is then scaled so that the height of the transformed content matches the given height. + +```typ +#import "@preview/droplet:0.1.0": dropcap + +#dropcap( + height: 2, + justify: true, + gap: 6pt, + transform: letter => style(styles => { + let height = measure(letter, styles).height + + grid(columns: 2, gutter: 6pt, + align(center + horizon, text(blue, letter)), + // Use "place" to ignore the line's height when + // the font size is calculated later on. + place(horizon, line( + angle: 90deg, + length: height + 6pt, + stroke: blue.lighten(40%) + 1pt + )), + ) + }), + lorem(21) +) + +``` + +![Result of example code.](assets/example-transform.svg) diff --git a/packages/preview/droplet/0.1.0/assets/example-transform.svg b/packages/preview/droplet/0.1.0/assets/example-transform.svg new file mode 100644 index 000000000..eb983aca2 --- /dev/null +++ b/packages/preview/droplet/0.1.0/assets/example-transform.svg @@ -0,0 +1,239 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/preview/droplet/0.1.0/assets/example-transform.typ b/packages/preview/droplet/0.1.0/assets/example-transform.typ new file mode 100644 index 000000000..4dbff8fba --- /dev/null +++ b/packages/preview/droplet/0.1.0/assets/example-transform.typ @@ -0,0 +1,35 @@ +#import "../src/lib.typ": dropcap + +#set text(size: 14pt) +#set page( + width: 8cm, + height: auto, + margin: 1em, + background: box( + width: 100%, + height: 100%, + radius: 4pt, + fill: white, + ), +) + +#dropcap( + height: 2, + justify: true, + gap: 6pt, + transform: letter => style(styles => { + let height = measure(letter, styles).height + + grid(columns: 2, gutter: 6pt, + align(center + horizon, text(blue, letter)), + // Use "place" to ignore the line's height when + // the font size is calculated later on. + place(horizon, line( + angle: 90deg, + length: height + 6pt, + stroke: blue.lighten(40%) + 1pt + )), + ) + }), + lorem(21) +) diff --git a/packages/preview/droplet/0.1.0/assets/example.svg b/packages/preview/droplet/0.1.0/assets/example.svg new file mode 100644 index 000000000..c2f259653 --- /dev/null +++ b/packages/preview/droplet/0.1.0/assets/example.svg @@ -0,0 +1,676 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/preview/droplet/0.1.0/assets/example.typ b/packages/preview/droplet/0.1.0/assets/example.typ new file mode 100644 index 000000000..7aad55f4e --- /dev/null +++ b/packages/preview/droplet/0.1.0/assets/example.typ @@ -0,0 +1,31 @@ +#import "../src/lib.typ": dropcap + +#set text(size: 14pt) +#set page( + width: 11cm, + height: auto, + margin: 1em, + background: box( + width: 100%, + height: 100%, + radius: 4pt, + fill: white, + ), +) + +#dropcap( + height: 3, + justify: true, + gap: 4pt, + hanging-indent: 1em, + font: "Curlz MT", +)[ + *Typst* is a new markup-based typesetting system that is designed to be as _powerful_ as LaTeX while being _much easier_ to learn and use. Typst has: + + - Built-in markup for the most common formatting tasks + - Flexible functions for everything else + - A tightly integrated scripting system + - Math typesetting, bibliography management, and more + - Fast compile times thanks to incremental compilation + - Friendly error messages in case something goes wrong +] diff --git a/packages/preview/droplet/0.1.0/src/droplet.typ b/packages/preview/droplet/0.1.0/src/droplet.typ new file mode 100644 index 000000000..bbb0b9c4f --- /dev/null +++ b/packages/preview/droplet/0.1.0/src/droplet.typ @@ -0,0 +1,282 @@ +// Element function for space. +#let space = [ ].func() + +// Elements that can be split and have a 'body' field. +#let splittable = (strong, emph, underline, stroke, overline, highlight) + +// Sets the font size so the resulting text height matches the given height. +// +// If not specified otherwise in "text-args", the top and bottom edge of the +// resulting text element will be set to "bounds". +// +// Parameters: +// - height: The target height of the resulting text. +// - threshold: The maximum difference between target and actual height. +// - text-args: Arguments to be passed to the underlying text element. +// - body: The content of the text element. +// +// Returns: The text with the set font size. +#let sized(height, ..text-args, threshold: 0.1pt, body) = style(styles => { + let text = text.with( + top-edge: "bounds", + bottom-edge: "bounds", + ..text-args.named(), + body + ) + + let size = height + let font-height = measure(text(size: size), styles).height + + // This should only take one iteration, but just in case... + while calc.abs(font-height - height) > threshold { + size *= 1 + (height - font-height) / font-height + font-height = measure(text(size: size), styles).height + } + + return text(size: size) +}) + +// Attaches a label after the split elements. +// +// The label is only attached to one of the elements, preferring the second +// one. If both elements are empty, the label is discarded. If the label is +// empty, the elements remain unchanged. +#let attach-label((first, second), label) = { + if label == none { + (first, second) + } else if second != none { + (first, [#second#label]) + } else if first != none { + ([#first#label], second) + } else { + (none, none) + } +} + +// Tries to extract the first letter of the given content. +// +// If the first letter cannot be extracted, the whole body is returned as rest. +// +// Returns: A tuple of the first letter and the rest. +#let extract-first-letter(body) = { + if type(body) == str { + let letter = body.clusters().at(0, default: none) + if letter == none { + return (none, body) + } + let rest = body.clusters().slice(1).join() + return (letter, rest) + } + + if body.has("text") { + let (text, ..fields) = body.fields() + let label = if "label" in fields { fields.remove("label") } + let func(it) = if it != none { body.func()(..fields, it) } + let (letter, rest) = extract-first-letter(body.text) + return attach-label((letter, func(rest)), label) + } + + if body.func() in splittable { + let (body: text, ..fields) = body.fields() + let label = if "label" in fields { fields.remove("label") } + let func(it) = if it != none { body.func()(..fields, it) } + let (letter, rest) = extract-first-letter(text) + return attach-label((letter, func(rest)), label) + } + + if body.has("child") { + // We cannot create a 'styled' element, so set/show rules are lost. + let (letter, rest) = extract-first-letter(body.child) + return (letter, rest) + } + + if body.has("children") { + let child-pos = body.children.position(c => { + c.func() not in (space, parbreak) + }) + + if child-pos == none { + return (none, body) + } + + let child = body.children.at(child-pos) + let (letter, rest) = extract-first-letter(child) + if body.children.len() > child-pos { + rest = (rest, ..body.children.slice(child-pos+1)).join() + } + return (letter, rest) + } +} + +// Gets the number of words in the given content. +#let size(body) = { + if type(body) == str { + body.split(" ").len() + } else if body.has("text") { + size(body.text) + } else if body.has("child") { + size(body.child) + } else if body.has("children") { + body.children.map(size).sum() + } else if body.func() in splittable { + size(body.body) + } else { + 1 + } +} + +// Tries to split the given content at a given index. +// +// Content is split at word boundaries. A sequence can be split at any of its +// childrens' word boundaries. +// +// Returns: A tuple of the first and second part. +#let split(body, index) = { + if type(body) == str { + let words = body.split(" ") + if index >= words.len() { + return (body, none) + } + let first = words.slice(0, index).join(" ") + let second = words.slice(index).join(" ") + return (first, second) + } + + if body.has("text") { + let (text, ..fields) = body.fields() + let label = if "label" in fields { fields.remove("label") } + let func(it) = if it != none { body.func()(..fields, it) } + let (first, second) = split(text, index) + return attach-label((func(first), func(second)), label) + } + + if body.func() in splittable { + let (body: text, ..fields) = body.fields() + let label = if "label" in fields { fields.remove("label") } + let func(it) = if it != none { body.func()(..fields, it) } + let (first, second) = split(text, index) + return attach-label((func(first), func(second)), label) + } + + if body.has("child") { + // We cannot create a 'styled' element, so set/show rules are lost. + let (first, second) = split(body.child, index) + return (first, second) + } + + if body.has("children") { + let first = () + let second = () + + // Find child containing the splitting point and split it. + for (i, child) in body.children.enumerate() { + let child-size = size(child) + index -= child-size + + if index <= 0 { + // Current child contains splitting point. + let sub-index = child-size + index + let (child-first, child-second) = split(child, sub-index) + first.push(child-first) + second.push(child-second) + second += body.children.slice(i + 1) // Add remaining children + break + } else { + first.push(child) + } + } + + return (first.join(), second.join()) + } + + // Element cannot be split, so put everything in second part. + return (none, body) +} + +// Shows the first letter of the given content in a larger font. +// +// The first letter is extracted from the content, and the content is split, so +// that the content is wrapped around the first letter. +// +// Parameters: +// - height: The height of the first letter. Can be given as the number of +// lines (integer) or as a length. +// - justify: Whether to justify the text next to the first letter. +// - gap: The space between the first letter and the text. +// - hanging-indent: The indent of lines after the first line. +// - transform: A function to be applied to the first letter. +// - text-args: Arguments to be passed to the underlying text element. +// - body: The content to be shown. +// +// Returns: The content with the first letter shown in a larger font. +#let dropcap( + height: 2, + justify: false, + gap: 0pt, + hanging-indent: 0pt, + transform: none, + ..text-args, + body +) = layout(bounds => style(styles => { + // Split body into first letter and rest of string + let (letter, rest) = extract-first-letter(body) + if transform != none { + letter = transform(letter) + } + + // Sample content for height of given amount of lines + let letter-height = if type(height) == int { + let sample-lines = range(height).map(_ => [x]).join(linebreak()) + measure(sample-lines, styles).height + } else { + measure(v(height), styles).height + } + + // Create dropcap with the height of sample content + let letter = sized(letter-height, letter, ..text-args) + let letter-width = measure(letter, styles).width + + // Try to justify as many words as possible next to dropcap + let bounded = box.with(width: bounds.width - letter-width - gap) + + let index = 1 + let (first, second) = while true { + let (first, second) = split(rest, index) + let first = { + set par(hanging-indent: hanging-indent, justify: justify) + first + } + + if second == none { + // All content fits next to dropcap. + (first, none) + break + } + + // Allow a bit more space to accommodate for larger elements. + let max-height = letter-height + measure([x], styles).height / 2 + let height = measure(bounded(first), styles).height + if height > max-height { + split(rest, index - 1) + break + } + + index += 1 + } + + // Layout dropcap and aside text as grid + set par(justify: justify) + + box(grid( + column-gutter: gap, + columns: (letter-width, 1fr), + letter, + { + set par(hanging-indent: hanging-indent) + first + if second != none { linebreak(justify: justify) } + } + )) + + second +})) diff --git a/packages/preview/droplet/0.1.0/src/lib.typ b/packages/preview/droplet/0.1.0/src/lib.typ new file mode 100644 index 000000000..2941ad22d --- /dev/null +++ b/packages/preview/droplet/0.1.0/src/lib.typ @@ -0,0 +1 @@ +#import "droplet.typ": dropcap diff --git a/packages/preview/droplet/0.1.0/typst.toml b/packages/preview/droplet/0.1.0/typst.toml new file mode 100644 index 000000000..940cc75d0 --- /dev/null +++ b/packages/preview/droplet/0.1.0/typst.toml @@ -0,0 +1,13 @@ +[package] +name = "droplet" +version = "0.1.0" +entrypoint = "src/lib.typ" +authors = ["Eric Biedert"] +repository = "https://github.com/EpicEricEE/typst-plugins" +license = "MIT" +description = "Customizable dropped capitals." +exclude = ["README.md", "assets"] +keywords = [ + "drop", "dropped", "dropcap", "big", "large", "caps", "capital", "capitals", + "initial", "initials", "letter", "letters", "lettrine" +]