Skip to content

Commit 9417af6

Browse files
committed
chore: 2.0.18
1 parent 48d677c commit 9417af6

100 files changed

Lines changed: 7293 additions & 6682 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ gl-bindings = { path = "./crates/gl-bindings" }
7676
canvas-c = { path = "./crates/canvas-c" }
7777
skia-safe = { version = "0.93.1", features = ["textlayout"] }
7878
itertools = "0.14.0"
79+
ustr = "1.1.0"
7980
wgpu-core = { git = "https://github.com/triniwiz/wgpu", rev = "4fb84f5a325d73d21416a2244d1cc062925350ef", features = ["wgsl"] }
8081
wgpu-hal = { git = "https://github.com/triniwiz/wgpu", rev = "4fb84f5a325d73d21416a2244d1cc062925350ef" }
8182
ureq = "2.10.1"
8283
jni = "0.21.1"
84+
regex-lite = "0.1.9"

apps/demo/src/app.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,63 @@ Application.on('launch', (args) => {
123123
monitor.appVersionHidden = true;
124124
monitor.deviceVersionHidden = true;
125125
}
126+
127+
/*
128+
const canvas = document.createElement('canvas');
129+
const ctx = canvas.getContext('2d')!;
130+
131+
const texts = ['Hello', 'Hello world', 'The quick brown fox jumps over the lazy dog', 'Lorem ipsum dolor sit amet consectetur adipiscing elit'];
132+
133+
const fonts = ['12px Arial', '16px Arial', '20px Arial', '16px Times New Roman'];
134+
135+
const ITERATIONS = 50_000;
136+
137+
const defaultFont = ctx.font;
138+
fonts.forEach((font) => {
139+
ctx.font = font;
140+
});
141+
ctx.font = defaultFont;
142+
143+
let i = 0;
144+
let start = performance.now();
145+
146+
for (let n = 0; n < ITERATIONS; n++) {
147+
ctx.font = fonts[n % fonts.length];
148+
ctx.measureText(texts[n % texts.length]);
149+
}
150+
151+
let end = performance.now();
152+
153+
if (__APPLE__) {
154+
//@ts-ignore
155+
__nslog('Total time: ' + (end - start) + ' ms');
156+
//@ts-ignore
157+
__nslog('Per call: ' + (end - start) / ITERATIONS + ' ms');
158+
} else {
159+
console.log('Total time:', end - start, 'ms');
160+
console.log('Per call:', (end - start) / ITERATIONS, 'ms');
161+
}
162+
163+
// i = 0;
164+
// start = performance.now();
165+
166+
// for (let n = 0; n < ITERATIONS; n++) {
167+
// ctx.font = fonts[n % fonts.length];
168+
// ctx.measureText(texts[n % texts.length]);
169+
// }
170+
171+
// end = performance.now();
172+
173+
// if (__APPLE__) {
174+
// //@ts-ignore
175+
// __nslog('Total time: ' + (end - start) + ' ms');
176+
// //@ts-ignore
177+
// __nslog('Per call: ' + (end - start) / ITERATIONS + ' ms');
178+
// } else {
179+
// console.log('Total time:', end - start, 'ms');
180+
// console.log('Per call:', (end - start) / ITERATIONS, 'ms');
181+
// }
182+
*/
126183
});
127184

128185
// fetch('https://github.com/mrdoob/three.js/blob/dev/examples/models/gltf/DamagedHelmet/glTF/Default_metalRoughness.jpg?raw=true')

apps/demo/src/plugin-demos/canvas.xml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,7 @@ loaded="{{ rectLoaded }}"
169169

170170
<!-- <WebView rowSpan="2" colSpan="2" height="100%" width="100%" loaded="loaded"/>" -->
171171

172-
<GridLayout verticalAlignment="top" rowSpan="2" colSpan="2" height="100%" width="100%">
173-
<canvas:Canvas isUserInteractionEnabled="true" ready="{{ canvasLoaded }}" style="width: 100%;height:100%;"/>
174-
</GridLayout>
172+
<canvas:Canvas isUserInteractionEnabled="true" ready="{{ canvasLoaded }}" style="width: 100%;height:100%;"/>
175173

176174
<!-- <Button height="40" text="Draw" tap="{{ draw }}"/> -->
177175

crates/canvas-2d/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ default = ["gl"]
1212
[dependencies]
1313
canvas-core = { workspace = true, features = ["2d"] }
1414
parking_lot.workspace = true
15-
regex-lite = "0.1.6"
15+
ustr.workspace = true
1616
base64 = "0.22.1"
1717
encoding_rs = "0.8.35"
1818
gl-bindings = { workspace = true }
@@ -24,7 +24,7 @@ env_logger = "0.11.2"
2424
ash = { version = "0.38.0", optional = true, features = ["libloading"] }
2525
raw-window-handle.workspace = true
2626
bitflags = "2.6.0"
27-
27+
regex-lite = { workspace = true }
2828
[target.'cfg(any(target_os = "ios", target_os="macos"))'.dependencies]
2929
foreign-types-shared = "0.3.1"
3030
objc2-foundation = { version = "0.2.2", features = ["NSAutoreleasePool"] }

