@drawmotive/textgraph 0.2.2-alpha.4 → 0.2.2-alpha.5
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/docs/api-v1.md +14 -2
- package/docs/react.md +7 -0
- package/generated/wasm/DrawMotive.TextGraph.Bridge.wasm +0 -0
- package/generated/wasm/Graphics.Core.wasm +0 -0
- package/generated/wasm/Grpc.Core.Api.wasm +0 -0
- package/generated/wasm/HarfBuzzSharp.wasm +0 -0
- package/generated/wasm/Polly.Core.wasm +0 -0
- package/generated/wasm/RBush.wasm +0 -0
- package/generated/wasm/SkiaSharp.wasm +0 -0
- package/generated/wasm/System.Collections.Concurrent.wasm +0 -0
- package/generated/wasm/System.Console.wasm +0 -0
- package/generated/wasm/System.IO.Compression.Brotli.wasm +0 -0
- package/generated/wasm/System.IO.Compression.wasm +0 -0
- package/generated/wasm/System.Net.Http.wasm +0 -0
- package/generated/wasm/System.Private.CoreLib.wasm +0 -0
- package/generated/wasm/System.Runtime.InteropServices.wasm +0 -0
- package/generated/wasm/System.Runtime.Intrinsics.wasm +0 -0
- package/generated/wasm/System.Runtime.wasm +0 -0
- package/generated/wasm/System.Threading.Thread.wasm +0 -0
- package/generated/wasm/dotnet.boot.js +19 -24
- package/generated/wasm/dotnet.native.wasm +0 -0
- package/generated/wasm-manifest.js +28 -34
- package/generated/wasm-manifest.json +28 -34
- package/package.json +1 -1
- package/src/index.d.ts +5 -1
- package/src/index.js +8 -2
- package/src/react/index.d.ts +3 -1
- package/src/react/index.js +15 -9
- package/src/runtime/font-cache.js +36 -0
- package/src/runtime/font-resources.js +11 -2
- package/src/runtime/render-resources.js +4 -2
- package/src/runtime/rendering.js +8 -7
- package/generated/wasm/ZstdSharp.wasm +0 -0
package/docs/api-v1.md
CHANGED
|
@@ -75,11 +75,23 @@ Use `languagePacks: []` with `fallback: false` to explicitly disable automatic f
|
|
|
75
75
|
|
|
76
76
|
Catalog fonts carry actual Unicode coverage, language hints, byte sizes and SHA-256.
|
|
77
77
|
They are verified before native installation. Catalog URLs are cached per instance;
|
|
78
|
-
HTTP font URLs include the content hash as a stable `v` parameter.
|
|
79
|
-
|
|
78
|
+
HTTP font URLs include the content hash as a stable `v` parameter. The SDK automatically
|
|
79
|
+
stores verified bundled and optional font bytes in browser/Worker Cache Storage
|
|
80
|
+
(`textgraph-fonts-v1`) and reuses them across instances and page reloads, independent
|
|
81
|
+
of server cache headers. Cached bytes are verified again before use; damaged entries
|
|
82
|
+
are replaced from the configured source. Storage denial or quota errors fall back
|
|
83
|
+
to normal loading. Local files and descriptors without hashes are not persisted. Offline operation
|
|
80
84
|
requires the complete runtime and font package on a local filesystem or local server;
|
|
81
85
|
an online page cannot fetch an uncached font after losing network access.
|
|
82
86
|
|
|
87
|
+
For interactive previews, pass `onPreview` to `renderPng` or `renderSvg`. When visible
|
|
88
|
+
labels need optional fonts, the callback receives a provisional successful image
|
|
89
|
+
using installed fonts before catalog/font downloads. Display it with a font-loading
|
|
90
|
+
notice; missing glyphs or different text geometry can appear temporarily. The returned
|
|
91
|
+
promise still resolves to the final output after fonts are installed and layout is
|
|
92
|
+
recomputed. Calls without the callback retain final-output semantics. Font-loading
|
|
93
|
+
failures reject so the host can keep the provisional image and offer a retry.
|
|
94
|
+
|
|
83
95
|
`renderPng(source, { language: "ja" })` and `renderSvg(source, { language: "ja" })` can disambiguate Han-only labels. Without a hint,
|
|
84
96
|
kana selects Japanese for that label and Han-only labels use Chinese. Grapheme-aware
|
|
85
97
|
emoji selection includes flags, ZWJ sequences, modifiers and keycaps. Font selection
|
package/docs/react.md
CHANGED
|
@@ -38,10 +38,17 @@ Use your deployed base path instead of `/` for subdirectory hosting. The [React/
|
|
|
38
38
|
| `alt` | Image description; defaults to `TextGraph diagram`. |
|
|
39
39
|
| `renderOptions` | Defaults to `scale: 1`; `padding` and `maxWidth` use SDK defaults. Set `scale: 2` explicitly for higher raster density. |
|
|
40
40
|
| `loading` | React content shown with `role="status"`; defaults to `Rendering diagram…`. |
|
|
41
|
+
| `fontsLoading` | Content shown beside the provisional image while optional fonts load; defaults to `Fonts are still loading…`. |
|
|
41
42
|
| Other image attributes | Passed to the successful `<img>`, including `className`, `style`, `width`, and `height`. |
|
|
42
43
|
|
|
43
44
|
The component owns `src`, encoding, and cancellation. While new input renders, old images are replaced by loading content. A stale result cannot replace newer input. DSL diagnostics and operational failures appear as escaped text with `role="alert"`; corrected input renders again. Changing only `alt` or image styles does not rerun layout.
|
|
44
45
|
|
|
46
|
+
When optional fonts are needed, the component displays a provisional image first,
|
|
47
|
+
shows `fontsLoading`, and replaces the image after loading and measuring the fonts.
|
|
48
|
+
If loading fails, the preview remains beside the error. The SDK automatically caches
|
|
49
|
+
verified font bytes across instances and reloads in browser Cache Storage; no host
|
|
50
|
+
cache setup is required.
|
|
51
|
+
|
|
45
52
|
Images use the render result's logical `displayWidth` and `displayHeight`, so increasing `scale` adds pixels without enlarging the diagram. `maxWidth` limits raster pixels rather than CSS width. Styles default to `maxWidth: '100%'` and `height: 'auto'`; supplied styles merge over these defaults, and explicit image dimensions override the logical dimensions.
|
|
46
53
|
|
|
47
54
|
Older runtimes omit display dimensions. The component then divides raster dimensions by the requested scale; this preserves logical size unless `maxWidth` has lowered the effective density. Deploy matching updated runtime assets for accurate sizing in that case. PNG DPI metadata does not control CSS size.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const config = /*json-start*/{
|
|
2
2
|
"mainAssemblyName": "DrawMotive.TextGraph.Bridge.dll",
|
|
3
3
|
"resources": {
|
|
4
|
-
"hash": "sha256-
|
|
4
|
+
"hash": "sha256-NkSXf6BaW3IAk8E3aIFzyrGG28CgnWT4/WC1onNX/Z0=",
|
|
5
5
|
"jsModuleNative": [
|
|
6
6
|
{
|
|
7
7
|
"name": "dotnet.native.js"
|
|
@@ -15,7 +15,7 @@ export const config = /*json-start*/{
|
|
|
15
15
|
"wasmNative": [
|
|
16
16
|
{
|
|
17
17
|
"name": "dotnet.native.wasm",
|
|
18
|
-
"hash": "sha256-
|
|
18
|
+
"hash": "sha256-Tqb9aNQTpsiBcpNevNGjnyxzrpZE0n77Sx4Rcn4UFhc="
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"coreAssembly": [
|
|
@@ -27,7 +27,7 @@ export const config = /*json-start*/{
|
|
|
27
27
|
{
|
|
28
28
|
"virtualPath": "System.Private.CoreLib.wasm",
|
|
29
29
|
"name": "System.Private.CoreLib.wasm",
|
|
30
|
-
"hash": "sha256-
|
|
30
|
+
"hash": "sha256-Iaa0Sf9pQ/5qOAIAghx8wjz/9jodVQVYvksdfYoyQo8="
|
|
31
31
|
}
|
|
32
32
|
],
|
|
33
33
|
"assembly": [
|
|
@@ -39,12 +39,12 @@ export const config = /*json-start*/{
|
|
|
39
39
|
{
|
|
40
40
|
"virtualPath": "Grpc.Core.Api.wasm",
|
|
41
41
|
"name": "Grpc.Core.Api.wasm",
|
|
42
|
-
"hash": "sha256-
|
|
42
|
+
"hash": "sha256-X6Xe6EsTbgz2wWCfm/jWGDbHliLWTyZ4/q8tq0mEXuA="
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
"virtualPath": "HarfBuzzSharp.wasm",
|
|
46
46
|
"name": "HarfBuzzSharp.wasm",
|
|
47
|
-
"hash": "sha256-
|
|
47
|
+
"hash": "sha256-ANRkrpepe+vbsz6PkjgjWbgkW2W68G7iYG9gU7qx+zs="
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
"virtualPath": "MagicOnion.Abstractions.wasm",
|
|
@@ -139,7 +139,7 @@ export const config = /*json-start*/{
|
|
|
139
139
|
{
|
|
140
140
|
"virtualPath": "Polly.Core.wasm",
|
|
141
141
|
"name": "Polly.Core.wasm",
|
|
142
|
-
"hash": "sha256
|
|
142
|
+
"hash": "sha256-dXCCNoxEuqdFCoVDH1ZfKecXtEldfinkUKjwj18RqKE="
|
|
143
143
|
},
|
|
144
144
|
{
|
|
145
145
|
"virtualPath": "R3.wasm",
|
|
@@ -154,7 +154,7 @@ export const config = /*json-start*/{
|
|
|
154
154
|
{
|
|
155
155
|
"virtualPath": "SkiaSharp.wasm",
|
|
156
156
|
"name": "SkiaSharp.wasm",
|
|
157
|
-
"hash": "sha256-
|
|
157
|
+
"hash": "sha256-bRa2qn2QJTeC0dSx2LmKFUS1lm/ZeqoaJ31c24l8NtI="
|
|
158
158
|
},
|
|
159
159
|
{
|
|
160
160
|
"virtualPath": "SkiaSharp.HarfBuzz.wasm",
|
|
@@ -166,15 +166,10 @@ export const config = /*json-start*/{
|
|
|
166
166
|
"name": "Stateless.wasm",
|
|
167
167
|
"hash": "sha256-EWplLf93o2pQw4CGMvCTYO234EMwTX1tSqc2xJ8JbCw="
|
|
168
168
|
},
|
|
169
|
-
{
|
|
170
|
-
"virtualPath": "ZstdSharp.wasm",
|
|
171
|
-
"name": "ZstdSharp.wasm",
|
|
172
|
-
"hash": "sha256-KR3cAHwB6xsvw0WXeCcP8OnTHPehbTBzJ5VbX5vlOVU="
|
|
173
|
-
},
|
|
174
169
|
{
|
|
175
170
|
"virtualPath": "Graphics.Core.wasm",
|
|
176
171
|
"name": "Graphics.Core.wasm",
|
|
177
|
-
"hash": "sha256-
|
|
172
|
+
"hash": "sha256-RkBXYEmkqaTS5u4oaD9LgCGW9BeM6Yx6y7Faf0anA0A="
|
|
178
173
|
},
|
|
179
174
|
{
|
|
180
175
|
"virtualPath": "Math.Core.wasm",
|
|
@@ -184,17 +179,17 @@ export const config = /*json-start*/{
|
|
|
184
179
|
{
|
|
185
180
|
"virtualPath": "RBush.wasm",
|
|
186
181
|
"name": "RBush.wasm",
|
|
187
|
-
"hash": "sha256-
|
|
182
|
+
"hash": "sha256-hB+961dw2Pyftv3Lyp+nlvtuEj0yUB5d/Fzby23Rq2k="
|
|
188
183
|
},
|
|
189
184
|
{
|
|
190
185
|
"virtualPath": "DrawMotive.TextGraph.Bridge.wasm",
|
|
191
186
|
"name": "DrawMotive.TextGraph.Bridge.wasm",
|
|
192
|
-
"hash": "sha256-
|
|
187
|
+
"hash": "sha256-FPqTWFElDDiTkAPoR5NrVyDpUDK4hTEj5naELw5Y21c="
|
|
193
188
|
},
|
|
194
189
|
{
|
|
195
190
|
"virtualPath": "System.Collections.Concurrent.wasm",
|
|
196
191
|
"name": "System.Collections.Concurrent.wasm",
|
|
197
|
-
"hash": "sha256-
|
|
192
|
+
"hash": "sha256-Jeoyh28M8wBA9+BQ0e4ANv2Jq6ITNW88s/DsalFTOII="
|
|
198
193
|
},
|
|
199
194
|
{
|
|
200
195
|
"virtualPath": "System.Collections.Immutable.wasm",
|
|
@@ -239,7 +234,7 @@ export const config = /*json-start*/{
|
|
|
239
234
|
{
|
|
240
235
|
"virtualPath": "System.Console.wasm",
|
|
241
236
|
"name": "System.Console.wasm",
|
|
242
|
-
"hash": "sha256-
|
|
237
|
+
"hash": "sha256-wWvOkB7Asdsz3ljcee0YzCbdSqWCXg1y/VElLjmc4IA="
|
|
243
238
|
},
|
|
244
239
|
{
|
|
245
240
|
"virtualPath": "System.Diagnostics.StackTrace.wasm",
|
|
@@ -254,12 +249,12 @@ export const config = /*json-start*/{
|
|
|
254
249
|
{
|
|
255
250
|
"virtualPath": "System.IO.Compression.Brotli.wasm",
|
|
256
251
|
"name": "System.IO.Compression.Brotli.wasm",
|
|
257
|
-
"hash": "sha256-
|
|
252
|
+
"hash": "sha256-DcPCQCYlBqXrd+tiArKhLDeuqXNSS6lnKVwPOoQ9NZg="
|
|
258
253
|
},
|
|
259
254
|
{
|
|
260
255
|
"virtualPath": "System.IO.Compression.wasm",
|
|
261
256
|
"name": "System.IO.Compression.wasm",
|
|
262
|
-
"hash": "sha256-
|
|
257
|
+
"hash": "sha256-smvpD00XQvKdSiwuc7hnhCoRz2Nk9VsSTg7MQ9QDipY="
|
|
263
258
|
},
|
|
264
259
|
{
|
|
265
260
|
"virtualPath": "System.IO.Pipelines.wasm",
|
|
@@ -284,7 +279,7 @@ export const config = /*json-start*/{
|
|
|
284
279
|
{
|
|
285
280
|
"virtualPath": "System.Net.Http.wasm",
|
|
286
281
|
"name": "System.Net.Http.wasm",
|
|
287
|
-
"hash": "sha256-
|
|
282
|
+
"hash": "sha256-ARrgegBDSux9EF+zqRrsot7EbG1Q9fVGFdJPdHm/r54="
|
|
288
283
|
},
|
|
289
284
|
{
|
|
290
285
|
"virtualPath": "System.Net.Primitives.wasm",
|
|
@@ -334,12 +329,12 @@ export const config = /*json-start*/{
|
|
|
334
329
|
{
|
|
335
330
|
"virtualPath": "System.Runtime.InteropServices.wasm",
|
|
336
331
|
"name": "System.Runtime.InteropServices.wasm",
|
|
337
|
-
"hash": "sha256-
|
|
332
|
+
"hash": "sha256-Q+92Kf8z4HWVuCYT5hM1wtDe4wQSltcDE8cvVbCH8fU="
|
|
338
333
|
},
|
|
339
334
|
{
|
|
340
335
|
"virtualPath": "System.Runtime.Intrinsics.wasm",
|
|
341
336
|
"name": "System.Runtime.Intrinsics.wasm",
|
|
342
|
-
"hash": "sha256-
|
|
337
|
+
"hash": "sha256-Amor9SnGJljEQGEHCbNnu8SJdtb2TN+4vW5fSBgwcMc="
|
|
343
338
|
},
|
|
344
339
|
{
|
|
345
340
|
"virtualPath": "System.Runtime.Loader.wasm",
|
|
@@ -359,7 +354,7 @@ export const config = /*json-start*/{
|
|
|
359
354
|
{
|
|
360
355
|
"virtualPath": "System.Runtime.wasm",
|
|
361
356
|
"name": "System.Runtime.wasm",
|
|
362
|
-
"hash": "sha256-
|
|
357
|
+
"hash": "sha256-f0aumL0L3t2Dj97l3TzNxbOS4K9q93bDGqeAdICfb/A="
|
|
363
358
|
},
|
|
364
359
|
{
|
|
365
360
|
"virtualPath": "System.Security.Cryptography.wasm",
|
|
@@ -394,7 +389,7 @@ export const config = /*json-start*/{
|
|
|
394
389
|
{
|
|
395
390
|
"virtualPath": "System.Threading.Thread.wasm",
|
|
396
391
|
"name": "System.Threading.Thread.wasm",
|
|
397
|
-
"hash": "sha256-
|
|
392
|
+
"hash": "sha256-xmNkGMgtSroWqZ6CxqqzoILHxIWocNCS8InItRUqIUE="
|
|
398
393
|
},
|
|
399
394
|
{
|
|
400
395
|
"virtualPath": "System.Threading.ThreadPool.wasm",
|
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"packageName": "@drawmotive/textgraph",
|
|
4
|
-
"packageVersion": "0.2.2-alpha.
|
|
4
|
+
"packageVersion": "0.2.2-alpha.5",
|
|
5
5
|
"abiVersion": "1.0.0",
|
|
6
6
|
"protocolVersion": 1,
|
|
7
7
|
"runtimeModule": "wasm/dotnet.js",
|
|
@@ -27,7 +27,7 @@ export default {
|
|
|
27
27
|
},
|
|
28
28
|
"targetFramework": "net10.0",
|
|
29
29
|
"privateSource": {
|
|
30
|
-
"commit": "
|
|
30
|
+
"commit": "f7627ef2646772503fb3d8165ee312d4f411566c",
|
|
31
31
|
"project": "DrawMotive.TextGraph.Bridge"
|
|
32
32
|
},
|
|
33
33
|
"entryAssembly": "wasm/DrawMotive.TextGraph.Bridge.wasm",
|
|
@@ -52,7 +52,7 @@ export default {
|
|
|
52
52
|
"path": "wasm/DrawMotive.TextGraph.Bridge.wasm",
|
|
53
53
|
"mediaType": "application/wasm",
|
|
54
54
|
"bytes": 87317,
|
|
55
|
-
"sha256": "
|
|
55
|
+
"sha256": "14fa935851250c38939003e847936b5720e95032b8853123e676842f0e58db57"
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
"path": "wasm/ExCSS.wasm",
|
|
@@ -75,20 +75,20 @@ export default {
|
|
|
75
75
|
{
|
|
76
76
|
"path": "wasm/Graphics.Core.wasm",
|
|
77
77
|
"mediaType": "application/wasm",
|
|
78
|
-
"bytes":
|
|
79
|
-
"sha256": "
|
|
78
|
+
"bytes": 4040985,
|
|
79
|
+
"sha256": "4640576049a4a9a4d2e6ee28683f4b802196f4178ce98c7acbb15a7f46a70340"
|
|
80
80
|
},
|
|
81
81
|
{
|
|
82
82
|
"path": "wasm/Grpc.Core.Api.wasm",
|
|
83
83
|
"mediaType": "application/wasm",
|
|
84
84
|
"bytes": 7957,
|
|
85
|
-
"sha256": "
|
|
85
|
+
"sha256": "5fa5dee84b136e0cf6c1609f9bf8d61836c79622d64f2678feaf2dab49845ee0"
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
88
|
"path": "wasm/HarfBuzzSharp.wasm",
|
|
89
89
|
"mediaType": "application/wasm",
|
|
90
90
|
"bytes": 30485,
|
|
91
|
-
"sha256": "
|
|
91
|
+
"sha256": "00d464ae97a97bebdbb33e8f92382359b8245b65baf06ee2606f6053bab1fb3b"
|
|
92
92
|
},
|
|
93
93
|
{
|
|
94
94
|
"path": "wasm/MagicOnion.Abstractions.wasm",
|
|
@@ -214,7 +214,7 @@ export default {
|
|
|
214
214
|
"path": "wasm/Polly.Core.wasm",
|
|
215
215
|
"mediaType": "application/wasm",
|
|
216
216
|
"bytes": 62741,
|
|
217
|
-
"sha256": "
|
|
217
|
+
"sha256": "757082368c44baa7450a85431f565f29e717b4495d7e29e450a8f08f5f11a8a1"
|
|
218
218
|
},
|
|
219
219
|
{
|
|
220
220
|
"path": "wasm/Polly.wasm",
|
|
@@ -238,7 +238,7 @@ export default {
|
|
|
238
238
|
"path": "wasm/RBush.wasm",
|
|
239
239
|
"mediaType": "application/wasm",
|
|
240
240
|
"bytes": 24341,
|
|
241
|
-
"sha256": "
|
|
241
|
+
"sha256": "841fbdeb5770d8fc9fb6fdcbca9fa796fb6e123d32501e5dfc5cdbcb6dd1ab69"
|
|
242
242
|
},
|
|
243
243
|
{
|
|
244
244
|
"path": "wasm/SkiaSharp.HarfBuzz.wasm",
|
|
@@ -250,7 +250,7 @@ export default {
|
|
|
250
250
|
"path": "wasm/SkiaSharp.wasm",
|
|
251
251
|
"mediaType": "application/wasm",
|
|
252
252
|
"bytes": 102677,
|
|
253
|
-
"sha256": "
|
|
253
|
+
"sha256": "6d16b6aa7d90253782d1d4b1d8b98a1544b5966fd97aaa1a277d5cdb897c36d2"
|
|
254
254
|
},
|
|
255
255
|
{
|
|
256
256
|
"path": "wasm/Stateless.wasm",
|
|
@@ -261,8 +261,8 @@ export default {
|
|
|
261
261
|
{
|
|
262
262
|
"path": "wasm/System.Collections.Concurrent.wasm",
|
|
263
263
|
"mediaType": "application/wasm",
|
|
264
|
-
"bytes":
|
|
265
|
-
"sha256": "
|
|
264
|
+
"bytes": 36117,
|
|
265
|
+
"sha256": "25ea32876f0cf30040f7e050d1ee0036fd89aba213356f3cb3f0ec6a51533882"
|
|
266
266
|
},
|
|
267
267
|
{
|
|
268
268
|
"path": "wasm/System.Collections.Immutable.wasm",
|
|
@@ -316,7 +316,7 @@ export default {
|
|
|
316
316
|
"path": "wasm/System.Console.wasm",
|
|
317
317
|
"mediaType": "application/wasm",
|
|
318
318
|
"bytes": 15125,
|
|
319
|
-
"sha256": "
|
|
319
|
+
"sha256": "c16bce901ec0b1db33de58dc79ed18cc26dd4aa5825e0d72fd51252e399ce080"
|
|
320
320
|
},
|
|
321
321
|
{
|
|
322
322
|
"path": "wasm/System.Diagnostics.StackTrace.wasm",
|
|
@@ -333,14 +333,14 @@ export default {
|
|
|
333
333
|
{
|
|
334
334
|
"path": "wasm/System.IO.Compression.Brotli.wasm",
|
|
335
335
|
"mediaType": "application/wasm",
|
|
336
|
-
"bytes":
|
|
337
|
-
"sha256": "
|
|
336
|
+
"bytes": 6933,
|
|
337
|
+
"sha256": "0dc3c240262506a5eb77eb6202b2a12c37aea973524ba967295c0f3a843d3598"
|
|
338
338
|
},
|
|
339
339
|
{
|
|
340
340
|
"path": "wasm/System.IO.Compression.wasm",
|
|
341
341
|
"mediaType": "application/wasm",
|
|
342
342
|
"bytes": 13077,
|
|
343
|
-
"sha256": "
|
|
343
|
+
"sha256": "b26be90f4d1742f29d4a2c2e73b867842a11cf6364f55b124e0ecc43d4038a96"
|
|
344
344
|
},
|
|
345
345
|
{
|
|
346
346
|
"path": "wasm/System.IO.Pipelines.wasm",
|
|
@@ -370,7 +370,7 @@ export default {
|
|
|
370
370
|
"path": "wasm/System.Net.Http.wasm",
|
|
371
371
|
"mediaType": "application/wasm",
|
|
372
372
|
"bytes": 142101,
|
|
373
|
-
"sha256": "
|
|
373
|
+
"sha256": "011ae07a00434aec7d105fb3a91aeca2dec46c6d50f5f54615d24f7479bfaf9e"
|
|
374
374
|
},
|
|
375
375
|
{
|
|
376
376
|
"path": "wasm/System.Net.Primitives.wasm",
|
|
@@ -393,8 +393,8 @@ export default {
|
|
|
393
393
|
{
|
|
394
394
|
"path": "wasm/System.Private.CoreLib.wasm",
|
|
395
395
|
"mediaType": "application/wasm",
|
|
396
|
-
"bytes":
|
|
397
|
-
"sha256": "
|
|
396
|
+
"bytes": 2173721,
|
|
397
|
+
"sha256": "21a6b449ff6943fe6a380200821c7cc23cfff63a1d550558be4b1d7d8a32428f"
|
|
398
398
|
},
|
|
399
399
|
{
|
|
400
400
|
"path": "wasm/System.Private.Uri.wasm",
|
|
@@ -442,13 +442,13 @@ export default {
|
|
|
442
442
|
"path": "wasm/System.Runtime.InteropServices.wasm",
|
|
443
443
|
"mediaType": "application/wasm",
|
|
444
444
|
"bytes": 8981,
|
|
445
|
-
"sha256": "
|
|
445
|
+
"sha256": "43ef7629ff33e07595b82613e61335c2d0dee3041296d70313c72f55b087f1f5"
|
|
446
446
|
},
|
|
447
447
|
{
|
|
448
448
|
"path": "wasm/System.Runtime.Intrinsics.wasm",
|
|
449
449
|
"mediaType": "application/wasm",
|
|
450
|
-
"bytes":
|
|
451
|
-
"sha256": "
|
|
450
|
+
"bytes": 4885,
|
|
451
|
+
"sha256": "026a2bf529c62658c440610709b367bbc48976d6f64cdfb8bd6e5f48183070c7"
|
|
452
452
|
},
|
|
453
453
|
{
|
|
454
454
|
"path": "wasm/System.Runtime.Loader.wasm",
|
|
@@ -472,7 +472,7 @@ export default {
|
|
|
472
472
|
"path": "wasm/System.Runtime.wasm",
|
|
473
473
|
"mediaType": "application/wasm",
|
|
474
474
|
"bytes": 16661,
|
|
475
|
-
"sha256": "
|
|
475
|
+
"sha256": "7f46ae98bd0bdedd838fdee5dd3ccdc5b392e0af6af776c31aa78074809f6ff0"
|
|
476
476
|
},
|
|
477
477
|
{
|
|
478
478
|
"path": "wasm/System.Security.Cryptography.wasm",
|
|
@@ -514,7 +514,7 @@ export default {
|
|
|
514
514
|
"path": "wasm/System.Threading.Thread.wasm",
|
|
515
515
|
"mediaType": "application/wasm",
|
|
516
516
|
"bytes": 4885,
|
|
517
|
-
"sha256": "
|
|
517
|
+
"sha256": "c6636418c82d4aba16a99e82c6aab3a082c7c485a870d092f089c8b5152a2141"
|
|
518
518
|
},
|
|
519
519
|
{
|
|
520
520
|
"path": "wasm/System.Threading.ThreadPool.wasm",
|
|
@@ -546,17 +546,11 @@ export default {
|
|
|
546
546
|
"bytes": 4885,
|
|
547
547
|
"sha256": "bcf126d8aaa05eaaee1b09f3ef5f224a0983a01f089a0ee21f71733b9edbd118"
|
|
548
548
|
},
|
|
549
|
-
{
|
|
550
|
-
"path": "wasm/ZstdSharp.wasm",
|
|
551
|
-
"mediaType": "application/wasm",
|
|
552
|
-
"bytes": 396565,
|
|
553
|
-
"sha256": "291ddc007c01eb1b2fc3459778270ff0e9d31cf7a16d307327955b5f9be53955"
|
|
554
|
-
},
|
|
555
549
|
{
|
|
556
550
|
"path": "wasm/dotnet.boot.js",
|
|
557
551
|
"mediaType": "text/javascript",
|
|
558
|
-
"bytes":
|
|
559
|
-
"sha256": "
|
|
552
|
+
"bytes": 17861,
|
|
553
|
+
"sha256": "693025847f303f2ec3bf731150746f4a6e9c28a53418d8b5ee4100beb55ece60"
|
|
560
554
|
},
|
|
561
555
|
{
|
|
562
556
|
"path": "wasm/dotnet.js",
|
|
@@ -573,8 +567,8 @@ export default {
|
|
|
573
567
|
{
|
|
574
568
|
"path": "wasm/dotnet.native.wasm",
|
|
575
569
|
"mediaType": "application/wasm",
|
|
576
|
-
"bytes":
|
|
577
|
-
"sha256": "
|
|
570
|
+
"bytes": 6306756,
|
|
571
|
+
"sha256": "4ea6fd68d413a6c88172935ebcd1a39f2c73ae9644d27efb4b1e11727e141617"
|
|
578
572
|
},
|
|
579
573
|
{
|
|
580
574
|
"path": "wasm/dotnet.runtime.js",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"packageName": "@drawmotive/textgraph",
|
|
4
|
-
"packageVersion": "0.2.2-alpha.
|
|
4
|
+
"packageVersion": "0.2.2-alpha.5",
|
|
5
5
|
"abiVersion": "1.0.0",
|
|
6
6
|
"protocolVersion": 1,
|
|
7
7
|
"runtimeModule": "wasm/dotnet.js",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"targetFramework": "net10.0",
|
|
29
29
|
"privateSource": {
|
|
30
|
-
"commit": "
|
|
30
|
+
"commit": "f7627ef2646772503fb3d8165ee312d4f411566c",
|
|
31
31
|
"project": "DrawMotive.TextGraph.Bridge"
|
|
32
32
|
},
|
|
33
33
|
"entryAssembly": "wasm/DrawMotive.TextGraph.Bridge.wasm",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"path": "wasm/DrawMotive.TextGraph.Bridge.wasm",
|
|
53
53
|
"mediaType": "application/wasm",
|
|
54
54
|
"bytes": 87317,
|
|
55
|
-
"sha256": "
|
|
55
|
+
"sha256": "14fa935851250c38939003e847936b5720e95032b8853123e676842f0e58db57"
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
"path": "wasm/ExCSS.wasm",
|
|
@@ -75,20 +75,20 @@
|
|
|
75
75
|
{
|
|
76
76
|
"path": "wasm/Graphics.Core.wasm",
|
|
77
77
|
"mediaType": "application/wasm",
|
|
78
|
-
"bytes":
|
|
79
|
-
"sha256": "
|
|
78
|
+
"bytes": 4040985,
|
|
79
|
+
"sha256": "4640576049a4a9a4d2e6ee28683f4b802196f4178ce98c7acbb15a7f46a70340"
|
|
80
80
|
},
|
|
81
81
|
{
|
|
82
82
|
"path": "wasm/Grpc.Core.Api.wasm",
|
|
83
83
|
"mediaType": "application/wasm",
|
|
84
84
|
"bytes": 7957,
|
|
85
|
-
"sha256": "
|
|
85
|
+
"sha256": "5fa5dee84b136e0cf6c1609f9bf8d61836c79622d64f2678feaf2dab49845ee0"
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
88
|
"path": "wasm/HarfBuzzSharp.wasm",
|
|
89
89
|
"mediaType": "application/wasm",
|
|
90
90
|
"bytes": 30485,
|
|
91
|
-
"sha256": "
|
|
91
|
+
"sha256": "00d464ae97a97bebdbb33e8f92382359b8245b65baf06ee2606f6053bab1fb3b"
|
|
92
92
|
},
|
|
93
93
|
{
|
|
94
94
|
"path": "wasm/MagicOnion.Abstractions.wasm",
|
|
@@ -214,7 +214,7 @@
|
|
|
214
214
|
"path": "wasm/Polly.Core.wasm",
|
|
215
215
|
"mediaType": "application/wasm",
|
|
216
216
|
"bytes": 62741,
|
|
217
|
-
"sha256": "
|
|
217
|
+
"sha256": "757082368c44baa7450a85431f565f29e717b4495d7e29e450a8f08f5f11a8a1"
|
|
218
218
|
},
|
|
219
219
|
{
|
|
220
220
|
"path": "wasm/Polly.wasm",
|
|
@@ -238,7 +238,7 @@
|
|
|
238
238
|
"path": "wasm/RBush.wasm",
|
|
239
239
|
"mediaType": "application/wasm",
|
|
240
240
|
"bytes": 24341,
|
|
241
|
-
"sha256": "
|
|
241
|
+
"sha256": "841fbdeb5770d8fc9fb6fdcbca9fa796fb6e123d32501e5dfc5cdbcb6dd1ab69"
|
|
242
242
|
},
|
|
243
243
|
{
|
|
244
244
|
"path": "wasm/SkiaSharp.HarfBuzz.wasm",
|
|
@@ -250,7 +250,7 @@
|
|
|
250
250
|
"path": "wasm/SkiaSharp.wasm",
|
|
251
251
|
"mediaType": "application/wasm",
|
|
252
252
|
"bytes": 102677,
|
|
253
|
-
"sha256": "
|
|
253
|
+
"sha256": "6d16b6aa7d90253782d1d4b1d8b98a1544b5966fd97aaa1a277d5cdb897c36d2"
|
|
254
254
|
},
|
|
255
255
|
{
|
|
256
256
|
"path": "wasm/Stateless.wasm",
|
|
@@ -261,8 +261,8 @@
|
|
|
261
261
|
{
|
|
262
262
|
"path": "wasm/System.Collections.Concurrent.wasm",
|
|
263
263
|
"mediaType": "application/wasm",
|
|
264
|
-
"bytes":
|
|
265
|
-
"sha256": "
|
|
264
|
+
"bytes": 36117,
|
|
265
|
+
"sha256": "25ea32876f0cf30040f7e050d1ee0036fd89aba213356f3cb3f0ec6a51533882"
|
|
266
266
|
},
|
|
267
267
|
{
|
|
268
268
|
"path": "wasm/System.Collections.Immutable.wasm",
|
|
@@ -316,7 +316,7 @@
|
|
|
316
316
|
"path": "wasm/System.Console.wasm",
|
|
317
317
|
"mediaType": "application/wasm",
|
|
318
318
|
"bytes": 15125,
|
|
319
|
-
"sha256": "
|
|
319
|
+
"sha256": "c16bce901ec0b1db33de58dc79ed18cc26dd4aa5825e0d72fd51252e399ce080"
|
|
320
320
|
},
|
|
321
321
|
{
|
|
322
322
|
"path": "wasm/System.Diagnostics.StackTrace.wasm",
|
|
@@ -333,14 +333,14 @@
|
|
|
333
333
|
{
|
|
334
334
|
"path": "wasm/System.IO.Compression.Brotli.wasm",
|
|
335
335
|
"mediaType": "application/wasm",
|
|
336
|
-
"bytes":
|
|
337
|
-
"sha256": "
|
|
336
|
+
"bytes": 6933,
|
|
337
|
+
"sha256": "0dc3c240262506a5eb77eb6202b2a12c37aea973524ba967295c0f3a843d3598"
|
|
338
338
|
},
|
|
339
339
|
{
|
|
340
340
|
"path": "wasm/System.IO.Compression.wasm",
|
|
341
341
|
"mediaType": "application/wasm",
|
|
342
342
|
"bytes": 13077,
|
|
343
|
-
"sha256": "
|
|
343
|
+
"sha256": "b26be90f4d1742f29d4a2c2e73b867842a11cf6364f55b124e0ecc43d4038a96"
|
|
344
344
|
},
|
|
345
345
|
{
|
|
346
346
|
"path": "wasm/System.IO.Pipelines.wasm",
|
|
@@ -370,7 +370,7 @@
|
|
|
370
370
|
"path": "wasm/System.Net.Http.wasm",
|
|
371
371
|
"mediaType": "application/wasm",
|
|
372
372
|
"bytes": 142101,
|
|
373
|
-
"sha256": "
|
|
373
|
+
"sha256": "011ae07a00434aec7d105fb3a91aeca2dec46c6d50f5f54615d24f7479bfaf9e"
|
|
374
374
|
},
|
|
375
375
|
{
|
|
376
376
|
"path": "wasm/System.Net.Primitives.wasm",
|
|
@@ -393,8 +393,8 @@
|
|
|
393
393
|
{
|
|
394
394
|
"path": "wasm/System.Private.CoreLib.wasm",
|
|
395
395
|
"mediaType": "application/wasm",
|
|
396
|
-
"bytes":
|
|
397
|
-
"sha256": "
|
|
396
|
+
"bytes": 2173721,
|
|
397
|
+
"sha256": "21a6b449ff6943fe6a380200821c7cc23cfff63a1d550558be4b1d7d8a32428f"
|
|
398
398
|
},
|
|
399
399
|
{
|
|
400
400
|
"path": "wasm/System.Private.Uri.wasm",
|
|
@@ -442,13 +442,13 @@
|
|
|
442
442
|
"path": "wasm/System.Runtime.InteropServices.wasm",
|
|
443
443
|
"mediaType": "application/wasm",
|
|
444
444
|
"bytes": 8981,
|
|
445
|
-
"sha256": "
|
|
445
|
+
"sha256": "43ef7629ff33e07595b82613e61335c2d0dee3041296d70313c72f55b087f1f5"
|
|
446
446
|
},
|
|
447
447
|
{
|
|
448
448
|
"path": "wasm/System.Runtime.Intrinsics.wasm",
|
|
449
449
|
"mediaType": "application/wasm",
|
|
450
|
-
"bytes":
|
|
451
|
-
"sha256": "
|
|
450
|
+
"bytes": 4885,
|
|
451
|
+
"sha256": "026a2bf529c62658c440610709b367bbc48976d6f64cdfb8bd6e5f48183070c7"
|
|
452
452
|
},
|
|
453
453
|
{
|
|
454
454
|
"path": "wasm/System.Runtime.Loader.wasm",
|
|
@@ -472,7 +472,7 @@
|
|
|
472
472
|
"path": "wasm/System.Runtime.wasm",
|
|
473
473
|
"mediaType": "application/wasm",
|
|
474
474
|
"bytes": 16661,
|
|
475
|
-
"sha256": "
|
|
475
|
+
"sha256": "7f46ae98bd0bdedd838fdee5dd3ccdc5b392e0af6af776c31aa78074809f6ff0"
|
|
476
476
|
},
|
|
477
477
|
{
|
|
478
478
|
"path": "wasm/System.Security.Cryptography.wasm",
|
|
@@ -514,7 +514,7 @@
|
|
|
514
514
|
"path": "wasm/System.Threading.Thread.wasm",
|
|
515
515
|
"mediaType": "application/wasm",
|
|
516
516
|
"bytes": 4885,
|
|
517
|
-
"sha256": "
|
|
517
|
+
"sha256": "c6636418c82d4aba16a99e82c6aab3a082c7c485a870d092f089c8b5152a2141"
|
|
518
518
|
},
|
|
519
519
|
{
|
|
520
520
|
"path": "wasm/System.Threading.ThreadPool.wasm",
|
|
@@ -546,17 +546,11 @@
|
|
|
546
546
|
"bytes": 4885,
|
|
547
547
|
"sha256": "bcf126d8aaa05eaaee1b09f3ef5f224a0983a01f089a0ee21f71733b9edbd118"
|
|
548
548
|
},
|
|
549
|
-
{
|
|
550
|
-
"path": "wasm/ZstdSharp.wasm",
|
|
551
|
-
"mediaType": "application/wasm",
|
|
552
|
-
"bytes": 396565,
|
|
553
|
-
"sha256": "291ddc007c01eb1b2fc3459778270ff0e9d31cf7a16d307327955b5f9be53955"
|
|
554
|
-
},
|
|
555
549
|
{
|
|
556
550
|
"path": "wasm/dotnet.boot.js",
|
|
557
551
|
"mediaType": "text/javascript",
|
|
558
|
-
"bytes":
|
|
559
|
-
"sha256": "
|
|
552
|
+
"bytes": 17861,
|
|
553
|
+
"sha256": "693025847f303f2ec3bf731150746f4a6e9c28a53418d8b5ee4100beb55ece60"
|
|
560
554
|
},
|
|
561
555
|
{
|
|
562
556
|
"path": "wasm/dotnet.js",
|
|
@@ -573,8 +567,8 @@
|
|
|
573
567
|
{
|
|
574
568
|
"path": "wasm/dotnet.native.wasm",
|
|
575
569
|
"mediaType": "application/wasm",
|
|
576
|
-
"bytes":
|
|
577
|
-
"sha256": "
|
|
570
|
+
"bytes": 6306756,
|
|
571
|
+
"sha256": "4ea6fd68d413a6c88172935ebcd1a39f2c73ae9644d27efb4b1e11727e141617"
|
|
578
572
|
},
|
|
579
573
|
{
|
|
580
574
|
"path": "wasm/dotnet.runtime.js",
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -65,7 +65,7 @@ export interface TextGraphRuntime {
|
|
|
65
65
|
readonly abiVersion: string;
|
|
66
66
|
readonly capabilities?: readonly string[];
|
|
67
67
|
validate?(source: string): string | Promise<string>;
|
|
68
|
-
execute?(requestJson: string, context?: { signal?: AbortSignal }): string | Promise<string>;
|
|
68
|
+
execute?(requestJson: string, context?: { signal?: AbortSignal; onPreview?(responseJson: string): void }): string | Promise<string>;
|
|
69
69
|
dispose?(): void | Promise<void>;
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -127,6 +127,8 @@ export interface TextGraphLanguagePack {
|
|
|
127
127
|
export type TextGraphPngEncoding = 'bytes' | 'base64';
|
|
128
128
|
|
|
129
129
|
export interface TextGraphRenderPngOptions<Encoding extends TextGraphPngEncoding = TextGraphPngEncoding> {
|
|
130
|
+
/** Receives a provisional image before optional fonts load; the promise returns the final image. */
|
|
131
|
+
onPreview?(result: Extract<TextGraphRenderPngResult<Encoding extends 'base64' ? string : Uint8Array>, { success: true }>): void;
|
|
130
132
|
/** Optional language hint for ambiguous Han text, e.g. ja or zh-CN. */
|
|
131
133
|
language?: string;
|
|
132
134
|
/** Defaults to bytes. Base64 contains no data-URL prefix. */
|
|
@@ -149,6 +151,8 @@ export type TextGraphRenderPngResult<Png extends Uint8Array | string = Uint8Arra
|
|
|
149
151
|
|
|
150
152
|
/** Vector export uses logical diagram units and requires textgraph-render-svg-v1. */
|
|
151
153
|
export interface TextGraphRenderSvgOptions {
|
|
154
|
+
/** Receives a provisional vector before optional fonts load; the promise returns the final vector. */
|
|
155
|
+
onPreview?(result: Extract<TextGraphRenderSvgResult, { success: true }>): void;
|
|
152
156
|
/** Optional language hint for ambiguous Han text, e.g. ja or zh-CN. */
|
|
153
157
|
language?: string;
|
|
154
158
|
/** Non-negative padding in diagram units. Defaults to 10. */
|
package/src/index.js
CHANGED
|
@@ -38,13 +38,19 @@ export async function initializeTextGraph(options = {}) {
|
|
|
38
38
|
// Every export format uses the same operation queue and resource-preparing executor.
|
|
39
39
|
const render = async (source, callOptions, normalize, decode, capability, format) => {
|
|
40
40
|
if (instance.state !== 'ready') throw new DrawMotiveError('INSTANCE_DISPOSED', 'The runtime instance is disposing or disposed');
|
|
41
|
-
const { request, encoding, signal } = normalize(source, callOptions);
|
|
41
|
+
const { request, encoding, signal, onPreview } = normalize(source, callOptions);
|
|
42
42
|
throwIfAborted(signal);
|
|
43
43
|
if (typeof runtime.execute !== 'function' || !capabilities.includes(capability)) throw new DrawMotiveError('UNSUPPORTED_CAPABILITY', `Runtime does not support ${format} rendering`);
|
|
44
44
|
return instance.mutate(async () => {
|
|
45
45
|
throwIfAborted(signal);
|
|
46
46
|
let response;
|
|
47
|
-
|
|
47
|
+
const context = { signal };
|
|
48
|
+
if (onPreview) context.onPreview = json => {
|
|
49
|
+
throwIfAborted(signal);
|
|
50
|
+
const preview = decode(json, encoding);
|
|
51
|
+
if (preview.success) onPreview(preview);
|
|
52
|
+
};
|
|
53
|
+
try { response = await runtime.execute(JSON.stringify(request), context); }
|
|
48
54
|
catch (cause) {
|
|
49
55
|
throwIfAborted(signal);
|
|
50
56
|
if (cause instanceof DrawMotiveError || cause?.name === 'AbortError') throw cause;
|
package/src/react/index.d.ts
CHANGED
|
@@ -12,8 +12,10 @@ export interface TextGraphProps extends Omit<ImgHTMLAttributes<HTMLImageElement>
|
|
|
12
12
|
source: string;
|
|
13
13
|
alt?: string;
|
|
14
14
|
/** Defaults to scale 1; the image displays at logical size. */
|
|
15
|
-
renderOptions?: Omit<TextGraphRenderPngOptions, 'encoding' | 'signal'>;
|
|
15
|
+
renderOptions?: Omit<TextGraphRenderPngOptions, 'encoding' | 'signal' | 'onPreview'>;
|
|
16
16
|
loading?: ReactNode;
|
|
17
|
+
/** Shown beside the provisional image until optional fonts finish loading. */
|
|
18
|
+
fontsLoading?: ReactNode;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export declare function TextGraphProvider(props: TextGraphProviderProps): ReactElement;
|
package/src/react/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { createContext, createElement, useContext, useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { Fragment, createContext, createElement, useContext, useEffect, useMemo, useState } from 'react';
|
|
4
4
|
import { createRuntimeResource } from './runtime.js';
|
|
5
5
|
|
|
6
6
|
const RuntimeContext = createContext(null);
|
|
@@ -15,12 +15,12 @@ export function TextGraphProvider({ options = defaultOptions, children }) {
|
|
|
15
15
|
|
|
16
16
|
/** Renders DSL as an image; only the latest committed input may publish a result. */
|
|
17
17
|
export function TextGraph({ source, alt = 'TextGraph diagram', renderOptions = defaultOptions,
|
|
18
|
-
loading = 'Rendering diagram…', style, ...imageProps }) {
|
|
18
|
+
loading = 'Rendering diagram…', fontsLoading = 'Fonts are still loading…', style, ...imageProps }) {
|
|
19
19
|
const shared = useContext(RuntimeContext);
|
|
20
20
|
const resource = useMemo(() => shared ?? createRuntimeResource(defaultOptions), [shared]);
|
|
21
|
-
const { scale = 1, padding, maxWidth } = renderOptions;
|
|
22
|
-
const request = useMemo(() => ({ source, scale, padding, maxWidth, resource }),
|
|
23
|
-
[source, scale, padding, maxWidth, resource]);
|
|
21
|
+
const { scale = 1, padding, maxWidth, language } = renderOptions;
|
|
22
|
+
const request = useMemo(() => ({ source, scale, padding, maxWidth, language, resource }),
|
|
23
|
+
[source, scale, padding, maxWidth, language, resource]);
|
|
24
24
|
const [state, setState] = useState(null);
|
|
25
25
|
|
|
26
26
|
useEffect(() => {
|
|
@@ -30,13 +30,16 @@ export function TextGraph({ source, alt = 'TextGraph diagram', renderOptions = d
|
|
|
30
30
|
try {
|
|
31
31
|
const runtime = await resource.get();
|
|
32
32
|
if (controller.signal.aborted) return;
|
|
33
|
-
const result = await runtime.renderPng(source, { scale, padding, maxWidth, encoding: 'base64', signal: controller.signal
|
|
33
|
+
const result = await runtime.renderPng(source, { scale, padding, maxWidth, language, encoding: 'base64', signal: controller.signal,
|
|
34
|
+
onPreview: result => { if (!controller.signal.aborted) setState({ request, result, fontsLoading: true }); },
|
|
35
|
+
});
|
|
34
36
|
if (!controller.signal.aborted) setState({ request, result });
|
|
35
37
|
} catch (error) {
|
|
36
38
|
if (!controller.signal.aborted) {
|
|
37
39
|
// Host loaders may reject arbitrary values, including null. Keep the
|
|
38
40
|
// failure branch explicit so reporting an error cannot break React render.
|
|
39
|
-
setState({ request,
|
|
41
|
+
setState(previous => ({ request, result: previous?.request === request ? previous.result : undefined,
|
|
42
|
+
error: error instanceof Error ? error : new Error(String(error ?? 'Could not render diagram')) }));
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
})();
|
|
@@ -46,14 +49,17 @@ export function TextGraph({ source, alt = 'TextGraph diagram', renderOptions = d
|
|
|
46
49
|
// State is keyed by the whole request so old images disappear immediately,
|
|
47
50
|
// even before passive effects clean up the previous asynchronous render.
|
|
48
51
|
if (state?.request !== request) return createElement('span', { role: 'status' }, loading);
|
|
49
|
-
if (
|
|
52
|
+
if (!state.result?.success) {
|
|
50
53
|
const message = state.error?.message ?? state.result.diagnostics.map(item => item.message).join('\n');
|
|
51
54
|
return createElement('span', { role: 'alert' }, message || 'Could not render diagram');
|
|
52
55
|
}
|
|
53
56
|
const result = state.result;
|
|
54
57
|
// Density improves sharpness without enlarging the diagram. Native dimensions
|
|
55
58
|
// also preserve logical size when maxWidth lowers the actual render density.
|
|
56
|
-
|
|
59
|
+
const image = createElement('img', { width: result.displayWidth ?? result.width / scale, height: result.displayHeight ?? result.height / scale, ...imageProps,
|
|
57
60
|
style: { maxWidth: '100%', ...(imageProps.height == null ? { height: 'auto' } : {}), ...style },
|
|
58
61
|
src: `data:image/png;base64,${result.png}`, alt });
|
|
62
|
+
if (state.error) return createElement(Fragment, null, image, createElement('span', { role: 'alert' }, state.error.message));
|
|
63
|
+
if (state.fontsLoading) return createElement(Fragment, null, image, createElement('span', { role: 'status' }, fontsLoading));
|
|
64
|
+
return image;
|
|
59
65
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readVerifiedAsset } from './integrity.js';
|
|
2
|
+
import { throwIfAborted } from './lifecycle.js';
|
|
3
|
+
|
|
4
|
+
const cacheName = 'textgraph-fonts-v1';
|
|
5
|
+
|
|
6
|
+
/** HTTP fonts are immutable by content hash. Cache verified bytes in browsers
|
|
7
|
+
* and Workers without requiring host headers or a service worker; local files
|
|
8
|
+
* stay authoritative. Storage is optional and cannot turn valid I/O into failure. */
|
|
9
|
+
export async function readVerifiedFontAsset(item, options, readAsset) {
|
|
10
|
+
if (!['http:', 'https:'].includes(item.url?.protocol)) return readVerifiedAsset(item, options, readAsset);
|
|
11
|
+
const url = new URL(item.url);
|
|
12
|
+
url.searchParams.set('v', item.asset.sha256);
|
|
13
|
+
const versioned = { ...item, url };
|
|
14
|
+
let cache;
|
|
15
|
+
try { cache = await globalThis.caches?.open(cacheName); } catch { /* Private contexts may deny storage. */ }
|
|
16
|
+
throwIfAborted(options.signal);
|
|
17
|
+
let cached;
|
|
18
|
+
try { cached = await cache?.match(url.href); } catch { /* Read from the configured source instead. */ }
|
|
19
|
+
throwIfAborted(options.signal);
|
|
20
|
+
if (cached) {
|
|
21
|
+
try {
|
|
22
|
+
const bytes = await readVerifiedAsset(versioned, options, async () => cached);
|
|
23
|
+
throwIfAborted(options.signal);
|
|
24
|
+
return bytes;
|
|
25
|
+
} catch {
|
|
26
|
+
throwIfAborted(options.signal);
|
|
27
|
+
// A damaged entry grants no authority: evict and verify the original source.
|
|
28
|
+
try { await cache.delete(url.href); } catch { /* A new download can still succeed. */ }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const bytes = await readVerifiedAsset(versioned, options, readAsset);
|
|
32
|
+
throwIfAborted(options.signal);
|
|
33
|
+
try { await cache?.put(url.href, new Response(bytes, { headers: { 'Content-Type': 'font/ttf' } })); } catch { /* Quota and storage failures are non-fatal. */ }
|
|
34
|
+
throwIfAborted(options.signal);
|
|
35
|
+
return bytes;
|
|
36
|
+
}
|
|
@@ -2,7 +2,7 @@ import { DrawMotiveError } from './errors.js';
|
|
|
2
2
|
import { normalizeFontMetadata } from './language-packs.js';
|
|
3
3
|
import { decodeDiagnostics } from './validation.js';
|
|
4
4
|
import { decodeConfiguration } from './rendering.js';
|
|
5
|
-
import {
|
|
5
|
+
import { readVerifiedFontAsset } from './font-cache.js';
|
|
6
6
|
import { throwIfAborted } from './lifecycle.js';
|
|
7
7
|
|
|
8
8
|
export const fontCapability = 'textgraph-fonts-v1';
|
|
@@ -102,6 +102,7 @@ export function createFontPreparation({ options, readAsset, execute }) {
|
|
|
102
102
|
const loading = { ...options, signal: context.signal };
|
|
103
103
|
const attempted = new Set();
|
|
104
104
|
let consumed = false;
|
|
105
|
+
let previewed = false;
|
|
105
106
|
try {
|
|
106
107
|
while (true) {
|
|
107
108
|
throwIfAborted(context.signal);
|
|
@@ -110,6 +111,14 @@ export function createFontPreparation({ options, readAsset, execute }) {
|
|
|
110
111
|
if (!prepared.success) return JSON.stringify({ protocolVersion: 1, success: false, diagnostics: prepared.diagnostics });
|
|
111
112
|
const missing = prepared.fontRuns.filter(run => run.missing && run.language);
|
|
112
113
|
if (!missing.length) break;
|
|
114
|
+
// Native can render with its installed fallback fonts. Publish that image
|
|
115
|
+
// before optional I/O; final measurement still runs after installation.
|
|
116
|
+
if (context.onPreview && !previewed) {
|
|
117
|
+
const preview = await execute(request);
|
|
118
|
+
throwIfAborted(context.signal);
|
|
119
|
+
context.onPreview(preview);
|
|
120
|
+
previewed = true;
|
|
121
|
+
}
|
|
113
122
|
const selected = new Set();
|
|
114
123
|
let available = explicit;
|
|
115
124
|
for (const run of missing) {
|
|
@@ -126,7 +135,7 @@ export function createFontPreparation({ options, readAsset, execute }) {
|
|
|
126
135
|
for (const font of selected) {
|
|
127
136
|
attempted.add(font.family);
|
|
128
137
|
throwIfAborted(context.signal);
|
|
129
|
-
const bytes = await
|
|
138
|
+
const bytes = await readVerifiedFontAsset({ asset: { path: `font:${font.family}`, bytes: font.bytes, sha256: font.sha256, mediaType: 'font/ttf' }, url: font.source }, loading,
|
|
130
139
|
font.source instanceof Uint8Array ? async () => new Response(font.source) : readAsset);
|
|
131
140
|
throwIfAborted(context.signal);
|
|
132
141
|
fonts.push({ family: font.family, data: fontBase64(bytes), languages: font.languages });
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRuntimeAssetPlan } from './shared.js';
|
|
2
2
|
import { readVerifiedAsset } from './integrity.js';
|
|
3
|
+
import { readVerifiedFontAsset } from './font-cache.js';
|
|
3
4
|
import { decodeConfiguration } from './rendering.js';
|
|
4
5
|
import { throwIfAborted } from './lifecycle.js';
|
|
5
6
|
import { createFontPreparation, fontBase64 as base64, fontCapability } from './font-resources.js';
|
|
@@ -18,9 +19,10 @@ export function createRenderingExecutor({ manifest, options, readAsset, execute
|
|
|
18
19
|
const selected = new Set([manifest.rendering.theme, ...manifest.rendering.fonts.map(font => font.asset)]);
|
|
19
20
|
const plan = createRuntimeAssetPlan({ manifest: { assets: manifest.assets.filter(asset => selected.has(asset.path)) }, moduleUrl: import.meta.url, resolveAsset: options.resolveAsset });
|
|
20
21
|
const byPath = new Map(plan.map(item => [item.asset.path, item]));
|
|
22
|
+
const fontPaths = new Set(manifest.rendering.fonts.map(font => font.asset));
|
|
21
23
|
const load = async path => {
|
|
22
24
|
throwIfAborted(context.signal);
|
|
23
|
-
const bytes = await readVerifiedAsset(byPath.get(path), loadingOptions, readAsset);
|
|
25
|
+
const bytes = await (fontPaths.has(path) ? readVerifiedFontAsset : readVerifiedAsset)(byPath.get(path), loadingOptions, readAsset);
|
|
24
26
|
throwIfAborted(context.signal);
|
|
25
27
|
return bytes;
|
|
26
28
|
};
|
|
@@ -34,7 +36,7 @@ export function createRenderingExecutor({ manifest, options, readAsset, execute
|
|
|
34
36
|
throwIfAborted(context.signal);
|
|
35
37
|
const item = { asset: { path: `font:${font.family}`, mediaType: 'font/ttf', bytes: font.bytes, sha256: font.sha256 }, url: font.source };
|
|
36
38
|
const bytes = font.coverage
|
|
37
|
-
? await
|
|
39
|
+
? await readVerifiedFontAsset(item, loadingOptions, font.source instanceof Uint8Array ? async () => new Response(font.source) : readAsset)
|
|
38
40
|
: font.source instanceof Uint8Array ? font.source : new Uint8Array(await (await readAsset(item, loadingOptions)).arrayBuffer());
|
|
39
41
|
throwIfAborted(context.signal);
|
|
40
42
|
fonts.push({ family: font.family, data: base64(bytes), ...(font.languages ? { languages: font.languages } : {}) });
|
package/src/runtime/rendering.js
CHANGED
|
@@ -7,30 +7,31 @@ const invalid = message => { throw new DrawMotiveError('INVALID_ARGUMENT', messa
|
|
|
7
7
|
/** Both export formats share source, logical padding, language and cancellation semantics. */
|
|
8
8
|
function normalizeCommonOptions(source, options) {
|
|
9
9
|
if (typeof source !== 'string' || !options || typeof options !== 'object' || Array.isArray(options)) invalid('Rendering requires a source string and options object');
|
|
10
|
-
const { padding = 10, signal, language } = options;
|
|
10
|
+
const { padding = 10, signal, language, onPreview } = options;
|
|
11
|
+
if (onPreview !== undefined && typeof onPreview !== 'function') invalid('onPreview must be a function');
|
|
11
12
|
if (language !== undefined && (typeof language !== 'string' || !/^[a-z]{2,8}(?:-[A-Za-z0-9]+)*$/.test(language))) invalid('language must be a language tag');
|
|
12
13
|
// The native geometry contract uses float32; reject values lost at that boundary.
|
|
13
14
|
if (!Number.isFinite(padding) || !Number.isFinite(Math.fround(padding)) || padding < 0) invalid('padding must be non-negative and representable as a finite float32');
|
|
14
15
|
validateSignal(signal);
|
|
15
|
-
return { padding, signal, request: { protocolVersion: 1, operation: 'render', source, ...(language === undefined ? {} : { language }) } };
|
|
16
|
+
return { padding, signal, onPreview, request: { protocolVersion: 1, operation: 'render', source, ...(language === undefined ? {} : { language }) } };
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
/** Normalize export settings before queuing; native receives only the render contract. */
|
|
19
20
|
export function normalizeRenderOptions(source, options) {
|
|
20
|
-
const { padding, signal, request } = normalizeCommonOptions(source, options);
|
|
21
|
+
const { padding, signal, onPreview, request } = normalizeCommonOptions(source, options);
|
|
21
22
|
const { encoding = 'bytes', scale = 1, maxWidth } = options;
|
|
22
23
|
if (!['bytes', 'base64'].includes(encoding)) invalid('encoding must be bytes or base64');
|
|
23
24
|
// The native geometry contract uses float32; reject values lost at that boundary.
|
|
24
25
|
if (!Number.isFinite(scale) || !Number.isFinite(Math.fround(scale)) || Math.fround(scale) <= 0) invalid('scale must be positive and representable as a finite float32');
|
|
25
26
|
if (maxWidth !== undefined && (!Number.isSafeInteger(maxWidth) || maxWidth <= 0)) invalid('maxWidth must be a positive integer');
|
|
26
|
-
return { encoding, signal, request: { ...request, export: { format: 'png', scale, padding, ...(maxWidth === undefined ? {} : { maxWidth }) } } };
|
|
27
|
+
return { encoding, signal, onPreview, request: { ...request, export: { format: 'png', scale, padding, ...(maxWidth === undefined ? {} : { maxWidth }) } } };
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
/** SVG accepts only logical export settings; PNG density and encoding have no vector meaning. */
|
|
30
31
|
export function normalizeSvgOptions(source, options) {
|
|
31
|
-
const { padding, signal, request } = normalizeCommonOptions(source, options);
|
|
32
|
-
if (Reflect.ownKeys(options).some(key => !['padding', 'language', 'signal'].includes(key))) invalid('Unknown SVG rendering option');
|
|
33
|
-
return { signal, request: { ...request, export: { format: 'svg', padding } } };
|
|
32
|
+
const { padding, signal, onPreview, request } = normalizeCommonOptions(source, options);
|
|
33
|
+
if (Reflect.ownKeys(options).some(key => !['padding', 'language', 'signal', 'onPreview'].includes(key))) invalid('Unknown SVG rendering option');
|
|
34
|
+
return { signal, onPreview, request: { ...request, export: { format: 'svg', padding } } };
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
/** Accept canonical base64 only; platform atob implementations otherwise tolerate corruption. */
|
|
Binary file
|