@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/authoring.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RendererAuthoringDocument,
|
|
3
|
+
RendererWorkerClient,
|
|
4
|
+
type RendererWorkerClientOptions,
|
|
5
|
+
} from "./worker-client.js";
|
|
6
|
+
import type { AuthoringCheckpoint } from "./types/authoring.js";
|
|
7
|
+
|
|
8
|
+
/** Worker-backed authoring runtime for game-compatible custom-profile documents. */
|
|
9
|
+
export class BrowserAuthoringClient {
|
|
10
|
+
private destroyed = false;
|
|
11
|
+
|
|
12
|
+
private constructor(private readonly worker: RendererWorkerClient) {}
|
|
13
|
+
|
|
14
|
+
static async create(options: RendererWorkerClientOptions = {}): Promise<BrowserAuthoringClient> {
|
|
15
|
+
return new BrowserAuthoringClient(await RendererWorkerClient.create(options));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async createBlank(): Promise<RendererAuthoringDocument> {
|
|
19
|
+
this.assertAlive();
|
|
20
|
+
return this.worker.createAuthoringDocument();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async importProfile(profile: unknown): Promise<RendererAuthoringDocument> {
|
|
24
|
+
this.assertAlive();
|
|
25
|
+
return this.worker.createAuthoringDocument(profile);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async restoreCheckpoint(checkpoint: AuthoringCheckpoint): Promise<RendererAuthoringDocument> {
|
|
29
|
+
this.assertAlive();
|
|
30
|
+
return this.worker.restoreAuthoringDocument(checkpoint);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
destroy(): void {
|
|
34
|
+
if (this.destroyed) return;
|
|
35
|
+
this.destroyed = true;
|
|
36
|
+
this.worker.terminate();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private assertAlive(): void {
|
|
40
|
+
if (this.destroyed) throw new Error("Browser authoring client is destroyed");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
use std::cell::RefCell;
|
|
2
|
+
use std::collections::BTreeMap;
|
|
3
|
+
|
|
4
|
+
use allium_renderer_core::authoring_document::GameProfileDocument;
|
|
5
|
+
use allium_renderer_core::authoring_session::{
|
|
6
|
+
AuthoringCommand, AuthoringElementId, AuthoringSession,
|
|
7
|
+
};
|
|
8
|
+
use serde::{Deserialize, Serialize};
|
|
9
|
+
use serde_json::Value;
|
|
10
|
+
|
|
11
|
+
thread_local! {
|
|
12
|
+
static SESSIONS: RefCell<SessionTable> = RefCell::new(SessionTable::default());
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#[derive(Default)]
|
|
16
|
+
struct SessionTable {
|
|
17
|
+
next_handle: u32,
|
|
18
|
+
sessions: BTreeMap<u32, AuthoringSession>,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
#[derive(Serialize)]
|
|
22
|
+
#[serde(rename_all = "camelCase")]
|
|
23
|
+
struct CreateResponse {
|
|
24
|
+
handle: u32,
|
|
25
|
+
revision: u64,
|
|
26
|
+
document: Value,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
pub fn create_blank() -> Result<String, String> {
|
|
30
|
+
create(GameProfileDocument::blank())
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pub fn import_profile(input: &str) -> Result<String, String> {
|
|
34
|
+
let value = serde_json::from_str(input)
|
|
35
|
+
.map_err(|error| format!("parse game profile failed: {error}"))?;
|
|
36
|
+
let document =
|
|
37
|
+
GameProfileDocument::from_profile_value(value).map_err(|error| error.to_string())?;
|
|
38
|
+
create(document)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
pub fn restore_checkpoint(input: &str) -> Result<String, String> {
|
|
42
|
+
let value = serde_json::from_str(input)
|
|
43
|
+
.map_err(|error| format!("parse authoring checkpoint failed: {error}"))?;
|
|
44
|
+
let session =
|
|
45
|
+
AuthoringSession::from_checkpoint_value(value).map_err(|error| error.to_string())?;
|
|
46
|
+
create_session(session)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fn create(document: GameProfileDocument) -> Result<String, String> {
|
|
50
|
+
create_session(AuthoringSession::new(document))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn create_session(session: AuthoringSession) -> Result<String, String> {
|
|
54
|
+
SESSIONS.with(|table| {
|
|
55
|
+
let mut table = table.borrow_mut();
|
|
56
|
+
table.next_handle = table.next_handle.wrapping_add(1).max(1);
|
|
57
|
+
let handle = table.next_handle;
|
|
58
|
+
let response = CreateResponse {
|
|
59
|
+
handle,
|
|
60
|
+
revision: session.revision(),
|
|
61
|
+
document: session.export_value(),
|
|
62
|
+
};
|
|
63
|
+
table.sessions.insert(handle, session);
|
|
64
|
+
serde_json::to_string(&response).map_err(|error| error.to_string())
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
pub fn apply(handle: u32, input: &str) -> Result<String, String> {
|
|
69
|
+
let command: AuthoringCommand = serde_json::from_str(input)
|
|
70
|
+
.map_err(|error| format!("parse authoring command failed: {error}"))?;
|
|
71
|
+
with_session(handle, |session| {
|
|
72
|
+
let delta = session.apply(command).map_err(|error| error.to_string())?;
|
|
73
|
+
serde_json::to_string(&delta).map_err(|error| error.to_string())
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#[derive(Deserialize)]
|
|
78
|
+
struct ElementRequest {
|
|
79
|
+
id: AuthoringElementId,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#[derive(Deserialize)]
|
|
83
|
+
struct SelectionRequest {
|
|
84
|
+
id: Option<AuthoringElementId>,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#[derive(Deserialize)]
|
|
88
|
+
struct PageRequest {
|
|
89
|
+
page: usize,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#[derive(Deserialize)]
|
|
93
|
+
#[serde(rename_all = "camelCase")]
|
|
94
|
+
struct MovePageRequest {
|
|
95
|
+
from_page: usize,
|
|
96
|
+
page: usize,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
pub fn select(handle: u32, input: &str) -> Result<String, String> {
|
|
100
|
+
let request: SelectionRequest = serde_json::from_str(input)
|
|
101
|
+
.map_err(|error| format!("parse authoring selection failed: {error}"))?;
|
|
102
|
+
with_session(handle, |session| {
|
|
103
|
+
serde_json::to_string(
|
|
104
|
+
&session
|
|
105
|
+
.select(request.id)
|
|
106
|
+
.map_err(|error| error.to_string())?,
|
|
107
|
+
)
|
|
108
|
+
.map_err(|error| error.to_string())
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
pub fn elements(handle: u32) -> Result<String, String> {
|
|
113
|
+
with_session(handle, |session| {
|
|
114
|
+
serde_json::to_string(&session.list_elements()).map_err(|error| error.to_string())
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
pub fn begin_gesture(handle: u32, input: &str) -> Result<String, String> {
|
|
119
|
+
let request: ElementRequest = serde_json::from_str(input)
|
|
120
|
+
.map_err(|error| format!("parse authoring gesture failed: {error}"))?;
|
|
121
|
+
with_session(handle, |session| {
|
|
122
|
+
serde_json::to_string(
|
|
123
|
+
&session
|
|
124
|
+
.begin_gesture(request.id)
|
|
125
|
+
.map_err(|error| error.to_string())?,
|
|
126
|
+
)
|
|
127
|
+
.map_err(|error| error.to_string())
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
pub fn preview_gesture(handle: u32, input: &str) -> Result<String, String> {
|
|
132
|
+
let command: AuthoringCommand = serde_json::from_str(input)
|
|
133
|
+
.map_err(|error| format!("parse authoring gesture command failed: {error}"))?;
|
|
134
|
+
with_session(handle, |session| {
|
|
135
|
+
serde_json::to_string(
|
|
136
|
+
&session
|
|
137
|
+
.preview_gesture(command)
|
|
138
|
+
.map_err(|error| error.to_string())?,
|
|
139
|
+
)
|
|
140
|
+
.map_err(|error| error.to_string())
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
pub fn commit_gesture(handle: u32) -> Result<String, String> {
|
|
145
|
+
with_session(handle, |session| {
|
|
146
|
+
serde_json::to_string(
|
|
147
|
+
&session
|
|
148
|
+
.commit_gesture()
|
|
149
|
+
.map_err(|error| error.to_string())?,
|
|
150
|
+
)
|
|
151
|
+
.map_err(|error| error.to_string())
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
pub fn cancel_gesture(handle: u32) -> Result<String, String> {
|
|
156
|
+
with_session(handle, |session| {
|
|
157
|
+
serde_json::to_string(
|
|
158
|
+
&session
|
|
159
|
+
.cancel_gesture()
|
|
160
|
+
.map_err(|error| error.to_string())?,
|
|
161
|
+
)
|
|
162
|
+
.map_err(|error| error.to_string())
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
pub fn append_page(handle: u32) -> Result<String, String> {
|
|
167
|
+
with_session(handle, |session| {
|
|
168
|
+
serde_json::to_string(
|
|
169
|
+
&session
|
|
170
|
+
.append_blank_page()
|
|
171
|
+
.map_err(|error| error.to_string())?,
|
|
172
|
+
)
|
|
173
|
+
.map_err(|error| error.to_string())
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
pub fn duplicate_page(handle: u32, input: &str) -> Result<String, String> {
|
|
178
|
+
let request: PageRequest = serde_json::from_str(input)
|
|
179
|
+
.map_err(|error| format!("parse authoring page failed: {error}"))?;
|
|
180
|
+
with_session(handle, |session| {
|
|
181
|
+
serde_json::to_string(
|
|
182
|
+
&session
|
|
183
|
+
.duplicate_page(request.page)
|
|
184
|
+
.map_err(|error| error.to_string())?,
|
|
185
|
+
)
|
|
186
|
+
.map_err(|error| error.to_string())
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
pub fn delete_page(handle: u32, input: &str) -> Result<String, String> {
|
|
191
|
+
let request: PageRequest = serde_json::from_str(input)
|
|
192
|
+
.map_err(|error| format!("parse authoring page failed: {error}"))?;
|
|
193
|
+
with_session(handle, |session| {
|
|
194
|
+
serde_json::to_string(
|
|
195
|
+
&session
|
|
196
|
+
.delete_page(request.page)
|
|
197
|
+
.map_err(|error| error.to_string())?,
|
|
198
|
+
)
|
|
199
|
+
.map_err(|error| error.to_string())
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
pub fn move_page(handle: u32, input: &str) -> Result<String, String> {
|
|
204
|
+
let request: MovePageRequest = serde_json::from_str(input)
|
|
205
|
+
.map_err(|error| format!("parse authoring page move failed: {error}"))?;
|
|
206
|
+
with_session(handle, |session| {
|
|
207
|
+
serde_json::to_string(
|
|
208
|
+
&session
|
|
209
|
+
.move_page(request.from_page, request.page)
|
|
210
|
+
.map_err(|error| error.to_string())?,
|
|
211
|
+
)
|
|
212
|
+
.map_err(|error| error.to_string())
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
pub fn undo(handle: u32) -> Result<String, String> {
|
|
217
|
+
with_session(handle, |session| {
|
|
218
|
+
serde_json::to_string(&session.undo().map_err(|error| error.to_string())?)
|
|
219
|
+
.map_err(|error| error.to_string())
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
pub fn redo(handle: u32) -> Result<String, String> {
|
|
224
|
+
with_session(handle, |session| {
|
|
225
|
+
serde_json::to_string(&session.redo().map_err(|error| error.to_string())?)
|
|
226
|
+
.map_err(|error| error.to_string())
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
pub fn export(handle: u32) -> Result<String, String> {
|
|
231
|
+
with_session(handle, |session| {
|
|
232
|
+
serde_json::to_string(&session.export_value()).map_err(|error| error.to_string())
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
pub fn checkpoint(handle: u32) -> Result<String, String> {
|
|
237
|
+
with_session(handle, |session| {
|
|
238
|
+
serde_json::to_string(
|
|
239
|
+
&session
|
|
240
|
+
.export_checkpoint_value()
|
|
241
|
+
.map_err(|error| error.to_string())?,
|
|
242
|
+
)
|
|
243
|
+
.map_err(|error| error.to_string())
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
pub fn destroy(handle: u32) -> bool {
|
|
248
|
+
SESSIONS.with(|table| table.borrow_mut().sessions.remove(&handle).is_some())
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
fn with_session<T>(
|
|
252
|
+
handle: u32,
|
|
253
|
+
call: impl FnOnce(&mut AuthoringSession) -> Result<T, String>,
|
|
254
|
+
) -> Result<T, String> {
|
|
255
|
+
SESSIONS.with(|table| {
|
|
256
|
+
let mut table = table.borrow_mut();
|
|
257
|
+
let session = table
|
|
258
|
+
.sessions
|
|
259
|
+
.get_mut(&handle)
|
|
260
|
+
.ok_or_else(|| format!("unknown authoring session handle {handle}"))?;
|
|
261
|
+
call(session)
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
#[cfg(test)]
|
|
266
|
+
mod tests {
|
|
267
|
+
use super::*;
|
|
268
|
+
use serde_json::json;
|
|
269
|
+
|
|
270
|
+
#[test]
|
|
271
|
+
fn runtime_handle_applies_and_exports_without_browser_metadata() {
|
|
272
|
+
let created: Value = serde_json::from_str(&create_blank().unwrap()).unwrap();
|
|
273
|
+
let handle = created["handle"].as_u64().unwrap() as u32;
|
|
274
|
+
let command = json!({
|
|
275
|
+
"kind": "create",
|
|
276
|
+
"page": 0,
|
|
277
|
+
"category": "texts",
|
|
278
|
+
"element": {
|
|
279
|
+
"objectData": {
|
|
280
|
+
"position": {"x": 0.0, "y": 0.0, "z": 0.0},
|
|
281
|
+
"scale": {"x": 1.0, "y": 1.0, "z": 1.0},
|
|
282
|
+
"rotation": {"x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0},
|
|
283
|
+
"layer": 0, "lock": false, "visible": true
|
|
284
|
+
},
|
|
285
|
+
"text": "请多关照!", "fontId": 1, "type": 513, "colorId": 1,
|
|
286
|
+
"size": 24.0, "outlineColorId": 1, "outlineSize": 0.0, "lineSpacing": 0.0
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
let delta: Value =
|
|
290
|
+
serde_json::from_str(&apply(handle, &command.to_string()).unwrap()).unwrap();
|
|
291
|
+
assert_eq!(delta["revision"], 1);
|
|
292
|
+
let exported: Value = serde_json::from_str(&export(handle).unwrap()).unwrap();
|
|
293
|
+
assert_eq!(
|
|
294
|
+
exported["userCustomProfileCards"][0]["customProfileCard"]["texts"]
|
|
295
|
+
.as_array()
|
|
296
|
+
.unwrap()
|
|
297
|
+
.len(),
|
|
298
|
+
1
|
|
299
|
+
);
|
|
300
|
+
assert!(exported.get("handle").is_none());
|
|
301
|
+
assert!(destroy(handle));
|
|
302
|
+
}
|
|
303
|
+
}
|