Echte Fonts im nativen 2D: glyphon-Glyphenatlas statt Browser-Overlay
Raumstempel-Text rendert jetzt im nativen wgpu-Fenster mit echten TrueType-Glyphen (Inter, Systemfonts via cosmic-text) ueber einen GPU-Glyphen-Atlas — im selben 4x-MSAA-Pass nach der Geometrie. Schriftgroesse in Papier-mm (gleiche Formel wie Strichbreiten, skaliert mit dem Zoom); die Bridge flacht Rich-Text-Stempel + Zusatzzeilen zeilenweise auf serde-kompatible texts ab (Layout wie der SVG-Pfad).
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
// Erzeugt aus dem echten Modell (sampleProject → generatePlan) die JSON-Dateien,
|
// Erzeugt aus dem echten Modell (sampleProject → generatePlan) die JSON-Dateien,
|
||||||
// die die nativen wgpu-Fenster (render2d/render3d) beim Start laden:
|
// die die nativen wgpu-Fenster (render2d/render3d) beim Start laden:
|
||||||
// src-tauri/assets/native2d_scene.json (RScene: fills/outlines/lines)
|
// src-tauri/assets/native2d_scene.json (RScene: fills/outlines/lines/texts)
|
||||||
// src-tauri/assets/native3d_walls.json (RWall[])
|
// src-tauri/assets/native3d_walls.json (RWall[])
|
||||||
// Wird via scripts/dump-native-scene.mjs (esbuild-Bundle) unter Node ausgeführt.
|
// Wird via scripts/dump-native-scene.mjs (esbuild-Bundle) unter Node ausgeführt.
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ writeFileSync(scenePath, JSON.stringify(scene));
|
|||||||
writeFileSync(wallsPath, JSON.stringify(walls));
|
writeFileSync(wallsPath, JSON.stringify(walls));
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`native2d_scene.json: ${scene.fills.length} fills, ${scene.outlines.length} outlines, ${scene.lines.length} lines`,
|
`native2d_scene.json: ${scene.fills.length} fills, ${scene.outlines.length} outlines, ${scene.lines.length} lines, ${scene.texts.length} texts`,
|
||||||
);
|
);
|
||||||
console.log(`native3d_walls.json: ${walls.length} walls`);
|
console.log(`native3d_walls.json: ${walls.length} walls`);
|
||||||
console.log(`→ ${outDir}`);
|
console.log(`→ ${outDir}`);
|
||||||
|
|||||||
Generated
+291
-5
@@ -702,6 +702,29 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cosmic-text"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59fd57d82eb4bfe7ffa9b1cec0c05e2fd378155b47f255a67983cb4afe0e80c2"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.0",
|
||||||
|
"fontdb",
|
||||||
|
"log",
|
||||||
|
"rangemap",
|
||||||
|
"rayon",
|
||||||
|
"rustc-hash 1.1.0",
|
||||||
|
"rustybuzz",
|
||||||
|
"self_cell",
|
||||||
|
"swash",
|
||||||
|
"sys-locale",
|
||||||
|
"ttf-parser 0.21.1",
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-linebreak",
|
||||||
|
"unicode-script",
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@@ -729,6 +752,25 @@ dependencies = [
|
|||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-deque"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-epoch",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-epoch"
|
||||||
|
version = "0.9.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.21"
|
version = "0.8.21"
|
||||||
@@ -797,7 +839,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017"
|
checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"libloading 0.7.4",
|
"libloading 0.8.9",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -974,7 +1016,7 @@ version = "0.5.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a"
|
checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libloading 0.7.4",
|
"libloading 0.8.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1081,6 +1123,12 @@ version = "1.0.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embed-resource"
|
name = "embed-resource"
|
||||||
version = "3.0.9"
|
version = "3.0.9"
|
||||||
@@ -1151,6 +1199,25 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "etagere"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342"
|
||||||
|
dependencies = [
|
||||||
|
"euclid",
|
||||||
|
"svg_fmt",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "euclid"
|
||||||
|
version = "0.22.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
@@ -1210,6 +1277,38 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||||
|
|
||||||
|
[[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.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646"
|
||||||
|
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]]
|
[[package]]
|
||||||
name = "foreign-types"
|
name = "foreign-types"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -1595,6 +1694,19 @@ dependencies = [
|
|||||||
"gl_generator",
|
"gl_generator",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glyphon"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b11b1afb04c1a1be055989042258499473d0a9447f16450b433aba10bc2a46e7"
|
||||||
|
dependencies = [
|
||||||
|
"cosmic-text",
|
||||||
|
"etagere",
|
||||||
|
"lru",
|
||||||
|
"rustc-hash 2.1.2",
|
||||||
|
"wgpu",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gobject-sys"
|
name = "gobject-sys"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
@@ -1740,7 +1852,7 @@ dependencies = [
|
|||||||
"bitflags 2.13.0",
|
"bitflags 2.13.0",
|
||||||
"com",
|
"com",
|
||||||
"libc",
|
"libc",
|
||||||
"libloading 0.7.4",
|
"libloading 0.8.9",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"widestring",
|
"widestring",
|
||||||
"winapi",
|
"winapi",
|
||||||
@@ -2313,6 +2425,12 @@ dependencies = [
|
|||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libm"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libredox"
|
name = "libredox"
|
||||||
version = "0.1.18"
|
version = "0.1.18"
|
||||||
@@ -2364,6 +2482,12 @@ version = "0.4.33"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lru"
|
||||||
|
version = "0.12.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "malloc_buf"
|
name = "malloc_buf"
|
||||||
version = "0.0.6"
|
version = "0.0.6"
|
||||||
@@ -3009,7 +3133,7 @@ version = "0.25.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b"
|
checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ttf-parser",
|
"ttf-parser 0.25.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3390,12 +3514,48 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08"
|
checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rangemap"
|
||||||
|
version = "1.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "raw-window-handle"
|
name = "raw-window-handle"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"rayon-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon-core"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-deque",
|
||||||
|
"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]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@@ -3489,6 +3649,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"glyphon",
|
||||||
"naga",
|
"naga",
|
||||||
"pollster",
|
"pollster",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -3551,6 +3712,12 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roxmltree"
|
||||||
|
version = "0.20.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -3604,6 +3771,23 @@ version = "1.0.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustybuzz"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.0",
|
||||||
|
"bytemuck",
|
||||||
|
"libm",
|
||||||
|
"smallvec",
|
||||||
|
"ttf-parser 0.21.1",
|
||||||
|
"unicode-bidi-mirroring",
|
||||||
|
"unicode-ccc",
|
||||||
|
"unicode-properties",
|
||||||
|
"unicode-script",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@@ -3708,6 +3892,12 @@ dependencies = [
|
|||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "self_cell"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.28"
|
version = "1.0.28"
|
||||||
@@ -3921,6 +4111,16 @@ version = "1.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
|
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "skrifa"
|
||||||
|
version = "0.22.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e1c44ad1f6c5bdd4eefed8326711b7dbda9ea45dfd36068c427d332aa382cbe"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"read-fonts",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
@@ -4091,6 +4291,23 @@ version = "0.11.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "svg_fmt"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "swash"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cbd59f3f359ddd2c95af4758c18270eddd9c730dde98598023cdabff472c2ca2"
|
||||||
|
dependencies = [
|
||||||
|
"skrifa",
|
||||||
|
"yazi",
|
||||||
|
"zeno",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "swift-rs"
|
name = "swift-rs"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@@ -4144,6 +4361,15 @@ dependencies = [
|
|||||||
"syn 2.0.118",
|
"syn 2.0.118",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sys-locale"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-deps"
|
name = "system-deps"
|
||||||
version = "6.2.2"
|
version = "6.2.2"
|
||||||
@@ -4805,6 +5031,18 @@ version = "0.2.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "ttf-parser"
|
name = "ttf-parser"
|
||||||
version = "0.25.1"
|
version = "0.25.1"
|
||||||
@@ -4864,12 +5102,48 @@ dependencies = [
|
|||||||
"unic-common",
|
"unic-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-bidi"
|
||||||
|
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-ccc"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.24"
|
version = "1.0.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-linebreak"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-properties"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-script"
|
||||||
|
version = "0.5.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.13.3"
|
version = "1.13.3"
|
||||||
@@ -5372,7 +5646,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"khronos-egl",
|
"khronos-egl",
|
||||||
"libc",
|
"libc",
|
||||||
"libloading 0.7.4",
|
"libloading 0.8.9",
|
||||||
"log",
|
"log",
|
||||||
"metal",
|
"metal",
|
||||||
"naga",
|
"naga",
|
||||||
@@ -6004,6 +6278,12 @@ version = "0.8.28"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
|
checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yazi"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
@@ -6027,6 +6307,12 @@ dependencies = [
|
|||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeno"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.52"
|
version = "0.8.52"
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ default = []
|
|||||||
|
|
||||||
# GPU-Pipelines (wgpu). Baut auch headless (wgpu laedt Vulkan/GL erst zur
|
# GPU-Pipelines (wgpu). Baut auch headless (wgpu laedt Vulkan/GL erst zur
|
||||||
# Laufzeit dynamisch), rendert aber nur mit einer aktiven Grafik-Session.
|
# Laufzeit dynamisch), rendert aber nur mit einer aktiven Grafik-Session.
|
||||||
render = ["dep:wgpu", "dep:bytemuck", "dep:pollster"]
|
# glyphon = GPU-Glyphen-Atlas fuer ECHTE TrueType-Schrift (cosmic-text) —
|
||||||
|
# gehoert bewusst in die GPU-Schicht, die serde-only-Default-Schicht bleibt fontfrei.
|
||||||
|
render = ["dep:wgpu", "dep:bytemuck", "dep:pollster", "dep:glyphon"]
|
||||||
|
|
||||||
# Fenster-Spike (winit) inkl. lauffaehigem Binary. Braucht Wayland/X11 + eine
|
# Fenster-Spike (winit) inkl. lauffaehigem Binary. Braucht Wayland/X11 + eine
|
||||||
# Display-Session zur visuellen Verifikation.
|
# Display-Session zur visuellen Verifikation.
|
||||||
@@ -39,6 +41,9 @@ serde = { version = "1", features = ["derive"] }
|
|||||||
wgpu = { version = "22", optional = true }
|
wgpu = { version = "22", optional = true }
|
||||||
bytemuck = { version = "1", optional = true, features = ["derive"] }
|
bytemuck = { version = "1", optional = true, features = ["derive"] }
|
||||||
pollster = { version = "0.3", optional = true }
|
pollster = { version = "0.3", optional = true }
|
||||||
|
# Echte TrueType-Glyphen ueber einen GPU-Atlas (cosmic-text + etagere). Version
|
||||||
|
# an wgpu gekoppelt: die 0.6er-Reihe ist die zu wgpu 22 passende.
|
||||||
|
glyphon = { version = "0.6", optional = true }
|
||||||
|
|
||||||
# --- Fenster (nur mit Feature "window") --------------------------------------
|
# --- Fenster (nur mit Feature "window") --------------------------------------
|
||||||
winit = { version = "0.30", optional = true }
|
winit = { version = "0.30", optional = true }
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// noetig), damit die Szene auch headless testbar bleibt.
|
// noetig), damit die Szene auch headless testbar bleibt.
|
||||||
|
|
||||||
use crate::tessellate::PX_PER_M;
|
use crate::tessellate::PX_PER_M;
|
||||||
use crate::types::{FillPolygon, Line, Outline, Scene, ViewBox};
|
use crate::types::{FillPolygon, Line, Outline, Scene, Text, TextAlign, ViewBox};
|
||||||
|
|
||||||
/// Demo-Szene in Modell-Metern: ein L-foermiger Wand-Poche (konkav!), ein Raum
|
/// Demo-Szene in Modell-Metern: ein L-foermiger Wand-Poche (konkav!), ein Raum
|
||||||
/// und ein paar Striche — genug, um Fuellung, Umriss und Papier-mm-Linien zu sehen.
|
/// und ein paar Striche — genug, um Fuellung, Umriss und Papier-mm-Linien zu sehen.
|
||||||
@@ -57,6 +57,14 @@ pub fn demo_scene() -> Scene {
|
|||||||
color: ink,
|
color: ink,
|
||||||
width_mm: 0.25,
|
width_mm: 0.25,
|
||||||
}],
|
}],
|
||||||
|
// Ein Raumstempel-artiger Text (echte Glyphen via Atlas), mittig im Raum.
|
||||||
|
texts: vec![Text {
|
||||||
|
pos: [7.0, 2.0],
|
||||||
|
content: "Raum 16.00 m²".into(),
|
||||||
|
size_mm: 3.5,
|
||||||
|
color: ink,
|
||||||
|
align: TextAlign::Center,
|
||||||
|
}],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,10 @@
|
|||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
use crate::ortho::{compute_ortho_matrix, mm_to_device_px, Mat4};
|
use crate::ortho::{compute_ortho_matrix, corrected_view_box, meet_scale, mm_to_device_px, Mat4};
|
||||||
use crate::shaders::{FILL_WGSL, LINE_WGSL};
|
use crate::shaders::{FILL_WGSL, LINE_WGSL};
|
||||||
use crate::tessellate::{compile_scene, GpuGeometry};
|
use crate::tessellate::{compile_scene, to_screen, GpuGeometry};
|
||||||
use crate::types::{Scene, ViewBox};
|
use crate::types::{Scene, Text, TextAlign, ViewBox};
|
||||||
|
|
||||||
/// MSAA-Faktor: 4x Multisampling fuer glatte Linien-/Fuellkanten (wie im Browser).
|
/// MSAA-Faktor: 4x Multisampling fuer glatte Linien-/Fuellkanten (wie im Browser).
|
||||||
const SAMPLE_COUNT: u32 = 4;
|
const SAMPLE_COUNT: u32 = 4;
|
||||||
@@ -64,8 +64,28 @@ struct SceneBuffers {
|
|||||||
line_vbo: Option<wgpu::Buffer>,
|
line_vbo: Option<wgpu::Buffer>,
|
||||||
line_ibo: Option<wgpu::Buffer>,
|
line_ibo: Option<wgpu::Buffer>,
|
||||||
geo: GpuGeometry,
|
geo: GpuGeometry,
|
||||||
|
/// Textzeilen der Szene (Modell-Anker + Papier-mm-Groesse). Werden nicht
|
||||||
|
/// tesselliert, sondern pro Frame ueber den Glyphen-Atlas gesetzt (die
|
||||||
|
/// Pixelgroesse haengt vom Zoom ab).
|
||||||
|
texts: Vec<Text>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Zustand des Glyphen-Textpasses (glyphon): Systemfonts + Rasterizer-Cache +
|
||||||
|
/// GPU-Atlas + Renderer. Lazily beim ersten Frame mit Texten erstellt (braucht
|
||||||
|
/// die Queue, die `Renderer::new` nicht bekommt — Signatur bleibt stabil).
|
||||||
|
struct TextState {
|
||||||
|
font_system: glyphon::FontSystem,
|
||||||
|
swash_cache: glyphon::SwashCache,
|
||||||
|
viewport: glyphon::Viewport,
|
||||||
|
atlas: glyphon::TextAtlas,
|
||||||
|
renderer: glyphon::TextRenderer,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Schrift des Plans: dieselbe Familie wie der Browser-Text (`--font` in
|
||||||
|
/// styles.css beginnt mit "Inter"); cosmic-text laedt die Systemfonts und
|
||||||
|
/// faellt bei fehlender Familie auf sans-serif zurueck.
|
||||||
|
const TEXT_FAMILY: &str = "Inter";
|
||||||
|
|
||||||
/// Der native 2D-Renderer: haelt beide Pipelines, den (dynamischen) Uniform-
|
/// Der native 2D-Renderer: haelt beide Pipelines, den (dynamischen) Uniform-
|
||||||
/// Puffer und die aktuell hochgeladene Szene. Pan/Zoom aktualisiert nur die
|
/// Puffer und die aktuell hochgeladene Szene. Pan/Zoom aktualisiert nur die
|
||||||
/// Ortho-Matrix (kein Re-Tessellieren).
|
/// Ortho-Matrix (kein Re-Tessellieren).
|
||||||
@@ -84,6 +104,8 @@ pub struct Renderer {
|
|||||||
msaa_view: Option<wgpu::TextureView>,
|
msaa_view: Option<wgpu::TextureView>,
|
||||||
/// Groesse, fuer die `msaa_view` angelegt wurde.
|
/// Groesse, fuer die `msaa_view` angelegt wurde.
|
||||||
msaa_size: (u32, u32),
|
msaa_size: (u32, u32),
|
||||||
|
/// Glyphen-Textpass (lazily beim ersten Frame mit Texten erstellt).
|
||||||
|
text: Option<TextState>,
|
||||||
/// Loeschfarbe (Zeichenblatt), Default hell wie im WebGL-Pfad (#f0f0f0).
|
/// Loeschfarbe (Zeichenblatt), Default hell wie im WebGL-Pfad (#f0f0f0).
|
||||||
pub clear_color: wgpu::Color,
|
pub clear_color: wgpu::Color,
|
||||||
/// Papier-Massstab-Nenner (1:N) fuer echte mm-Strichbreiten.
|
/// Papier-Massstab-Nenner (1:N) fuer echte mm-Strichbreiten.
|
||||||
@@ -229,6 +251,7 @@ impl Renderer {
|
|||||||
format: color_format,
|
format: color_format,
|
||||||
msaa_view: None,
|
msaa_view: None,
|
||||||
msaa_size: (0, 0),
|
msaa_size: (0, 0),
|
||||||
|
text: None,
|
||||||
// #f0f0f0 (== --sheet). 0.941 entspricht dem WebGL-clearColor.
|
// #f0f0f0 (== --sheet). 0.941 entspricht dem WebGL-clearColor.
|
||||||
clear_color: wgpu::Color {
|
clear_color: wgpu::Color {
|
||||||
r: 0.941,
|
r: 0.941,
|
||||||
@@ -271,6 +294,37 @@ impl Renderer {
|
|||||||
line_vbo: mk_vbo(&geo.line_verts, "line.vbo"),
|
line_vbo: mk_vbo(&geo.line_verts, "line.vbo"),
|
||||||
line_ibo: mk_ibo(&geo.line_idx, "line.ibo"),
|
line_ibo: mk_ibo(&geo.line_idx, "line.ibo"),
|
||||||
geo,
|
geo,
|
||||||
|
texts: scene.texts.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Erstellt den Glyphen-Textpass beim ersten Bedarf (FontSystem laedt die
|
||||||
|
/// Systemfonts einmalig). Der TextRenderer MUSS mit demselben
|
||||||
|
/// MultisampleState wie die Geometrie-Pipelines gebaut werden (MSAA 4x),
|
||||||
|
/// sonst passt er nicht in den gemeinsamen Render-Pass.
|
||||||
|
fn ensure_text(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
|
||||||
|
if self.text.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let cache = glyphon::Cache::new(device);
|
||||||
|
let viewport = glyphon::Viewport::new(device, &cache);
|
||||||
|
let mut atlas = glyphon::TextAtlas::new(device, queue, &cache, self.format);
|
||||||
|
let renderer = glyphon::TextRenderer::new(
|
||||||
|
&mut atlas,
|
||||||
|
device,
|
||||||
|
wgpu::MultisampleState {
|
||||||
|
count: SAMPLE_COUNT,
|
||||||
|
mask: !0,
|
||||||
|
alpha_to_coverage_enabled: false,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
self.text = Some(TextState {
|
||||||
|
font_system: glyphon::FontSystem::new(),
|
||||||
|
swash_cache: glyphon::SwashCache::new(),
|
||||||
|
viewport,
|
||||||
|
atlas,
|
||||||
|
renderer,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,6 +428,122 @@ impl Renderer {
|
|||||||
|
|
||||||
self.ensure_uniform_capacity(device, blocks.len() as u64);
|
self.ensure_uniform_capacity(device, blocks.len() as u64);
|
||||||
|
|
||||||
|
// ── Textpass vorbereiten (echte Glyphen via Atlas) ────────────────────
|
||||||
|
// Pixelgroesse = Papier-mm * mm_px (dieselbe Formel wie die Strich-
|
||||||
|
// breiten) -> Text waechst beim Zoomen exakt wie im Browser. Die Anker
|
||||||
|
// werden ueber den aspekt-korrigierten viewBox in Fenster-Pixel
|
||||||
|
// projiziert; die Ausrichtung nutzt die gemessene Zeilenbreite.
|
||||||
|
let has_texts = self
|
||||||
|
.scene
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |s| !s.texts.is_empty());
|
||||||
|
if has_texts || self.text.is_some() {
|
||||||
|
self.ensure_text(device, queue);
|
||||||
|
let ts = self.text.as_mut().expect("ensure_text");
|
||||||
|
let cvb = corrected_view_box(view_box, vw, vh);
|
||||||
|
let meet = meet_scale(view_box, vw, vh);
|
||||||
|
|
||||||
|
// Je sichtbarer Zeile ein geshapter Buffer + Platzierung.
|
||||||
|
struct Placed {
|
||||||
|
buffer: glyphon::Buffer,
|
||||||
|
left: f32,
|
||||||
|
top: f32,
|
||||||
|
color: glyphon::Color,
|
||||||
|
}
|
||||||
|
let mut placed: Vec<Placed> = Vec::new();
|
||||||
|
if let Some(scene) = &self.scene {
|
||||||
|
let attrs = glyphon::Attrs::new()
|
||||||
|
.family(glyphon::Family::Name(TEXT_FAMILY));
|
||||||
|
for t in &scene.texts {
|
||||||
|
let font_px = t.size_mm * mm_px;
|
||||||
|
// Unlesbar kleine Glyphen ueberspringen (spart Shaping/Atlas).
|
||||||
|
if !(font_px >= 1.0) || t.content.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Anker (Modell-Meter) -> Fenster-Pixel: erst Bildschirm-Raum
|
||||||
|
// (to_screen), dann viewBox-Projektion (meet-Skala).
|
||||||
|
let s = to_screen(t.pos);
|
||||||
|
let ax = (s[0] - cvb.x) * meet;
|
||||||
|
let ay = (s[1] - cvb.y) * meet;
|
||||||
|
// Grober Sichtbarkeits-Test VOR dem Shaping (Buffer sparen):
|
||||||
|
// grosszuegige Marge, da die Zeilenbreite noch unbekannt ist.
|
||||||
|
let margin = font_px * (t.content.len() as f32 + 2.0);
|
||||||
|
if ax < -margin || ax > vw + margin || ay < -2.0 * font_px || ay > vh + 2.0 * font_px {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buffer = glyphon::Buffer::new(
|
||||||
|
&mut ts.font_system,
|
||||||
|
glyphon::Metrics::new(font_px, font_px * 1.2),
|
||||||
|
);
|
||||||
|
// Unbegrenzt: eine Zeile, kein Umbruch (Zeilen sind vorgeflacht).
|
||||||
|
buffer.set_size(&mut ts.font_system, None, None);
|
||||||
|
buffer.set_text(&mut ts.font_system, &t.content, attrs, glyphon::Shaping::Advanced);
|
||||||
|
|
||||||
|
// Gemessene Breite + Baseline fuer Ausrichtung/Anker.
|
||||||
|
let (line_w, baseline) = buffer
|
||||||
|
.layout_runs()
|
||||||
|
.next()
|
||||||
|
.map(|r| (r.line_w, r.line_y))
|
||||||
|
.unwrap_or((0.0, font_px * 0.8));
|
||||||
|
let left = match t.align {
|
||||||
|
TextAlign::Left => ax,
|
||||||
|
TextAlign::Center => ax - line_w / 2.0,
|
||||||
|
TextAlign::Right => ax - line_w,
|
||||||
|
};
|
||||||
|
let top = ay - baseline; // Anker-Y ist die BASELINE der Zeile.
|
||||||
|
// Praeziser Sichtbarkeits-Test mit gemessener Breite.
|
||||||
|
if left > vw || left + line_w < 0.0 || top > vh || top + font_px * 1.4 < 0.0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let c = t.color;
|
||||||
|
let to8 = |v: f32| (v.clamp(0.0, 1.0) * 255.0).round() as u8;
|
||||||
|
placed.push(Placed {
|
||||||
|
buffer,
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
color: glyphon::Color::rgba(to8(c[0]), to8(c[1]), to8(c[2]), to8(c[3])),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ts.viewport.update(
|
||||||
|
queue,
|
||||||
|
glyphon::Resolution {
|
||||||
|
width: viewport.0,
|
||||||
|
height: viewport.1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let bounds = glyphon::TextBounds {
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
right: viewport.0 as i32,
|
||||||
|
bottom: viewport.1 as i32,
|
||||||
|
};
|
||||||
|
let areas = placed.iter().map(|p| glyphon::TextArea {
|
||||||
|
buffer: &p.buffer,
|
||||||
|
left: p.left,
|
||||||
|
top: p.top,
|
||||||
|
scale: 1.0,
|
||||||
|
bounds,
|
||||||
|
default_color: p.color,
|
||||||
|
custom_glyphs: &[],
|
||||||
|
});
|
||||||
|
if let Err(e) = ts.renderer.prepare(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
&mut ts.font_system,
|
||||||
|
&mut ts.atlas,
|
||||||
|
&ts.viewport,
|
||||||
|
areas,
|
||||||
|
&mut ts.swash_cache,
|
||||||
|
) {
|
||||||
|
eprintln!("render2d Text-prepare fehlgeschlagen: {e:?}");
|
||||||
|
}
|
||||||
|
// Nicht mehr benoetigte Glyphen aus dem Atlas werfen (haelt ihn klein).
|
||||||
|
ts.atlas.trim();
|
||||||
|
}
|
||||||
|
|
||||||
// Alle Bloecke in den (auf uniform_stride gepolsterten) Puffer schreiben.
|
// Alle Bloecke in den (auf uniform_stride gepolsterten) Puffer schreiben.
|
||||||
if let Some(u) = &self.uniform {
|
if let Some(u) = &self.uniform {
|
||||||
let stride = self.uniform_stride as usize;
|
let stride = self.uniform_stride as usize;
|
||||||
@@ -444,6 +614,14 @@ impl Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3) Text ZUOBERST im selben MSAA-Pass (der TextRenderer wurde mit
|
||||||
|
// identischem MultisampleState erstellt, siehe ensure_text).
|
||||||
|
if let Some(ts) = &self.text {
|
||||||
|
if let Err(e) = ts.renderer.render(&ts.atlas, &ts.viewport, &mut pass) {
|
||||||
|
eprintln!("render2d Text-render fehlgeschlagen: {e:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
queue.submit(std::iter::once(encoder.finish()));
|
queue.submit(std::iter::once(encoder.finish()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ pub mod gpu;
|
|||||||
pub use demo::{demo_scene, initial_view_box};
|
pub use demo::{demo_scene, initial_view_box};
|
||||||
pub use ortho::{compute_ortho_matrix, meet_scale, mm_to_device_px, Mat4};
|
pub use ortho::{compute_ortho_matrix, meet_scale, mm_to_device_px, Mat4};
|
||||||
pub use tessellate::{compile_scene, triangulate, GpuGeometry, PX_PER_M};
|
pub use tessellate::{compile_scene, triangulate, GpuGeometry, PX_PER_M};
|
||||||
pub use types::{FillPolygon, Line, Outline, Point, Rgba, Scene, ViewBox};
|
pub use types::{FillPolygon, Line, Outline, Point, Rgba, Scene, Text, TextAlign, ViewBox};
|
||||||
|
|
||||||
// --- Tests: Tessellierung (Muster wie glPlanCompile.test.ts) -----------------
|
// --- Tests: Tessellierung (Muster wie glPlanCompile.test.ts) -----------------
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,17 @@ fn ortho(left: f32, right: f32, bottom: f32, top: f32) -> Mat4 {
|
|||||||
/// zusaetzliche Meter-Skalierung. Bildschirm-Y zeigt nach unten -> top = vb.y,
|
/// zusaetzliche Meter-Skalierung. Bildschirm-Y zeigt nach unten -> top = vb.y,
|
||||||
/// bottom = vb.y + vb.h (kleineres Y oben -> Clip +1).
|
/// bottom = vb.y + vb.h (kleineres Y oben -> Clip +1).
|
||||||
pub fn compute_ortho_matrix(view_box: ViewBox, canvas_w: f32, canvas_h: f32) -> Mat4 {
|
pub fn compute_ortho_matrix(view_box: ViewBox, canvas_w: f32, canvas_h: f32) -> Mat4 {
|
||||||
|
let ViewBox { x, y, w, h } = corrected_view_box(view_box, canvas_w, canvas_h);
|
||||||
|
// top = y (Bildschirm-Y oben, kleiner), bottom = y + h.
|
||||||
|
ortho(x, x + w, y + h, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Der aufs Canvas-Seitenverhaeltnis gedehnte (zentrierte) viewBox — exakt die
|
||||||
|
/// Korrektur, die auch in der Ortho-Matrix steckt. Mit ihm laesst sich ein
|
||||||
|
/// Bildschirm-Raum-Punkt direkt in Fenster-Pixel projizieren:
|
||||||
|
/// `px = (s - corrected.xy) * meet_scale(corrected, ...)` (der Textpass braucht
|
||||||
|
/// genau das fuer die Glyphen-Anker).
|
||||||
|
pub fn corrected_view_box(view_box: ViewBox, canvas_w: f32, canvas_h: f32) -> ViewBox {
|
||||||
let ViewBox {
|
let ViewBox {
|
||||||
mut x,
|
mut x,
|
||||||
mut y,
|
mut y,
|
||||||
@@ -69,8 +80,7 @@ pub fn compute_ortho_matrix(view_box: ViewBox, canvas_w: f32, canvas_h: f32) ->
|
|||||||
h = nh;
|
h = nh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// top = y (Bildschirm-Y oben, kleiner), bottom = y + h.
|
ViewBox::new(x, y, w, h)
|
||||||
ortho(x, x + w, y + h, y)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// meet-Skala: Geraete-px je viewBox-Einheit (SVG `xMidYMid meet`).
|
/// meet-Skala: Geraete-px je viewBox-Einheit (SVG `xMidYMid meet`).
|
||||||
|
|||||||
@@ -68,9 +68,42 @@ pub struct Polyline {
|
|||||||
pub width_mm: f32,
|
pub width_mm: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Horizontale Ausrichtung eines Textes relativ zu seinem Anker (deckungsgleich
|
||||||
|
/// zur Absatz-Ausrichtung des Plan-Text-Primitivs: left/center/right).
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum TextAlign {
|
||||||
|
#[default]
|
||||||
|
Left,
|
||||||
|
Center,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// EINE Textzeile mit echter Papier-mm-Schriftgroesse. Der Web-Renderer flacht
|
||||||
|
/// mehrzeilige Raumstempel (Rich-Text + Zusatzzeilen) zeilenweise auf dieses
|
||||||
|
/// Format ab; gerendert wird mit ECHTEN TrueType-Glyphen ueber einen GPU-Atlas
|
||||||
|
/// (glyphon/cosmic-text) — bewusst KEINE Einstrich-Vektorschrift.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Text {
|
||||||
|
/// Anker in Modell-Metern: X gemaess `align`, Y = BASELINE der Zeile.
|
||||||
|
pub pos: Point,
|
||||||
|
/// Zeileninhalt (bereits geflachte Runs, plain).
|
||||||
|
pub content: String,
|
||||||
|
/// Schriftgroesse in ECHTEN Papier-Millimetern (skaliert mit dem Zoom wie
|
||||||
|
/// die Strichbreiten, siehe `ortho::mm_to_device_px`).
|
||||||
|
#[serde(rename = "sizeMm")]
|
||||||
|
pub size_mm: f32,
|
||||||
|
/// Textfarbe (RGBA 0..1).
|
||||||
|
pub color: Rgba,
|
||||||
|
/// Ausrichtung relativ zu `pos` (Default: left).
|
||||||
|
#[serde(default)]
|
||||||
|
pub align: TextAlign,
|
||||||
|
}
|
||||||
|
|
||||||
/// Die vollstaendige Szene: alles, was ein Frame zeichnet. Reihenfolge ist
|
/// Die vollstaendige Szene: alles, was ein Frame zeichnet. Reihenfolge ist
|
||||||
/// signifikant (Z-/Alpha-Ueberlagerung): erst Fuellungen, dann Umrisse, dann
|
/// signifikant (Z-/Alpha-Ueberlagerung): erst Fuellungen, dann Umrisse, dann
|
||||||
/// offene Polylinien, dann freie Linien — analog zur Draw-Reihenfolge im WebGL-Pfad.
|
/// offene Polylinien, dann freie Linien — analog zur Draw-Reihenfolge im WebGL-Pfad.
|
||||||
|
/// Texte liegen zuoberst (eigener Glyphen-Pass nach der Geometrie).
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
pub struct Scene {
|
pub struct Scene {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -81,6 +114,8 @@ pub struct Scene {
|
|||||||
pub polylines: Vec<Polyline>,
|
pub polylines: Vec<Polyline>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub lines: Vec<Line>,
|
pub lines: Vec<Line>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub texts: Vec<Text>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rechteckiger Ausschnitt in BILDSCHIRM-Einheiten (viewBox), analog `ViewBox`
|
/// Rechteckiger Ausschnitt in BILDSCHIRM-Einheiten (viewBox), analog `ViewBox`
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
// die AKTUELL sichtbare 2D-Szene bzw. die geflachten 3D-Wände debounced per
|
// die AKTUELL sichtbare 2D-Szene bzw. die geflachten 3D-Wände debounced per
|
||||||
// Tauri-Command an die native Event-Loop. Es wird EXAKT dieselbe Ableitung
|
// Tauri-Command an die native Event-Loop. Es wird EXAKT dieselbe Ableitung
|
||||||
// benutzt wie für die Browser-Ansicht (planToRenderScene auf dem sichtbaren
|
// benutzt wie für die Browser-Ansicht (planToRenderScene auf dem sichtbaren
|
||||||
// Plan, projectToWalls3d aufs Projekt) — 1:1-Parität inkl. Türschwenk und
|
// Plan, projectToWalls3d aufs Projekt) — 1:1-Parität inkl. Türschwenk,
|
||||||
// Fenster-Symbolen (Text bleibt bewusst dem Browser-Overlay überlassen).
|
// Fenster-Symbolen und Raumstempel-Text (echte Glyphen via GPU-Atlas in Rust).
|
||||||
//
|
//
|
||||||
// Im reinen Browser (kein Tauri) sind beide Funktionen No-ops: erkannt wird
|
// Im reinen Browser (kein Tauri) sind beide Funktionen No-ops: erkannt wird
|
||||||
// Tauri an `window.__TAURI_INTERNALS__`; Fehler beim Invoke werden still
|
// Tauri an `window.__TAURI_INTERNALS__`; Fehler beim Invoke werden still
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
// Flacht einen {@link Plan} (die eine Wahrheit aus generatePlan) auf das
|
// Flacht einen {@link Plan} (die eine Wahrheit aus generatePlan) auf das
|
||||||
// GPU-nahe Szenenformat des nativen render2d-Renderers ab: Füllpolygone,
|
// GPU-nahe Szenenformat des nativen render2d-Renderers ab: Füllpolygone,
|
||||||
// Umrisse und Linien. Hatch und Text bleiben (wie im Rust-Renderer, siehe
|
// Umrisse, Linien und Textzeilen. Text wird zeilenweise geflacht (Rich-Text-Doc
|
||||||
// render2d/src/types.rs) vorerst außen vor — sie sind Overlay/späteres M3.
|
// + Live-Zusatzzeilen) — die echten Glyphen rastert der Rust-Renderer über
|
||||||
|
// einen GPU-Glyphen-Atlas (glyphon/cosmic-text), keine Vektor-Ersatzschrift.
|
||||||
//
|
//
|
||||||
// Das erzeugte Objekt matcht 1:1 die serde-Structs render2d::types::Scene
|
// Das erzeugte Objekt matcht 1:1 die serde-Structs render2d::types::Scene
|
||||||
// { fills:[{pts,color}], outlines:[{pts,color,widthMm}], lines:[{a,b,color,widthMm}] }
|
// { fills:[{pts,color}], outlines:[{pts,color,widthMm}], lines:[{a,b,color,widthMm}],
|
||||||
|
// texts:[{pos,content,sizeMm,color,align}] }
|
||||||
// mit Point = [x,y] (Meter) und Rgba = [r,g,b,a] (0..1).
|
// mit Point = [x,y] (Meter) und Rgba = [r,g,b,a] (0..1).
|
||||||
|
|
||||||
import type { Plan, Primitive } from "./generatePlan";
|
import type { Plan, Primitive } from "./generatePlan";
|
||||||
import { applyDashRuns, buildHatchRuns } from "./glPlan/glPlanHatch";
|
import { applyDashRuns, buildHatchRuns } from "./glPlan/glPlanHatch";
|
||||||
|
import { docToLines } from "../text/renderHtml";
|
||||||
|
|
||||||
type LinePrim = Extract<Primitive, { kind: "line" }>;
|
type LinePrim = Extract<Primitive, { kind: "line" }>;
|
||||||
|
|
||||||
@@ -35,15 +38,39 @@ export interface RLine {
|
|||||||
color: RRgba;
|
color: RRgba;
|
||||||
widthMm: number;
|
widthMm: number;
|
||||||
}
|
}
|
||||||
|
export type RTextAlign = "left" | "center" | "right";
|
||||||
|
/** EINE Textzeile; serde-kompatibel zu render2d::types::Text. */
|
||||||
|
export interface RText {
|
||||||
|
/** Anker in Modell-Metern: X gemäß align, Y = Baseline der Zeile. */
|
||||||
|
pos: RPoint;
|
||||||
|
content: string;
|
||||||
|
/** Schriftgröße in echten Papier-mm (skaliert mit Zoom wie Strichbreiten). */
|
||||||
|
sizeMm: number;
|
||||||
|
color: RRgba;
|
||||||
|
align: RTextAlign;
|
||||||
|
}
|
||||||
export interface RScene {
|
export interface RScene {
|
||||||
fills: RFill[];
|
fills: RFill[];
|
||||||
outlines: ROutline[];
|
outlines: ROutline[];
|
||||||
polylines: RPolyline[];
|
polylines: RPolyline[];
|
||||||
lines: RLine[];
|
lines: RLine[];
|
||||||
|
texts: RText[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_LINE = "#1a1a1a";
|
const DEFAULT_LINE = "#1a1a1a";
|
||||||
|
|
||||||
|
/** 1 Punkt (pt) in Papier-Millimetern. */
|
||||||
|
const PT_TO_MM = 25.4 / 72;
|
||||||
|
/**
|
||||||
|
* Referenzmaßstab des Display-Modus (1:100) — identisch zu PlanView (`nRef`):
|
||||||
|
* dort belegt 1 pt Schrift (1/72)·0.0254·100 Modell-Meter. Der native Renderer
|
||||||
|
* rechnet mit `paper_scale_n = 100`, damit ist `sizeMm = pt · PT_TO_MM` exakt
|
||||||
|
* die Browser-Darstellung.
|
||||||
|
*/
|
||||||
|
const STAMP_REF_N = 100;
|
||||||
|
/** Papier-mm → Modell-Meter beim Referenzmaßstab. */
|
||||||
|
const MM_TO_M = STAMP_REF_N / 1000;
|
||||||
|
|
||||||
/** Wenige benannte Farben, die in Klassen/Defaults vorkommen können. */
|
/** Wenige benannte Farben, die in Klassen/Defaults vorkommen können. */
|
||||||
const NAMED: Record<string, string> = {
|
const NAMED: Record<string, string> = {
|
||||||
black: "#000000",
|
black: "#000000",
|
||||||
@@ -208,6 +235,7 @@ export function planToRenderScene(plan: Plan): RScene {
|
|||||||
const outlines: ROutline[] = [];
|
const outlines: ROutline[] = [];
|
||||||
const polylines: RPolyline[] = [];
|
const polylines: RPolyline[] = [];
|
||||||
const lines: RLine[] = [];
|
const lines: RLine[] = [];
|
||||||
|
const texts: RText[] = [];
|
||||||
|
|
||||||
// Sichtbare Umriss-Läufe werden erst GESAMMELT (nach Stil gruppiert) und am
|
// Sichtbare Umriss-Läufe werden erst GESAMMELT (nach Stil gruppiert) und am
|
||||||
// Ende über Polygon-Grenzen hinweg zusammengenäht — erst dadurch bekommen
|
// Ende über Polygon-Grenzen hinweg zusammengenäht — erst dadurch bekommen
|
||||||
@@ -303,10 +331,56 @@ export function planToRenderScene(plan: Plan): RScene {
|
|||||||
const poly = tessellateArc(p.center, p.from, p.to, p.r);
|
const poly = tessellateArc(p.center, p.from, p.to, p.r);
|
||||||
if (poly.length >= 2) polylines.push({ pts: poly, color: col, widthMm: p.weightMm });
|
if (poly.length >= 2) polylines.push({ pts: poly, color: col, widthMm: p.weightMm });
|
||||||
} else {
|
} else {
|
||||||
// "text" wird bewusst NICHT nativ gerendert — eine Einstrich-Vektorschrift
|
// "text": zeilenweise flachen — die ECHTEN Glyphen rastert der Rust-
|
||||||
// sieht neben dem echten Browser-Font schlecht aus. Der Raumstempel-Text
|
// Renderer über einen GPU-Glyphen-Atlas (glyphon, dieselbe Font-Familie
|
||||||
// bleibt dem Browser/Overlay überlassen.
|
// wie im Browser). Layout identisch zum SVG-Pfad in PlanView (case
|
||||||
|
// "text"): Rich-Text-Zeilen + Live-Zusatzzeilen, Block vertikal um den
|
||||||
|
// Anker zentriert, Zeilenvorschub 1.3·Basisgröße.
|
||||||
flushRun();
|
flushRun();
|
||||||
|
const defCol = toRgba(p.color, 1) ?? [0.1, 0.1, 0.1, 1];
|
||||||
|
// unitPerPt = PT_TO_MM → tspan.fontSize ist direkt die Papier-mm-Größe.
|
||||||
|
const docLines = docToLines(p.doc, {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
lineHeight: 0,
|
||||||
|
unitPerPt: PT_TO_MM,
|
||||||
|
basePt: p.basePt,
|
||||||
|
color: p.color,
|
||||||
|
});
|
||||||
|
const baseMm = p.basePt * PT_TO_MM;
|
||||||
|
const flat: { content: string; sizeMm: number; color: RRgba; align: RTextAlign }[] = [];
|
||||||
|
for (const line of docLines) {
|
||||||
|
flat.push({
|
||||||
|
// Runs einer Zeile werden zusammengelegt (ein Buffer je Zeile);
|
||||||
|
// Größe/Farbe des größten bzw. ersten Runs gelten für die Zeile.
|
||||||
|
content: line.tspans.map((t) => t.text).join(""),
|
||||||
|
sizeMm: line.tspans.length
|
||||||
|
? Math.max(...line.tspans.map((t) => t.fontSize))
|
||||||
|
: baseMm,
|
||||||
|
color: toRgba(line.tspans[0]?.fill ?? p.color, 1) ?? defCol,
|
||||||
|
align: line.align,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const line of p.extraLines) {
|
||||||
|
flat.push({ content: line, sizeMm: baseMm * 0.8, color: defCol, align: "center" });
|
||||||
|
}
|
||||||
|
if (flat.length > 0) {
|
||||||
|
const baseH = baseMm * MM_TO_M; // Basisgröße in Modell-Metern
|
||||||
|
const lineGap = baseH * 1.3;
|
||||||
|
const totalH = baseH + (flat.length - 1) * lineGap;
|
||||||
|
flat.forEach((line, k) => {
|
||||||
|
if (!line.content.trim()) return; // Leerzeile: nur Vorschub
|
||||||
|
texts.push({
|
||||||
|
// Baseline der Zeile k (Modell-Y nach oben, deshalb minus):
|
||||||
|
// Browser: startY = c.y − H/2 + 0.8·fs (Bildschirm-Y nach unten).
|
||||||
|
pos: [p.at.x, p.at.y + totalH / 2 - baseH * 0.8 - k * lineGap],
|
||||||
|
content: line.content,
|
||||||
|
sizeMm: line.sizeMm,
|
||||||
|
color: line.color,
|
||||||
|
align: line.align,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flushRun();
|
flushRun();
|
||||||
@@ -324,5 +398,5 @@ export function planToRenderScene(plan: Plan): RScene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { fills, outlines, polylines, lines };
|
return { fills, outlines, polylines, lines, texts };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user