mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
Replace Rustybuzz with Parley for text layout, and add text tilt parameter (#2739)
* replace rustybuzz with parley for text layout handling change text input direction based on text direction * Code review * change default character spacing to 0 * add shear to text node this also adds migration code for documents that don't have shear * shear migration for text node - add shear property - set character spacing to 0 * use old max_width and max_height in text migration if available * Final code review pass * Add units to the parameters --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
d8e15aeb93
commit
83773baa00
18 changed files with 488 additions and 300 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,3 +5,4 @@ perf.data*
|
|||
profile.json
|
||||
flamegraph.svg
|
||||
.idea/
|
||||
.direnv
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
libraw
|
||||
|
||||
|
||||
# Tauri dependencies: keep in sync with https://v2.tauri.app/start/prerequisites/
|
||||
# Tauri dependencies: keep in sync with https://v2.tauri.app/start/prerequisites/#system-dependencies (under the NixOS tab)
|
||||
at-spi2-atk
|
||||
atkmm
|
||||
cairo
|
||||
|
|
340
Cargo.lock
generated
340
Cargo.lock
generated
|
@ -82,6 +82,12 @@ dependencies = [
|
|||
"alloc-no-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android-activity"
|
||||
version = "0.5.2"
|
||||
|
@ -486,11 +492,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d59b4c170e16f0405a2e95aff44432a0d41aa97675f3d52623effe95792a037"
|
||||
checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2"
|
||||
dependencies = [
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -528,9 +534,9 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
|||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.22.0"
|
||||
version = "1.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
|
||||
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
@ -822,6 +828,12 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ae467d04a8a8aea5d9a49018a6ade2e4221d92968e8ce55a48c0b1164e5f698"
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
|
@ -1255,6 +1267,16 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||
|
||||
[[package]]
|
||||
name = "dispatch2"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2 0.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
|
@ -1569,6 +1591,25 @@ dependencies = [
|
|||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "font-types"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02a596f5713680923a2080d86de50fe472fb290693cf0f701187a1c8b36996b7"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontconfig-cache-parser"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7f8afb20c8069fd676d27b214559a337cc619a605d25a87baa90b49a06f3b18"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontconfig-parser"
|
||||
version = "0.5.7"
|
||||
|
@ -1592,6 +1633,29 @@ dependencies = [
|
|||
"ttf-parser 0.24.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontique"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39f97079e1293b8c1e9fb03a2875d328bd2ee8f3b95ce62959c0acc04049c708"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"fontconfig-cache-parser",
|
||||
"hashbrown 0.15.4",
|
||||
"icu_locid",
|
||||
"memmap2",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-text",
|
||||
"objc2-foundation 0.3.1",
|
||||
"peniko 0.4.0",
|
||||
"read-fonts 0.29.3",
|
||||
"roxmltree",
|
||||
"smallvec",
|
||||
"windows 0.58.0",
|
||||
"windows-core 0.58.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
|
@ -2109,7 +2173,7 @@ checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca"
|
|||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"gpu-descriptor-types",
|
||||
"hashbrown 0.15.2",
|
||||
"hashbrown 0.15.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2200,13 +2264,14 @@ dependencies = [
|
|||
"node-macro",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"parley",
|
||||
"petgraph 0.7.1",
|
||||
"rand 0.9.0",
|
||||
"rand_chacha 0.9.0",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustybuzz 0.20.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"skrifa 0.32.0",
|
||||
"specta",
|
||||
"tinyvec",
|
||||
"tokio",
|
||||
|
@ -2470,10 +2535,12 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
|||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.2"
|
||||
version = "0.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
|
@ -2949,7 +3016,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.2",
|
||||
"hashbrown 0.15.4",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
@ -3204,9 +3271,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "kurbo"
|
||||
version = "0.11.1"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f"
|
||||
checksum = "1077d333efea6170d9ccb96d3c3026f300ca0773da4938cc4c811daa6df68b0c"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"serde",
|
||||
|
@ -3282,7 +3349,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3506,10 +3573,10 @@ dependencies = [
|
|||
"dpi",
|
||||
"gtk",
|
||||
"keyboard-types",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
"once_cell",
|
||||
"png",
|
||||
"serde",
|
||||
|
@ -3790,9 +3857,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3531f65190d9cff863b77a99857e74c314dd16bf56c538c4b57c7cbc3f3a6e59"
|
||||
checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551"
|
||||
dependencies = [
|
||||
"objc2-encode 4.1.0",
|
||||
"objc2-exception-helper",
|
||||
|
@ -3805,15 +3872,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "5906f93257178e2f7ae069efb89fbd6ee94f0592740b5f8a1512ca498814d0fb"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"block2 0.6.0",
|
||||
"block2 0.6.1",
|
||||
"libc",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-cloud-kit",
|
||||
"objc2-core-data",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-core-image",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-quartz-core 0.3.0",
|
||||
]
|
||||
|
||||
|
@ -3824,8 +3891,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "6c1948a9be5f469deadbd6bcb86ad7ff9e47b4f632380139722f7d9840c0d42c"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3835,18 +3902,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1f860f8e841f6d32f754836f51e6bc7777cd7e7053cf18528233f6811d3eceb4"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925"
|
||||
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2 0.6.0",
|
||||
"dispatch2",
|
||||
"objc2 0.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3856,7 +3924,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "f8dca602628b65356b6513290a21a6405b4d4027b8b250f0b98dddbb28b7de02"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-surface",
|
||||
]
|
||||
|
@ -3867,8 +3935,18 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ffa6bea72bf42c78b0b34e89c0bafac877d5f80bf91e159a5d96ea7f693ca56"
|
||||
dependencies = [
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-text"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ba833d4a1cb1aac330f8c973fd92b6ff1858e4aef5cdd00a255eefb28022fb5"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3906,14 +3984,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "objc2-foundation"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998"
|
||||
checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"block2 0.6.0",
|
||||
"block2 0.6.1",
|
||||
"libc",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
|
@ -3924,7 +4002,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "161a8b87e32610086e1a7a9e9ec39f84459db7b3a0881c1f16ca5a2605581c19"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
|
@ -3960,8 +4038,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "6fb3794501bb1bee12f08dcad8c61f2a5875791ad1c6f47faa71a0f033f20071"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3971,9 +4049,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "777a571be14a42a3990d4ebedaeb8b54cd17377ec21b92e8200ac03797b3bee1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3983,11 +4061,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b717127e4014b0f9f3e8bba3d3f2acec81f1bde01f656823036e823ed2c94dce"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"block2 0.6.0",
|
||||
"objc2 0.6.0",
|
||||
"block2 0.6.1",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4149,6 +4227,19 @@ dependencies = [
|
|||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parley"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13e57638545cf2ba4c3e72cc5715e53b1880b829cc3dbefda3d1700c58efe723"
|
||||
dependencies = [
|
||||
"fontique",
|
||||
"hashbrown 0.15.4",
|
||||
"peniko 0.4.0",
|
||||
"skrifa 0.31.3",
|
||||
"swash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
|
@ -4180,7 +4271,18 @@ name = "peniko"
|
|||
version = "0.2.0"
|
||||
source = "git+https://github.com/linebender/peniko.git?rev=d114c62#d114c6292dbcfb03e7360692198be423168a0edd"
|
||||
dependencies = [
|
||||
"color",
|
||||
"color 0.1.0",
|
||||
"kurbo",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peniko"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f9529efd019889b2a205193c14ffb6e2839b54ed9d2720674f10f4b04d87ac9"
|
||||
dependencies = [
|
||||
"color 0.3.1",
|
||||
"kurbo",
|
||||
"smallvec",
|
||||
]
|
||||
|
@ -4968,7 +5070,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "f6f9e8a4f503e5c8750e4cd3b32a4e090035c46374b305a15c70bad833dca05f"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"font-types",
|
||||
"font-types 0.8.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "read-fonts"
|
||||
version = "0.29.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04ca636dac446b5664bd16c069c00a9621806895b8bb02c2dc68542b23b8f25d"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"font-types 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "read-fonts"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "192735ef611aac958468e670cb98432c925426f3cb71521fda202130f7388d91"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"font-types 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5261,26 +5383,8 @@ dependencies = [
|
|||
"log",
|
||||
"smallvec",
|
||||
"ttf-parser 0.24.1",
|
||||
"unicode-bidi-mirroring 0.3.0",
|
||||
"unicode-ccc 0.3.0",
|
||||
"unicode-properties",
|
||||
"unicode-script",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustybuzz"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bytemuck",
|
||||
"core_maths",
|
||||
"log",
|
||||
"smallvec",
|
||||
"ttf-parser 0.25.1",
|
||||
"unicode-bidi-mirroring 0.4.0",
|
||||
"unicode-ccc 0.4.0",
|
||||
"unicode-bidi-mirroring",
|
||||
"unicode-ccc",
|
||||
"unicode-properties",
|
||||
"unicode-script",
|
||||
]
|
||||
|
@ -5415,9 +5519,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.218"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
@ -5446,9 +5550,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.218"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -5652,7 +5756,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8cc1aa86c26dbb1b63875a7180aa0819709b33348eb5b1491e4321fae388179d"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"read-fonts",
|
||||
"read-fonts 0.25.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "skrifa"
|
||||
version = "0.31.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbeb4ca4399663735553a09dd17ce7e49a0a0203f03b706b39628c4d913a8607"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"read-fonts 0.29.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "skrifa"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d632b5a73f566303dbeabd344dc3e716fd4ddc9a70d6fc8ea8e6f06617da97"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"read-fonts 0.30.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5675,9 +5799,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.14.0"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -5895,6 +6019,17 @@ dependencies = [
|
|||
"siphasher 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swash"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f745de914febc7c9ab4388dfaf94bbc87e69f57bb41133a9b0c84d4be49856f3"
|
||||
dependencies = [
|
||||
"skrifa 0.31.3",
|
||||
"yazi",
|
||||
"zeno",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swift-rs"
|
||||
version = "1.0.7"
|
||||
|
@ -6005,9 +6140,9 @@ dependencies = [
|
|||
"ndk 0.9.0",
|
||||
"ndk-context",
|
||||
"ndk-sys 0.6.0+11769913",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"raw-window-handle",
|
||||
|
@ -6060,9 +6195,9 @@ dependencies = [
|
|||
"log",
|
||||
"mime",
|
||||
"muda",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
"percent-encoding",
|
||||
"plist",
|
||||
"raw-window-handle",
|
||||
|
@ -6266,9 +6401,9 @@ dependencies = [
|
|||
"http",
|
||||
"jni",
|
||||
"log",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"raw-window-handle",
|
||||
|
@ -6688,11 +6823,11 @@ dependencies = [
|
|||
"dirs",
|
||||
"libappindicator",
|
||||
"muda",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
"once_cell",
|
||||
"png",
|
||||
"serde",
|
||||
|
@ -6720,9 +6855,6 @@ name = "ttf-parser"
|
|||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
|
||||
dependencies = [
|
||||
"core_maths",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
|
@ -6795,24 +6927,12 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64af057ad7466495ca113126be61838d8af947f41d93a949980b2389a118082f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi-mirroring"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ccc"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ccc"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
|
@ -6900,7 +7020,7 @@ dependencies = [
|
|||
"log",
|
||||
"pico-args",
|
||||
"roxmltree",
|
||||
"rustybuzz 0.18.0",
|
||||
"rustybuzz",
|
||||
"simplecss",
|
||||
"siphasher 1.0.1",
|
||||
"strict-num",
|
||||
|
@ -6971,9 +7091,9 @@ dependencies = [
|
|||
"bytemuck",
|
||||
"futures-intrusive",
|
||||
"log",
|
||||
"peniko",
|
||||
"peniko 0.2.0",
|
||||
"png",
|
||||
"skrifa",
|
||||
"skrifa 0.26.6",
|
||||
"static_assertions",
|
||||
"thiserror 2.0.12",
|
||||
"vello_encoding",
|
||||
|
@ -6988,8 +7108,8 @@ source = "git+https://github.com/linebender/vello.git?rev=3275ec8#3275ec85d83118
|
|||
dependencies = [
|
||||
"bytemuck",
|
||||
"guillotiere",
|
||||
"peniko",
|
||||
"skrifa",
|
||||
"peniko 0.2.0",
|
||||
"skrifa 0.26.6",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
|
@ -7556,10 +7676,10 @@ version = "0.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c"
|
||||
dependencies = [
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
"raw-window-handle",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-version",
|
||||
|
@ -8077,7 +8197,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b19b78efae8b853c6c817e8752fc1dbf9cab8a8ffe9c30f399bd750ccf0f0730"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2 0.6.0",
|
||||
"block2 0.6.1",
|
||||
"cookie",
|
||||
"crossbeam-channel",
|
||||
"dpi",
|
||||
|
@ -8091,10 +8211,10 @@ dependencies = [
|
|||
"kuchikiki",
|
||||
"libc",
|
||||
"ndk 0.9.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-ui-kit",
|
||||
"objc2-web-kit",
|
||||
"once_cell",
|
||||
|
@ -8199,6 +8319,12 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||
|
||||
[[package]]
|
||||
name = "yazi"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.7.5"
|
||||
|
@ -8223,6 +8349,12 @@ dependencies = [
|
|||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeno"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
|
|
|
@ -116,7 +116,8 @@ rand_chacha = "0.9"
|
|||
glam = { version = "0.29", default-features = false, features = ["serde", "scalar-math", "debug-glam-assert"] }
|
||||
base64 = "0.22"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp"] }
|
||||
rustybuzz = "0.20"
|
||||
parley = "0.5.0"
|
||||
skrifa = "0.32.0"
|
||||
pretty_assertions = "1.4.1"
|
||||
fern = { version = "0.7", features = ["colored"] }
|
||||
num_enum = "0.7"
|
||||
|
|
|
@ -190,6 +190,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
Some(NodeInput::value(TaggedValue::F64(typesetting.character_spacing), false)),
|
||||
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_width), false)),
|
||||
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_height), false)),
|
||||
Some(NodeInput::value(TaggedValue::F64(typesetting.tilt), false)),
|
||||
]);
|
||||
|
||||
let text_id = NodeId::new();
|
||||
|
|
|
@ -1556,6 +1556,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().character_spacing), false),
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false),
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false),
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -1577,6 +1578,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
"Line Height",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some("x".to_string()),
|
||||
min: Some(0.),
|
||||
step: Some(0.1),
|
||||
..Default::default()
|
||||
|
@ -1586,6 +1588,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
"Character Spacing",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some(" px".to_string()),
|
||||
min: Some(0.),
|
||||
step: Some(0.1),
|
||||
..Default::default()
|
||||
|
@ -1595,6 +1598,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
"Max Width",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some(" px".to_string()),
|
||||
min: Some(1.),
|
||||
blank_assist: false,
|
||||
..Default::default()
|
||||
|
@ -1604,11 +1608,22 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
"Max Height",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some(" px".to_string()),
|
||||
min: Some(1.),
|
||||
blank_assist: false,
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
"Tilt",
|
||||
"Faux italic",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
min: Some(-85.),
|
||||
max: Some(85.),
|
||||
unit: Some("°".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
],
|
||||
output_names: vec!["Vector".to_string()],
|
||||
..Default::default()
|
||||
|
|
|
@ -1161,8 +1161,7 @@ impl NodeNetworkInterface {
|
|||
.and_then(|node_metadata| node_metadata.persistent_metadata.input_properties.get(index))
|
||||
}
|
||||
|
||||
pub fn insert_input_properties_row(&mut self, node_id: &NodeId, index: usize, network_path: &[NodeId]) {
|
||||
let row = ("", "TODO").into();
|
||||
pub fn insert_input_properties_row(&mut self, node_id: &NodeId, index: usize, network_path: &[NodeId], row: PropertiesRow) {
|
||||
let _ = self
|
||||
.node_metadata_mut(node_id, network_path)
|
||||
.map(|node_metadata| node_metadata.persistent_metadata.input_properties.insert(index - 1, row));
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// TODO: Eventually remove this document upgrade code
|
||||
// This file contains lots of hacky code for upgrading old documents to the new format
|
||||
|
||||
use super::document::utility_types::network_interface::{NumberInputSettings, PropertiesRow, WidgetOverride};
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, OutputConnector};
|
||||
|
@ -308,8 +309,8 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document.network_interface.insert_input_properties_row(node_id, 8, network_path);
|
||||
document.network_interface.insert_input_properties_row(node_id, 9, network_path);
|
||||
document.network_interface.insert_input_properties_row(node_id, 8, network_path, ("", "TODO").into());
|
||||
document.network_interface.insert_input_properties_row(node_id, 9, network_path, ("", "TODO").into());
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false);
|
||||
|
@ -402,7 +403,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
|
||||
if reference == "Text" && inputs_count != 8 {
|
||||
if reference == "Text" && inputs_count != 9 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
|
@ -433,12 +434,44 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 6),
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false),
|
||||
if inputs_count >= 7 {
|
||||
old_inputs[6].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 7),
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false),
|
||||
if inputs_count >= 8 {
|
||||
old_inputs[7].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.insert_input_properties_row(
|
||||
node_id,
|
||||
9,
|
||||
network_path,
|
||||
PropertiesRow::with_override(
|
||||
"Tilt",
|
||||
"Faux italic",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
min: Some(-85.),
|
||||
max: Some(85.),
|
||||
unit: Some("°".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 8),
|
||||
if inputs_count >= 9 {
|
||||
old_inputs[8].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -361,6 +361,7 @@ pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
|
|||
let Some(&TaggedValue::F64(character_spacing)) = inputs[5].as_value() else { return None };
|
||||
let Some(&TaggedValue::OptionalF64(max_width)) = inputs[6].as_value() else { return None };
|
||||
let Some(&TaggedValue::OptionalF64(max_height)) = inputs[7].as_value() else { return None };
|
||||
let Some(&TaggedValue::F64(tilt)) = inputs[8].as_value() else { return None };
|
||||
|
||||
let typesetting = TypesettingConfig {
|
||||
font_size,
|
||||
|
@ -368,6 +369,7 @@ pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
|
|||
max_width,
|
||||
character_spacing,
|
||||
max_height,
|
||||
tilt,
|
||||
};
|
||||
Some((text, font, typesetting))
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::messages::tool::utility_types::ToolType;
|
|||
use bezier_rs::{Bezier, BezierHandles};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::{FontCache, load_face};
|
||||
use graphene_std::text::{FontCache, load_font};
|
||||
use graphene_std::vector::{HandleExt, HandleId, ManipulatorPointId, PointId, SegmentId, VectorData, VectorModificationType};
|
||||
use kurbo::{CubicBez, Line, ParamCurveExtrema, PathSeg, Point, QuadBez};
|
||||
|
||||
|
@ -70,8 +70,8 @@ pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageH
|
|||
return Quad::from_box([DVec2::ZERO, DVec2::ZERO]);
|
||||
};
|
||||
|
||||
let buzz_face = font_cache.get(font).map(|data| load_face(data));
|
||||
let far = graphene_std::text::bounding_box(text, buzz_face.as_ref(), typesetting, false);
|
||||
let font_data = font_cache.get(font).map(|data| load_font(data));
|
||||
let far = graphene_std::text::bounding_box(text, font_data, typesetting, false);
|
||||
|
||||
Quad::from_box([DVec2::ZERO, far])
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ use graph_craft::document::value::TaggedValue;
|
|||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_face};
|
||||
use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_font};
|
||||
use graphene_std::vector::style::Fill;
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -35,6 +35,7 @@ pub struct TextOptions {
|
|||
font_name: String,
|
||||
font_style: String,
|
||||
fill: ToolColorOptions,
|
||||
tilt: f64,
|
||||
}
|
||||
|
||||
impl Default for TextOptions {
|
||||
|
@ -42,10 +43,11 @@ impl Default for TextOptions {
|
|||
Self {
|
||||
font_size: 24.,
|
||||
line_height_ratio: 1.2,
|
||||
character_spacing: 1.,
|
||||
character_spacing: 0.,
|
||||
font_name: graphene_std::consts::DEFAULT_FONT_FAMILY.into(),
|
||||
font_style: graphene_std::consts::DEFAULT_FONT_STYLE.into(),
|
||||
fill: ToolColorOptions::new_primary(),
|
||||
tilt: 0.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -468,8 +470,8 @@ impl Fsm for TextToolFsmState {
|
|||
transform: document.metadata().transform_to_viewport(tool_data.layer).to_cols_array(),
|
||||
});
|
||||
if let Some(editing_text) = tool_data.editing_text.as_mut() {
|
||||
let buzz_face = font_cache.get(&editing_text.font).map(|data| load_face(data));
|
||||
let far = graphene_std::text::bounding_box(&tool_data.new_text, buzz_face.as_ref(), editing_text.typesetting, false);
|
||||
let font_data = font_cache.get(&editing_text.font).map(|data| load_font(data));
|
||||
let far = graphene_std::text::bounding_box(&tool_data.new_text, font_data, editing_text.typesetting, false);
|
||||
if far.x != 0. && far.y != 0. {
|
||||
let quad = Quad::from_box([DVec2::ZERO, far]);
|
||||
let transformed_quad = document.metadata().transform_to_viewport(tool_data.layer) * quad;
|
||||
|
@ -517,8 +519,8 @@ impl Fsm for TextToolFsmState {
|
|||
// Draw red overlay if text is clipped
|
||||
let transformed_quad = layer_transform * bounds;
|
||||
if let Some((text, font, typesetting)) = graph_modification_utils::get_text(layer.unwrap(), &document.network_interface) {
|
||||
let buzz_face = font_cache.get(font).map(|data| load_face(data));
|
||||
if lines_clipping(text.as_str(), buzz_face, typesetting) {
|
||||
let font_data = font_cache.get(font).map(|data| load_font(data));
|
||||
if lines_clipping(text.as_str(), font_data, typesetting) {
|
||||
overlay_context.line(transformed_quad.0[2], transformed_quad.0[3], Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
}
|
||||
}
|
||||
|
@ -784,6 +786,7 @@ impl Fsm for TextToolFsmState {
|
|||
max_width: constraint_size.map(|size| size.x),
|
||||
character_spacing: tool_options.character_spacing,
|
||||
max_height: constraint_size.map(|size| size.y),
|
||||
tilt: tool_options.tilt,
|
||||
},
|
||||
font: Font::new(tool_options.font_name.clone(), tool_options.font_style.clone()),
|
||||
color: tool_options.fill.active_color(),
|
||||
|
|
|
@ -760,6 +760,8 @@
|
|||
|
||||
.text-input {
|
||||
word-break: break-all;
|
||||
unicode-bidi: plaintext;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-input div {
|
||||
|
@ -773,6 +775,8 @@
|
|||
overflow-wrap: anywhere;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
unicode-bidi: plaintext;
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
// Workaround to force Chrome to display the flashing text entry cursor when text is empty
|
||||
padding-left: 1px;
|
||||
|
|
|
@ -158,6 +158,7 @@
|
|||
background: none;
|
||||
color: var(--color-e-nearwhite);
|
||||
caret-color: var(--color-e-nearwhite);
|
||||
unicode-bidi: plaintext;
|
||||
|
||||
&::selection {
|
||||
background-color: var(--color-4-dimgray);
|
||||
|
|
|
@ -29,10 +29,11 @@ ctor = { workspace = true }
|
|||
rand_chacha = { workspace = true }
|
||||
bezier-rs = { workspace = true }
|
||||
specta = { workspace = true }
|
||||
rustybuzz = { workspace = true }
|
||||
image = { workspace = true }
|
||||
half = { workspace = true }
|
||||
tinyvec = { workspace = true }
|
||||
parley = { workspace = true }
|
||||
skrifa = { workspace = true }
|
||||
kurbo = { workspace = true }
|
||||
log = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
|
|
|
@ -22,11 +22,12 @@ impl Default for Font {
|
|||
/// A cache of all loaded font data and preview urls along with the default font (send from `init_app` in `editor_api.rs`)
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq, DynAny)]
|
||||
pub struct FontCache {
|
||||
/// Actual font file data used for rendering a font with ttf_parser and rustybuzz
|
||||
/// Actual font file data used for rendering a font
|
||||
font_file_data: HashMap<Font, Vec<u8>>,
|
||||
/// Web font preview URLs used for showing fonts when live editing
|
||||
preview_urls: HashMap<Font, String>,
|
||||
}
|
||||
|
||||
impl FontCache {
|
||||
/// Returns the font family name if the font is cached, otherwise returns the fallback font family name if that is cached
|
||||
pub fn resolve_font<'a>(&'a self, font: &'a Font) -> Option<&'a Font> {
|
||||
|
@ -40,7 +41,7 @@ impl FontCache {
|
|||
}
|
||||
|
||||
/// Try to get the bytes for a font
|
||||
pub fn get<'a>(&'a self, font: &Font) -> Option<&'a Vec<u8>> {
|
||||
pub fn get(&self, font: &Font) -> Option<&Vec<u8>> {
|
||||
self.resolve_font(font).and_then(|font| self.font_file_data.get(font))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,29 +1,67 @@
|
|||
use crate::vector::PointId;
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use glam::DVec2;
|
||||
use rustybuzz::ttf_parser::{GlyphId, OutlineBuilder};
|
||||
use rustybuzz::{GlyphBuffer, UnicodeBuffer};
|
||||
use core::cell::RefCell;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use parley::fontique::Blob;
|
||||
use parley::{Alignment, AlignmentOptions, FontContext, GlyphRun, Layout, LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty};
|
||||
use skrifa::GlyphId;
|
||||
use skrifa::instance::{LocationRef, NormalizedCoord, Size};
|
||||
use skrifa::outline::{DrawSettings, OutlinePen};
|
||||
use skrifa::raw::FontRef as ReadFontsRef;
|
||||
use skrifa::{MetadataProvider, OutlineGlyph};
|
||||
use std::sync::Arc;
|
||||
|
||||
struct Builder {
|
||||
// Thread-local storage avoids expensive re-initialization of font and layout contexts
|
||||
// across multiple text rendering operations within the same thread
|
||||
thread_local! {
|
||||
static FONT_CONTEXT: RefCell<FontContext> = RefCell::new(FontContext::new());
|
||||
static LAYOUT_CONTEXT: RefCell<LayoutContext<()>> = RefCell::new(LayoutContext::new());
|
||||
}
|
||||
|
||||
struct PathBuilder {
|
||||
current_subpath: Subpath<PointId>,
|
||||
glyph_subpaths: Vec<Subpath<PointId>>,
|
||||
other_subpaths: Vec<Subpath<PointId>>,
|
||||
text_cursor: DVec2,
|
||||
offset: DVec2,
|
||||
ascender: f64,
|
||||
origin: DVec2,
|
||||
scale: f64,
|
||||
id: PointId,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
impl PathBuilder {
|
||||
fn point(&self, x: f32, y: f32) -> DVec2 {
|
||||
self.text_cursor + self.offset + DVec2::new(x as f64, self.ascender - y as f64) * self.scale
|
||||
// Y-axis inversion converts from font coordinate system (Y-up) to graphics coordinate system (Y-down)
|
||||
DVec2::new(self.origin.x + x as f64, self.origin.y - y as f64) * self.scale
|
||||
}
|
||||
|
||||
fn set_origin(&mut self, x: f64, y: f64) {
|
||||
self.origin = DVec2::new(x, y);
|
||||
}
|
||||
|
||||
fn draw_glyph(&mut self, glyph: &OutlineGlyph<'_>, size: f32, normalized_coords: &[NormalizedCoord], style_skew: Option<DAffine2>, skew: DAffine2) {
|
||||
let location_ref = LocationRef::new(normalized_coords);
|
||||
let settings = DrawSettings::unhinted(Size::new(size), location_ref);
|
||||
glyph.draw(settings, self).unwrap();
|
||||
|
||||
// Apply transforms in correct order: style-based skew first, then user-requested skew
|
||||
// This ensures font synthesis (italic) is applied before user transformations
|
||||
for glyph_subpath in &mut self.glyph_subpaths {
|
||||
if let Some(style_skew) = style_skew {
|
||||
glyph_subpath.apply_transform(style_skew);
|
||||
}
|
||||
|
||||
glyph_subpath.apply_transform(skew);
|
||||
}
|
||||
|
||||
if !self.glyph_subpaths.is_empty() {
|
||||
self.other_subpaths.extend(core::mem::take(&mut self.glyph_subpaths));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OutlineBuilder for Builder {
|
||||
impl OutlinePen for PathBuilder {
|
||||
fn move_to(&mut self, x: f32, y: f32) {
|
||||
if !self.current_subpath.is_empty() {
|
||||
self.other_subpaths.push(std::mem::replace(&mut self.current_subpath, Subpath::new(Vec::new(), false)));
|
||||
self.glyph_subpaths.push(std::mem::replace(&mut self.current_subpath, Subpath::new(Vec::new(), false)));
|
||||
}
|
||||
self.current_subpath.push_manipulator_group(ManipulatorGroup::new_anchor_with_id(self.point(x, y), self.id.next_id()));
|
||||
}
|
||||
|
@ -47,36 +85,10 @@ impl OutlineBuilder for Builder {
|
|||
|
||||
fn close(&mut self) {
|
||||
self.current_subpath.set_closed(true);
|
||||
self.other_subpaths.push(std::mem::replace(&mut self.current_subpath, Subpath::new(Vec::new(), false)));
|
||||
self.glyph_subpaths.push(std::mem::replace(&mut self.current_subpath, Subpath::new(Vec::new(), false)));
|
||||
}
|
||||
}
|
||||
|
||||
fn font_properties(buzz_face: &rustybuzz::Face, font_size: f64, line_height_ratio: f64) -> (f64, f64, UnicodeBuffer) {
|
||||
let scale = (buzz_face.units_per_em() as f64).recip() * font_size;
|
||||
let line_height = font_size * line_height_ratio;
|
||||
let buffer = UnicodeBuffer::new();
|
||||
(scale, line_height, buffer)
|
||||
}
|
||||
|
||||
fn push_str(buffer: &mut UnicodeBuffer, word: &str) {
|
||||
buffer.push_str(word);
|
||||
}
|
||||
|
||||
fn wrap_word(max_width: Option<f64>, glyph_buffer: &GlyphBuffer, font_size: f64, character_spacing: f64, x_pos: f64, space_glyph: Option<GlyphId>) -> bool {
|
||||
if let Some(max_width) = max_width {
|
||||
// We don't word wrap spaces (to match the browser)
|
||||
let all_glyphs = glyph_buffer.glyph_positions().iter().zip(glyph_buffer.glyph_infos());
|
||||
let non_space_glyphs = all_glyphs.take_while(|(_, info)| space_glyph != Some(GlyphId(info.glyph_id as u16)));
|
||||
let word_length: f64 = non_space_glyphs.map(|(pos, _)| pos.x_advance as f64 * character_spacing).sum();
|
||||
let scaled_word_length = word_length * font_size;
|
||||
|
||||
if scaled_word_length + x_pos > max_width {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct TypesettingConfig {
|
||||
pub font_size: f64,
|
||||
|
@ -84,6 +96,7 @@ pub struct TypesettingConfig {
|
|||
pub character_spacing: f64,
|
||||
pub max_width: Option<f64>,
|
||||
pub max_height: Option<f64>,
|
||||
pub tilt: f64,
|
||||
}
|
||||
|
||||
impl Default for TypesettingConfig {
|
||||
|
@ -91,163 +104,130 @@ impl Default for TypesettingConfig {
|
|||
Self {
|
||||
font_size: 24.,
|
||||
line_height_ratio: 1.2,
|
||||
character_spacing: 1.,
|
||||
character_spacing: 0.,
|
||||
max_width: None,
|
||||
max_height: None,
|
||||
tilt: 0.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, typesetting: TypesettingConfig) -> Vec<Subpath<PointId>> {
|
||||
let Some(buzz_face) = buzz_face else { return vec![] };
|
||||
let space_glyph = buzz_face.glyph_index(' ');
|
||||
fn render_glyph_run(glyph_run: &GlyphRun<'_, ()>, path_builder: &mut PathBuilder, tilt: f64) {
|
||||
let mut run_x = glyph_run.offset();
|
||||
let run_y = glyph_run.baseline();
|
||||
|
||||
let (scale, line_height, mut buffer) = font_properties(&buzz_face, typesetting.font_size, typesetting.line_height_ratio);
|
||||
let run = glyph_run.run();
|
||||
|
||||
let mut builder = Builder {
|
||||
// User-requested tilt applied around baseline to avoid vertical displacement
|
||||
// Translation ensures rotation point is at the baseline, not origin
|
||||
let skew = DAffine2::from_translation(DVec2::new(0., run_y as f64))
|
||||
* DAffine2::from_cols_array(&[1., 0., -tilt.to_radians().tan(), 1., 0., 0.])
|
||||
* DAffine2::from_translation(DVec2::new(0., -run_y as f64));
|
||||
|
||||
let synthesis = run.synthesis();
|
||||
|
||||
// Font synthesis (e.g., synthetic italic) applied separately from user transforms
|
||||
// This preserves the distinction between font styling and user transformations
|
||||
let style_skew = synthesis.skew().map(|angle| {
|
||||
DAffine2::from_translation(DVec2::new(0., run_y as f64))
|
||||
* DAffine2::from_cols_array(&[1., 0., -angle.to_radians().tan() as f64, 1., 0., 0.])
|
||||
* DAffine2::from_translation(DVec2::new(0., -run_y as f64))
|
||||
});
|
||||
|
||||
let font = run.font();
|
||||
let font_size = run.font_size();
|
||||
|
||||
let normalized_coords = run.normalized_coords().iter().map(|coord| NormalizedCoord::from_bits(*coord)).collect::<Vec<_>>();
|
||||
|
||||
// TODO: This can be cached for better performance
|
||||
let font_collection_ref = font.data.as_ref();
|
||||
let font_ref = ReadFontsRef::from_index(font_collection_ref, font.index).unwrap();
|
||||
let outlines = font_ref.outline_glyphs();
|
||||
|
||||
for glyph in glyph_run.glyphs() {
|
||||
let glyph_x = run_x + glyph.x;
|
||||
let glyph_y = run_y - glyph.y;
|
||||
run_x += glyph.advance;
|
||||
|
||||
let glyph_id = GlyphId::from(glyph.id);
|
||||
if let Some(glyph_outline) = outlines.get(glyph_id) {
|
||||
path_builder.set_origin(glyph_x as f64, glyph_y as f64);
|
||||
path_builder.draw_glyph(&glyph_outline, font_size, &normalized_coords, style_skew, skew);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_text(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig) -> Option<Layout<()>> {
|
||||
let font_cx = FONT_CONTEXT.with(Clone::clone);
|
||||
let mut font_cx = font_cx.borrow_mut();
|
||||
let layout_cx = LAYOUT_CONTEXT.with(Clone::clone);
|
||||
let mut layout_cx = layout_cx.borrow_mut();
|
||||
|
||||
let font_family = font_data.and_then(|font_data| {
|
||||
font_cx
|
||||
.collection
|
||||
.register_fonts(font_data, None)
|
||||
.first()
|
||||
.and_then(|(family_id, _)| font_cx.collection.family_name(*family_id).map(String::from))
|
||||
})?;
|
||||
|
||||
const DISPLAY_SCALE: f32 = 1.;
|
||||
let mut builder = layout_cx.ranged_builder(&mut font_cx, str, DISPLAY_SCALE, true);
|
||||
|
||||
builder.push_default(StyleProperty::FontSize(typesetting.font_size as f32));
|
||||
builder.push_default(StyleProperty::LetterSpacing(typesetting.character_spacing as f32));
|
||||
builder.push_default(StyleProperty::FontStack(parley::FontStack::Single(parley::FontFamily::Named(std::borrow::Cow::Owned(font_family)))));
|
||||
builder.push_default(LineHeight::FontSizeRelative(typesetting.line_height_ratio as f32));
|
||||
|
||||
let mut layout: Layout<()> = builder.build(str);
|
||||
|
||||
layout.break_all_lines(typesetting.max_width.map(|mw| mw as f32));
|
||||
layout.align(typesetting.max_width.map(|max_w| max_w as f32), Alignment::Left, AlignmentOptions::default());
|
||||
|
||||
Some(layout)
|
||||
}
|
||||
|
||||
pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig) -> Vec<Subpath<PointId>> {
|
||||
let Some(layout) = layout_text(str, font_data, typesetting) else { return Vec::new() };
|
||||
|
||||
let mut path_builder = PathBuilder {
|
||||
current_subpath: Subpath::new(Vec::new(), false),
|
||||
glyph_subpaths: Vec::new(),
|
||||
other_subpaths: Vec::new(),
|
||||
text_cursor: DVec2::ZERO,
|
||||
offset: DVec2::ZERO,
|
||||
ascender: (buzz_face.ascender() as f64 / buzz_face.height() as f64) * typesetting.font_size / scale,
|
||||
scale,
|
||||
origin: DVec2::ZERO,
|
||||
scale: layout.scale() as f64,
|
||||
id: PointId::ZERO,
|
||||
};
|
||||
|
||||
for line in str.split('\n') {
|
||||
for (index, word) in SplitWordsIncludingSpaces::new(line).enumerate() {
|
||||
push_str(&mut buffer, word);
|
||||
let glyph_buffer = rustybuzz::shape(&buzz_face, &[], buffer);
|
||||
|
||||
// Don't wrap the first word
|
||||
if index != 0 && wrap_word(typesetting.max_width, &glyph_buffer, scale, typesetting.character_spacing, builder.text_cursor.x, space_glyph) {
|
||||
builder.text_cursor = DVec2::new(0., builder.text_cursor.y + line_height);
|
||||
for line in layout.lines() {
|
||||
for item in line.items() {
|
||||
if let PositionedLayoutItem::GlyphRun(glyph_run) = item {
|
||||
render_glyph_run(&glyph_run, &mut path_builder, typesetting.tilt);
|
||||
}
|
||||
|
||||
for (glyph_position, glyph_info) in glyph_buffer.glyph_positions().iter().zip(glyph_buffer.glyph_infos()) {
|
||||
let glyph_id = GlyphId(glyph_info.glyph_id as u16);
|
||||
if let Some(max_width) = typesetting.max_width {
|
||||
if space_glyph != Some(glyph_id) && builder.text_cursor.x + (glyph_position.x_advance as f64 * builder.scale * typesetting.character_spacing) >= max_width {
|
||||
builder.text_cursor = DVec2::new(0., builder.text_cursor.y + line_height);
|
||||
}
|
||||
}
|
||||
// Clip when the height is exceeded
|
||||
if typesetting.max_height.is_some_and(|max_height| builder.text_cursor.y > max_height - line_height) {
|
||||
return builder.other_subpaths;
|
||||
}
|
||||
|
||||
builder.offset = DVec2::new(glyph_position.x_offset as f64, glyph_position.y_offset as f64) * builder.scale;
|
||||
buzz_face.outline_glyph(glyph_id, &mut builder);
|
||||
if !builder.current_subpath.is_empty() {
|
||||
builder.other_subpaths.push(std::mem::replace(&mut builder.current_subpath, Subpath::new(Vec::new(), false)));
|
||||
}
|
||||
|
||||
builder.text_cursor += DVec2::new(glyph_position.x_advance as f64 * typesetting.character_spacing, glyph_position.y_advance as f64) * builder.scale;
|
||||
}
|
||||
|
||||
buffer = glyph_buffer.clear();
|
||||
}
|
||||
|
||||
builder.text_cursor = DVec2::new(0., builder.text_cursor.y + line_height);
|
||||
}
|
||||
|
||||
builder.other_subpaths
|
||||
path_builder.other_subpaths
|
||||
}
|
||||
|
||||
pub fn bounding_box(str: &str, buzz_face: Option<&rustybuzz::Face>, typesetting: TypesettingConfig, for_clipping_test: bool) -> DVec2 {
|
||||
// Show blank layer if font has not loaded
|
||||
let Some(buzz_face) = buzz_face else { return DVec2::ZERO };
|
||||
let space_glyph = buzz_face.glyph_index(' ');
|
||||
|
||||
let (scale, line_height, mut buffer) = font_properties(buzz_face, typesetting.font_size, typesetting.line_height_ratio);
|
||||
|
||||
let [mut text_cursor, mut bounds] = [DVec2::ZERO; 2];
|
||||
pub fn bounding_box(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig, for_clipping_test: bool) -> DVec2 {
|
||||
if !for_clipping_test {
|
||||
if let (Some(max_height), Some(max_width)) = (typesetting.max_height, typesetting.max_width) {
|
||||
return DVec2::new(max_width, max_height);
|
||||
}
|
||||
}
|
||||
|
||||
for line in str.split('\n') {
|
||||
for (index, word) in SplitWordsIncludingSpaces::new(line).enumerate() {
|
||||
push_str(&mut buffer, word);
|
||||
let Some(layout) = layout_text(str, font_data, typesetting) else { return DVec2::ZERO };
|
||||
|
||||
let glyph_buffer = rustybuzz::shape(buzz_face, &[], buffer);
|
||||
|
||||
// Don't wrap the first word
|
||||
if index != 0 && wrap_word(typesetting.max_width, &glyph_buffer, scale, typesetting.character_spacing, text_cursor.x, space_glyph) {
|
||||
text_cursor = DVec2::new(0., text_cursor.y + line_height);
|
||||
}
|
||||
|
||||
for (glyph_position, glyph_info) in glyph_buffer.glyph_positions().iter().zip(glyph_buffer.glyph_infos()) {
|
||||
let glyph_id = GlyphId(glyph_info.glyph_id as u16);
|
||||
if let Some(max_width) = typesetting.max_width {
|
||||
if space_glyph != Some(glyph_id) && text_cursor.x + (glyph_position.x_advance as f64 * scale * typesetting.character_spacing) >= max_width {
|
||||
text_cursor = DVec2::new(0., text_cursor.y + line_height);
|
||||
}
|
||||
}
|
||||
text_cursor += DVec2::new(glyph_position.x_advance as f64 * typesetting.character_spacing, glyph_position.y_advance as f64) * scale;
|
||||
bounds = bounds.max(text_cursor + DVec2::new(0., line_height));
|
||||
}
|
||||
|
||||
buffer = glyph_buffer.clear();
|
||||
}
|
||||
text_cursor = DVec2::new(0., text_cursor.y + line_height);
|
||||
bounds = bounds.max(text_cursor);
|
||||
}
|
||||
|
||||
if !for_clipping_test {
|
||||
if let Some(max_width) = typesetting.max_width {
|
||||
bounds.x = max_width;
|
||||
}
|
||||
if let Some(max_height) = typesetting.max_height {
|
||||
bounds.y = max_height;
|
||||
}
|
||||
}
|
||||
|
||||
bounds
|
||||
DVec2::new(layout.full_width() as f64, layout.height() as f64)
|
||||
}
|
||||
|
||||
pub fn load_face(data: &[u8]) -> rustybuzz::Face<'_> {
|
||||
rustybuzz::Face::from_slice(data, 0).expect("Loading font failed")
|
||||
pub fn load_font(data: &[u8]) -> Blob<u8> {
|
||||
Blob::new(Arc::new(data.to_vec()))
|
||||
}
|
||||
|
||||
pub fn lines_clipping(str: &str, buzz_face: Option<rustybuzz::Face>, typesetting: TypesettingConfig) -> bool {
|
||||
pub fn lines_clipping(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig) -> bool {
|
||||
let Some(max_height) = typesetting.max_height else { return false };
|
||||
let bounds = bounding_box(str, buzz_face.as_ref(), typesetting, true);
|
||||
let bounds = bounding_box(str, font_data, typesetting, true);
|
||||
max_height < bounds.y
|
||||
}
|
||||
|
||||
struct SplitWordsIncludingSpaces<'a> {
|
||||
text: &'a str,
|
||||
start_byte: usize,
|
||||
}
|
||||
|
||||
impl<'a> SplitWordsIncludingSpaces<'a> {
|
||||
pub fn new(text: &'a str) -> Self {
|
||||
Self { text, start_byte: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for SplitWordsIncludingSpaces<'a> {
|
||||
type Item = &'a str;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut eaten_chars = self.text[self.start_byte..].char_indices().skip_while(|(_, c)| *c != ' ').skip_while(|(_, c)| *c == ' ');
|
||||
let start_byte = self.start_byte;
|
||||
self.start_byte = eaten_chars.next().map_or(self.text.len(), |(offset, _)| self.start_byte + offset);
|
||||
(self.start_byte > start_byte).then(|| self.text.get(start_byte..self.start_byte)).flatten()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn split_words_including_spaces() {
|
||||
let mut split_words = SplitWordsIncludingSpaces::new("hello world .");
|
||||
assert_eq!(split_words.next(), Some("hello "));
|
||||
assert_eq!(split_words.next(), Some("world "));
|
||||
assert_eq!(split_words.next(), Some("."));
|
||||
assert_eq!(split_words.next(), None);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,10 +19,10 @@ async fn transform<T: 'n + 'static>(
|
|||
translate: DVec2,
|
||||
rotate: f64,
|
||||
scale: DVec2,
|
||||
shear: DVec2,
|
||||
skew: DVec2,
|
||||
_pivot: DVec2,
|
||||
) -> Instances<T> {
|
||||
let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
|
||||
let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]);
|
||||
|
||||
let footprint = ctx.try_footprint().copied();
|
||||
|
||||
|
|
|
@ -9,23 +9,37 @@ fn text<'i: 'n>(
|
|||
editor: &'i WasmEditorApi,
|
||||
text: String,
|
||||
font_name: Font,
|
||||
#[default(24.)] font_size: f64,
|
||||
#[default(1.2)] line_height_ratio: f64,
|
||||
#[default(1.)] character_spacing: f64,
|
||||
#[default(None)] max_width: Option<f64>,
|
||||
#[default(None)] max_height: Option<f64>,
|
||||
#[unit(" px")]
|
||||
#[default(24.)]
|
||||
font_size: f64,
|
||||
#[unit("x")]
|
||||
#[default(1.2)]
|
||||
line_height_ratio: f64,
|
||||
#[unit(" px")]
|
||||
#[default(0.)]
|
||||
character_spacing: f64,
|
||||
#[unit(" px")]
|
||||
#[default(None)]
|
||||
max_width: Option<f64>,
|
||||
#[unit(" px")]
|
||||
#[default(None)]
|
||||
max_height: Option<f64>,
|
||||
#[unit("°")]
|
||||
#[default(0.)]
|
||||
tilt: f64,
|
||||
) -> VectorDataTable {
|
||||
let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data));
|
||||
|
||||
let typesetting = TypesettingConfig {
|
||||
font_size,
|
||||
line_height_ratio,
|
||||
character_spacing,
|
||||
max_width,
|
||||
max_height,
|
||||
tilt,
|
||||
};
|
||||
|
||||
let result = VectorData::from_subpaths(to_path(&text, buzz_face, typesetting), false);
|
||||
let font_data = editor.font_cache.get(&font_name).map(|f| load_font(f));
|
||||
|
||||
let result = VectorData::from_subpaths(to_path(&text, font_data, typesetting), false);
|
||||
|
||||
VectorDataTable::new(result)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue