Remove showbits-common
This commit is contained in:
parent
6e6dfb2b66
commit
a55109c677
17 changed files with 18 additions and 1789 deletions
252
Cargo.lock
generated
252
Cargo.lock
generated
|
|
@ -333,20 +333,6 @@ name = "bytemuck"
|
|||
version = "1.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck_derive"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
|
|
@ -560,29 +546,6 @@ dependencies = [
|
|||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmic-text"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59fd57d82eb4bfe7ffa9b1cec0c05e2fd378155b47f255a67983cb4afe0e80c2"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"fontdb 0.16.2",
|
||||
"log",
|
||||
"rangemap",
|
||||
"rayon",
|
||||
"rustc-hash",
|
||||
"rustybuzz 0.14.1",
|
||||
"self_cell",
|
||||
"swash",
|
||||
"sys-locale",
|
||||
"ttf-parser 0.21.1",
|
||||
"unicode-bidi",
|
||||
"unicode-linebreak",
|
||||
"unicode-script",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
|
|
@ -904,15 +867,6 @@ version = "0.1.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
|
||||
|
||||
[[package]]
|
||||
name = "font-types"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3971f9a5ca983419cdc386941ba3b9e1feba01a0ab888adf78739feb2798492"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontconfig-parser"
|
||||
version = "0.5.7"
|
||||
|
|
@ -922,20 +876,6 @@ dependencies = [
|
|||
"roxmltree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontdb"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3"
|
||||
dependencies = [
|
||||
"fontconfig-parser",
|
||||
"log",
|
||||
"memmap2",
|
||||
"slotmap",
|
||||
"tinyvec",
|
||||
"ttf-parser 0.20.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontdb"
|
||||
version = "0.21.0"
|
||||
|
|
@ -947,7 +887,7 @@ dependencies = [
|
|||
"memmap2",
|
||||
"slotmap",
|
||||
"tinyvec",
|
||||
"ttf-parser 0.24.1",
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1056,12 +996,6 @@ version = "0.31.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "grid"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36119f3a540b086b4e436bb2b588cf98a68863470e0e880f4d0842f112a3183a"
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.4.1"
|
||||
|
|
@ -1650,16 +1584,6 @@ dependencies = [
|
|||
"imgref",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mark"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/Garmelon/mark.git?rev=2a862a69d69abc64ddd7eefd1e1ff3d05ce3b6e4#2a862a69d69abc64ddd7eefd1e1ff3d05ce3b6e4"
|
||||
dependencies = [
|
||||
"image",
|
||||
"palette",
|
||||
"rand 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.8.4"
|
||||
|
|
@ -2055,7 +1979,7 @@ version = "0.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d15afa937836bf3d876f5a04ce28810c06045857bf46c3d0d31073b8aada5494"
|
||||
dependencies = [
|
||||
"ttf-parser 0.24.1",
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2279,12 +2203,6 @@ dependencies = [
|
|||
"zerocopy 0.8.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rangemap"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684"
|
||||
|
||||
[[package]]
|
||||
name = "rav1e"
|
||||
version = "0.7.1"
|
||||
|
|
@ -2355,16 +2273,6 @@ dependencies = [
|
|||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "read-fonts"
|
||||
version = "0.22.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69aacb76b5c29acfb7f90155d39759a29496aebb49395830e928a9703d2eec2f"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"font-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.9"
|
||||
|
|
@ -2496,12 +2404,6 @@ version = "0.1.24"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.44"
|
||||
|
|
@ -2521,23 +2423,6 @@ version = "1.0.19"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
|
||||
|
||||
[[package]]
|
||||
name = "rustybuzz"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"bytemuck",
|
||||
"libm",
|
||||
"smallvec",
|
||||
"ttf-parser 0.21.1",
|
||||
"unicode-bidi-mirroring 0.2.0",
|
||||
"unicode-ccc 0.2.0",
|
||||
"unicode-properties",
|
||||
"unicode-script",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustybuzz"
|
||||
version = "0.18.0"
|
||||
|
|
@ -2549,9 +2434,9 @@ dependencies = [
|
|||
"core_maths",
|
||||
"log",
|
||||
"smallvec",
|
||||
"ttf-parser 0.24.1",
|
||||
"unicode-bidi-mirroring 0.3.0",
|
||||
"unicode-ccc 0.3.0",
|
||||
"ttf-parser",
|
||||
"unicode-bidi-mirroring",
|
||||
"unicode-ccc",
|
||||
"unicode-properties",
|
||||
"unicode-script",
|
||||
]
|
||||
|
|
@ -2609,12 +2494,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "self_cell"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.218"
|
||||
|
|
@ -2712,23 +2591,6 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|||
name = "showbits-assets"
|
||||
version = "0.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "showbits-common"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cosmic-text",
|
||||
"image",
|
||||
"mark",
|
||||
"palette",
|
||||
"paste",
|
||||
"showbits-assets",
|
||||
"taffy",
|
||||
"typst",
|
||||
"typst-assets",
|
||||
"typst-render",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "showbits-thermal-printer"
|
||||
version = "0.0.0"
|
||||
|
|
@ -2803,16 +2665,6 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
|
||||
[[package]]
|
||||
name = "skrifa"
|
||||
version = "0.22.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e1c44ad1f6c5bdd4eefed8326711b7dbda9ea45dfd36068c427d332aa382cbe"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"read-fonts",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slotmap"
|
||||
version = "1.0.7"
|
||||
|
|
@ -2920,17 +2772,6 @@ dependencies = [
|
|||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swash"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbd59f3f359ddd2c95af4758c18270eddd9c730dde98598023cdabff472c2ca2"
|
||||
dependencies = [
|
||||
"skrifa",
|
||||
"yazi",
|
||||
"zeno",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.98"
|
||||
|
|
@ -2981,15 +2822,6 @@ dependencies = [
|
|||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sys-locale"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "6.2.2"
|
||||
|
|
@ -3003,18 +2835,6 @@ dependencies = [
|
|||
"version-compare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "taffy"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50e3102cd96acfdb0b3f037cb06e9b40d3dca1d7c6e4742147441daf6f34e8a6"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"grid",
|
||||
"serde",
|
||||
"slotmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tar"
|
||||
version = "0.4.44"
|
||||
|
|
@ -3297,18 +3117,6 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4"
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8"
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.24.1"
|
||||
|
|
@ -3412,7 +3220,7 @@ dependencies = [
|
|||
"ecow",
|
||||
"env_proxy",
|
||||
"flate2",
|
||||
"fontdb 0.21.0",
|
||||
"fontdb",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"openssl",
|
||||
|
|
@ -3443,9 +3251,9 @@ dependencies = [
|
|||
"icu_provider_blob",
|
||||
"icu_segmenter",
|
||||
"kurbo",
|
||||
"rustybuzz 0.18.0",
|
||||
"rustybuzz",
|
||||
"smallvec",
|
||||
"ttf-parser 0.24.1",
|
||||
"ttf-parser",
|
||||
"typst-assets",
|
||||
"typst-library",
|
||||
"typst-macros",
|
||||
|
|
@ -3474,7 +3282,7 @@ dependencies = [
|
|||
"csv",
|
||||
"ecow",
|
||||
"flate2",
|
||||
"fontdb 0.21.0",
|
||||
"fontdb",
|
||||
"hayagriva",
|
||||
"icu_properties",
|
||||
"icu_provider",
|
||||
|
|
@ -3494,7 +3302,7 @@ dependencies = [
|
|||
"regex-syntax",
|
||||
"roxmltree",
|
||||
"rust_decimal",
|
||||
"rustybuzz 0.18.0",
|
||||
"rustybuzz",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
|
|
@ -3503,7 +3311,7 @@ dependencies = [
|
|||
"syntect",
|
||||
"time",
|
||||
"toml",
|
||||
"ttf-parser 0.24.1",
|
||||
"ttf-parser",
|
||||
"two-face",
|
||||
"typed-arena",
|
||||
"typst-assets",
|
||||
|
|
@ -3561,7 +3369,7 @@ dependencies = [
|
|||
"pixglyph",
|
||||
"resvg",
|
||||
"tiny-skia",
|
||||
"ttf-parser 0.24.1",
|
||||
"ttf-parser",
|
||||
"typst-library",
|
||||
"typst-macros",
|
||||
"typst-timing",
|
||||
|
|
@ -3578,7 +3386,7 @@ dependencies = [
|
|||
"ecow",
|
||||
"flate2",
|
||||
"image",
|
||||
"ttf-parser 0.24.1",
|
||||
"ttf-parser",
|
||||
"typst-library",
|
||||
"typst-macros",
|
||||
"typst-timing",
|
||||
|
|
@ -3661,24 +3469,12 @@ version = "0.3.18"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi-mirroring"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi-mirroring"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64af057ad7466495ca113126be61838d8af947f41d93a949980b2389a118082f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ccc"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ccc"
|
||||
version = "0.3.0"
|
||||
|
|
@ -3691,12 +3487,6 @@ version = "1.0.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-linebreak"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-math-class"
|
||||
version = "0.1.0"
|
||||
|
|
@ -3785,13 +3575,13 @@ dependencies = [
|
|||
"base64",
|
||||
"data-url",
|
||||
"flate2",
|
||||
"fontdb 0.21.0",
|
||||
"fontdb",
|
||||
"imagesize",
|
||||
"kurbo",
|
||||
"log",
|
||||
"pico-args",
|
||||
"roxmltree",
|
||||
"rustybuzz 0.18.0",
|
||||
"rustybuzz",
|
||||
"simplecss",
|
||||
"siphasher",
|
||||
"strict-num",
|
||||
|
|
@ -4146,12 +3936,6 @@ dependencies = [
|
|||
"linked-hash-map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yazi"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.7.5"
|
||||
|
|
@ -4176,12 +3960,6 @@ dependencies = [
|
|||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeno"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
|
|
|
|||
21
Cargo.toml
21
Cargo.toml
|
|
@ -1,11 +1,6 @@
|
|||
[workspace]
|
||||
resolver = "3"
|
||||
members = [
|
||||
"showbits-assets",
|
||||
"showbits-common",
|
||||
"showbits-thermal-printer",
|
||||
"showbits-typst",
|
||||
]
|
||||
members = ["showbits-assets", "showbits-thermal-printer", "showbits-typst"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.0.0"
|
||||
|
|
@ -15,18 +10,15 @@ edition = "2024"
|
|||
anyhow = "1.0.96"
|
||||
axum = "0.8.1"
|
||||
clap = { version = "4.5.30", features = ["derive", "deprecated"] }
|
||||
cosmic-text = "0.12.1"
|
||||
escpos = "0.15.0"
|
||||
image = { version = "0.25.5", default-features = false }
|
||||
image = "0.25.5"
|
||||
jiff = "0.2.1"
|
||||
mime_guess = "2.0.5"
|
||||
palette = "0.7.6"
|
||||
paste = "1.0.15"
|
||||
rand = "0.9.0"
|
||||
rust-embed = "8.5.0"
|
||||
serde = { version = "1.0.218", features = ["derive"] }
|
||||
showbits-assets.path = "./showbits-assets"
|
||||
showbits-common.path = "./showbits-common"
|
||||
showbits-typst.path = "./showbits-typst"
|
||||
tokio = "1.43.0"
|
||||
typst = "0.13.0"
|
||||
|
|
@ -34,15 +26,6 @@ typst-assets = { version = "0.13.0", features = ["fonts"] }
|
|||
typst-kit = "0.13.0"
|
||||
typst-render = "0.13.0"
|
||||
|
||||
[workspace.dependencies.taffy]
|
||||
version = "0.7.6"
|
||||
default-features = false
|
||||
features = ["std", "taffy_tree", "flexbox", "grid", "block_layout"]
|
||||
|
||||
[workspace.dependencies.mark]
|
||||
git = "https://github.com/Garmelon/mark.git"
|
||||
rev = "2a862a69d69abc64ddd7eefd1e1ff3d05ce3b6e4"
|
||||
|
||||
[workspace.lints]
|
||||
rust.unsafe_code = { level = "forbid", priority = 1 }
|
||||
# Lint groups
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
[package]
|
||||
name = "showbits-common"
|
||||
version = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
cosmic-text = { workspace = true }
|
||||
image = { workspace = true }
|
||||
mark = { workspace = true }
|
||||
palette = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
showbits-assets = { workspace = true }
|
||||
taffy = { workspace = true }
|
||||
typst = { workspace = true }
|
||||
typst-assets = { workspace = true }
|
||||
typst-render = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
use palette::Srgba;
|
||||
|
||||
pub const TRANSPARENT: Srgba = Srgba::new(0.0, 0.0, 0.0, 0.0);
|
||||
pub const BLACK: Srgba = Srgba::new(0.0, 0.0, 0.0, 1.0);
|
||||
pub const WHITE: Srgba = Srgba::new(1.0, 1.0, 1.0, 1.0);
|
||||
|
||||
pub fn from_image_color(color: image::Rgba<u8>) -> Srgba {
|
||||
let [r, g, b, a] = color.0;
|
||||
Srgba::new(r, g, b, a).into_format()
|
||||
}
|
||||
|
||||
pub fn to_image_color(color: Srgba) -> image::Rgba<u8> {
|
||||
let color = color.into_format::<u8, u8>();
|
||||
image::Rgba([color.red, color.green, color.blue, color.alpha])
|
||||
}
|
||||
|
||||
pub fn from_text_color(color: cosmic_text::Color) -> Srgba {
|
||||
let [r, g, b, a] = color.as_rgba();
|
||||
Srgba::new(r, g, b, a).into_format()
|
||||
}
|
||||
|
||||
pub fn to_text_color(color: Srgba) -> cosmic_text::Color {
|
||||
let color = color.into_format::<u8, u8>();
|
||||
cosmic_text::Color::rgba(color.red, color.green, color.blue, color.alpha)
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
pub use crate::{node::*, rect::*, tree::*, vec2::*, view::*, widget::*};
|
||||
|
||||
pub mod color;
|
||||
mod node;
|
||||
mod rect;
|
||||
mod tree;
|
||||
mod vec2;
|
||||
mod view;
|
||||
mod widget;
|
||||
pub mod widgets;
|
||||
|
|
@ -1,221 +0,0 @@
|
|||
use paste::paste;
|
||||
use taffy::{
|
||||
AlignContent, AlignItems, AlignSelf, Dimension, Display, FlexDirection, FlexWrap, GridAutoFlow,
|
||||
GridPlacement, JustifyContent, LengthPercentage, LengthPercentageAuto, Line, NodeId,
|
||||
NonRepeatedTrackSizingFunction, Overflow, Point, Position, Rect, Size, Style, TaffyResult,
|
||||
TrackSizingFunction,
|
||||
};
|
||||
|
||||
use crate::{BoxedWidget, Tree, Widget};
|
||||
|
||||
pub struct Node<C> {
|
||||
layout: Style,
|
||||
children: Vec<NodeId>,
|
||||
widget: Option<BoxedWidget<C>>,
|
||||
}
|
||||
|
||||
impl<C> Node<C> {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
layout: Style::default(),
|
||||
children: vec![],
|
||||
widget: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn and_child(mut self, node: NodeId) -> Self {
|
||||
self.children.push(node);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_widget<W: Widget<C> + 'static>(mut self, widget: W) -> Self {
|
||||
self.widget = Some(Box::new(widget));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn register(self, tree: &mut Tree<C>) -> TaffyResult<NodeId> {
|
||||
let tree = tree.taffy_tree();
|
||||
let id = tree.new_with_children(self.layout, &self.children)?;
|
||||
tree.set_node_context(id, self.widget)?;
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
|
||||
// Layout helper functions
|
||||
|
||||
macro_rules! layout_setter {
|
||||
( $name:ident : $type:ty ) => {
|
||||
paste! {
|
||||
pub fn [<with_ $name>](mut self, $name: $type) -> Self {
|
||||
self.layout.$name = $name;
|
||||
self
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! layout_setter_point {
|
||||
( $name:ident : Point<$type:ty> ) => {
|
||||
paste! {
|
||||
pub fn [<with_ $name>](mut self, $name: Point<$type>) -> Self {
|
||||
self.layout.$name = $name;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn [<with_ $name _x>](mut self, x: $type) -> Self {
|
||||
self.layout.$name.x = x;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn [<with_ $name _y>](mut self, y: $type) -> Self {
|
||||
self.layout.$name.y = y;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn [<with_ $name _all>](mut self, all: $type) -> Self {
|
||||
self.layout.$name.x = all;
|
||||
self.layout.$name.y = all;
|
||||
self
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! layout_setter_size {
|
||||
( $name:ident : Size<$type:ty> ) => {
|
||||
paste! {
|
||||
pub fn [<with_ $name>](mut self, $name: Size<$type>) -> Self {
|
||||
self.layout.$name = $name;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn [<with_ $name _width>](mut self, width: $type) -> Self {
|
||||
self.layout.$name.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn [<with_ $name _height>](mut self, height: $type) -> Self {
|
||||
self.layout.$name.height = height;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn [<with_ $name _all>](mut self, all: $type) -> Self {
|
||||
self.layout.$name.width = all;
|
||||
self.layout.$name.height = all;
|
||||
self
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! layout_setter_line {
|
||||
( $name:ident : Line<$type:ty> ) => {
|
||||
paste! {
|
||||
pub fn [<with_ $name>](mut self, $name: Line<$type>) -> Self {
|
||||
self.layout.$name = $name;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn [<with_ $name _start>](mut self, start: $type) -> Self {
|
||||
self.layout.$name.start = start;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn [<with_ $name _end>](mut self, end: $type) -> Self {
|
||||
self.layout.$name.end = end;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn [<with_ $name _all>](mut self, all: $type) -> Self {
|
||||
self.layout.$name.start = all;
|
||||
self.layout.$name.end = all;
|
||||
self
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! layout_setter_rect {
|
||||
( $name:ident : Rect<$type:ty> ) => {
|
||||
paste! {
|
||||
pub fn [<with_ $name>](mut self, $name: Rect<$type>) -> Self {
|
||||
self.layout.$name = $name;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn [<with_ $name _left>](mut self, left: $type) -> Self {
|
||||
self.layout.$name.left = left;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn [<with_ $name _right>](mut self, right: $type) -> Self {
|
||||
self.layout.$name.right = right;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn [<with_ $name _top>](mut self, top: $type) -> Self {
|
||||
self.layout.$name.top = top;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn [<with_ $name _bottom>](mut self, bottom: $type) -> Self {
|
||||
self.layout.$name.bottom = bottom;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn [<with_ $name _horiz>](mut self, horizontal: $type) -> Self {
|
||||
self.layout.$name.left = horizontal;
|
||||
self.layout.$name.right = horizontal;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn [<with_ $name _vert>](mut self, vertical: $type) -> Self {
|
||||
self.layout.$name.top = vertical;
|
||||
self.layout.$name.bottom = vertical;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn [<with_ $name _all>](mut self, all: $type) -> Self {
|
||||
self.layout.$name.left = all;
|
||||
self.layout.$name.right = all;
|
||||
self.layout.$name.top = all;
|
||||
self.layout.$name.bottom = all;
|
||||
self
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<C> Node<C> {
|
||||
layout_setter!(display: Display);
|
||||
layout_setter_point!(overflow: Point<Overflow>);
|
||||
layout_setter!(scrollbar_width: f32);
|
||||
layout_setter!(position: Position);
|
||||
layout_setter_rect!(inset: Rect<LengthPercentageAuto>);
|
||||
layout_setter_size!(size: Size<Dimension>);
|
||||
layout_setter_size!(min_size: Size<Dimension>);
|
||||
layout_setter_size!(max_size: Size<Dimension>);
|
||||
layout_setter!(aspect_ratio: Option<f32>);
|
||||
layout_setter_rect!(margin: Rect<LengthPercentageAuto>);
|
||||
layout_setter_rect!(padding: Rect<LengthPercentage>);
|
||||
layout_setter_rect!(border: Rect<LengthPercentage>);
|
||||
layout_setter!(align_items: Option<AlignItems>);
|
||||
layout_setter!(align_self: Option<AlignSelf>);
|
||||
layout_setter!(justify_items: Option<AlignItems>);
|
||||
layout_setter!(justify_self: Option<AlignSelf>);
|
||||
layout_setter!(align_content: Option<AlignContent>);
|
||||
layout_setter!(justify_content: Option<JustifyContent>);
|
||||
layout_setter_size!(gap: Size<LengthPercentage>);
|
||||
layout_setter!(flex_direction: FlexDirection);
|
||||
layout_setter!(flex_wrap: FlexWrap);
|
||||
layout_setter!(flex_basis: Dimension);
|
||||
layout_setter!(flex_grow: f32);
|
||||
layout_setter!(flex_shrink: f32);
|
||||
layout_setter!(grid_template_rows: Vec<TrackSizingFunction>);
|
||||
layout_setter!(grid_template_columns: Vec<TrackSizingFunction>);
|
||||
layout_setter!(grid_auto_rows: Vec<NonRepeatedTrackSizingFunction>);
|
||||
layout_setter!(grid_auto_columns: Vec<NonRepeatedTrackSizingFunction>);
|
||||
layout_setter!(grid_auto_flow: GridAutoFlow);
|
||||
layout_setter_line!(grid_row: Line<GridPlacement>);
|
||||
layout_setter_line!(grid_column: Line<GridPlacement>);
|
||||
}
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
use std::ops::{Add, Sub};
|
||||
|
||||
use crate::Vec2;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Rect {
|
||||
north: i32,
|
||||
south: i32,
|
||||
west: i32,
|
||||
east: i32,
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
/// A zero-sized rectangle located at the origin.
|
||||
pub const ZERO: Self = Self::new(0, 0, -1, -1);
|
||||
|
||||
/// Whenever a rectangle is constructed, this function must be used. This
|
||||
/// ensures invariants are always checked.
|
||||
const fn new(north: i32, south: i32, west: i32, east: i32) -> Self {
|
||||
let result = Self {
|
||||
north,
|
||||
south,
|
||||
west,
|
||||
east,
|
||||
};
|
||||
|
||||
let size = result.size();
|
||||
assert!(size.x >= 0);
|
||||
assert!(size.y >= 0);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Construct a rectangle that is a bounding box around two points.
|
||||
///
|
||||
/// It is not possible to construct a rectangle with a width or height of 0
|
||||
/// through this method. Use one of the other constructor functions instead.
|
||||
pub fn from_points(a: Vec2, b: Vec2) -> Self {
|
||||
Self::new(a.y.min(b.y), a.y.max(b.y), a.x.min(b.x), a.x.max(b.x))
|
||||
}
|
||||
|
||||
/// Construct a rectangle from its north-west and south-east corners.
|
||||
pub const fn from_nw_se(nw: Vec2, se: Vec2) -> Self {
|
||||
Self::new(nw.y, se.y, nw.x, se.x)
|
||||
}
|
||||
|
||||
/// Construct a rectangle from its north-east and south-west corners.
|
||||
pub const fn from_ne_sw(ne: Vec2, sw: Vec2) -> Self {
|
||||
Self::new(ne.y, sw.y, sw.x, ne.x)
|
||||
}
|
||||
|
||||
/// Construct a rectangle from its north-west corner and size.
|
||||
pub fn from_nw(nw: Vec2, size: Vec2) -> Self {
|
||||
let se = nw + (size - 1);
|
||||
Self::from_nw_se(nw, se)
|
||||
}
|
||||
|
||||
/// Construct a rectangle from its north-east corner and size.
|
||||
pub fn from_ne(ne: Vec2, size: Vec2) -> Self {
|
||||
let sw = ne + (size - 1).neg_x();
|
||||
Self::from_ne_sw(ne, sw)
|
||||
}
|
||||
|
||||
/// Construct a rectangle from its south-west corner and size.
|
||||
pub fn from_sw(sw: Vec2, size: Vec2) -> Self {
|
||||
let ne = sw + (size - 1).neg_y();
|
||||
Self::from_ne_sw(ne, sw)
|
||||
}
|
||||
|
||||
/// Construct a rectangle from its south-east corner and size.
|
||||
pub fn from_se(se: Vec2, size: Vec2) -> Self {
|
||||
let nw = se - (size - 1);
|
||||
Self::from_nw_se(nw, se)
|
||||
}
|
||||
|
||||
pub const fn north(self) -> i32 {
|
||||
self.north
|
||||
}
|
||||
|
||||
pub const fn south(self) -> i32 {
|
||||
self.south
|
||||
}
|
||||
|
||||
pub const fn west(self) -> i32 {
|
||||
self.west
|
||||
}
|
||||
|
||||
pub const fn east(self) -> i32 {
|
||||
self.east
|
||||
}
|
||||
|
||||
pub const fn corner_nw(self) -> Vec2 {
|
||||
Vec2::new(self.west, self.north)
|
||||
}
|
||||
|
||||
pub const fn corner_ne(self) -> Vec2 {
|
||||
Vec2::new(self.east, self.north)
|
||||
}
|
||||
|
||||
pub const fn corner_sw(self) -> Vec2 {
|
||||
Vec2::new(self.west, self.south)
|
||||
}
|
||||
|
||||
pub const fn corner_se(self) -> Vec2 {
|
||||
Vec2::new(self.east, self.south)
|
||||
}
|
||||
|
||||
pub const fn size(self) -> Vec2 {
|
||||
Vec2::new(self.east - self.west + 1, self.south - self.north + 1)
|
||||
}
|
||||
|
||||
/// An iterator of all points contained within the rectangle.
|
||||
pub fn points(self) -> impl Iterator<Item = Vec2> {
|
||||
(self.north..=self.south)
|
||||
.flat_map(move |y| (self.west..=self.east).map(move |x| Vec2::new(x, y)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Vec2> for Rect {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Vec2) -> Self::Output {
|
||||
Self::new(
|
||||
self.north + rhs.y,
|
||||
self.south + rhs.y,
|
||||
self.west + rhs.x,
|
||||
self.east + rhs.x,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Vec2> for Rect {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Vec2) -> Self::Output {
|
||||
Self::new(
|
||||
self.north - rhs.y,
|
||||
self.south - rhs.y,
|
||||
self.west - rhs.x,
|
||||
self.east - rhs.x,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
use image::RgbaImage;
|
||||
use palette::Srgba;
|
||||
use taffy::{AvailableSpace, NodeId, Point, Size, TaffyResult, TaffyTree};
|
||||
|
||||
use crate::{BoxedWidget, Rect, Vec2, View, color};
|
||||
|
||||
fn point_to_vec2(point: Point<f32>) -> Vec2 {
|
||||
Vec2::new(point.x as i32, point.y as i32)
|
||||
}
|
||||
|
||||
fn size_to_vec2(size: Size<f32>) -> Vec2 {
|
||||
Vec2::new(size.width as i32, size.height as i32)
|
||||
}
|
||||
|
||||
pub struct Tree<C> {
|
||||
tree: TaffyTree<BoxedWidget<C>>,
|
||||
background: Srgba,
|
||||
}
|
||||
|
||||
impl<C> Tree<C> {
|
||||
pub fn new(background: Srgba) -> Self {
|
||||
Self {
|
||||
tree: TaffyTree::new(),
|
||||
background,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn taffy_tree(&mut self) -> &mut TaffyTree<BoxedWidget<C>> {
|
||||
&mut self.tree
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
ctx: &mut C,
|
||||
root: NodeId,
|
||||
available: Size<AvailableSpace>,
|
||||
) -> TaffyResult<()> {
|
||||
self.tree.enable_rounding(); // Just to make sure
|
||||
self.tree.compute_layout_with_measure(
|
||||
root,
|
||||
available,
|
||||
|known, available, _node, context, _style| {
|
||||
if let Some(widget) = context {
|
||||
widget.size(ctx, known, available)
|
||||
} else {
|
||||
Size::ZERO
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn render_to_view(
|
||||
&mut self,
|
||||
ctx: &mut C,
|
||||
node: NodeId,
|
||||
view: &mut View<'_>,
|
||||
) -> anyhow::Result<()> {
|
||||
let layout = *self.tree.layout(node)?;
|
||||
let area = Rect::from_nw(point_to_vec2(layout.location), size_to_vec2(layout.size));
|
||||
let mut view = view.dup().zoom(area);
|
||||
|
||||
// First pass
|
||||
if let Some(widget) = self.tree.get_node_context_mut(node) {
|
||||
widget.draw_below(ctx, &mut view, &layout)?;
|
||||
}
|
||||
|
||||
// Render children
|
||||
let mut children = vec![];
|
||||
for child in self.tree.children(node)? {
|
||||
let order = self.tree.layout(child)?.order;
|
||||
children.push((order, child));
|
||||
}
|
||||
children.sort_unstable_by_key(|(order, _)| *order);
|
||||
for (_, child) in children {
|
||||
self.render_to_view(ctx, child, &mut view)?;
|
||||
}
|
||||
|
||||
// Second pass
|
||||
if let Some(widget) = self.tree.get_node_context_mut(node) {
|
||||
widget.draw_above(ctx, &mut view, &layout)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&mut self,
|
||||
ctx: &mut C,
|
||||
root: NodeId,
|
||||
available: Size<AvailableSpace>,
|
||||
) -> anyhow::Result<RgbaImage> {
|
||||
self.layout(ctx, root, available)?;
|
||||
|
||||
let layout = self.tree.layout(root)?;
|
||||
assert_eq!(layout.location.x, 0.0);
|
||||
assert_eq!(layout.location.y, 0.0);
|
||||
// TODO Check how taffy treats the border?
|
||||
|
||||
let (width, height) = size_to_vec2(layout.size).to_u32();
|
||||
let mut image =
|
||||
RgbaImage::from_pixel(width, height, color::to_image_color(self.background));
|
||||
self.render_to_view(ctx, root, &mut View::new(&mut image))?;
|
||||
|
||||
Ok(image)
|
||||
}
|
||||
|
||||
pub fn print_debug(&mut self, root: NodeId) {
|
||||
self.tree.print_tree(root)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,187 +0,0 @@
|
|||
use std::{
|
||||
fmt,
|
||||
ops::{Add, Mul, Neg, Sub},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Vec2 {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
}
|
||||
|
||||
impl Vec2 {
|
||||
pub const ZERO: Self = Self::new(0, 0);
|
||||
pub const NORTH: Self = Self::new(0, -1);
|
||||
pub const SOUTH: Self = Self::new(0, 1);
|
||||
pub const WEST: Self = Self::new(-1, 0);
|
||||
pub const EAST: Self = Self::new(1, 0);
|
||||
|
||||
pub const fn new(x: i32, y: i32) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
pub fn from_u32_checked(x: u32, y: u32) -> Option<Self> {
|
||||
let x: i32 = x.try_into().ok()?;
|
||||
let y: i32 = y.try_into().ok()?;
|
||||
Some(Self::new(x, y))
|
||||
}
|
||||
|
||||
pub fn from_u32(x: u32, y: u32) -> Self {
|
||||
let x: i32 = x.try_into().expect("x too large");
|
||||
let y: i32 = y.try_into().expect("y too large");
|
||||
Self::new(x, y)
|
||||
}
|
||||
|
||||
pub fn to_u32_checked(self) -> Option<(u32, u32)> {
|
||||
let x: u32 = self.x.try_into().ok()?;
|
||||
let y: u32 = self.y.try_into().ok()?;
|
||||
Some((x, y))
|
||||
}
|
||||
|
||||
pub fn to_u32(self) -> (u32, u32) {
|
||||
let x: u32 = self.x.try_into().expect("x too small");
|
||||
let y: u32 = self.y.try_into().expect("y too small");
|
||||
(x, y)
|
||||
}
|
||||
|
||||
pub fn with_x(self, x: i32) -> Self {
|
||||
Self { x, ..self }
|
||||
}
|
||||
|
||||
pub fn with_y(self, y: i32) -> Self {
|
||||
Self { y, ..self }
|
||||
}
|
||||
|
||||
/// The vector pointing from `self` to `other`.
|
||||
///
|
||||
/// ```
|
||||
/// # use showbits_common::Vec2;
|
||||
/// let a = Vec2::new(1, 3);
|
||||
/// let b = Vec2::new(3, 7);
|
||||
/// assert_eq!(a.to(b), b - a);
|
||||
/// ```
|
||||
pub fn to(self, other: Self) -> Self {
|
||||
other - self
|
||||
}
|
||||
|
||||
/// Negate the `x` component of the vector.
|
||||
///
|
||||
/// ```
|
||||
/// # use showbits_common::Vec2;
|
||||
/// let v = Vec2::new(3, 4);
|
||||
/// assert_eq!(v.neg_x(), v * Vec2::new(-1, 1));
|
||||
/// ```
|
||||
pub fn neg_x(self) -> Self {
|
||||
Self { x: -self.x, ..self }
|
||||
}
|
||||
|
||||
/// Negate the `y` component of the vector.
|
||||
///
|
||||
/// ```
|
||||
/// # use showbits_common::Vec2;
|
||||
/// let v = Vec2::new(3, 4);
|
||||
/// assert_eq!(v.neg_y(), v * Vec2::new(1, -1));
|
||||
/// ```
|
||||
pub fn neg_y(self) -> Self {
|
||||
Self { y: -self.y, ..self }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Vec2 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("Vec2").field(&self.x).field(&self.y).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Vec2 {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self::Output {
|
||||
Self {
|
||||
x: -self.x,
|
||||
y: -self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Vec2 {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
x: self.x + rhs.x,
|
||||
y: self.y + rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<i32> for Vec2 {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: i32) -> Self::Output {
|
||||
self + Self::new(rhs, rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Vec2 {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
x: self.x - rhs.x,
|
||||
y: self.y - rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<i32> for Vec2 {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: i32) -> Self::Output {
|
||||
self - Self::new(rhs, rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for Vec2 {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
x: self.x * rhs.x,
|
||||
y: self.y * rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<i32> for Vec2 {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: i32) -> Self::Output {
|
||||
self * Self::new(rhs, rhs)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Vec2;
|
||||
|
||||
#[test]
|
||||
fn arithmetic() {
|
||||
let a = Vec2::new(1, 3);
|
||||
let b = Vec2::new(3, 7);
|
||||
|
||||
assert_eq!(-a, Vec2::new(-1, -3));
|
||||
assert_eq!(a.neg_x(), Vec2::new(-1, 3));
|
||||
assert_eq!(a.neg_y(), Vec2::new(1, -3));
|
||||
|
||||
assert_eq!(a + b, Vec2::new(4, 10));
|
||||
assert_eq!(a + 2, Vec2::new(3, 5));
|
||||
|
||||
assert_eq!(a - b, Vec2::new(-2, -4));
|
||||
assert_eq!(a - 2, Vec2::new(-1, 1));
|
||||
assert_eq!(a - b, b.to(a));
|
||||
|
||||
assert_eq!(a * b, Vec2::new(3, 21));
|
||||
assert_eq!(a * 2, Vec2::new(2, 6));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
use image::RgbaImage;
|
||||
use palette::{Srgba, blend::Compose};
|
||||
|
||||
use crate::{Rect, Vec2, color};
|
||||
|
||||
pub struct View<'a> {
|
||||
area: Rect,
|
||||
buffer: &'a mut RgbaImage,
|
||||
}
|
||||
|
||||
impl<'a> View<'a> {
|
||||
pub fn new(buffer: &'a mut RgbaImage) -> Self {
|
||||
let size = Vec2::from_u32(buffer.width(), buffer.height());
|
||||
let area = Rect::from_nw(Vec2::ZERO, size);
|
||||
Self { area, buffer }
|
||||
}
|
||||
|
||||
pub fn dup(&mut self) -> View<'_> {
|
||||
View {
|
||||
area: self.area,
|
||||
buffer: self.buffer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zoom(mut self, area: Rect) -> Self {
|
||||
self.area = area + self.area.corner_nw();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn size(&self) -> Vec2 {
|
||||
self.area.size()
|
||||
}
|
||||
|
||||
pub fn area(&self) -> Rect {
|
||||
Rect::from_nw(Vec2::ZERO, self.size())
|
||||
}
|
||||
|
||||
fn pos_to_buffer_pos(&self, pos: Vec2) -> Vec2 {
|
||||
pos + self.area.corner_nw()
|
||||
}
|
||||
|
||||
pub fn get(&self, pos: Vec2) -> Option<Srgba> {
|
||||
let (x, y) = self.pos_to_buffer_pos(pos).to_u32_checked()?;
|
||||
let pixel = self.buffer.get_pixel_checked(x, y)?;
|
||||
Some(color::from_image_color(*pixel))
|
||||
}
|
||||
|
||||
pub fn set(&mut self, pos: Vec2, color: Srgba) {
|
||||
let Some((x, y)) = self.pos_to_buffer_pos(pos).to_u32_checked() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pixel) = self.buffer.get_pixel_mut_checked(x, y) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let below = color::from_image_color(*pixel);
|
||||
*pixel = color::to_image_color(color.atop(below));
|
||||
}
|
||||
|
||||
pub fn replace(&mut self, pos: Vec2, color: Srgba) {
|
||||
let Some((x, y)) = self.pos_to_buffer_pos(pos).to_u32_checked() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pixel) = self.buffer.get_pixel_mut_checked(x, y) else {
|
||||
return;
|
||||
};
|
||||
|
||||
*pixel = color::to_image_color(color);
|
||||
}
|
||||
|
||||
// More complicated drawing primitives
|
||||
|
||||
pub fn rect(&mut self, area: Rect, color: Srgba) {
|
||||
for pos in area.points() {
|
||||
self.set(pos, color);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image(&mut self, image: &RgbaImage) {
|
||||
for y in 0..image.height() {
|
||||
for x in 0..image.width() {
|
||||
let color = color::from_image_color(*image.get_pixel(x, y));
|
||||
self.set(Vec2::from_u32(x, y), color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
use taffy::{AvailableSpace, Layout, Size};
|
||||
|
||||
use crate::{Node, View};
|
||||
|
||||
pub trait Widget<C> {
|
||||
/// Used for the measure function in
|
||||
/// [`taffy::TaffyTree::compute_layout_with_measure`].
|
||||
#[allow(unused_variables)]
|
||||
fn size(
|
||||
&mut self,
|
||||
ctx: &mut C,
|
||||
known: Size<Option<f32>>,
|
||||
available: Size<AvailableSpace>,
|
||||
) -> Size<f32> {
|
||||
Size::ZERO
|
||||
}
|
||||
|
||||
/// Called before all children are drawn.
|
||||
///
|
||||
/// Prefer this over [`Self::draw_above`] when implementing a leaf widget.
|
||||
#[allow(unused_variables)]
|
||||
fn draw_below(
|
||||
&mut self,
|
||||
ctx: &mut C,
|
||||
view: &mut View<'_>,
|
||||
layout: &Layout,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Called after all children are drawn.
|
||||
#[allow(unused_variables)]
|
||||
fn draw_above(
|
||||
&mut self,
|
||||
ctx: &mut C,
|
||||
view: &mut View<'_>,
|
||||
layout: &Layout,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub type BoxedWidget<C> = Box<dyn Widget<C>>;
|
||||
|
||||
pub trait WidgetExt<C> {
|
||||
fn node(self) -> Node<C>;
|
||||
}
|
||||
|
||||
impl<C, W: Widget<C> + 'static> WidgetExt<C> for W {
|
||||
fn node(self) -> Node<C> {
|
||||
Node::empty().with_widget(self)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
pub use block::*;
|
||||
pub use image::*;
|
||||
pub use text::*;
|
||||
pub use typst::*;
|
||||
|
||||
mod block;
|
||||
mod image;
|
||||
mod text;
|
||||
mod typst;
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
use palette::Srgba;
|
||||
use taffy::Layout;
|
||||
|
||||
use crate::{Rect, Vec2, View, Widget, color};
|
||||
|
||||
pub struct Block {
|
||||
border: Srgba,
|
||||
background: Srgba,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
border: color::TRANSPARENT,
|
||||
background: color::TRANSPARENT,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_border(mut self, color: Srgba) -> Self {
|
||||
self.border = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_background(mut self, color: Srgba) -> Self {
|
||||
self.background = color;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Block {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> Widget<C> for Block {
|
||||
fn draw_below(
|
||||
&mut self,
|
||||
_ctx: &mut C,
|
||||
view: &mut View<'_>,
|
||||
layout: &Layout,
|
||||
) -> anyhow::Result<()> {
|
||||
let area = view.area();
|
||||
|
||||
// Background
|
||||
view.rect(area, self.background);
|
||||
|
||||
// And now... the border!
|
||||
//
|
||||
// It is important not to draw pixels twice in case the border color is
|
||||
// transparent. That's why the logic below is a bit more complex.
|
||||
let left = layout.border.left as i32;
|
||||
let right = layout.border.right as i32;
|
||||
let top = layout.border.top as i32;
|
||||
let bottom = layout.border.bottom as i32;
|
||||
if top > 0 {
|
||||
let border = Rect::from_nw(area.corner_nw(), area.size().with_y(top));
|
||||
view.rect(border, self.border);
|
||||
}
|
||||
if bottom > 0 {
|
||||
let border = Rect::from_sw(area.corner_sw(), area.size().with_y(bottom));
|
||||
view.rect(border, self.border);
|
||||
}
|
||||
if left > 0 {
|
||||
let nw = area.corner_nw() + Vec2::new(0, top);
|
||||
let size = Vec2::new(left, area.size().y - top - bottom);
|
||||
view.rect(Rect::from_nw(nw, size), self.border);
|
||||
}
|
||||
if right > 0 {
|
||||
let ne = area.corner_ne() + Vec2::new(0, top);
|
||||
let size = Vec2::new(right, area.size().y - top - bottom);
|
||||
view.rect(Rect::from_ne(ne, size), self.border);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
use image::{
|
||||
RgbaImage,
|
||||
imageops::{self, FilterType},
|
||||
};
|
||||
use mark::dither::{AlgoFloydSteinberg, AlgoStucki, Algorithm, DiffEuclid, Palette};
|
||||
use palette::{IntoColor, LinSrgb, Srgba};
|
||||
use taffy::prelude::{AvailableSpace, Layout, Size};
|
||||
|
||||
use crate::Widget;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DitherAlgorithm {
|
||||
FloydSteinberg,
|
||||
Stucki,
|
||||
}
|
||||
|
||||
impl DitherAlgorithm {
|
||||
fn dither(self, image: RgbaImage, palette: &Palette<LinSrgb>) -> RgbaImage {
|
||||
match self {
|
||||
Self::FloydSteinberg => {
|
||||
<AlgoFloydSteinberg as Algorithm<LinSrgb, DiffEuclid>>::run(image, palette)
|
||||
}
|
||||
Self::Stucki => <AlgoStucki as Algorithm<LinSrgb, DiffEuclid>>::run(image, palette),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Image {
|
||||
image: RgbaImage,
|
||||
shrink: bool,
|
||||
grow: bool,
|
||||
scale: u32,
|
||||
filter: FilterType,
|
||||
|
||||
dither_palette: Option<Palette<LinSrgb>>,
|
||||
dither_algorithm: DitherAlgorithm,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub fn new(image: RgbaImage) -> Self {
|
||||
Self {
|
||||
image,
|
||||
shrink: true,
|
||||
grow: false,
|
||||
filter: FilterType::CatmullRom,
|
||||
scale: 1,
|
||||
dither_palette: None,
|
||||
dither_algorithm: DitherAlgorithm::FloydSteinberg,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_shrink(mut self, shrink: bool) -> Self {
|
||||
self.shrink = shrink;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_grow(mut self, grow: bool) -> Self {
|
||||
self.grow = grow;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_scale(mut self, scale: u32) -> Self {
|
||||
self.scale = scale.max(1);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_filter(mut self, filter: FilterType) -> Self {
|
||||
self.filter = filter;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_dither_palette(mut self, palette: &[Srgba]) -> Self {
|
||||
let palette = palette
|
||||
.iter()
|
||||
.map(|c| c.color.into_color())
|
||||
.collect::<Vec<LinSrgb>>();
|
||||
|
||||
self.dither_palette = Some(Palette::new(palette));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_dither_algorithm(mut self, algorithm: DitherAlgorithm) -> Self {
|
||||
self.dither_algorithm = algorithm;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> Widget<C> for Image {
|
||||
fn size(
|
||||
&mut self,
|
||||
_ctx: &mut C,
|
||||
known: Size<Option<f32>>,
|
||||
available: Size<AvailableSpace>,
|
||||
) -> Size<f32> {
|
||||
if self.image.width() == 0 || self.image.height() == 0 {
|
||||
// We don't want to divide by zero later on
|
||||
return Size {
|
||||
width: 0.0,
|
||||
height: 0.0,
|
||||
};
|
||||
}
|
||||
|
||||
let size = Size {
|
||||
width: (self.image.width() * self.scale) as f32,
|
||||
height: (self.image.height() * self.scale) as f32,
|
||||
};
|
||||
|
||||
let max_width = known.width.or(match available.width {
|
||||
AvailableSpace::Definite(width) => Some(width),
|
||||
AvailableSpace::MinContent => Some(0.0),
|
||||
AvailableSpace::MaxContent => None,
|
||||
});
|
||||
|
||||
let max_height = known.height.or(match available.height {
|
||||
AvailableSpace::Definite(height) => Some(height),
|
||||
AvailableSpace::MinContent => Some(0.0),
|
||||
AvailableSpace::MaxContent => None,
|
||||
});
|
||||
|
||||
let scale_factor = match (max_width, max_height) {
|
||||
(None, None) => 1.0,
|
||||
(None, Some(height)) => height / size.height,
|
||||
(Some(width), None) => width / size.width,
|
||||
(Some(width), Some(height)) => (width / size.width).min(height / size.height),
|
||||
};
|
||||
|
||||
if (scale_factor < 1.0 && self.shrink) || (scale_factor > 1.0 && self.grow) {
|
||||
Size {
|
||||
width: size.width * scale_factor,
|
||||
height: size.height * scale_factor,
|
||||
}
|
||||
} else {
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_below(
|
||||
&mut self,
|
||||
_ctx: &mut C,
|
||||
view: &mut crate::View<'_>,
|
||||
_layout: &Layout,
|
||||
) -> anyhow::Result<()> {
|
||||
let (width, height) = view.size().to_u32();
|
||||
|
||||
let iwidth = width / self.scale;
|
||||
let iheight = height / self.scale;
|
||||
|
||||
let image = imageops::resize(&self.image, iwidth, iheight, self.filter);
|
||||
|
||||
let image = if let Some(palette) = &self.dither_palette {
|
||||
self.dither_algorithm.dither(image, palette)
|
||||
} else {
|
||||
image
|
||||
};
|
||||
|
||||
let image = imageops::resize(&image, width, height, FilterType::Nearest);
|
||||
|
||||
view.image(&image);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,210 +0,0 @@
|
|||
use cosmic_text::{Attrs, AttrsOwned, Buffer, Family, FontSystem, Metrics, Shaping, SwashCache};
|
||||
use palette::Srgba;
|
||||
use showbits_assets::{UNIFONT, UNIFONT_JP, UNIFONT_NAME, UNIFONT_SIZE, UNIFONT_UPPER};
|
||||
use taffy::{
|
||||
Layout,
|
||||
prelude::{AvailableSpace, Size},
|
||||
};
|
||||
|
||||
use crate::{Rect, Vec2, View, Widget, color};
|
||||
|
||||
// https://github.com/DioxusLabs/taffy/blob/main/examples/cosmic_text.rs
|
||||
|
||||
pub struct FontStuff {
|
||||
font_system: FontSystem,
|
||||
swash_cache: SwashCache,
|
||||
}
|
||||
|
||||
impl FontStuff {
|
||||
pub fn new() -> Self {
|
||||
let mut font_system = FontSystem::new();
|
||||
let db = font_system.db_mut();
|
||||
db.load_font_data(UNIFONT.to_vec());
|
||||
db.load_font_data(UNIFONT_JP.to_vec());
|
||||
db.load_font_data(UNIFONT_UPPER.to_vec());
|
||||
db.set_monospace_family(UNIFONT_NAME);
|
||||
|
||||
Self {
|
||||
font_system,
|
||||
swash_cache: SwashCache::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FontStuff {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasFontStuff {
|
||||
fn font_stuff(&mut self) -> &mut FontStuff;
|
||||
}
|
||||
|
||||
pub struct Text {
|
||||
metrics: Metrics,
|
||||
default_attrs: AttrsOwned,
|
||||
chunks: Vec<(AttrsOwned, String)>,
|
||||
shaping: Shaping,
|
||||
color: Srgba,
|
||||
}
|
||||
|
||||
impl Text {
|
||||
pub const fn default_metrics() -> Metrics {
|
||||
Metrics::new(UNIFONT_SIZE, UNIFONT_SIZE)
|
||||
}
|
||||
|
||||
pub fn default_attrs<'a>() -> Attrs<'a> {
|
||||
Attrs::new().family(Family::Monospace)
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
metrics: Self::default_metrics(),
|
||||
default_attrs: AttrsOwned::new(Self::default_attrs()),
|
||||
chunks: vec![],
|
||||
shaping: Shaping::Advanced,
|
||||
color: color::BLACK,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_metrics(mut self, metrics: Metrics) -> Self {
|
||||
self.metrics = metrics;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_font_size(mut self, size: f32) -> Self {
|
||||
self.metrics.font_size = size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_line_height(mut self, height: f32) -> Self {
|
||||
self.metrics.line_height = height;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_default_attrs(mut self, attrs: Attrs<'_>) -> Self {
|
||||
self.default_attrs = AttrsOwned::new(attrs);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_shaping(mut self, shaping: Shaping) -> Self {
|
||||
self.shaping = shaping;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_color(mut self, color: Srgba) -> Self {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn and_plain<S: ToString>(mut self, text: S) -> Self {
|
||||
let chunk = (self.default_attrs.clone(), text.to_string());
|
||||
self.chunks.push(chunk);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn and_rich<S: ToString>(mut self, attrs: Attrs<'_>, text: S) -> Self {
|
||||
let chunk = (AttrsOwned::new(attrs), text.to_string());
|
||||
self.chunks.push(chunk);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn and_chunks<I>(mut self, chunks: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = (AttrsOwned, String)>,
|
||||
{
|
||||
self.chunks.extend(chunks);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn widget<C: HasFontStuff>(self, font_stuff: &mut FontStuff) -> impl Widget<C> + use<C> {
|
||||
let fs = &mut font_stuff.font_system;
|
||||
let mut buffer = Buffer::new_empty(self.metrics);
|
||||
buffer.set_size(fs, None, None);
|
||||
|
||||
let spans = self
|
||||
.chunks
|
||||
.iter()
|
||||
.map(|(attrs, text)| (text as &str, attrs.as_attrs()));
|
||||
buffer.set_rich_text(fs, spans, self.default_attrs.as_attrs(), self.shaping);
|
||||
|
||||
TextWidget {
|
||||
buffer,
|
||||
color: self.color,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Text {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
struct TextWidget {
|
||||
buffer: Buffer,
|
||||
color: Srgba,
|
||||
}
|
||||
|
||||
impl<C: HasFontStuff> Widget<C> for TextWidget {
|
||||
fn size(
|
||||
&mut self,
|
||||
ctx: &mut C,
|
||||
known: Size<Option<f32>>,
|
||||
available: Size<AvailableSpace>,
|
||||
) -> Size<f32> {
|
||||
let width = known.width.unwrap_or(match available.width {
|
||||
AvailableSpace::Definite(width) => width,
|
||||
AvailableSpace::MinContent => 0.0,
|
||||
AvailableSpace::MaxContent => f32::INFINITY,
|
||||
});
|
||||
|
||||
let fs = &mut ctx.font_stuff().font_system;
|
||||
self.buffer.set_size(fs, Some(width), None);
|
||||
self.buffer.shape_until_scroll(fs, false);
|
||||
|
||||
let runs = self.buffer.layout_runs().collect::<Vec<_>>();
|
||||
let height = runs.len() as f32 * self.buffer.metrics().line_height;
|
||||
let width = runs
|
||||
.into_iter()
|
||||
.map(|run| run.line_w)
|
||||
.max_by(|a, b| a.partial_cmp(b).unwrap())
|
||||
.unwrap_or(0.0);
|
||||
|
||||
// If we don't round up here, the layout rounding may round down our
|
||||
// size slightly. This may lead to more line breaks, moving some words
|
||||
// below our visible area.
|
||||
let width = width.ceil();
|
||||
let height = height.ceil();
|
||||
|
||||
Size { width, height }
|
||||
}
|
||||
|
||||
fn draw_below(
|
||||
&mut self,
|
||||
ctx: &mut C,
|
||||
view: &mut View<'_>,
|
||||
_layout: &Layout,
|
||||
) -> anyhow::Result<()> {
|
||||
let size = view.size();
|
||||
|
||||
let FontStuff {
|
||||
font_system: fs,
|
||||
swash_cache: sc,
|
||||
} = ctx.font_stuff();
|
||||
|
||||
self.buffer
|
||||
.set_size(fs, Some(size.x as f32), Some(size.y as f32));
|
||||
self.buffer.shape_until_scroll(fs, true);
|
||||
|
||||
let color = color::to_text_color(self.color);
|
||||
self.buffer.draw(fs, sc, color, |x, y, w, h, color| {
|
||||
let color = color::from_text_color(color);
|
||||
let area = Rect::from_nw(Vec2::new(x, y), Vec2::from_u32(w, h));
|
||||
view.rect(area, color);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
use std::{fs, path::PathBuf, sync::OnceLock};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use image::RgbaImage;
|
||||
use taffy::{
|
||||
Layout,
|
||||
prelude::{AvailableSpace, Size},
|
||||
};
|
||||
use typst::{
|
||||
Library, World,
|
||||
diag::{FileError, FileResult},
|
||||
foundations::{Bytes, Datetime},
|
||||
layout::Abs,
|
||||
syntax::{FileId, Source},
|
||||
text::{Font, FontBook},
|
||||
utils::LazyHash,
|
||||
visualize::Color,
|
||||
};
|
||||
|
||||
use crate::{View, Widget};
|
||||
|
||||
// The logic for detecting and loading fonts was ripped straight from:
|
||||
// https://github.com/typst/typst/blob/69dcc89d84176838c293b2d59747cd65e28843ad/crates/typst-cli/src/fonts.rs
|
||||
// https://github.com/typst/typst/blob/69dcc89d84176838c293b2d59747cd65e28843ad/crates/typst-cli/src/world.rs#L193-L195
|
||||
|
||||
struct FontSlot {
|
||||
path: PathBuf,
|
||||
index: u32,
|
||||
font: OnceLock<Option<Font>>,
|
||||
}
|
||||
|
||||
impl FontSlot {
|
||||
pub fn get(&self) -> Option<Font> {
|
||||
self.font
|
||||
.get_or_init(|| {
|
||||
let data = fs::read(&self.path).ok()?;
|
||||
Font::new(Bytes::new(data), self.index)
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
struct FontLoader {
|
||||
book: FontBook,
|
||||
fonts: Vec<FontSlot>,
|
||||
}
|
||||
|
||||
impl FontLoader {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
book: FontBook::new(),
|
||||
fonts: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn load_embedded_fonts(&mut self) {
|
||||
// https://github.com/typst/typst/blob/be12762d942e978ddf2e0ac5c34125264ab483b7/crates/typst-cli/src/fonts.rs#L107-L121
|
||||
for font_file in typst_assets::fonts() {
|
||||
let font_data = Bytes::new(font_file);
|
||||
for (i, font) in Font::iter(font_data).enumerate() {
|
||||
self.book.push(font.info().clone());
|
||||
self.fonts.push(FontSlot {
|
||||
path: PathBuf::new(),
|
||||
index: i as u32,
|
||||
font: OnceLock::from(Some(font)),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DummyWorld {
|
||||
library: LazyHash<Library>,
|
||||
book: LazyHash<FontBook>,
|
||||
main: Source,
|
||||
fonts: Vec<FontSlot>,
|
||||
}
|
||||
|
||||
impl DummyWorld {
|
||||
fn new(main: String) -> Self {
|
||||
let mut loader = FontLoader::new();
|
||||
loader.load_embedded_fonts();
|
||||
|
||||
Self {
|
||||
library: LazyHash::new(Library::builder().build()),
|
||||
book: LazyHash::new(loader.book),
|
||||
main: Source::detached(main),
|
||||
fonts: loader.fonts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl World for DummyWorld {
|
||||
fn library(&self) -> &LazyHash<Library> {
|
||||
&self.library
|
||||
}
|
||||
|
||||
fn book(&self) -> &LazyHash<FontBook> {
|
||||
&self.book
|
||||
}
|
||||
|
||||
fn main(&self) -> FileId {
|
||||
self.main.id()
|
||||
}
|
||||
|
||||
fn source(&self, id: FileId) -> FileResult<Source> {
|
||||
if id == self.main.id() {
|
||||
return Ok(self.main.clone());
|
||||
}
|
||||
Err(FileError::AccessDenied)
|
||||
}
|
||||
|
||||
fn file(&self, _id: FileId) -> FileResult<Bytes> {
|
||||
Err(FileError::AccessDenied)
|
||||
}
|
||||
|
||||
fn font(&self, index: usize) -> Option<Font> {
|
||||
self.fonts[index].get()
|
||||
}
|
||||
|
||||
fn today(&self, _offset: Option<i64>) -> Option<Datetime> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
const SCALE: f32 = 3.0;
|
||||
|
||||
pub struct Typst {
|
||||
code: String,
|
||||
}
|
||||
|
||||
impl Typst {
|
||||
pub fn new(code: String) -> Self {
|
||||
Self { code }
|
||||
}
|
||||
|
||||
fn render(&self, width: Option<f32>, height: Option<f32>) -> Result<RgbaImage, Vec<String>> {
|
||||
let width = match width {
|
||||
Some(width) => format!("{}pt", width / SCALE),
|
||||
None => "auto".to_string(),
|
||||
};
|
||||
|
||||
let height = match height {
|
||||
Some(height) => format!("{}pt", height / SCALE),
|
||||
None => "auto".to_string(),
|
||||
};
|
||||
|
||||
let mut source = String::new();
|
||||
source.push_str(&format!("#set page(width: {width}, height: {height})\n"));
|
||||
source.push_str("#set page(margin: (left: 0mm, right: 0mm, top: 1mm, bottom: 2mm))\n");
|
||||
source.push_str(&self.code);
|
||||
|
||||
let world = DummyWorld::new(source);
|
||||
|
||||
let document = typst::compile(&world).output.map_err(|errs| {
|
||||
errs.into_iter()
|
||||
.map(|sd| sd.message.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
})?;
|
||||
|
||||
let pixmap = typst_render::render_merged(&document, SCALE, Abs::zero(), Some(Color::WHITE));
|
||||
let buffer = RgbaImage::from_raw(pixmap.width(), pixmap.height(), pixmap.take()).unwrap();
|
||||
Ok(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> Widget<C> for Typst {
|
||||
fn size(
|
||||
&mut self,
|
||||
_ctx: &mut C,
|
||||
known: Size<Option<f32>>,
|
||||
available: Size<AvailableSpace>,
|
||||
) -> Size<f32> {
|
||||
let width = known.width.or(match available.width {
|
||||
AvailableSpace::Definite(width) => Some(width),
|
||||
AvailableSpace::MinContent => None, // auto
|
||||
AvailableSpace::MaxContent => Some(4096.0),
|
||||
});
|
||||
|
||||
let height = known.height.or(match available.height {
|
||||
AvailableSpace::Definite(width) => Some(width),
|
||||
AvailableSpace::MinContent | AvailableSpace::MaxContent => None, // auto
|
||||
});
|
||||
|
||||
let Ok(buffer) = self.render(width, height) else {
|
||||
return Size {
|
||||
width: width.unwrap_or(0.0),
|
||||
height: height.unwrap_or(0.0),
|
||||
};
|
||||
};
|
||||
|
||||
// Round up so we definitely have enough space.
|
||||
// let width = (buffer.width() as f32 / SCALE).ceil() * SCALE + 1.0;
|
||||
// let height = (buffer.height() as f32 / SCALE).ceil() * SCALE + 1.0;
|
||||
let width = (buffer.width() as f32).ceil() + 1.0;
|
||||
let height = (buffer.height() as f32).ceil() + 1.0;
|
||||
|
||||
Size { width, height }
|
||||
}
|
||||
|
||||
fn draw_below(
|
||||
&mut self,
|
||||
_ctx: &mut C,
|
||||
view: &mut View<'_>,
|
||||
_layout: &Layout,
|
||||
) -> anyhow::Result<()> {
|
||||
let size = view.size();
|
||||
|
||||
let buffer = self
|
||||
.render(Some(size.x as f32), Some(size.y as f32))
|
||||
.map_err(|errs| anyhow!("{}", errs.join("\n")))?;
|
||||
|
||||
view.image(&buffer);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ anyhow = { workspace = true }
|
|||
axum = { workspace = true, features = ["multipart"] }
|
||||
clap = { workspace = true }
|
||||
escpos = { workspace = true }
|
||||
image = { workspace = true, default-features = true }
|
||||
image = { workspace = true }
|
||||
jiff = { workspace = true }
|
||||
mime_guess = { workspace = true }
|
||||
palette = { workspace = true }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue