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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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
+)
+```
+
+
+
+```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
+)
+```
+
+
+
+```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
+)
+```
+
+
+
+```typst
+// uses the font that supports ligatures
+#ansi-render(read("test.txt"), inset: 5pt, radius: 3pt, font: "Cascadia Code", theme: terminal-themes.putty)
+```
+
+
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
+]
+```
+
+
+
+## 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)
+)
+
+```
+
+
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"
+]