| author | |
| committer | |
| log | 7865e773c24cb9a24fbe145bf63fc3fee561fc49 |
| tree | ee5914bf4c50d4475ed9376af8ebe0c29342ecc8 |
| parent | 36add75c8aac03116e2ce66440c25e5c8b9bc2de |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
A build script deflates the clones the target keeps into the table layout.rs
includes; miniz_oxide was already linked for PNG and PDF.
Release executables after the debug split: Linux x86_64 52.35 to 48.78 MB
(-3.57), macOS aarch64 36.42 to 34.85 MB (-1.57). Inflating all fourteen faces takes
18 ms on an M3 Ultra, Carlito alone 6 ms.
Assisted-by: claude-opus-5.53 files changed, 101 insertions(+), 55 deletions(-)
crates/canvas/Cargo.toml+4-1| ... | @@ -18,6 +18,7 @@ parley.workspace = true | ... | @@ -18,6 +18,7 @@ parley.workspace = true |
| 18 | serde = { version = "1.0.229", features = ["derive"] } | 18 | serde = { version = "1.0.229", features = ["derive"] } |
| 19 | serde_json = "1.0.151" | 19 | serde_json = "1.0.151" |
| 20 | icu_normalizer = "2.3" | 20 | icu_normalizer = "2.3" |
| 21 | miniz_oxide = "0.8" | ||
| 21 | skrifa = "0.44.0" | 22 | skrifa = "0.44.0" |
| 22 | # std's clock where there is one; the browser's on wasm32-unknown-unknown, whose std has none. | 23 | # std's clock where there is one; the browser's on wasm32-unknown-unknown, whose std has none. |
| 23 | web-time = "1.1" | 24 | web-time = "1.1" |
| ... | @@ -39,11 +40,13 @@ path = "src/bin/page_probe.rs" | ... | @@ -39,11 +40,13 @@ path = "src/bin/page_probe.rs" |
| 39 | 40 | ||
| 40 | [dev-dependencies] | 41 | [dev-dependencies] |
| 41 | draw = { path = "../draw", features = ["wgpu"] } | 42 | draw = { path = "../draw", features = ["wgpu"] } |
| 42 | miniz_oxide = "0.8" | ||
| 43 | accesskit_consumer = "0.39.0" | 43 | accesskit_consumer = "0.39.0" |
| 44 | pollster = "0.4" | 44 | pollster = "0.4" |
| 45 | wgpu.workspace = true | 45 | wgpu.workspace = true |
| 46 | 46 | ||
| 47 | [build-dependencies] | ||
| 48 | miniz_oxide = "0.8" | ||
| 49 | |||
| 47 | [[example]] | 50 | [[example]] |
| 48 | name = "render_page" | 51 | name = "render_page" |
| 49 | required-features = ["gpu"] | 52 | required-features = ["gpu"] |
crates/canvas/build.rs created+71| ... | @@ -0,0 +1,71 @@ | ||
| 1 | //! Deflates the font clones the target embeds into `bundled.rs`, a table `layout.rs` includes, | ||
| 2 | //! leaving out each whose family the target's system ships. | ||
| 3 | |||
| 4 | /// Metric-compatible clones, by the family each stands in for. | ||
| 5 | const CLONES: [(&str, &[&str]); 4] = [ | ||
| 6 | ( | ||
| 7 | "Calibri", | ||
| 8 | &[ | ||
| 9 | "Carlito-Regular", | ||
| 10 | "Carlito-Bold", | ||
| 11 | "Carlito-Italic", | ||
| 12 | "Carlito-BoldItalic", | ||
| 13 | ], | ||
| 14 | ), | ||
| 15 | ("Arial", &["Arimo", "Arimo-Italic"]), | ||
| 16 | ( | ||
| 17 | "Times New Roman", | ||
| 18 | &[ | ||
| 19 | "Tinos-Regular", | ||
| 20 | "Tinos-Bold", | ||
| 21 | "Tinos-Italic", | ||
| 22 | "Tinos-BoldItalic", | ||
| 23 | ], | ||
| 24 | ), | ||
| 25 | ( | ||
| 26 | "Courier New", | ||
| 27 | &[ | ||
| 28 | "Cousine-Regular", | ||
| 29 | "Cousine-Bold", | ||
| 30 | "Cousine-Italic", | ||
| 31 | "Cousine-BoldItalic", | ||
| 32 | ], | ||
| 33 | ), | ||
| 34 | ]; | ||
| 35 | |||
| 36 | fn main() { | ||
| 37 | println!("cargo::rerun-if-changed=build.rs"); | ||
| 38 | println!("cargo::rerun-if-changed=assets/fonts"); | ||
| 39 | let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); | ||
| 40 | let browser = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap() == "wasm32"; | ||
| 41 | let out = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); | ||
| 42 | let mut table = String::from("&[\n"); | ||
| 43 | for (family, faces) in CLONES { | ||
| 44 | // The browser fetches them beside the module instead. | ||
| 45 | let shipped = browser | ||
| 46 | || match os.as_str() { | ||
| 47 | "windows" => true, | ||
| 48 | "macos" | "ios" => family != "Calibri", | ||
| 49 | _ => false, | ||
| 50 | }; | ||
| 51 | if shipped { | ||
| 52 | continue; | ||
| 53 | } | ||
| 54 | table += &format!(" ({family:?}, &[\n"); | ||
| 55 | for face in faces { | ||
| 56 | let font = std::fs::read(format!("assets/fonts/{face}.ttf")).unwrap(); | ||
| 57 | let deflated = format!("{face}.ttf.deflate"); | ||
| 58 | std::fs::write( | ||
| 59 | out.join(&deflated), | ||
| 60 | miniz_oxide::deflate::compress_to_vec(&font, 10), | ||
| 61 | ) | ||
| 62 | .unwrap(); | ||
| 63 | // Relative to OUT_DIR, so a target folder reached by another path still finds them. | ||
| 64 | table += | ||
| 65 | &format!(" include_bytes!(concat!(env!(\"OUT_DIR\"), \"/{deflated}\")),\n"); | ||
| 66 | } | ||
| 67 | table += " ]),\n"; | ||
| 68 | } | ||
| 69 | table += "]\n"; | ||
| 70 | std::fs::write(out.join("bundled.rs"), table).unwrap(); | ||
| 71 | } | ||
crates/canvas/src/layout.rs+26-54| ... | @@ -12,7 +12,7 @@ use std::{ | ... | @@ -12,7 +12,7 @@ use std::{ |
| 12 | fmt, | 12 | fmt, |
| 13 | ops::Range, | 13 | ops::Range, |
| 14 | sync::{ | 14 | sync::{ |
| 15 | Arc, | 15 | Arc, OnceLock, |
| 16 | atomic::{AtomicU64, Ordering}, | 16 | atomic::{AtomicU64, Ordering}, |
| 17 | }, | 17 | }, |
| 18 | }; | 18 | }; |
| ... | @@ -113,55 +113,18 @@ pub(crate) const DEFAULT_FONT: &str = "Arial"; | ... | @@ -113,55 +113,18 @@ pub(crate) const DEFAULT_FONT: &str = "Arial"; |
| 113 | pub(crate) const DEFAULT_FONT_SIZE: f32 = 11.0; | 113 | pub(crate) const DEFAULT_FONT_SIZE: f32 = 11.0; |
| 114 | 114 | ||
| 115 | /// Metric-compatible faces for the fonts OneNote pages use most, under the SIL Open Font | 115 | /// Metric-compatible faces for the fonts OneNote pages use most, under the SIL Open Font |
| 116 | /// Licence files beside them, by the family each stands in for. Each is left out where the | 116 | /// Licence files beside them, deflated, by the family each stands in for. The build script |
| 117 | /// system ships the family it stands in for: Windows ships all four, macOS and iOS all but | 117 | /// leaves out each whose family the system ships, and every one from the browser's module, |
| 118 | /// Calibri. The browser fetches them beside the module instead (`register_substitute`), | 118 | /// which fetches them beside it instead (`register_substitute`). |
| 119 | /// keeping them out of it. | 119 | const BUNDLED: &[(&str, &[&[u8]])] = include!(concat!(env!("OUT_DIR"), "/bundled.rs")); |
| 120 | #[cfg(not(any(windows, target_arch = "wasm32")))] | 120 | |
| 121 | const BUNDLED: &[(&str, &[&[u8]])] = &[ | 121 | /// Each bundled family's faces, inflated the first time an engine lacks the family. |
| 122 | ( | 122 | static INFLATED: [OnceLock<Vec<Blob<u8>>>; BUNDLED.len()] = |
| 123 | "Calibri", | 123 | [const { OnceLock::new() }; BUNDLED.len()]; |
| 124 | &[ | ||
| 125 | include_bytes!("../assets/fonts/Carlito-Regular.ttf"), | ||
| 126 | include_bytes!("../assets/fonts/Carlito-Bold.ttf"), | ||
| 127 | include_bytes!("../assets/fonts/Carlito-Italic.ttf"), | ||
| 128 | include_bytes!("../assets/fonts/Carlito-BoldItalic.ttf"), | ||
| 129 | ], | ||
| 130 | ), | ||
| 131 | #[cfg(not(any(target_os = "macos", target_os = "ios")))] | ||
| 132 | ( | ||
| 133 | "Arial", | ||
| 134 | &[ | ||
| 135 | include_bytes!("../assets/fonts/Arimo.ttf"), | ||
| 136 | include_bytes!("../assets/fonts/Arimo-Italic.ttf"), | ||
| 137 | ], | ||
| 138 | ), | ||
| 139 | #[cfg(not(any(target_os = "macos", target_os = "ios")))] | ||
| 140 | ( | ||
| 141 | "Times New Roman", | ||
| 142 | &[ | ||
| 143 | include_bytes!("../assets/fonts/Tinos-Regular.ttf"), | ||
| 144 | include_bytes!("../assets/fonts/Tinos-Bold.ttf"), | ||
| 145 | include_bytes!("../assets/fonts/Tinos-Italic.ttf"), | ||
| 146 | include_bytes!("../assets/fonts/Tinos-BoldItalic.ttf"), | ||
| 147 | ], | ||
| 148 | ), | ||
| 149 | #[cfg(not(any(target_os = "macos", target_os = "ios")))] | ||
| 150 | ( | ||
| 151 | "Courier New", | ||
| 152 | &[ | ||
| 153 | include_bytes!("../assets/fonts/Cousine-Regular.ttf"), | ||
| 154 | include_bytes!("../assets/fonts/Cousine-Bold.ttf"), | ||
| 155 | include_bytes!("../assets/fonts/Cousine-Italic.ttf"), | ||
| 156 | include_bytes!("../assets/fonts/Cousine-BoldItalic.ttf"), | ||
| 157 | ], | ||
| 158 | ), | ||
| 159 | ]; | ||
| 160 | 124 | ||
| 161 | impl Default for TextEngine { | 125 | impl Default for TextEngine { |
| 162 | /// An engine that lays out each bundled family in its substitute where it is missing. | 126 | /// An engine that lays out each bundled family in its substitute where it is missing. |
| 163 | fn default() -> Self { | 127 | fn default() -> Self { |
| 164 | #[cfg_attr(any(windows, target_arch = "wasm32"), allow(unused_mut))] | ||
| 165 | let mut engine = Self { | 128 | let mut engine = Self { |
| 166 | // Clones share loaded font files, so glyphs one lays out draw from the same cache. | 129 | // Clones share loaded font files, so glyphs one lays out draw from the same cache. |
| 167 | fonts: FontContext { | 130 | fonts: FontContext { |
| ... | @@ -172,14 +135,23 @@ impl Default for TextEngine { | ... | @@ -172,14 +135,23 @@ impl Default for TextEngine { |
| 172 | arial_substitutes: BTreeSet::new(), | 135 | arial_substitutes: BTreeSet::new(), |
| 173 | substitutes: BTreeMap::new(), | 136 | substitutes: BTreeMap::new(), |
| 174 | }; | 137 | }; |
| 175 | #[cfg(not(any(windows, target_arch = "wasm32")))] | 138 | for (&(family, faces), inflated) in BUNDLED.iter().zip(&INFLATED) { |
| 176 | for &(family, faces) in BUNDLED { | 139 | if engine.fonts.collection.family_id(family).is_some() { |
| 177 | if engine.fonts.collection.family_id(family).is_none() { | 140 | continue; |
| 178 | for face in faces { | 141 | } |
| 179 | engine | 142 | let faces = inflated.get_or_init(|| { |
| 180 | .register(Blob::new(Arc::new(*face)), true) | 143 | faces |
| 181 | .expect("bundled substitutes register"); | 144 | .iter() |
| 182 | } | 145 | .map(|face| { |
| 146 | let face = miniz_oxide::inflate::decompress_to_vec(face); | ||
| 147 | Blob::new(Arc::new(face.expect("bundled substitutes inflate"))) | ||
| 148 | }) | ||
| 149 | .collect() | ||
| 150 | }); | ||
| 151 | for face in faces { | ||
| 152 | engine | ||
| 153 | .register(face.clone(), true) | ||
| 154 | .expect("bundled substitutes register"); | ||
| 183 | } | 155 | } |
| 184 | } | 156 | } |
| 185 | engine | 157 | engine |