@empty-sekai/sekai-custom-profile-sdk 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +235 -0
- package/LICENSE-EXCEPTION +32 -0
- package/NOTICE +11 -0
- package/README.en.md +456 -0
- package/README.md +458 -0
- package/dist/allium_renderer_wasm.js +14 -0
- package/dist/allium_renderer_wasm.wasm +0 -0
- package/dist/authoring.d.ts +14 -0
- package/dist/authoring.js +34 -0
- package/dist/cache/glyphPersistentCache.d.ts +145 -0
- package/dist/cache/glyphPersistentCache.js +346 -0
- package/dist/cache/indexedDbGlyphRecordStore.d.ts +26 -0
- package/dist/cache/indexedDbGlyphRecordStore.js +214 -0
- package/dist/cache/sessionImageResourceCache.d.ts +39 -0
- package/dist/cache/sessionImageResourceCache.js +156 -0
- package/dist/emscripten.d.ts +11 -0
- package/dist/emscripten.js +1 -0
- package/dist/fontProvider.d.ts +36 -0
- package/dist/fontProvider.js +84 -0
- package/dist/fontSdfAtlas.d.ts +92 -0
- package/dist/fontSdfAtlas.js +296 -0
- package/dist/gpu/browserSemanticResources.d.ts +64 -0
- package/dist/gpu/browserSemanticResources.js +232 -0
- package/dist/gpu/generalTextRenderPlacement.d.ts +8 -0
- package/dist/gpu/generalTextRenderPlacement.js +131 -0
- package/dist/gpu/previewTransformTextureLayout.d.ts +1 -0
- package/dist/gpu/previewTransformTextureLayout.js +12 -0
- package/dist/gpu/semanticCommandGeometry.d.ts +21 -0
- package/dist/gpu/semanticCommandGeometry.js +226 -0
- package/dist/gpu/semanticCommandPlanner.d.ts +131 -0
- package/dist/gpu/semanticCommandPlanner.js +203 -0
- package/dist/gpu/semanticTextGlyphBridge.d.ts +3 -0
- package/dist/gpu/semanticTextGlyphBridge.js +22 -0
- package/dist/gpu/semanticWebglSceneRenderer.d.ts +70 -0
- package/dist/gpu/semanticWebglSceneRenderer.js +211 -0
- package/dist/gpu/slotRanges.d.ts +5 -0
- package/dist/gpu/slotRanges.js +14 -0
- package/dist/gpu/webglSdfAtlasTexture.d.ts +17 -0
- package/dist/gpu/webglSdfAtlasTexture.js +72 -0
- package/dist/gpu/webglSdfGlyphPipeline.d.ts +26 -0
- package/dist/gpu/webglSdfGlyphPipeline.js +267 -0
- package/dist/gpu/webglSemanticCommandExecutor.d.ts +79 -0
- package/dist/gpu/webglSemanticCommandExecutor.js +686 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +8 -0
- package/dist/interaction/numericTextRegions.d.ts +46 -0
- package/dist/interaction/numericTextRegions.js +44 -0
- package/dist/localizationProvider.d.ts +33 -0
- package/dist/localizationProvider.js +74 -0
- package/dist/originPrebuiltSdfAtlasPackage.d.ts +39 -0
- package/dist/originPrebuiltSdfAtlasPackage.js +316 -0
- package/dist/prebuiltSdfAtlas.d.ts +36 -0
- package/dist/prebuiltSdfAtlas.js +261 -0
- package/dist/protocol.d.ts +456 -0
- package/dist/protocol.js +1 -0
- package/dist/renderer.d.ts +218 -0
- package/dist/renderer.js +641 -0
- package/dist/resourceProvider.d.ts +24 -0
- package/dist/resourceProvider.js +41 -0
- package/dist/telemetry/rendererTelemetry.d.ts +158 -0
- package/dist/telemetry/rendererTelemetry.js +232 -0
- package/dist/third-party/freetype/FTL.txt +169 -0
- package/dist/types/atlas.d.ts +90 -0
- package/dist/types/atlas.js +1 -0
- package/dist/types/authoring.d.ts +81 -0
- package/dist/types/authoring.js +1 -0
- package/dist/types/core.d.ts +128 -0
- package/dist/types/core.js +1 -0
- package/dist/types/freeType.d.ts +54 -0
- package/dist/types/freeType.js +13 -0
- package/dist/types/glyph.d.ts +33 -0
- package/dist/types/glyph.js +1 -0
- package/dist/types/layout.d.ts +25 -0
- package/dist/types/layout.js +1 -0
- package/dist/types/text.d.ts +28 -0
- package/dist/types/text.js +1 -0
- package/dist/worker/glyphWorkScheduler.d.ts +29 -0
- package/dist/worker/glyphWorkScheduler.js +107 -0
- package/dist/worker-client.d.ts +114 -0
- package/dist/worker-client.js +413 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +708 -0
- package/package.json +46 -0
- package/src/atlas.rs +652 -0
- package/src/authoring.ts +42 -0
- package/src/authoring_runtime.rs +303 -0
- package/src/cache/glyphPersistentCache.ts +463 -0
- package/src/cache/indexedDbGlyphRecordStore.ts +238 -0
- package/src/cache/sessionImageResourceCache.ts +186 -0
- package/src/edt.rs +97 -0
- package/src/emscripten.ts +17 -0
- package/src/fontProvider.ts +127 -0
- package/src/fontSdfAtlas.ts +431 -0
- package/src/geometry.rs +149 -0
- package/src/glyph_plan.rs +226 -0
- package/src/gpu/browserSemanticResources.ts +294 -0
- package/src/gpu/generalTextRenderPlacement.ts +175 -0
- package/src/gpu/previewTransformTextureLayout.ts +12 -0
- package/src/gpu/semanticCommandGeometry.ts +260 -0
- package/src/gpu/semanticCommandPlanner.ts +280 -0
- package/src/gpu/semanticTextGlyphBridge.ts +35 -0
- package/src/gpu/semanticWebglSceneRenderer.ts +261 -0
- package/src/gpu/slotRanges.ts +15 -0
- package/src/gpu/webglSdfAtlasTexture.ts +69 -0
- package/src/gpu/webglSdfGlyphPipeline.ts +299 -0
- package/src/gpu/webglSemanticCommandExecutor.ts +739 -0
- package/src/index.ts +84 -0
- package/src/interaction/numericTextRegions.ts +80 -0
- package/src/layout.rs +1882 -0
- package/src/lib.rs +1443 -0
- package/src/localizationProvider.ts +113 -0
- package/src/main.rs +55 -0
- package/src/masterdata_runtime.rs +496 -0
- package/src/originPrebuiltSdfAtlasPackage.ts +403 -0
- package/src/prebuiltSdfAtlas.ts +306 -0
- package/src/protocol.ts +150 -0
- package/src/renderer.ts +792 -0
- package/src/resourceProvider.ts +69 -0
- package/src/scene.rs +665 -0
- package/src/telemetry/rendererTelemetry.ts +358 -0
- package/src/types/atlas.ts +80 -0
- package/src/types/authoring.ts +85 -0
- package/src/types/core.ts +110 -0
- package/src/types/freeType.ts +63 -0
- package/src/types/glyph.ts +33 -0
- package/src/types/layout.ts +28 -0
- package/src/types/text.ts +28 -0
- package/src/worker/glyphWorkScheduler.ts +130 -0
- package/src/worker-client.ts +484 -0
- package/src/worker.ts +895 -0
package/src/lib.rs
ADDED
|
@@ -0,0 +1,1443 @@
|
|
|
1
|
+
mod atlas;
|
|
2
|
+
mod authoring_runtime;
|
|
3
|
+
mod edt;
|
|
4
|
+
mod geometry;
|
|
5
|
+
mod glyph_plan;
|
|
6
|
+
mod layout;
|
|
7
|
+
mod masterdata_runtime;
|
|
8
|
+
mod scene;
|
|
9
|
+
|
|
10
|
+
use std::ffi::{CStr, CString};
|
|
11
|
+
use std::os::raw::c_char;
|
|
12
|
+
use std::rc::Rc;
|
|
13
|
+
use std::slice;
|
|
14
|
+
use std::sync::Once;
|
|
15
|
+
|
|
16
|
+
use allium_renderer_core::sdf_geometry::{AnalyticDistanceField, Vec2};
|
|
17
|
+
use base64::Engine;
|
|
18
|
+
use freetype::{face::LoadFlag, Library, RenderMode};
|
|
19
|
+
use serde::Serialize;
|
|
20
|
+
use web_time::Instant;
|
|
21
|
+
|
|
22
|
+
use self::geometry::extract_segments;
|
|
23
|
+
|
|
24
|
+
const TMP_POINT_SIZE: f32 = 75.0;
|
|
25
|
+
const TMP_SPREAD: f32 = 6.0;
|
|
26
|
+
const FONT_ENGINE_FINGERPRINT: &str = match option_env!("ALLIUM_FONT_ENGINE_FINGERPRINT") {
|
|
27
|
+
Some(value) => value,
|
|
28
|
+
None => "freetype-unknown:dev",
|
|
29
|
+
};
|
|
30
|
+
static PANIC_HOOK: Once = Once::new();
|
|
31
|
+
|
|
32
|
+
#[no_mangle]
|
|
33
|
+
pub extern "C" fn sdf_layout_freetype_probe() -> i32 {
|
|
34
|
+
install_panic_hook();
|
|
35
|
+
match Library::init() {
|
|
36
|
+
Ok(_) => 1,
|
|
37
|
+
Err(_) => -1,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
#[no_mangle]
|
|
41
|
+
pub extern "C" fn sdf_layout_freetype_contract_json() -> *mut c_char {
|
|
42
|
+
into_c_string(
|
|
43
|
+
serde_json::to_string(&FreeTypeContract {
|
|
44
|
+
font_engine_fingerprint: FONT_ENGINE_FINGERPRINT,
|
|
45
|
+
freetype_version: "2.13.2",
|
|
46
|
+
modules: &["truetype", "cff", "sfnt", "psaux", "psnames", "smooth"],
|
|
47
|
+
load_contract: "analytic:NO_BITMAP|NO_HINTING;edt:NO_HINTING;metrics:26d6-v1",
|
|
48
|
+
})
|
|
49
|
+
.unwrap_or_else(|_| "{\"error\":\"contract serialization failed\"}".to_string()),
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#[no_mangle]
|
|
54
|
+
pub unsafe extern "C" fn sdf_layout_freetype_build_glyph_json(
|
|
55
|
+
font_ptr: *const u8,
|
|
56
|
+
font_len: usize,
|
|
57
|
+
codepoints_ptr: *const u32,
|
|
58
|
+
codepoints_len: usize,
|
|
59
|
+
region_ptr: *const c_char,
|
|
60
|
+
family_ptr: *const c_char,
|
|
61
|
+
font_source_hash_ptr: *const c_char,
|
|
62
|
+
) -> *mut c_char {
|
|
63
|
+
install_panic_hook();
|
|
64
|
+
let result = build_glyph_batch_json(
|
|
65
|
+
font_ptr,
|
|
66
|
+
font_len,
|
|
67
|
+
codepoints_ptr,
|
|
68
|
+
codepoints_len,
|
|
69
|
+
region_ptr,
|
|
70
|
+
family_ptr,
|
|
71
|
+
font_source_hash_ptr,
|
|
72
|
+
0,
|
|
73
|
+
);
|
|
74
|
+
into_c_string(result.unwrap_or_else(|message| {
|
|
75
|
+
serde_json::to_string(&GlyphBatchError { error: message })
|
|
76
|
+
.unwrap_or_else(|_| "{\"error\":\"serialization failed\"}".to_string())
|
|
77
|
+
}))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#[no_mangle]
|
|
81
|
+
pub unsafe extern "C" fn sdf_layout_freetype_build_glyph_json_edt(
|
|
82
|
+
font_ptr: *const u8,
|
|
83
|
+
font_len: usize,
|
|
84
|
+
codepoints_ptr: *const u32,
|
|
85
|
+
codepoints_len: usize,
|
|
86
|
+
region_ptr: *const c_char,
|
|
87
|
+
family_ptr: *const c_char,
|
|
88
|
+
font_source_hash_ptr: *const c_char,
|
|
89
|
+
supersample: usize,
|
|
90
|
+
) -> *mut c_char {
|
|
91
|
+
install_panic_hook();
|
|
92
|
+
let result = build_glyph_batch_json(
|
|
93
|
+
font_ptr,
|
|
94
|
+
font_len,
|
|
95
|
+
codepoints_ptr,
|
|
96
|
+
codepoints_len,
|
|
97
|
+
region_ptr,
|
|
98
|
+
family_ptr,
|
|
99
|
+
font_source_hash_ptr,
|
|
100
|
+
supersample.clamp(1, 4),
|
|
101
|
+
);
|
|
102
|
+
into_c_string(result.unwrap_or_else(|message| {
|
|
103
|
+
serde_json::to_string(&GlyphBatchError { error: message })
|
|
104
|
+
.unwrap_or_else(|_| "{\"error\":\"serialization failed\"}".to_string())
|
|
105
|
+
}))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#[no_mangle]
|
|
109
|
+
pub unsafe extern "C" fn sdf_layout_freetype_map_glyphs_json(
|
|
110
|
+
font_ptr: *const u8,
|
|
111
|
+
font_len: usize,
|
|
112
|
+
codepoints_ptr: *const u32,
|
|
113
|
+
codepoints_len: usize,
|
|
114
|
+
region_ptr: *const c_char,
|
|
115
|
+
family_ptr: *const c_char,
|
|
116
|
+
font_source_hash_ptr: *const c_char,
|
|
117
|
+
) -> *mut c_char {
|
|
118
|
+
install_panic_hook();
|
|
119
|
+
let result = map_glyphs_json(
|
|
120
|
+
font_ptr,
|
|
121
|
+
font_len,
|
|
122
|
+
codepoints_ptr,
|
|
123
|
+
codepoints_len,
|
|
124
|
+
region_ptr,
|
|
125
|
+
family_ptr,
|
|
126
|
+
font_source_hash_ptr,
|
|
127
|
+
);
|
|
128
|
+
into_c_string(result.unwrap_or_else(|message| {
|
|
129
|
+
serde_json::to_string(&GlyphBatchError { error: message })
|
|
130
|
+
.unwrap_or_else(|_| "{\"error\":\"serialization failed\"}".to_string())
|
|
131
|
+
}))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#[no_mangle]
|
|
135
|
+
pub unsafe extern "C" fn sdf_layout_freetype_plan_glyphs_json(
|
|
136
|
+
font_ptr: *const u8,
|
|
137
|
+
font_len: usize,
|
|
138
|
+
codepoints_ptr: *const u32,
|
|
139
|
+
codepoints_len: usize,
|
|
140
|
+
region_ptr: *const c_char,
|
|
141
|
+
family_ptr: *const c_char,
|
|
142
|
+
font_source_hash_ptr: *const c_char,
|
|
143
|
+
backend_ptr: *const c_char,
|
|
144
|
+
supersample: usize,
|
|
145
|
+
) -> *mut c_char {
|
|
146
|
+
install_panic_hook();
|
|
147
|
+
let result = (|| {
|
|
148
|
+
if font_ptr.is_null() || codepoints_ptr.is_null() {
|
|
149
|
+
return Err("null font or codepoint pointer".to_string());
|
|
150
|
+
}
|
|
151
|
+
glyph_plan::plan(
|
|
152
|
+
slice::from_raw_parts(font_ptr, font_len).to_vec(),
|
|
153
|
+
slice::from_raw_parts(codepoints_ptr, codepoints_len),
|
|
154
|
+
read_c_string(region_ptr)?,
|
|
155
|
+
read_c_string(family_ptr)?,
|
|
156
|
+
read_c_string(font_source_hash_ptr)?,
|
|
157
|
+
glyph_plan::RasterBackend::parse(&read_c_string(backend_ptr)?)?,
|
|
158
|
+
supersample,
|
|
159
|
+
)
|
|
160
|
+
.and_then(|plan| serde_json::to_string(&plan).map_err(|error| error.to_string()))
|
|
161
|
+
})();
|
|
162
|
+
into_c_string(result.unwrap_or_else(|message| {
|
|
163
|
+
serde_json::to_string(&GlyphBatchError { error: message })
|
|
164
|
+
.unwrap_or_else(|_| "{\"error\":\"serialization failed\"}".to_string())
|
|
165
|
+
}))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#[no_mangle]
|
|
169
|
+
pub unsafe extern "C" fn sdf_layout_freetype_build_layout_json(
|
|
170
|
+
input_json_ptr: *const u8,
|
|
171
|
+
input_json_len: usize,
|
|
172
|
+
) -> *mut c_char {
|
|
173
|
+
install_panic_hook();
|
|
174
|
+
let result = std::panic::catch_unwind(|| {
|
|
175
|
+
read_utf8_slice(input_json_ptr, input_json_len).and_then(layout::build_layout_json)
|
|
176
|
+
})
|
|
177
|
+
.unwrap_or_else(|panic| {
|
|
178
|
+
Err(if let Some(message) = panic.downcast_ref::<&str>() {
|
|
179
|
+
format!("layout panic: {message}")
|
|
180
|
+
} else if let Some(message) = panic.downcast_ref::<String>() {
|
|
181
|
+
format!("layout panic: {message}")
|
|
182
|
+
} else {
|
|
183
|
+
"layout panic: unknown".to_string()
|
|
184
|
+
})
|
|
185
|
+
});
|
|
186
|
+
into_c_string(result.unwrap_or_else(|message| {
|
|
187
|
+
serde_json::to_string(&GlyphBatchError { error: message })
|
|
188
|
+
.unwrap_or_else(|_| "{\"error\":\"serialization failed\"}".to_string())
|
|
189
|
+
}))
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
#[no_mangle]
|
|
193
|
+
pub unsafe extern "C" fn sdf_layout_freetype_glyph_demand_json(
|
|
194
|
+
input_json_ptr: *const u8,
|
|
195
|
+
input_json_len: usize,
|
|
196
|
+
) -> *mut c_char {
|
|
197
|
+
install_panic_hook();
|
|
198
|
+
let result = std::panic::catch_unwind(|| {
|
|
199
|
+
read_utf8_slice(input_json_ptr, input_json_len).and_then(layout::build_glyph_demand_json)
|
|
200
|
+
})
|
|
201
|
+
.unwrap_or_else(|_| Err("glyph-demand panic".to_string()));
|
|
202
|
+
into_c_string(result.unwrap_or_else(|message| {
|
|
203
|
+
serde_json::to_string(&GlyphBatchError { error: message })
|
|
204
|
+
.unwrap_or_else(|_| "{\"error\":\"serialization failed\"}".to_string())
|
|
205
|
+
}))
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
#[no_mangle]
|
|
209
|
+
pub unsafe extern "C" fn sdf_atlas_create_json(
|
|
210
|
+
input_json_ptr: *const u8,
|
|
211
|
+
input_json_len: usize,
|
|
212
|
+
) -> *mut c_char {
|
|
213
|
+
core_json_call(|| {
|
|
214
|
+
let input = read_utf8_slice(input_json_ptr, input_json_len)?;
|
|
215
|
+
let config: atlas::AtlasConfig =
|
|
216
|
+
serde_json::from_str(input).map_err(|error| error.to_string())?;
|
|
217
|
+
let (handle, stats) = atlas::create(config)?;
|
|
218
|
+
serde_json::to_string(&serde_json::json!({ "handle": handle, "stats": stats }))
|
|
219
|
+
.map_err(|error| error.to_string())
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#[no_mangle]
|
|
224
|
+
pub unsafe extern "C" fn sdf_atlas_resolve_json(
|
|
225
|
+
handle: u32,
|
|
226
|
+
input_json_ptr: *const u8,
|
|
227
|
+
input_json_len: usize,
|
|
228
|
+
) -> *mut c_char {
|
|
229
|
+
core_json_call(|| {
|
|
230
|
+
let input = read_utf8_slice(input_json_ptr, input_json_len)?;
|
|
231
|
+
let request: atlas::AtlasResolveRequest =
|
|
232
|
+
serde_json::from_str(input).map_err(|error| error.to_string())?;
|
|
233
|
+
serde_json::to_string(&atlas::resolve(handle, request)?).map_err(|error| error.to_string())
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
#[no_mangle]
|
|
238
|
+
pub unsafe extern "C" fn sdf_atlas_pages_since_json(
|
|
239
|
+
handle: u32,
|
|
240
|
+
input_json_ptr: *const u8,
|
|
241
|
+
input_json_len: usize,
|
|
242
|
+
) -> *mut c_char {
|
|
243
|
+
core_json_call(|| {
|
|
244
|
+
let input = read_utf8_slice(input_json_ptr, input_json_len)?;
|
|
245
|
+
let request: atlas::AtlasPagesRequest =
|
|
246
|
+
serde_json::from_str(input).map_err(|error| error.to_string())?;
|
|
247
|
+
serde_json::to_string(&atlas::pages_since(handle, request)?)
|
|
248
|
+
.map_err(|error| error.to_string())
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
#[no_mangle]
|
|
253
|
+
pub extern "C" fn sdf_atlas_page_pixels_ptr(handle: u32, page: usize) -> *const u8 {
|
|
254
|
+
atlas::page_pixels(handle, page)
|
|
255
|
+
.map(|(pointer, _)| pointer)
|
|
256
|
+
.unwrap_or(std::ptr::null())
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#[no_mangle]
|
|
260
|
+
pub extern "C" fn sdf_atlas_page_pixels_len(handle: u32, page: usize) -> usize {
|
|
261
|
+
atlas::page_pixels(handle, page)
|
|
262
|
+
.map(|(_, length)| length)
|
|
263
|
+
.unwrap_or(0)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
#[no_mangle]
|
|
267
|
+
pub extern "C" fn sdf_atlas_release(handle: u32, lease: u32) -> i32 {
|
|
268
|
+
match atlas::release(handle, lease) {
|
|
269
|
+
Ok(true) => 1,
|
|
270
|
+
Ok(false) => 0,
|
|
271
|
+
Err(_) => -1,
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
#[no_mangle]
|
|
276
|
+
pub extern "C" fn sdf_atlas_destroy(handle: u32) -> i32 {
|
|
277
|
+
if atlas::destroy(handle) {
|
|
278
|
+
1
|
|
279
|
+
} else {
|
|
280
|
+
0
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
#[no_mangle]
|
|
285
|
+
pub unsafe extern "C" fn sdf_renderer_core_scene_create_json(
|
|
286
|
+
input_json_ptr: *const u8,
|
|
287
|
+
input_json_len: usize,
|
|
288
|
+
) -> *mut c_char {
|
|
289
|
+
core_json_call(|| read_utf8_slice(input_json_ptr, input_json_len).and_then(scene::create))
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
#[no_mangle]
|
|
293
|
+
pub extern "C" fn sdf_renderer_authoring_create_blank_json() -> *mut c_char {
|
|
294
|
+
core_json_call(authoring_runtime::create_blank)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
#[no_mangle]
|
|
298
|
+
pub unsafe extern "C" fn sdf_renderer_authoring_import_profile_json(
|
|
299
|
+
input_json_ptr: *const u8,
|
|
300
|
+
input_json_len: usize,
|
|
301
|
+
) -> *mut c_char {
|
|
302
|
+
core_json_call(|| {
|
|
303
|
+
read_utf8_slice(input_json_ptr, input_json_len).and_then(authoring_runtime::import_profile)
|
|
304
|
+
})
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
#[no_mangle]
|
|
308
|
+
pub unsafe extern "C" fn sdf_renderer_authoring_restore_checkpoint_json(
|
|
309
|
+
input_json_ptr: *const u8,
|
|
310
|
+
input_json_len: usize,
|
|
311
|
+
) -> *mut c_char {
|
|
312
|
+
core_json_call(|| {
|
|
313
|
+
read_utf8_slice(input_json_ptr, input_json_len)
|
|
314
|
+
.and_then(authoring_runtime::restore_checkpoint)
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
#[no_mangle]
|
|
319
|
+
pub unsafe extern "C" fn sdf_renderer_authoring_apply_json(
|
|
320
|
+
handle: u32,
|
|
321
|
+
input_json_ptr: *const u8,
|
|
322
|
+
input_json_len: usize,
|
|
323
|
+
) -> *mut c_char {
|
|
324
|
+
core_json_call(|| {
|
|
325
|
+
read_utf8_slice(input_json_ptr, input_json_len)
|
|
326
|
+
.and_then(|input| authoring_runtime::apply(handle, input))
|
|
327
|
+
})
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
#[no_mangle]
|
|
331
|
+
pub unsafe extern "C" fn sdf_renderer_authoring_select_json(
|
|
332
|
+
handle: u32,
|
|
333
|
+
input_json_ptr: *const u8,
|
|
334
|
+
input_json_len: usize,
|
|
335
|
+
) -> *mut c_char {
|
|
336
|
+
core_json_call(|| {
|
|
337
|
+
read_utf8_slice(input_json_ptr, input_json_len)
|
|
338
|
+
.and_then(|input| authoring_runtime::select(handle, input))
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
#[no_mangle]
|
|
343
|
+
pub extern "C" fn sdf_renderer_authoring_elements_json(handle: u32) -> *mut c_char {
|
|
344
|
+
core_json_call(|| authoring_runtime::elements(handle))
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
#[no_mangle]
|
|
348
|
+
pub unsafe extern "C" fn sdf_renderer_authoring_begin_gesture_json(
|
|
349
|
+
handle: u32,
|
|
350
|
+
input_json_ptr: *const u8,
|
|
351
|
+
input_json_len: usize,
|
|
352
|
+
) -> *mut c_char {
|
|
353
|
+
core_json_call(|| {
|
|
354
|
+
read_utf8_slice(input_json_ptr, input_json_len)
|
|
355
|
+
.and_then(|input| authoring_runtime::begin_gesture(handle, input))
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
#[no_mangle]
|
|
360
|
+
pub unsafe extern "C" fn sdf_renderer_authoring_preview_gesture_json(
|
|
361
|
+
handle: u32,
|
|
362
|
+
input_json_ptr: *const u8,
|
|
363
|
+
input_json_len: usize,
|
|
364
|
+
) -> *mut c_char {
|
|
365
|
+
core_json_call(|| {
|
|
366
|
+
read_utf8_slice(input_json_ptr, input_json_len)
|
|
367
|
+
.and_then(|input| authoring_runtime::preview_gesture(handle, input))
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
#[no_mangle]
|
|
372
|
+
pub extern "C" fn sdf_renderer_authoring_commit_gesture_json(handle: u32) -> *mut c_char {
|
|
373
|
+
core_json_call(|| authoring_runtime::commit_gesture(handle))
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
#[no_mangle]
|
|
377
|
+
pub extern "C" fn sdf_renderer_authoring_cancel_gesture_json(handle: u32) -> *mut c_char {
|
|
378
|
+
core_json_call(|| authoring_runtime::cancel_gesture(handle))
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
#[no_mangle]
|
|
382
|
+
pub extern "C" fn sdf_renderer_authoring_append_page_json(handle: u32) -> *mut c_char {
|
|
383
|
+
core_json_call(|| authoring_runtime::append_page(handle))
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
#[no_mangle]
|
|
387
|
+
pub unsafe extern "C" fn sdf_renderer_authoring_duplicate_page_json(
|
|
388
|
+
handle: u32,
|
|
389
|
+
input_json_ptr: *const u8,
|
|
390
|
+
input_json_len: usize,
|
|
391
|
+
) -> *mut c_char {
|
|
392
|
+
core_json_call(|| {
|
|
393
|
+
read_utf8_slice(input_json_ptr, input_json_len)
|
|
394
|
+
.and_then(|input| authoring_runtime::duplicate_page(handle, input))
|
|
395
|
+
})
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
#[no_mangle]
|
|
399
|
+
pub unsafe extern "C" fn sdf_renderer_authoring_delete_page_json(
|
|
400
|
+
handle: u32,
|
|
401
|
+
input_json_ptr: *const u8,
|
|
402
|
+
input_json_len: usize,
|
|
403
|
+
) -> *mut c_char {
|
|
404
|
+
core_json_call(|| {
|
|
405
|
+
read_utf8_slice(input_json_ptr, input_json_len)
|
|
406
|
+
.and_then(|input| authoring_runtime::delete_page(handle, input))
|
|
407
|
+
})
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
#[no_mangle]
|
|
411
|
+
pub unsafe extern "C" fn sdf_renderer_authoring_move_page_json(
|
|
412
|
+
handle: u32,
|
|
413
|
+
input_json_ptr: *const u8,
|
|
414
|
+
input_json_len: usize,
|
|
415
|
+
) -> *mut c_char {
|
|
416
|
+
core_json_call(|| {
|
|
417
|
+
read_utf8_slice(input_json_ptr, input_json_len)
|
|
418
|
+
.and_then(|input| authoring_runtime::move_page(handle, input))
|
|
419
|
+
})
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
#[no_mangle]
|
|
423
|
+
pub extern "C" fn sdf_renderer_authoring_undo_json(handle: u32) -> *mut c_char {
|
|
424
|
+
core_json_call(|| authoring_runtime::undo(handle))
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
#[no_mangle]
|
|
428
|
+
pub extern "C" fn sdf_renderer_authoring_redo_json(handle: u32) -> *mut c_char {
|
|
429
|
+
core_json_call(|| authoring_runtime::redo(handle))
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
#[no_mangle]
|
|
433
|
+
pub extern "C" fn sdf_renderer_authoring_export_json(handle: u32) -> *mut c_char {
|
|
434
|
+
core_json_call(|| authoring_runtime::export(handle))
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
#[no_mangle]
|
|
438
|
+
pub extern "C" fn sdf_renderer_authoring_checkpoint_json(handle: u32) -> *mut c_char {
|
|
439
|
+
core_json_call(|| authoring_runtime::checkpoint(handle))
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
#[no_mangle]
|
|
443
|
+
pub extern "C" fn sdf_renderer_authoring_destroy(handle: u32) -> i32 {
|
|
444
|
+
if authoring_runtime::destroy(handle) {
|
|
445
|
+
1
|
|
446
|
+
} else {
|
|
447
|
+
0
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
#[no_mangle]
|
|
452
|
+
pub unsafe extern "C" fn sdf_renderer_core_profile_scene_create_json(
|
|
453
|
+
input_json_ptr: *const u8,
|
|
454
|
+
input_json_len: usize,
|
|
455
|
+
) -> *mut c_char {
|
|
456
|
+
core_json_call(|| {
|
|
457
|
+
read_utf8_slice(input_json_ptr, input_json_len).and_then(scene::create_resolved_profile)
|
|
458
|
+
})
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
#[no_mangle]
|
|
462
|
+
pub unsafe extern "C" fn sdf_renderer_core_masterdata_create_json(
|
|
463
|
+
input_json_ptr: *const u8,
|
|
464
|
+
input_json_len: usize,
|
|
465
|
+
) -> *mut c_char {
|
|
466
|
+
core_json_call(|| {
|
|
467
|
+
read_utf8_slice(input_json_ptr, input_json_len).and_then(masterdata_runtime::create)
|
|
468
|
+
})
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
#[no_mangle]
|
|
472
|
+
pub unsafe extern "C" fn sdf_renderer_core_masterdata_put_table_json(
|
|
473
|
+
handle: u32,
|
|
474
|
+
input_json_ptr: *const u8,
|
|
475
|
+
input_json_len: usize,
|
|
476
|
+
) -> *mut c_char {
|
|
477
|
+
core_json_call(|| {
|
|
478
|
+
read_utf8_slice(input_json_ptr, input_json_len)
|
|
479
|
+
.and_then(|input| masterdata_runtime::put_table(handle, input))
|
|
480
|
+
})
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
#[no_mangle]
|
|
484
|
+
pub extern "C" fn sdf_renderer_core_masterdata_seal_json(handle: u32) -> *mut c_char {
|
|
485
|
+
core_json_call(|| masterdata_runtime::seal(handle))
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
#[no_mangle]
|
|
489
|
+
pub unsafe extern "C" fn sdf_renderer_core_profile_prepare_json(
|
|
490
|
+
handle: u32,
|
|
491
|
+
input_json_ptr: *const u8,
|
|
492
|
+
input_json_len: usize,
|
|
493
|
+
) -> *mut c_char {
|
|
494
|
+
core_json_call(|| {
|
|
495
|
+
read_utf8_slice(input_json_ptr, input_json_len)
|
|
496
|
+
.and_then(|input| masterdata_runtime::prepare(handle, input))
|
|
497
|
+
})
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
#[no_mangle]
|
|
501
|
+
pub unsafe extern "C" fn sdf_renderer_core_profile_create_json(
|
|
502
|
+
handle: u32,
|
|
503
|
+
input_json_ptr: *const u8,
|
|
504
|
+
input_json_len: usize,
|
|
505
|
+
) -> *mut c_char {
|
|
506
|
+
core_json_call(|| {
|
|
507
|
+
read_utf8_slice(input_json_ptr, input_json_len)
|
|
508
|
+
.and_then(|input| masterdata_runtime::create_scene(handle, input))
|
|
509
|
+
})
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
#[no_mangle]
|
|
513
|
+
pub extern "C" fn sdf_renderer_core_masterdata_stats_json(handle: u32) -> *mut c_char {
|
|
514
|
+
core_json_call(|| masterdata_runtime::stats(handle))
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
#[no_mangle]
|
|
518
|
+
pub extern "C" fn sdf_renderer_core_masterdata_destroy(handle: u32) -> i32 {
|
|
519
|
+
if masterdata_runtime::destroy(handle) {
|
|
520
|
+
1
|
|
521
|
+
} else {
|
|
522
|
+
0
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
#[no_mangle]
|
|
527
|
+
pub extern "C" fn sdf_renderer_core_scene_advance_json(handle: u32, tick: u32) -> *mut c_char {
|
|
528
|
+
core_json_call(|| scene::advance(handle, tick as u64))
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
#[no_mangle]
|
|
532
|
+
pub unsafe extern "C" fn sdf_renderer_core_scene_advance_binary(
|
|
533
|
+
handle: u32,
|
|
534
|
+
tick: u32,
|
|
535
|
+
output: *mut u8,
|
|
536
|
+
capacity: usize,
|
|
537
|
+
) -> usize {
|
|
538
|
+
unsafe { scene::advance_binary(handle, tick as u64, output, capacity) }.unwrap_or_default()
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
#[no_mangle]
|
|
542
|
+
pub unsafe extern "C" fn sdf_renderer_core_scene_set_mask_json(
|
|
543
|
+
handle: u32,
|
|
544
|
+
input_json_ptr: *const u8,
|
|
545
|
+
input_json_len: usize,
|
|
546
|
+
) -> *mut c_char {
|
|
547
|
+
core_json_call(|| {
|
|
548
|
+
read_utf8_slice(input_json_ptr, input_json_len)
|
|
549
|
+
.and_then(|input| scene::set_mask(handle, input))
|
|
550
|
+
})
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
#[no_mangle]
|
|
554
|
+
pub unsafe extern "C" fn sdf_renderer_core_scene_set_masks_json(
|
|
555
|
+
handle: u32,
|
|
556
|
+
input_json_ptr: *const u8,
|
|
557
|
+
input_json_len: usize,
|
|
558
|
+
) -> *mut c_char {
|
|
559
|
+
core_json_call(|| {
|
|
560
|
+
read_utf8_slice(input_json_ptr, input_json_len)
|
|
561
|
+
.and_then(|input| scene::set_masks(handle, input))
|
|
562
|
+
})
|
|
563
|
+
}
|
|
564
|
+
#[no_mangle]
|
|
565
|
+
pub unsafe extern "C" fn sdf_renderer_core_scene_set_tab_json(
|
|
566
|
+
handle: u32,
|
|
567
|
+
input_json_ptr: *const u8,
|
|
568
|
+
input_json_len: usize,
|
|
569
|
+
) -> *mut c_char {
|
|
570
|
+
core_json_call(|| {
|
|
571
|
+
read_utf8_slice(input_json_ptr, input_json_len)
|
|
572
|
+
.and_then(|input| scene::set_tab(handle, input))
|
|
573
|
+
})
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
#[no_mangle]
|
|
577
|
+
pub unsafe extern "C" fn sdf_renderer_core_scene_scroll_json(
|
|
578
|
+
handle: u32,
|
|
579
|
+
input_json_ptr: *const u8,
|
|
580
|
+
input_json_len: usize,
|
|
581
|
+
) -> *mut c_char {
|
|
582
|
+
core_json_call(|| {
|
|
583
|
+
read_utf8_slice(input_json_ptr, input_json_len)
|
|
584
|
+
.and_then(|input| scene::scroll(handle, input))
|
|
585
|
+
})
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
#[no_mangle]
|
|
589
|
+
pub extern "C" fn sdf_renderer_core_scene_dump_json(handle: u32) -> *mut c_char {
|
|
590
|
+
core_json_call(|| scene::dump(handle))
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
#[no_mangle]
|
|
594
|
+
pub extern "C" fn sdf_renderer_core_scene_destroy(handle: u32) -> i32 {
|
|
595
|
+
if scene::destroy(handle) {
|
|
596
|
+
1
|
|
597
|
+
} else {
|
|
598
|
+
0
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
#[derive(serde::Deserialize)]
|
|
603
|
+
struct LocaleResolveRequest {
|
|
604
|
+
region: String,
|
|
605
|
+
key: String,
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
#[derive(serde::Serialize)]
|
|
609
|
+
struct LocaleResolveResponse {
|
|
610
|
+
region: String,
|
|
611
|
+
key: String,
|
|
612
|
+
value: Option<String>,
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
#[derive(serde::Deserialize)]
|
|
616
|
+
#[serde(rename_all = "camelCase")]
|
|
617
|
+
struct ProfileResolveRequest {
|
|
618
|
+
document_key: String,
|
|
619
|
+
card: allium_renderer_core::profile_source::CustomProfileCard,
|
|
620
|
+
snapshot: allium_renderer_core::profile_scene::ProfileResolveSnapshot,
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
#[no_mangle]
|
|
624
|
+
pub unsafe extern "C" fn sdf_renderer_core_resolve_locale_json(
|
|
625
|
+
input_json_ptr: *const u8,
|
|
626
|
+
input_json_len: usize,
|
|
627
|
+
) -> *mut c_char {
|
|
628
|
+
core_json_call(|| {
|
|
629
|
+
let input = read_utf8_slice(input_json_ptr, input_json_len)?;
|
|
630
|
+
let request: LocaleResolveRequest =
|
|
631
|
+
serde_json::from_str(input).map_err(|error| error.to_string())?;
|
|
632
|
+
serde_json::to_string(&LocaleResolveResponse {
|
|
633
|
+
value: allium_renderer_core::locale::resolve(&request.region, &request.key),
|
|
634
|
+
region: request.region,
|
|
635
|
+
key: request.key,
|
|
636
|
+
})
|
|
637
|
+
.map_err(|error| error.to_string())
|
|
638
|
+
})
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
#[no_mangle]
|
|
642
|
+
pub unsafe extern "C" fn sdf_renderer_core_resolve_profile_json(
|
|
643
|
+
input_json_ptr: *const u8,
|
|
644
|
+
input_json_len: usize,
|
|
645
|
+
) -> *mut c_char {
|
|
646
|
+
core_json_call(|| {
|
|
647
|
+
let input = read_utf8_slice(input_json_ptr, input_json_len)?;
|
|
648
|
+
let request: ProfileResolveRequest =
|
|
649
|
+
serde_json::from_str(input).map_err(|error| error.to_string())?;
|
|
650
|
+
let resolved = allium_renderer_core::profile_scene::resolve_profile_scene(
|
|
651
|
+
&request.card,
|
|
652
|
+
&request.document_key,
|
|
653
|
+
&request.snapshot,
|
|
654
|
+
)
|
|
655
|
+
.map_err(|error| error.to_string())?;
|
|
656
|
+
serde_json::to_string(&resolved).map_err(|error| error.to_string())
|
|
657
|
+
})
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
#[no_mangle]
|
|
661
|
+
pub unsafe extern "C" fn sdf_layout_freetype_free_string(ptr: *mut c_char) {
|
|
662
|
+
if !ptr.is_null() {
|
|
663
|
+
drop(CString::from_raw(ptr));
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
fn install_panic_hook() {
|
|
668
|
+
PANIC_HOOK.call_once(|| {
|
|
669
|
+
std::panic::set_hook(Box::new(|info| {
|
|
670
|
+
eprintln!("[sdf-freetype panic] {info}");
|
|
671
|
+
}));
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
fn core_json_call<F>(call: F) -> *mut c_char
|
|
676
|
+
where
|
|
677
|
+
F: FnOnce() -> Result<String, String>,
|
|
678
|
+
{
|
|
679
|
+
install_panic_hook();
|
|
680
|
+
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(call))
|
|
681
|
+
.unwrap_or_else(|_| Err("renderer core panic".to_string()));
|
|
682
|
+
into_c_string(result.unwrap_or_else(|message| {
|
|
683
|
+
serde_json::to_string(&GlyphBatchError { error: message })
|
|
684
|
+
.unwrap_or_else(|_| "{\"error\":\"serialization failed\"}".to_string())
|
|
685
|
+
}))
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
unsafe fn build_glyph_batch_json(
|
|
689
|
+
font_ptr: *const u8,
|
|
690
|
+
font_len: usize,
|
|
691
|
+
codepoints_ptr: *const u32,
|
|
692
|
+
codepoints_len: usize,
|
|
693
|
+
region_ptr: *const c_char,
|
|
694
|
+
family_ptr: *const c_char,
|
|
695
|
+
font_source_hash_ptr: *const c_char,
|
|
696
|
+
supersample: usize,
|
|
697
|
+
) -> Result<String, String> {
|
|
698
|
+
if font_ptr.is_null() || codepoints_ptr.is_null() {
|
|
699
|
+
return Err("null font or codepoint pointer".to_string());
|
|
700
|
+
}
|
|
701
|
+
let t0 = Instant::now();
|
|
702
|
+
let font_bytes = slice::from_raw_parts(font_ptr, font_len).to_vec();
|
|
703
|
+
let codepoints = slice::from_raw_parts(codepoints_ptr, codepoints_len);
|
|
704
|
+
let region = read_c_string(region_ptr)?;
|
|
705
|
+
let family = read_c_string(family_ptr)?;
|
|
706
|
+
let font_source_hash = read_c_string(font_source_hash_ptr)?;
|
|
707
|
+
|
|
708
|
+
let library = Library::init().map_err(|err| format!("FreeType init failed: {err:?}"))?;
|
|
709
|
+
let t1 = Instant::now();
|
|
710
|
+
let face = library
|
|
711
|
+
.new_memory_face(Rc::new(font_bytes), 0)
|
|
712
|
+
.map_err(|err| format!("load memory face failed: {err:?}"))?;
|
|
713
|
+
face.set_char_size((TMP_POINT_SIZE as isize) * 64, 0, 72, 72)
|
|
714
|
+
.map_err(|err| format!("set char size failed: {err:?}"))?;
|
|
715
|
+
let t2 = Instant::now();
|
|
716
|
+
|
|
717
|
+
let mut glyphs = Vec::new();
|
|
718
|
+
let mut missing = Vec::new();
|
|
719
|
+
let mut glyph_total_ms = 0.0f64;
|
|
720
|
+
let mut total_pixel_count: usize = 0;
|
|
721
|
+
for codepoint in codepoints {
|
|
722
|
+
let Some(ch) = char::from_u32(*codepoint) else {
|
|
723
|
+
missing.push(format!("U+{codepoint:04X}"));
|
|
724
|
+
continue;
|
|
725
|
+
};
|
|
726
|
+
if ch == '\n' || ch == '\r' {
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
let g0 = Instant::now();
|
|
730
|
+
let glyph_result = if supersample > 0 {
|
|
731
|
+
build_glyph_edt(&face, ®ion, &family, &font_source_hash, ch, supersample)
|
|
732
|
+
.or_else(|_| build_glyph(&face, ®ion, &family, &font_source_hash, ch))
|
|
733
|
+
} else {
|
|
734
|
+
build_glyph(&face, ®ion, &family, &font_source_hash, ch)
|
|
735
|
+
};
|
|
736
|
+
match glyph_result {
|
|
737
|
+
Ok(glyph) => {
|
|
738
|
+
total_pixel_count += glyph.width * glyph.height;
|
|
739
|
+
glyphs.push(glyph);
|
|
740
|
+
}
|
|
741
|
+
Err(message) => missing.push(format!("{family}:{ch}:{message}")),
|
|
742
|
+
}
|
|
743
|
+
glyph_total_ms += g0.elapsed().as_secs_f64() * 1000.0;
|
|
744
|
+
}
|
|
745
|
+
let t3 = Instant::now();
|
|
746
|
+
|
|
747
|
+
let glyph_count = glyphs.len();
|
|
748
|
+
serde_json::to_string(&GlyphBatch {
|
|
749
|
+
region,
|
|
750
|
+
family,
|
|
751
|
+
font_source_hash,
|
|
752
|
+
base_size: TMP_POINT_SIZE,
|
|
753
|
+
spread: TMP_SPREAD,
|
|
754
|
+
glyphs,
|
|
755
|
+
missing,
|
|
756
|
+
perf: GlyphBatchPerf {
|
|
757
|
+
total_ms: duration_ms(t0, t3),
|
|
758
|
+
face_load_ms: duration_ms(t1, t2),
|
|
759
|
+
glyph_total_ms,
|
|
760
|
+
glyph_count,
|
|
761
|
+
per_glyph_avg_ms: if glyph_count > 0 {
|
|
762
|
+
glyph_total_ms / glyph_count as f64
|
|
763
|
+
} else {
|
|
764
|
+
0.0
|
|
765
|
+
},
|
|
766
|
+
total_pixel_count,
|
|
767
|
+
avg_pixels_per_glyph: if glyph_count > 0 {
|
|
768
|
+
total_pixel_count as f64 / glyph_count as f64
|
|
769
|
+
} else {
|
|
770
|
+
0.0
|
|
771
|
+
},
|
|
772
|
+
},
|
|
773
|
+
})
|
|
774
|
+
.map_err(|err| format!("serialize glyph batch failed: {err}"))
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
unsafe fn map_glyphs_json(
|
|
778
|
+
font_ptr: *const u8,
|
|
779
|
+
font_len: usize,
|
|
780
|
+
codepoints_ptr: *const u32,
|
|
781
|
+
codepoints_len: usize,
|
|
782
|
+
region_ptr: *const c_char,
|
|
783
|
+
family_ptr: *const c_char,
|
|
784
|
+
font_source_hash_ptr: *const c_char,
|
|
785
|
+
) -> Result<String, String> {
|
|
786
|
+
if font_ptr.is_null() || codepoints_ptr.is_null() {
|
|
787
|
+
return Err("null font or codepoint pointer".to_string());
|
|
788
|
+
}
|
|
789
|
+
let font_bytes = slice::from_raw_parts(font_ptr, font_len).to_vec();
|
|
790
|
+
let codepoints = slice::from_raw_parts(codepoints_ptr, codepoints_len);
|
|
791
|
+
let region = read_c_string(region_ptr)?;
|
|
792
|
+
let family = read_c_string(family_ptr)?;
|
|
793
|
+
let font_source_hash = read_c_string(font_source_hash_ptr)?;
|
|
794
|
+
let library = Library::init().map_err(|err| format!("FreeType init failed: {err:?}"))?;
|
|
795
|
+
let face = library
|
|
796
|
+
.new_memory_face(Rc::new(font_bytes), 0)
|
|
797
|
+
.map_err(|err| format!("load memory face failed: {err:?}"))?;
|
|
798
|
+
let mut glyphs = Vec::with_capacity(codepoints.len());
|
|
799
|
+
let mut missing = Vec::new();
|
|
800
|
+
for codepoint in codepoints {
|
|
801
|
+
let Some(ch) = char::from_u32(*codepoint) else {
|
|
802
|
+
missing.push(format!("U+{codepoint:04X}"));
|
|
803
|
+
continue;
|
|
804
|
+
};
|
|
805
|
+
match face.get_char_index(ch as usize) {
|
|
806
|
+
Some(glyph_index) => glyphs.push(GlyphMapEntry {
|
|
807
|
+
ch: ch.to_string(),
|
|
808
|
+
glyph_index,
|
|
809
|
+
}),
|
|
810
|
+
None => missing.push(format!("U+{codepoint:04X}")),
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
serde_json::to_string(&GlyphMapBatch {
|
|
814
|
+
region,
|
|
815
|
+
family,
|
|
816
|
+
font_source_hash,
|
|
817
|
+
glyphs,
|
|
818
|
+
missing,
|
|
819
|
+
})
|
|
820
|
+
.map_err(|err| format!("serialize glyph map failed: {err}"))
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
fn duration_ms(start: Instant, end: Instant) -> f64 {
|
|
824
|
+
(end - start).as_secs_f64() * 1000.0
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
unsafe fn read_c_string(ptr: *const c_char) -> Result<String, String> {
|
|
828
|
+
if ptr.is_null() {
|
|
829
|
+
return Ok(String::new());
|
|
830
|
+
}
|
|
831
|
+
CStr::from_ptr(ptr)
|
|
832
|
+
.to_str()
|
|
833
|
+
.map(str::to_string)
|
|
834
|
+
.map_err(|err| format!("invalid utf8 string: {err}"))
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
unsafe fn read_utf8_slice<'a>(ptr: *const u8, len: usize) -> Result<&'a str, String> {
|
|
838
|
+
if ptr.is_null() {
|
|
839
|
+
return Err("null utf8 pointer".to_string());
|
|
840
|
+
}
|
|
841
|
+
let bytes = slice::from_raw_parts(ptr, len);
|
|
842
|
+
std::str::from_utf8(bytes).map_err(|err| format!("invalid utf8 slice: {err}"))
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
fn into_c_string(value: String) -> *mut c_char {
|
|
846
|
+
CString::new(value)
|
|
847
|
+
.unwrap_or_else(|_| CString::new("{\"error\":\"interior nul byte\"}").unwrap())
|
|
848
|
+
.into_raw()
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
fn build_glyph(
|
|
852
|
+
face: &freetype::Face,
|
|
853
|
+
region: &str,
|
|
854
|
+
family: &str,
|
|
855
|
+
font_source_hash: &str,
|
|
856
|
+
ch: char,
|
|
857
|
+
) -> Result<GlyphSdf, String> {
|
|
858
|
+
let glyph_id = face
|
|
859
|
+
.get_char_index(ch as usize)
|
|
860
|
+
.ok_or_else(|| "missing cmap entry".to_string())?;
|
|
861
|
+
face.load_glyph(glyph_id, LoadFlag::NO_BITMAP | LoadFlag::NO_HINTING)
|
|
862
|
+
.map_err(|err| format!("load glyph failed: {err:?}"))?;
|
|
863
|
+
|
|
864
|
+
let glyph = face.glyph();
|
|
865
|
+
let metrics = glyph.metrics();
|
|
866
|
+
let bear_x = metrics.horiBearingX as f32 / 64.0;
|
|
867
|
+
let bear_y = metrics.horiBearingY as f32 / 64.0;
|
|
868
|
+
let met_w = metrics.width as f32 / 64.0;
|
|
869
|
+
let met_h = metrics.height as f32 / 64.0;
|
|
870
|
+
let advance = metrics.horiAdvance as f32 / 64.0;
|
|
871
|
+
|
|
872
|
+
let outline = &glyph.raw().outline;
|
|
873
|
+
if outline.n_contours <= 0 || outline.n_points <= 0 {
|
|
874
|
+
return Ok(empty_metric_glyph(
|
|
875
|
+
region,
|
|
876
|
+
family,
|
|
877
|
+
font_source_hash,
|
|
878
|
+
ch,
|
|
879
|
+
glyph_id,
|
|
880
|
+
advance,
|
|
881
|
+
));
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
let contours = unsafe { extract_segments(outline) };
|
|
885
|
+
if contours.is_empty() {
|
|
886
|
+
return Ok(empty_metric_glyph(
|
|
887
|
+
region,
|
|
888
|
+
family,
|
|
889
|
+
font_source_hash,
|
|
890
|
+
ch,
|
|
891
|
+
glyph_id,
|
|
892
|
+
advance,
|
|
893
|
+
));
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
let rect_left_px = bear_x.floor();
|
|
897
|
+
let rect_top_px = bear_y.ceil();
|
|
898
|
+
let rect_right_px = (bear_x + met_w).ceil();
|
|
899
|
+
let rect_bottom_px = (bear_y - met_h).floor();
|
|
900
|
+
let spread_px = TMP_SPREAD.ceil();
|
|
901
|
+
let sample_left_px = rect_left_px - spread_px;
|
|
902
|
+
let sample_top_px = rect_top_px + spread_px;
|
|
903
|
+
let sample_right_px = rect_right_px + spread_px;
|
|
904
|
+
let sample_bottom_px = rect_bottom_px - spread_px;
|
|
905
|
+
|
|
906
|
+
let width = (sample_right_px - sample_left_px).max(1.0) as usize;
|
|
907
|
+
let height = (sample_top_px - sample_bottom_px).max(1.0) as usize;
|
|
908
|
+
let rect_left_26_6 = sample_left_px * 64.0;
|
|
909
|
+
let rect_top_26_6 = sample_top_px * 64.0;
|
|
910
|
+
|
|
911
|
+
let distance_field = AnalyticDistanceField::new(&contours);
|
|
912
|
+
let mut pixels = vec![0u8; width * height];
|
|
913
|
+
for py in 0..height {
|
|
914
|
+
for px in 0..width {
|
|
915
|
+
let point = Vec2::new(
|
|
916
|
+
rect_left_26_6 + (px as f32 + 0.5) * 64.0,
|
|
917
|
+
rect_top_26_6 - (py as f32 + 0.5) * 64.0,
|
|
918
|
+
);
|
|
919
|
+
let signed_distance_px = distance_field.signed_distance(point) / 64.0;
|
|
920
|
+
let gray = (0.5 - signed_distance_px / (2.0 * TMP_SPREAD)).clamp(0.0, 1.0);
|
|
921
|
+
pixels[py * width + px] = (gray * 255.0).round().clamp(0.0, 255.0) as u8;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
Ok(GlyphSdf {
|
|
926
|
+
key: glyph_key(region, family, font_source_hash, ch),
|
|
927
|
+
region: region.to_string(),
|
|
928
|
+
family: family.to_string(),
|
|
929
|
+
font_source_hash: font_source_hash.to_string(),
|
|
930
|
+
ch: ch.to_string(),
|
|
931
|
+
glyph_index: glyph_id,
|
|
932
|
+
width,
|
|
933
|
+
height,
|
|
934
|
+
bearing_x: sample_left_px,
|
|
935
|
+
bearing_y: sample_top_px,
|
|
936
|
+
x_offset: sample_left_px,
|
|
937
|
+
y_offset: -sample_top_px,
|
|
938
|
+
advance,
|
|
939
|
+
plane_bearing_x: bear_x,
|
|
940
|
+
plane_bearing_y: bear_y,
|
|
941
|
+
plane_width: met_w.max(1.0 / 64.0),
|
|
942
|
+
plane_height: met_h.max(1.0 / 64.0),
|
|
943
|
+
drawable: true,
|
|
944
|
+
pixels_base64: encode_pixels(&pixels),
|
|
945
|
+
})
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
fn build_glyph_edt(
|
|
949
|
+
face: &freetype::Face,
|
|
950
|
+
region: &str,
|
|
951
|
+
family: &str,
|
|
952
|
+
font_source_hash: &str,
|
|
953
|
+
ch: char,
|
|
954
|
+
supersample: usize,
|
|
955
|
+
) -> Result<GlyphSdf, String> {
|
|
956
|
+
let glyph_id = face
|
|
957
|
+
.get_char_index(ch as usize)
|
|
958
|
+
.ok_or_else(|| "missing cmap entry".to_string())?;
|
|
959
|
+
face.load_glyph(glyph_id, LoadFlag::NO_HINTING)
|
|
960
|
+
.map_err(|err| format!("load glyph failed: {err:?}"))?;
|
|
961
|
+
|
|
962
|
+
let glyph = face.glyph();
|
|
963
|
+
let metrics = glyph.metrics();
|
|
964
|
+
let bear_x = metrics.horiBearingX as f32 / 64.0;
|
|
965
|
+
let bear_y = metrics.horiBearingY as f32 / 64.0;
|
|
966
|
+
let met_w = metrics.width as f32 / 64.0;
|
|
967
|
+
let met_h = metrics.height as f32 / 64.0;
|
|
968
|
+
let advance = metrics.horiAdvance as f32 / 64.0;
|
|
969
|
+
|
|
970
|
+
let outline = &glyph.raw().outline;
|
|
971
|
+
if outline.n_contours <= 0 || outline.n_points <= 0 {
|
|
972
|
+
return Ok(empty_metric_glyph(
|
|
973
|
+
region,
|
|
974
|
+
family,
|
|
975
|
+
font_source_hash,
|
|
976
|
+
ch,
|
|
977
|
+
glyph_id,
|
|
978
|
+
advance,
|
|
979
|
+
));
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
let rect_left_px = bear_x.floor();
|
|
983
|
+
let rect_top_px = bear_y.ceil();
|
|
984
|
+
let rect_right_px = (bear_x + met_w).ceil();
|
|
985
|
+
let rect_bottom_px = (bear_y - met_h).floor();
|
|
986
|
+
let spread_px = TMP_SPREAD.ceil();
|
|
987
|
+
let sample_left_px = rect_left_px - spread_px;
|
|
988
|
+
let sample_top_px = rect_top_px + spread_px;
|
|
989
|
+
let sample_right_px = rect_right_px + spread_px;
|
|
990
|
+
let sample_bottom_px = rect_bottom_px - spread_px;
|
|
991
|
+
|
|
992
|
+
let width = (sample_right_px - sample_left_px).max(1.0) as usize;
|
|
993
|
+
let height = (sample_top_px - sample_bottom_px).max(1.0) as usize;
|
|
994
|
+
|
|
995
|
+
let ss = supersample.max(1);
|
|
996
|
+
let raster_w = width * ss;
|
|
997
|
+
let raster_h = height * ss;
|
|
998
|
+
glyph
|
|
999
|
+
.render_glyph(RenderMode::Normal)
|
|
1000
|
+
.map_err(|err| format!("render glyph failed: {err:?}"))?;
|
|
1001
|
+
let bitmap = glyph.bitmap();
|
|
1002
|
+
let bm_w = bitmap.width() as usize;
|
|
1003
|
+
let bm_h = bitmap.rows() as usize;
|
|
1004
|
+
let bm_left = glyph.bitmap_left();
|
|
1005
|
+
let bm_top = glyph.bitmap_top();
|
|
1006
|
+
|
|
1007
|
+
let mut inside = vec![false; raster_w * raster_h];
|
|
1008
|
+
if bm_w > 0 && bm_h > 0 {
|
|
1009
|
+
let buffer = bitmap.buffer();
|
|
1010
|
+
let pitch = bitmap.pitch().unsigned_abs() as usize;
|
|
1011
|
+
for ry in 0..raster_h {
|
|
1012
|
+
for rx in 0..raster_w {
|
|
1013
|
+
let px_26_6 = (sample_left_px + (rx as f32 + 0.5) / ss as f32) * 64.0;
|
|
1014
|
+
let py_26_6 = (sample_top_px - (ry as f32 + 0.5) / ss as f32) * 64.0;
|
|
1015
|
+
let bx = ((px_26_6 / 64.0) - bm_left as f32).floor() as isize;
|
|
1016
|
+
let by = (bm_top as f32 - (py_26_6 / 64.0)).floor() as isize;
|
|
1017
|
+
if bx >= 0 && by >= 0 && (bx as usize) < bm_w && (by as usize) < bm_h {
|
|
1018
|
+
let coverage = buffer[by as usize * pitch + bx as usize];
|
|
1019
|
+
inside[ry * raster_w + rx] = coverage >= 128;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
let sd_ss = edt::signed_distance_from_mask(&inside, raster_w, raster_h);
|
|
1026
|
+
|
|
1027
|
+
let mut pixels = vec![0u8; width * height];
|
|
1028
|
+
for py in 0..height {
|
|
1029
|
+
for px in 0..width {
|
|
1030
|
+
let mut sum = 0.0f32;
|
|
1031
|
+
for sy in 0..ss {
|
|
1032
|
+
for sx in 0..ss {
|
|
1033
|
+
let idx = (py * ss + sy) * raster_w + (px * ss + sx);
|
|
1034
|
+
sum += sd_ss[idx];
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
let dist_px = sum / (ss * ss) as f32 / ss as f32;
|
|
1038
|
+
let gray = (0.5 - dist_px / (2.0 * TMP_SPREAD)).clamp(0.0, 1.0);
|
|
1039
|
+
pixels[py * width + px] = (gray * 255.0).round().clamp(0.0, 255.0) as u8;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
Ok(GlyphSdf {
|
|
1044
|
+
key: glyph_key(region, family, font_source_hash, ch),
|
|
1045
|
+
region: region.to_string(),
|
|
1046
|
+
family: family.to_string(),
|
|
1047
|
+
font_source_hash: font_source_hash.to_string(),
|
|
1048
|
+
ch: ch.to_string(),
|
|
1049
|
+
glyph_index: glyph_id,
|
|
1050
|
+
width,
|
|
1051
|
+
height,
|
|
1052
|
+
bearing_x: sample_left_px,
|
|
1053
|
+
bearing_y: sample_top_px,
|
|
1054
|
+
x_offset: sample_left_px,
|
|
1055
|
+
y_offset: -sample_top_px,
|
|
1056
|
+
advance,
|
|
1057
|
+
plane_bearing_x: bear_x,
|
|
1058
|
+
plane_bearing_y: bear_y,
|
|
1059
|
+
plane_width: met_w.max(1.0 / 64.0),
|
|
1060
|
+
plane_height: met_h.max(1.0 / 64.0),
|
|
1061
|
+
drawable: true,
|
|
1062
|
+
pixels_base64: encode_pixels(&pixels),
|
|
1063
|
+
})
|
|
1064
|
+
}
|
|
1065
|
+
fn empty_metric_glyph(
|
|
1066
|
+
region: &str,
|
|
1067
|
+
family: &str,
|
|
1068
|
+
font_source_hash: &str,
|
|
1069
|
+
ch: char,
|
|
1070
|
+
glyph_index: u32,
|
|
1071
|
+
advance: f32,
|
|
1072
|
+
) -> GlyphSdf {
|
|
1073
|
+
GlyphSdf {
|
|
1074
|
+
key: glyph_key(region, family, font_source_hash, ch),
|
|
1075
|
+
region: region.to_string(),
|
|
1076
|
+
family: family.to_string(),
|
|
1077
|
+
font_source_hash: font_source_hash.to_string(),
|
|
1078
|
+
ch: ch.to_string(),
|
|
1079
|
+
glyph_index,
|
|
1080
|
+
width: 1,
|
|
1081
|
+
height: 1,
|
|
1082
|
+
bearing_x: 0.0,
|
|
1083
|
+
bearing_y: 0.0,
|
|
1084
|
+
x_offset: 0.0,
|
|
1085
|
+
y_offset: 0.0,
|
|
1086
|
+
advance,
|
|
1087
|
+
plane_bearing_x: 0.0,
|
|
1088
|
+
plane_bearing_y: 0.0,
|
|
1089
|
+
plane_width: advance.max(1.0 / 64.0),
|
|
1090
|
+
plane_height: 0.0,
|
|
1091
|
+
drawable: false,
|
|
1092
|
+
pixels_base64: encode_pixels(&[0]),
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
fn encode_pixels(pixels: &[u8]) -> String {
|
|
1097
|
+
base64::engine::general_purpose::STANDARD.encode(pixels)
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
fn glyph_key(region: &str, family: &str, font_source_hash: &str, ch: char) -> String {
|
|
1101
|
+
format!("{region}\u{0}{font_source_hash}\u{0}{family}\u{0}{ch}")
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
#[derive(Serialize)]
|
|
1105
|
+
struct GlyphBatch {
|
|
1106
|
+
region: String,
|
|
1107
|
+
family: String,
|
|
1108
|
+
font_source_hash: String,
|
|
1109
|
+
base_size: f32,
|
|
1110
|
+
spread: f32,
|
|
1111
|
+
glyphs: Vec<GlyphSdf>,
|
|
1112
|
+
missing: Vec<String>,
|
|
1113
|
+
perf: GlyphBatchPerf,
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
#[derive(Serialize)]
|
|
1117
|
+
struct GlyphBatchPerf {
|
|
1118
|
+
total_ms: f64,
|
|
1119
|
+
face_load_ms: f64,
|
|
1120
|
+
glyph_total_ms: f64,
|
|
1121
|
+
glyph_count: usize,
|
|
1122
|
+
per_glyph_avg_ms: f64,
|
|
1123
|
+
total_pixel_count: usize,
|
|
1124
|
+
avg_pixels_per_glyph: f64,
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
#[derive(Serialize)]
|
|
1128
|
+
struct GlyphBatchError {
|
|
1129
|
+
error: String,
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
#[derive(Serialize)]
|
|
1133
|
+
struct FreeTypeContract<'a> {
|
|
1134
|
+
font_engine_fingerprint: &'a str,
|
|
1135
|
+
freetype_version: &'a str,
|
|
1136
|
+
modules: &'a [&'a str],
|
|
1137
|
+
load_contract: &'a str,
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
#[derive(Serialize)]
|
|
1141
|
+
struct GlyphMapBatch {
|
|
1142
|
+
region: String,
|
|
1143
|
+
family: String,
|
|
1144
|
+
font_source_hash: String,
|
|
1145
|
+
glyphs: Vec<GlyphMapEntry>,
|
|
1146
|
+
missing: Vec<String>,
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
#[derive(Serialize)]
|
|
1150
|
+
struct GlyphMapEntry {
|
|
1151
|
+
ch: String,
|
|
1152
|
+
glyph_index: u32,
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
#[derive(Serialize)]
|
|
1156
|
+
struct GlyphSdf {
|
|
1157
|
+
key: String,
|
|
1158
|
+
region: String,
|
|
1159
|
+
family: String,
|
|
1160
|
+
font_source_hash: String,
|
|
1161
|
+
ch: String,
|
|
1162
|
+
glyph_index: u32,
|
|
1163
|
+
width: usize,
|
|
1164
|
+
height: usize,
|
|
1165
|
+
bearing_x: f32,
|
|
1166
|
+
bearing_y: f32,
|
|
1167
|
+
x_offset: f32,
|
|
1168
|
+
y_offset: f32,
|
|
1169
|
+
advance: f32,
|
|
1170
|
+
plane_bearing_x: f32,
|
|
1171
|
+
plane_bearing_y: f32,
|
|
1172
|
+
plane_width: f32,
|
|
1173
|
+
plane_height: f32,
|
|
1174
|
+
drawable: bool,
|
|
1175
|
+
pixels_base64: String,
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// ===== Mask batch (for WebGPU SDF path) =====
|
|
1179
|
+
|
|
1180
|
+
#[derive(Serialize)]
|
|
1181
|
+
struct GlyphMaskBatch {
|
|
1182
|
+
region: String,
|
|
1183
|
+
family: String,
|
|
1184
|
+
font_source_hash: String,
|
|
1185
|
+
base_size: f32,
|
|
1186
|
+
spread: f32,
|
|
1187
|
+
glyphs: Vec<GlyphMask>,
|
|
1188
|
+
missing: Vec<String>,
|
|
1189
|
+
perf: GlyphBatchPerf,
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
#[derive(Serialize)]
|
|
1193
|
+
struct GlyphMask {
|
|
1194
|
+
key: String,
|
|
1195
|
+
region: String,
|
|
1196
|
+
family: String,
|
|
1197
|
+
font_source_hash: String,
|
|
1198
|
+
ch: String,
|
|
1199
|
+
width: usize,
|
|
1200
|
+
height: usize,
|
|
1201
|
+
raster_width: usize,
|
|
1202
|
+
raster_height: usize,
|
|
1203
|
+
supersample: usize,
|
|
1204
|
+
bearing_x: f32,
|
|
1205
|
+
bearing_y: f32,
|
|
1206
|
+
x_offset: f32,
|
|
1207
|
+
y_offset: f32,
|
|
1208
|
+
advance: f32,
|
|
1209
|
+
plane_bearing_x: f32,
|
|
1210
|
+
plane_bearing_y: f32,
|
|
1211
|
+
plane_width: f32,
|
|
1212
|
+
plane_height: f32,
|
|
1213
|
+
drawable: bool,
|
|
1214
|
+
mask_base64: String,
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
#[no_mangle]
|
|
1218
|
+
pub unsafe extern "C" fn sdf_layout_freetype_build_mask_json(
|
|
1219
|
+
font_ptr: *const u8,
|
|
1220
|
+
font_len: usize,
|
|
1221
|
+
codepoints_ptr: *const u32,
|
|
1222
|
+
codepoints_len: usize,
|
|
1223
|
+
region_ptr: *const c_char,
|
|
1224
|
+
family_ptr: *const c_char,
|
|
1225
|
+
font_source_hash_ptr: *const c_char,
|
|
1226
|
+
supersample: usize,
|
|
1227
|
+
) -> *mut c_char {
|
|
1228
|
+
install_panic_hook();
|
|
1229
|
+
let result = build_glyph_batch_mask_json(
|
|
1230
|
+
font_ptr,
|
|
1231
|
+
font_len,
|
|
1232
|
+
codepoints_ptr,
|
|
1233
|
+
codepoints_len,
|
|
1234
|
+
region_ptr,
|
|
1235
|
+
family_ptr,
|
|
1236
|
+
font_source_hash_ptr,
|
|
1237
|
+
supersample.clamp(1, 4),
|
|
1238
|
+
);
|
|
1239
|
+
into_c_string(result.unwrap_or_else(|message| {
|
|
1240
|
+
serde_json::to_string(&GlyphBatchError { error: message })
|
|
1241
|
+
.unwrap_or_else(|_| "{\"error\":\"serialization failed\"}".to_string())
|
|
1242
|
+
}))
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
unsafe fn build_glyph_batch_mask_json(
|
|
1246
|
+
font_ptr: *const u8,
|
|
1247
|
+
font_len: usize,
|
|
1248
|
+
codepoints_ptr: *const u32,
|
|
1249
|
+
codepoints_len: usize,
|
|
1250
|
+
region_ptr: *const c_char,
|
|
1251
|
+
family_ptr: *const c_char,
|
|
1252
|
+
font_source_hash_ptr: *const c_char,
|
|
1253
|
+
supersample: usize,
|
|
1254
|
+
) -> Result<String, String> {
|
|
1255
|
+
if font_ptr.is_null() || codepoints_ptr.is_null() {
|
|
1256
|
+
return Err("null font or codepoint pointer".to_string());
|
|
1257
|
+
}
|
|
1258
|
+
let t0 = Instant::now();
|
|
1259
|
+
let font_bytes = slice::from_raw_parts(font_ptr, font_len).to_vec();
|
|
1260
|
+
let codepoints = slice::from_raw_parts(codepoints_ptr, codepoints_len);
|
|
1261
|
+
let region = read_c_string(region_ptr)?;
|
|
1262
|
+
let family = read_c_string(family_ptr)?;
|
|
1263
|
+
let font_source_hash = read_c_string(font_source_hash_ptr)?;
|
|
1264
|
+
|
|
1265
|
+
let library = Library::init().map_err(|err| format!("FreeType init failed: {err:?}"))?;
|
|
1266
|
+
let t1 = Instant::now();
|
|
1267
|
+
let face = library
|
|
1268
|
+
.new_memory_face(Rc::new(font_bytes), 0)
|
|
1269
|
+
.map_err(|err| format!("load memory face failed: {err:?}"))?;
|
|
1270
|
+
face.set_char_size((TMP_POINT_SIZE as isize) * 64, 0, 72, 72)
|
|
1271
|
+
.map_err(|err| format!("set char size failed: {err:?}"))?;
|
|
1272
|
+
let t2 = Instant::now();
|
|
1273
|
+
|
|
1274
|
+
let mut glyphs = Vec::new();
|
|
1275
|
+
let mut missing = Vec::new();
|
|
1276
|
+
let mut glyph_total_ms = 0.0f64;
|
|
1277
|
+
let mut total_pixel_count: usize = 0;
|
|
1278
|
+
for codepoint in codepoints {
|
|
1279
|
+
let Some(ch) = char::from_u32(*codepoint) else {
|
|
1280
|
+
missing.push(format!("U+{codepoint:04X}"));
|
|
1281
|
+
continue;
|
|
1282
|
+
};
|
|
1283
|
+
if ch == '\n' || ch == '\r' {
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
let g0 = Instant::now();
|
|
1287
|
+
match build_glyph_mask(&face, ®ion, &family, &font_source_hash, ch, supersample) {
|
|
1288
|
+
Ok(glyph) => {
|
|
1289
|
+
total_pixel_count += glyph.raster_width * glyph.raster_height;
|
|
1290
|
+
glyphs.push(glyph);
|
|
1291
|
+
}
|
|
1292
|
+
Err(message) => missing.push(format!("{family}:{ch}:{message}")),
|
|
1293
|
+
}
|
|
1294
|
+
glyph_total_ms += g0.elapsed().as_secs_f64() * 1000.0;
|
|
1295
|
+
}
|
|
1296
|
+
let t3 = Instant::now();
|
|
1297
|
+
|
|
1298
|
+
let glyph_count = glyphs.len();
|
|
1299
|
+
serde_json::to_string(&GlyphMaskBatch {
|
|
1300
|
+
region,
|
|
1301
|
+
family,
|
|
1302
|
+
font_source_hash,
|
|
1303
|
+
base_size: TMP_POINT_SIZE,
|
|
1304
|
+
spread: TMP_SPREAD,
|
|
1305
|
+
glyphs,
|
|
1306
|
+
missing,
|
|
1307
|
+
perf: GlyphBatchPerf {
|
|
1308
|
+
total_ms: duration_ms(t0, t3),
|
|
1309
|
+
face_load_ms: duration_ms(t1, t2),
|
|
1310
|
+
glyph_total_ms,
|
|
1311
|
+
glyph_count,
|
|
1312
|
+
per_glyph_avg_ms: if glyph_count > 0 {
|
|
1313
|
+
glyph_total_ms / glyph_count as f64
|
|
1314
|
+
} else {
|
|
1315
|
+
0.0
|
|
1316
|
+
},
|
|
1317
|
+
total_pixel_count,
|
|
1318
|
+
avg_pixels_per_glyph: if glyph_count > 0 {
|
|
1319
|
+
total_pixel_count as f64 / glyph_count as f64
|
|
1320
|
+
} else {
|
|
1321
|
+
0.0
|
|
1322
|
+
},
|
|
1323
|
+
},
|
|
1324
|
+
})
|
|
1325
|
+
.map_err(|err| format!("serialize mask batch failed: {err}"))
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
fn build_glyph_mask(
|
|
1329
|
+
face: &freetype::Face,
|
|
1330
|
+
region: &str,
|
|
1331
|
+
family: &str,
|
|
1332
|
+
font_source_hash: &str,
|
|
1333
|
+
ch: char,
|
|
1334
|
+
supersample: usize,
|
|
1335
|
+
) -> Result<GlyphMask, String> {
|
|
1336
|
+
let glyph_id = face
|
|
1337
|
+
.get_char_index(ch as usize)
|
|
1338
|
+
.ok_or_else(|| "missing cmap entry".to_string())?;
|
|
1339
|
+
face.load_glyph(glyph_id, LoadFlag::NO_HINTING)
|
|
1340
|
+
.map_err(|err| format!("load glyph failed: {err:?}"))?;
|
|
1341
|
+
|
|
1342
|
+
let glyph = face.glyph();
|
|
1343
|
+
let metrics = glyph.metrics();
|
|
1344
|
+
let bear_x = metrics.horiBearingX as f32 / 64.0;
|
|
1345
|
+
let bear_y = metrics.horiBearingY as f32 / 64.0;
|
|
1346
|
+
let met_w = metrics.width as f32 / 64.0;
|
|
1347
|
+
let met_h = metrics.height as f32 / 64.0;
|
|
1348
|
+
let advance = metrics.horiAdvance as f32 / 64.0;
|
|
1349
|
+
|
|
1350
|
+
let outline = &glyph.raw().outline;
|
|
1351
|
+
if outline.n_contours <= 0 || outline.n_points <= 0 {
|
|
1352
|
+
return Ok(GlyphMask {
|
|
1353
|
+
key: glyph_key(region, family, font_source_hash, ch),
|
|
1354
|
+
region: region.to_string(),
|
|
1355
|
+
family: family.to_string(),
|
|
1356
|
+
font_source_hash: font_source_hash.to_string(),
|
|
1357
|
+
ch: ch.to_string(),
|
|
1358
|
+
width: 1,
|
|
1359
|
+
height: 1,
|
|
1360
|
+
raster_width: 1,
|
|
1361
|
+
raster_height: 1,
|
|
1362
|
+
supersample,
|
|
1363
|
+
bearing_x: 0.0,
|
|
1364
|
+
bearing_y: 0.0,
|
|
1365
|
+
x_offset: 0.0,
|
|
1366
|
+
y_offset: 0.0,
|
|
1367
|
+
advance,
|
|
1368
|
+
plane_bearing_x: 0.0,
|
|
1369
|
+
plane_bearing_y: 0.0,
|
|
1370
|
+
plane_width: advance.max(1.0 / 64.0),
|
|
1371
|
+
plane_height: 0.0,
|
|
1372
|
+
drawable: false,
|
|
1373
|
+
mask_base64: encode_pixels(&[0]),
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
let rect_left_px = bear_x.floor();
|
|
1378
|
+
let rect_top_px = bear_y.ceil();
|
|
1379
|
+
let rect_right_px = (bear_x + met_w).ceil();
|
|
1380
|
+
let rect_bottom_px = (bear_y - met_h).floor();
|
|
1381
|
+
let spread_px = TMP_SPREAD.ceil();
|
|
1382
|
+
let sample_left_px = rect_left_px - spread_px;
|
|
1383
|
+
let sample_top_px = rect_top_px + spread_px;
|
|
1384
|
+
let sample_right_px = rect_right_px + spread_px;
|
|
1385
|
+
let sample_bottom_px = rect_bottom_px - spread_px;
|
|
1386
|
+
|
|
1387
|
+
let width = (sample_right_px - sample_left_px).max(1.0) as usize;
|
|
1388
|
+
let height = (sample_top_px - sample_bottom_px).max(1.0) as usize;
|
|
1389
|
+
|
|
1390
|
+
let ss = supersample.max(1);
|
|
1391
|
+
let raster_w = width * ss;
|
|
1392
|
+
let raster_h = height * ss;
|
|
1393
|
+
glyph
|
|
1394
|
+
.render_glyph(RenderMode::Normal)
|
|
1395
|
+
.map_err(|err| format!("render glyph failed: {err:?}"))?;
|
|
1396
|
+
let bitmap = glyph.bitmap();
|
|
1397
|
+
let bm_w = bitmap.width() as usize;
|
|
1398
|
+
let bm_h = bitmap.rows() as usize;
|
|
1399
|
+
let bm_left = glyph.bitmap_left();
|
|
1400
|
+
let bm_top = glyph.bitmap_top();
|
|
1401
|
+
|
|
1402
|
+
let mut mask = vec![0u8; raster_w * raster_h];
|
|
1403
|
+
if bm_w > 0 && bm_h > 0 {
|
|
1404
|
+
let buffer = bitmap.buffer();
|
|
1405
|
+
let pitch = bitmap.pitch().unsigned_abs() as usize;
|
|
1406
|
+
for ry in 0..raster_h {
|
|
1407
|
+
for rx in 0..raster_w {
|
|
1408
|
+
let px_26_6 = (sample_left_px + (rx as f32 + 0.5) / ss as f32) * 64.0;
|
|
1409
|
+
let py_26_6 = (sample_top_px - (ry as f32 + 0.5) / ss as f32) * 64.0;
|
|
1410
|
+
let bx = ((px_26_6 / 64.0) - bm_left as f32).floor() as isize;
|
|
1411
|
+
let by = (bm_top as f32 - (py_26_6 / 64.0)).floor() as isize;
|
|
1412
|
+
if bx >= 0 && by >= 0 && (bx as usize) < bm_w && (by as usize) < bm_h {
|
|
1413
|
+
let coverage = buffer[by as usize * pitch + bx as usize];
|
|
1414
|
+
mask[ry * raster_w + rx] = if coverage >= 128 { 1 } else { 0 };
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
Ok(GlyphMask {
|
|
1421
|
+
key: glyph_key(region, family, font_source_hash, ch),
|
|
1422
|
+
region: region.to_string(),
|
|
1423
|
+
family: family.to_string(),
|
|
1424
|
+
font_source_hash: font_source_hash.to_string(),
|
|
1425
|
+
ch: ch.to_string(),
|
|
1426
|
+
width,
|
|
1427
|
+
height,
|
|
1428
|
+
raster_width: raster_w,
|
|
1429
|
+
raster_height: raster_h,
|
|
1430
|
+
supersample: ss,
|
|
1431
|
+
bearing_x: sample_left_px,
|
|
1432
|
+
bearing_y: sample_top_px,
|
|
1433
|
+
x_offset: sample_left_px,
|
|
1434
|
+
y_offset: -sample_top_px,
|
|
1435
|
+
advance,
|
|
1436
|
+
plane_bearing_x: bear_x,
|
|
1437
|
+
plane_bearing_y: bear_y,
|
|
1438
|
+
plane_width: met_w.max(1.0 / 64.0),
|
|
1439
|
+
plane_height: met_h.max(1.0 / 64.0),
|
|
1440
|
+
drawable: true,
|
|
1441
|
+
mask_base64: encode_pixels(&mask),
|
|
1442
|
+
})
|
|
1443
|
+
}
|