crates/canvas-2d/src/context/drawing_text/global_fonts.rs

Lines changed: 54 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,19 @@ use skia_safe::font_style::Slant;
77
use skia_safe::textlayout::{FontCollection, TextStyle, TypefaceFontProvider};
88
use skia_safe::{FontArguments, FontMgr, Typeface};
99
use std::sync::LazyLock;
10+
use ustr::Ustr;
1011

1112
#[derive(PartialEq, Eq, Hash)]
1213
struct CollectionKey {
13-
families: String,
14+
families: Vec<Ustr>,
1415
weight: i32,
1516
slant: Slant,
1617
}
1718

1819
impl CollectionKey {
1920
pub fn new(style: &TextStyle) -> Self {
2021
let families = style.font_families();
21-
let families = families.iter().collect::<Vec<&str>>().join(", ");
22+
let families: Vec<Ustr> = families.iter().map(Ustr::from).collect();
2223
let weight = *style.font_style().weight();
2324
let slant = style.font_style().slant();
2425
CollectionKey {
@@ -33,17 +34,16 @@ impl CollectionKey {
3334
// Font collection management
3435
//
3536

36-
pub static FONT_LIBRARY: LazyLock<Mutex<FontLibrary>> = LazyLock::new(|| FontLibrary::shared());
37+
pub static FONT_LIBRARY: LazyLock<Mutex<FontLibrary>> = LazyLock::new(FontLibrary::shared);
3738

3839
pub struct FontLibrary {
3940
pub fonts: Vec<(Typeface, Option<String>)>,
4041
pub collection: FontCollection,
4142
collection_cache: HashMap<CollectionKey, FontCollection>,
43+
font_mgr: FontMgr,
4244
}
4345

44-
unsafe impl Send for FontLibrary {
45-
// famous last words: this ‘should’ be safe in practice because the singleton is behind a mutex
46-
}
46+
unsafe impl Send for FontLibrary {}
4747

4848
pub enum CanvasFontWeight {
4949
Thin,
@@ -82,14 +82,14 @@ pub struct FontDescriptors {
8282

8383
impl FontLibrary {
8484
pub fn shared() -> Mutex<Self> {
85-
let fonts = vec![];
86-
let collection_cache = HashMap::new();
85+
let font_mgr = FontMgr::new();
8786
let mut collection = FontCollection::new();
88-
collection.set_default_font_manager(FontMgr::new(), None);
87+
collection.set_default_font_manager(font_mgr.clone(), None);
8988
Mutex::new(FontLibrary {
9089
collection,
91-
collection_cache,
92-
fonts,
90+
collection_cache: HashMap::new(),
91+
fonts: vec![],
92+
font_mgr,
9393
})
9494
}
9595

@@ -100,36 +100,39 @@ impl FontLibrary {
100100
}
101101

102102
let mut collection = FontCollection::new();
103-
collection.set_default_font_manager(FontMgr::new(), None);
103+
collection.set_default_font_manager(self.font_mgr.clone(), None);
104104
collection.set_asset_font_manager(Some(assets.into()));
105105
self.collection = collection;
106-
self.collection_cache.drain();
106+
self.collection_cache.clear();
107107
}
108108

109-
fn add_typeface(&mut self, font: Typeface, alias: Option<String>) {
109+
/// Inserts or replaces a typeface **without** rebuilding the collection.
110+
/// Callers must call `register_fonts()` when all additions are done.
111+
fn add_typeface_no_rebuild(&mut self, font: Typeface, alias: Option<String>) {
110112
if let Some(idx) = self.fonts.iter().position(|(old_font, old_alias)|
111113
match alias.is_some() {
112114
true => old_alias.as_deref() == alias.as_deref(),
113115
false => old_font.family_name() == font.family_name()
114116
} && old_font.font_style() == font.font_style()
115117
) {
116-
self.fonts.remove(idx);
118+
self.fonts[idx] = (font, alias);
119+
} else {
120+
self.fonts.push((font, alias));
117121
}
122+
}
118123

119-
self.fonts.push((font, alias));
124+
fn add_typeface(&mut self, font: Typeface, alias: Option<String>) {
125+
self.add_typeface_no_rebuild(font, alias);
120126
self.register_fonts();
121127
}
122128

123129
fn remove_typeface(&mut self, font: &Typeface, alias: Option<&str>) {
124-
if let Some(idx) = self.fonts.iter().position(|(old_font, old_alias)|
125-
match alias.is_some() {
130+
self.fonts.retain(|(old_font, old_alias)| {
131+
!(match alias.is_some() {
126132
true => old_alias.as_deref() == alias,
127133
false => old_font.family_name() == font.family_name()
128-
} && old_font.font_style() == font.font_style()
129-
) {
130-
self.fonts.remove(idx);
131-
}
132-
134+
} && old_font.font_style() == font.font_style())
135+
});
133136
self.register_fonts();
134137
}
135138

@@ -140,18 +143,14 @@ impl FontLibrary {
140143
.collection
141144
.find_typefaces(&families, style.font_style());
142145

143-
// if the matched typeface is a variable font, create an instance that matches
144-
// the current weight settings and return early with a new FontCollection that
145-
// contains just that single font instance
146+
146147
if let Some(font) = matches.first() {
147148
if let Some(params) = font.variation_design_parameters() {
148-
// memoize the generation of single-weight FontCollections for variable fonts
149149
let key = CollectionKey::new(style);
150150
if let Some(collection) = self.collection_cache.get(&key) {
151151
return collection.clone();
152152
}
153153

154-
// reconnect to the user-specified family name (if provided)
155154
let alias = self.fonts.iter().find_map(|(face, alias)| {
156155
if Typeface::equal(font, face) {
157156
alias.clone()
@@ -164,11 +163,6 @@ impl FontLibrary {
164163
let chars = vec![param.tag.a(), param.tag.b(), param.tag.c(), param.tag.d()];
165164
let tag = String::from_utf8(chars).unwrap();
166165
if tag == "wght" {
167-
// NB: currently setting the value to *one less* than what was requested
168-
// to work around weird Skia behavior that returns something nonlinearly
169-
// weighted in many cases (but not for ±1 of that value). This makes it so
170-
// that n × 100 values will render correctly (and the bug will manifest at
171-
// n × 100 + 1 instead)
172166
let weight = *style.font_style().weight() - 1;
173167
let value = (weight as f32).max(param.min).min(param.max);
174168
let coords = [Coordinate {
@@ -185,7 +179,7 @@ impl FontLibrary {
185179
dynamic.register_typeface(face, alias.as_deref());
186180

187181
let mut collection = FontCollection::new();
188-
collection.set_default_font_manager(FontMgr::new(), None);
182+
collection.set_default_font_manager(self.font_mgr.clone(), None);
189183
collection.set_asset_font_manager(Some(dynamic.into()));
190184
self.collection_cache.insert(key, collection.clone());
191185
return collection;
@@ -194,46 +188,50 @@ impl FontLibrary {
194188
}
195189
}
196190

197-
// if the matched font wasn't variable, then just return the standard collection
198191
self.collection.clone()
199192
}
200193

201194
pub fn add_family(alias: Option<&str>, filenames: &[&str]) -> Result<(), String> {
195+
let decode_mgr = FontMgr::new();
196+
let mut typefaces = Vec::with_capacity(filenames.len());
202197
for filename in filenames.iter() {
203-
let path = std::path::Path::new(&filename);
204-
let typeface = match std::fs::read(path) {
198+
let path = std::path::Path::new(filename);
199+
let bytes = match std::fs::read(path) {
205200
Err(why) => return Err(format!("{}: \"{}\"", why, path.display())),
206-
Ok(bytes) => FontMgr::new().new_from_data(bytes.as_slice(), None),
201+
Ok(b) => b,
207202
};
208-
209-
match typeface {
210-
Some(font) => {
211-
// register the typeface
212-
let mut library = FONT_LIBRARY.lock();
213-
let alias = alias.map(|v| v.to_owned());
214-
library.add_typeface(font, alias);
215-
}
203+
match decode_mgr.new_from_data(bytes.as_slice(), None) {
204+
Some(font) => typefaces.push(font),
216205
None => return Err(format!("Could not decode font data in {}", path.display())),
217206
}
218207
}
208+
let mut library = FONT_LIBRARY.lock();
209+
let alias_owned = alias.map(|v| v.to_owned());
210+
for font in typefaces {
211+
library.add_typeface_no_rebuild(font, alias_owned.clone());
212+
}
213+
library.register_fonts();
219214

220215
Ok(())
221216
}
222217

223218
pub fn add_family_bytes(alias: Option<&str>, data: &[&[u8]]) -> Result<(), String> {
224-
let mgr = FontMgr::new();
219+
let decode_mgr = FontMgr::new();
220+
let mut typefaces = Vec::with_capacity(data.len());
225221
for bytes in data.iter() {
226-
match mgr.new_from_data(bytes, None) {
227-
Some(font) => {
228-
// register the typeface
229-
let mut library = FONT_LIBRARY.lock();
230-
let alias = alias.map(|v| v.to_owned());
231-
library.add_typeface(font, alias);
232-
}
222+
match decode_mgr.new_from_data(bytes, None) {
223+
Some(font) => typefaces.push(font),
233224
None => return Err("Could not decode font data".to_string()),
234225
}
235226
}
236227

228+
let mut library = FONT_LIBRARY.lock();
229+
let alias_owned = alias.map(|v| v.to_owned());
230+
for font in typefaces {
231+
library.add_typeface_no_rebuild(font, alias_owned.clone());
232+
}
233+
library.register_fonts();
234+
237235
Ok(())
238236
}
239237

@@ -242,8 +240,8 @@ impl FontLibrary {
242240
library.fonts.clear();
243241

244242
let mut collection = FontCollection::new();
245-
collection.set_default_font_manager(FontMgr::new(), None);
243+
collection.set_default_font_manager(library.font_mgr.clone(), None);
246244
library.collection = collection;
247-
library.collection_cache.drain();
245+
library.collection_cache.clear();
248246
}
249247
}

0 commit comments

Comments
 (0)