@mermanjs/node 0.8.0-alpha.5 → 0.8.0-alpha.7

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/dist/engine.mjs CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  NODE_TRANSPORT_FIELD_LIMITS,
5
5
  NODE_TRANSPORT_LIMITS,
6
6
  NODE_WIRE_CONTRACT,
7
+ abortError,
7
8
  assertUtf8Field,
8
9
  decodeWireInvocationError,
9
10
  decodeWireResponse,
@@ -28,7 +29,7 @@ import {
28
29
  TEXT_MEASUREMENT_PROTOCOL_VERSION,
29
30
  TEXT_MEASUREMENT_PROVIDER_IDS,
30
31
  TEXT_MEASUREMENT_PROVIDER_SPECS,
31
- VENDORED_TEXT_MEASUREMENT_PROVIDER_ID,
32
+ DETERMINISTIC_TEXT_MEASUREMENT_PROVIDER_ID,
32
33
  } from "./generated/binding-contract.mjs";
33
34
 
34
35
  const RESOURCE_PROFILES = new Set([
@@ -86,6 +87,7 @@ const COMPILED_PREREQUISITES_BY_OPERATION_ID = new Map(
86
87
  const KNOWN_OUTPUT_CONTRACT_BY_ID = new Map(
87
88
  NODE_WIRE_CONTRACT.artifact.output_contracts.map((contract) => [contract.id, contract]),
88
89
  );
90
+ const MAX_OPERATION_TIMEOUT_MS = 0xffff_ffff;
89
91
  const MERMAN_ENGINE_CONSTRUCTION_TOKEN = Symbol("MermanEngine construction");
90
92
 
91
93
  export async function createNodeEngine(
@@ -144,6 +146,19 @@ export function normalizeBindingOptions(value = {}) {
144
146
  if (!RESOURCE_PROFILES.has(normalized.resources.profile)) {
145
147
  throw new RangeError(`Unknown resource profile \`${normalized.resources.profile}\`.`);
146
148
  }
149
+ if (normalized.environment !== undefined) {
150
+ if (!isPlainObject(normalized.environment)) {
151
+ throw new TypeError("bindingOptions.environment must be a plain object.");
152
+ }
153
+ if (
154
+ normalized.environment.text_measurement !== undefined &&
155
+ normalized.environment.text_measurement !== "deterministic"
156
+ ) {
157
+ throw new RangeError(
158
+ "bindingOptions.environment.text_measurement must be `deterministic`.",
159
+ );
160
+ }
161
+ }
147
162
  return normalized;
148
163
  }
149
164
 
@@ -181,31 +196,37 @@ export class MermanEngine {
181
196
  renderSvg(source, options = {}) {
182
197
  return this.executeOperation(
183
198
  { operationId: "svg", source, optionsJson: options.optionsJson },
184
- { signal: options.signal },
199
+ { signal: options.signal, timeoutMs: options.timeoutMs },
185
200
  ).then((result) => result.data);
186
201
  }
187
202
 
188
203
  renderSvgSync(source, options = {}) {
189
- return this.executeOperationSync({
190
- operationId: "svg",
191
- source,
192
- optionsJson: options.optionsJson,
193
- }).data;
204
+ return this.executeOperationSync(
205
+ {
206
+ operationId: "svg",
207
+ source,
208
+ optionsJson: options.optionsJson,
209
+ },
210
+ { timeoutMs: options.timeoutMs },
211
+ ).data;
194
212
  }
195
213
 
196
214
  svgPlanJson(source, options = {}) {
197
215
  return this.executeOperation(
198
216
  { operationId: "svg-plan-json", source, optionsJson: options.optionsJson },
199
- { signal: options.signal },
217
+ { signal: options.signal, timeoutMs: options.timeoutMs },
200
218
  ).then((result) => result.data);
201
219
  }
202
220
 
203
221
  svgPlanJsonSync(source, options = {}) {
204
- return this.executeOperationSync({
205
- operationId: "svg-plan-json",
206
- source,
207
- optionsJson: options.optionsJson,
208
- }).data;
222
+ return this.executeOperationSync(
223
+ {
224
+ operationId: "svg-plan-json",
225
+ source,
226
+ optionsJson: options.optionsJson,
227
+ },
228
+ { timeoutMs: options.timeoutMs },
229
+ ).data;
209
230
  }
210
231
 
211
232
  metadataJson(id) {
@@ -232,17 +253,33 @@ export class MermanEngine {
232
253
  return value;
233
254
  }
234
255
 
235
- executeOperation(request, { signal } = {}) {
236
- const encoded = operationRequestJson(request);
256
+ executeOperation(request, { signal, timeoutMs } = {}) {
257
+ try {
258
+ this.#executor.assertOpen();
259
+ } catch (error) {
260
+ return Promise.reject(error);
261
+ }
262
+ const prepared = prepareOperationRequest(request, { timeoutMs });
263
+ if (signal?.aborted) return Promise.reject(abortError());
264
+ const encoded = operationRequestJson(prepared);
237
265
  return this.#executor.submit(
238
266
  async () => {
267
+ // A signal can flip after queue admission but before this start microtask runs. That work
268
+ // has not crossed the transport boundary, so preserve the established AbortError result.
269
+ if (signal?.aborted) throw abortError();
239
270
  let responseJson;
240
271
  try {
241
- responseJson = await this.#transport.execute(encoded.json);
272
+ responseJson = await executeTransportOperation(
273
+ this.#transport,
274
+ encoded.json,
275
+ signal,
276
+ timeoutMs,
277
+ );
242
278
  } catch (cause) {
243
279
  throw decodeWireInvocationError(cause, "Merman operation");
244
280
  }
245
281
  return decodeWireResponse(responseJson, encoded.expectation, {
282
+ allowedCancellationReasons: invocationCancellationReasons(signal, timeoutMs),
246
283
  requireUnavailable: !this.#operationIds.has(encoded.expectation.operation_id),
247
284
  });
248
285
  },
@@ -250,16 +287,17 @@ export class MermanEngine {
250
287
  );
251
288
  }
252
289
 
253
- executeOperationSync(request) {
290
+ executeOperationSync(request, { timeoutMs } = {}) {
254
291
  this.#executor.assertSyncAvailable();
255
- const encoded = operationRequestJson(request);
292
+ const encoded = operationRequestJson(prepareOperationRequest(request, { timeoutMs }));
256
293
  let responseJson;
257
294
  try {
258
- responseJson = this.#transport.executeSync(encoded.json);
295
+ responseJson = this.#transport.executeSync(encoded.json, timeoutMs);
259
296
  } catch (cause) {
260
297
  throw decodeWireInvocationError(cause, "Merman operation");
261
298
  }
262
299
  return decodeWireResponse(responseJson, encoded.expectation, {
300
+ allowedCancellationReasons: invocationCancellationReasons(undefined, timeoutMs),
263
301
  requireUnavailable: !this.#operationIds.has(encoded.expectation.operation_id),
264
302
  });
265
303
  }
@@ -273,7 +311,14 @@ export class MermanEngine {
273
311
  }
274
312
  }
275
313
 
276
- function operationRequestJson(value) {
314
+ function invocationCancellationReasons(signal, timeoutMs) {
315
+ const reasons = [];
316
+ if (signal?.aborted) reasons.push("requested");
317
+ if (timeoutMs !== undefined) reasons.push("deadline_exceeded");
318
+ return reasons;
319
+ }
320
+
321
+ function prepareOperationRequest(value, { timeoutMs } = {}) {
277
322
  if (!isPlainObject(value)) throw new TypeError("operation request must be a plain object.");
278
323
  const knownFields = new Set(["operationId", "source", "uri", "optionsJson"]);
279
324
  for (const field of Object.keys(value)) {
@@ -281,12 +326,9 @@ function operationRequestJson(value) {
281
326
  throw new TypeError(`unknown operation request field \`${field}\`.`);
282
327
  }
283
328
  }
284
- const {
285
- operationId,
286
- source,
287
- uri = null,
288
- optionsJson = undefined,
289
- } = value;
329
+ const operationId = value.operationId;
330
+ const source = value.source;
331
+ const uri = value.uri === undefined ? null : value.uri;
290
332
  if (typeof operationId !== "string" || operationId.length === 0) {
291
333
  throw new TypeError("operationId must be a non-empty string.");
292
334
  }
@@ -327,6 +369,23 @@ function operationRequestJson(value) {
327
369
  source,
328
370
  uri: normalizedUri,
329
371
  };
372
+ if (timeoutMs !== undefined) {
373
+ if (
374
+ !Number.isSafeInteger(timeoutMs) ||
375
+ timeoutMs < 0 ||
376
+ timeoutMs > MAX_OPERATION_TIMEOUT_MS
377
+ ) {
378
+ throw new RangeError(
379
+ `operation timeoutMs must be an integer from 0 through ${MAX_OPERATION_TIMEOUT_MS}.`,
380
+ );
381
+ }
382
+ request.operation_control = { timeout_ms: timeoutMs };
383
+ }
384
+ return { expectation, request, value };
385
+ }
386
+
387
+ function operationRequestJson({ expectation, request, value }) {
388
+ const optionsJson = value.optionsJson;
330
389
  if (optionsJson !== undefined) {
331
390
  if (typeof optionsJson !== "string") {
332
391
  throw new TypeError("optionsJson must be a JSON string when provided.");
@@ -349,6 +408,32 @@ function operationRequestJson(value) {
349
408
  };
350
409
  }
351
410
 
411
+ async function executeTransportOperation(transport, requestJson, signal, timeoutMs) {
412
+ if (!signal) return transport.execute(requestJson, undefined, timeoutMs);
413
+
414
+ // Forward into a private signal so the N-API bridge cannot replace a caller-owned `onabort`
415
+ // handler while it installs its cooperative cancellation callback.
416
+ const transportAbort = new AbortController();
417
+ let transportReady = false;
418
+ let pendingAbort = false;
419
+ const forwardAbort = () => {
420
+ if (transportReady) {
421
+ transportAbort.abort(signal.reason);
422
+ } else {
423
+ pendingAbort = true;
424
+ }
425
+ };
426
+ signal.addEventListener("abort", forwardAbort, { once: true });
427
+ try {
428
+ const operation = transport.execute(requestJson, transportAbort.signal, timeoutMs);
429
+ transportReady = true;
430
+ if (pendingAbort || signal.aborted) transportAbort.abort(signal.reason);
431
+ return await operation;
432
+ } finally {
433
+ signal.removeEventListener("abort", forwardAbort);
434
+ }
435
+ }
436
+
352
437
  function assertTransport(transport) {
353
438
  if (
354
439
  !transport ||
@@ -893,7 +978,7 @@ function validateTextMeasurement(value, { requiresSvgPipeline }) {
893
978
  providerIds.some(
894
979
  (id) =>
895
980
  KNOWN_TEXT_MEASUREMENT_PROVIDER_IDS.has(id) &&
896
- id !== VENDORED_TEXT_MEASUREMENT_PROVIDER_ID,
981
+ id !== DETERMINISTIC_TEXT_MEASUREMENT_PROVIDER_ID,
897
982
  )
898
983
  ) {
899
984
  throw new MermanInvalidTransportError(
@@ -902,10 +987,10 @@ function validateTextMeasurement(value, { requiresSvgPipeline }) {
902
987
  }
903
988
  if (
904
989
  requiresSvgPipeline &&
905
- !providerIds.includes(VENDORED_TEXT_MEASUREMENT_PROVIDER_ID)
990
+ !providerIds.includes(DETERMINISTIC_TEXT_MEASUREMENT_PROVIDER_ID)
906
991
  ) {
907
992
  throw new MermanInvalidTransportError(
908
- "Merman runtime catalog must expose the callable vendored text measurement provider for the SVG pipeline.",
993
+ "Merman runtime catalog must expose the callable deterministic text measurement provider for the SVG pipeline.",
909
994
  );
910
995
  }
911
996
  return providerIds;
@@ -914,7 +999,12 @@ function validateTextMeasurement(value, { requiresSvgPipeline }) {
914
999
  function validateRegistry(value) {
915
1000
  if (
916
1001
  !safeInteger(value.diagram_family_count) ||
917
- value.diagram_family_count < 0
1002
+ value.diagram_family_count < 0 ||
1003
+ !Array.isArray(value.diagram_families) ||
1004
+ value.diagram_families.some((family, index) =>
1005
+ typeof family !== "string" || !/^[a-z][A-Za-z0-9]*$/.test(family) ||
1006
+ family === "error" || (index > 0 && value.diagram_families[index - 1] >= family)
1007
+ )
918
1008
  ) {
919
1009
  throw new MermanInvalidTransportError(
920
1010
  "Merman runtime catalog has invalid registry metadata.",
package/dist/errors.mjs CHANGED
@@ -29,7 +29,10 @@ const BINDING_STATUS_NAME_BY_CODE = new Map([
29
29
  [9, "MERMAN_INTERNAL_ERROR"],
30
30
  [10, "MERMAN_RESOURCE_LIMIT_EXCEEDED"],
31
31
  [11, "MERMAN_BUSY"],
32
+ [12, "MERMAN_CANCELLED"],
32
33
  ]);
34
+ const CANCELLATION_REASONS = new Set(["requested", "deadline_exceeded"]);
35
+ const OPERATION_PHASE_IDENTIFIER = /^[a-z][a-z0-9_-]{0,63}$/;
33
36
  const JSON_NUMBER_TOKEN = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
34
37
  const JSON_NUMBER_PARTS = /^-?(0|[1-9]\d*)(?:\.(\d+))?(?:[eE]([+-]?)(\d+))?$/;
35
38
  const MAX_SAFE_INTEGER_DECIMAL = String(RUNTIME_CATALOG_MAX_SAFE_INTEGER);
@@ -67,6 +70,10 @@ const RESPONSE_EXACT_SAFE_INTEGER_PATHS = Object.freeze([
67
70
  ["error", "code"],
68
71
  ["error", "details", "resource", "actual"],
69
72
  ["error", "details", "resource", "max"],
73
+ ["error", "details", "diagnostic", "span", "start"],
74
+ ["error", "details", "diagnostic", "span", "end"],
75
+ ["error", "details", "diagnostic", "requested_max_width"],
76
+ ["error", "details", "diagnostic", "actual_width"],
70
77
  ["error", "details", "icon_registry", "pack_index"],
71
78
  ]);
72
79
  const OPERATION_METADATA_EXACT_SAFE_INTEGER_PATHS = Object.freeze([
@@ -96,6 +103,8 @@ export class MermanOperationError extends MermanError {
96
103
  this.kind = payload.kind ?? "generic";
97
104
  this.capabilityId = payload.capability_id ?? null;
98
105
  this.resourceDetails = payload.details?.resource ?? null;
106
+ this.diagnosticDetails = payload.details?.diagnostic ?? null;
107
+ this.cancellationDetails = payload.details?.cancellation ?? null;
99
108
  }
100
109
  }
101
110
 
@@ -143,6 +152,17 @@ export class MermanMissingPlatformPackageError extends MermanError {
143
152
  }
144
153
  }
145
154
 
155
+ export class MermanNativeLoadError extends MermanError {
156
+ constructor({ packageName, target, cause }) {
157
+ super(
158
+ `The installed Merman platform package ${packageName} could not load its native addon for ${target}. This is a native ABI or shared-library loader failure, not a missing npm package. Use a compatible glibc baseline or opt into @mermanjs/node-wasm.`,
159
+ { code: "MERMAN_NATIVE_LOAD_ERROR", cause },
160
+ );
161
+ this.packageName = packageName;
162
+ this.target = target;
163
+ }
164
+ }
165
+
146
166
  export class MermanInvalidTransportError extends MermanError {
147
167
  constructor(message, cause) {
148
168
  super(message, { code: "MERMAN_INVALID_TRANSPORT", cause });
@@ -843,8 +863,9 @@ export function abortError() {
843
863
  export function decodeWireResponse(
844
864
  value,
845
865
  expectation,
846
- { requireUnavailable = false } = {},
866
+ { allowedCancellationReasons = [], requireUnavailable = false } = {},
847
867
  ) {
868
+ const cancellationReasons = validateAllowedCancellationReasons(allowedCancellationReasons);
848
869
  const envelope = parseTransportJsonText(
849
870
  value,
850
871
  "response",
@@ -863,7 +884,12 @@ export function decodeWireResponse(
863
884
  "Merman transport error envelopes must contain error only.",
864
885
  );
865
886
  }
866
- const error = validateErrorPayload(envelope.error, expectation, requireUnavailable);
887
+ const error = validateErrorPayload(
888
+ envelope.error,
889
+ expectation,
890
+ requireUnavailable,
891
+ cancellationReasons,
892
+ );
867
893
  throw new MermanOperationError(error);
868
894
  }
869
895
  if (envelope.ok !== true || Object.hasOwn(envelope, "error")) {
@@ -879,6 +905,16 @@ export function decodeWireResponse(
879
905
  return validateSuccessResult(envelope.result, expectation);
880
906
  }
881
907
 
908
+ function validateAllowedCancellationReasons(reasons) {
909
+ if (
910
+ !Array.isArray(reasons) ||
911
+ reasons.some((reason) => !CANCELLATION_REASONS.has(reason))
912
+ ) {
913
+ throw new TypeError("allowedCancellationReasons must contain known cancellation reasons.");
914
+ }
915
+ return new Set(reasons);
916
+ }
917
+
882
918
  export function decodeWireCreationError(cause, label) {
883
919
  return decodeWireFailure(cause, `${label} failed to initialize.`);
884
920
  }
@@ -990,7 +1026,12 @@ function validateSuccessResult(result, expectation) {
990
1026
  return result;
991
1027
  }
992
1028
 
993
- function validateErrorPayload(error, expectation = null, requireUnavailable = false) {
1029
+ function validateErrorPayload(
1030
+ error,
1031
+ expectation = null,
1032
+ requireUnavailable = false,
1033
+ allowedCancellationReasons = new Set(),
1034
+ ) {
994
1035
  if (
995
1036
  !isPlainJsonObject(error) ||
996
1037
  !Number.isSafeInteger(error.code) ||
@@ -1029,9 +1070,18 @@ function validateErrorPayload(error, expectation = null, requireUnavailable = fa
1029
1070
  );
1030
1071
  }
1031
1072
  validateKnownErrorRelations(error);
1032
- validateResourceDetails(error.details);
1073
+ validateErrorDetails(error.details);
1033
1074
 
1034
- if (requireUnavailable && expectation?.unavailable) {
1075
+ if (
1076
+ error.code === 12 &&
1077
+ !allowedCancellationReasons.has(error.details.cancellation.reason)
1078
+ ) {
1079
+ throw new MermanInvalidTransportError(
1080
+ "Merman transport returned cancellation without a matching invocation control.",
1081
+ );
1082
+ }
1083
+
1084
+ if (requireUnavailable && expectation?.unavailable && error.code !== 12) {
1035
1085
  const unavailable = expectation.unavailable;
1036
1086
  if (
1037
1087
  error.code !== unavailable.status_code ||
@@ -1105,9 +1155,29 @@ function validateKnownErrorRelations(error) {
1105
1155
  } else {
1106
1156
  throw new MermanInvalidTransportError("Merman transport returned an unknown error kind.");
1107
1157
  }
1158
+ const cancellation = error.details?.cancellation;
1159
+ if (error.code === 12) {
1160
+ if (
1161
+ error.kind !== "generic" ||
1162
+ error.capability_id !== null ||
1163
+ !isPlainJsonObject(error.details) ||
1164
+ cancellation === undefined ||
1165
+ error.details.resource !== undefined ||
1166
+ error.details.diagnostic !== undefined ||
1167
+ error.details.icon_registry !== undefined
1168
+ ) {
1169
+ throw new MermanInvalidTransportError(
1170
+ "Merman transport returned an inconsistent cancellation error.",
1171
+ );
1172
+ }
1173
+ } else if (cancellation !== undefined) {
1174
+ throw new MermanInvalidTransportError(
1175
+ "Merman transport attached cancellation details to a non-cancellation error.",
1176
+ );
1177
+ }
1108
1178
  }
1109
1179
 
1110
- function validateResourceDetails(details) {
1180
+ function validateErrorDetails(details) {
1111
1181
  if (details === undefined) return;
1112
1182
  if (!isPlainJsonObject(details)) {
1113
1183
  throw new MermanInvalidTransportError("Merman transport error details must be an object.");
@@ -1132,6 +1202,49 @@ function validateResourceDetails(details) {
1132
1202
  );
1133
1203
  }
1134
1204
  }
1205
+ if (details.diagnostic !== undefined) {
1206
+ const diagnostic = details.diagnostic;
1207
+ const span = diagnostic?.span;
1208
+ if (
1209
+ !isPlainJsonObject(diagnostic) ||
1210
+ typeof diagnostic.code !== "string" ||
1211
+ diagnostic.code.length === 0 ||
1212
+ (diagnostic.field !== null && typeof diagnostic.field !== "string") ||
1213
+ (diagnostic.diagram_type !== null && typeof diagnostic.diagram_type !== "string") ||
1214
+ !isOptionalDiagnosticUnsignedInteger(diagnostic.requested_max_width) ||
1215
+ !isOptionalDiagnosticUnsignedInteger(diagnostic.actual_width) ||
1216
+ (diagnostic.width_profile !== undefined &&
1217
+ diagnostic.width_profile !== null &&
1218
+ typeof diagnostic.width_profile !== "string") ||
1219
+ (diagnostic.fallback_reason !== undefined &&
1220
+ diagnostic.fallback_reason !== null &&
1221
+ typeof diagnostic.fallback_reason !== "string") ||
1222
+ (span !== null &&
1223
+ (!isPlainJsonObject(span) ||
1224
+ !Number.isSafeInteger(span.start) ||
1225
+ span.start < 0 ||
1226
+ !Number.isSafeInteger(span.end) ||
1227
+ span.end < span.start ||
1228
+ !["exact", "insertion-point", "fallback"].includes(span.kind)))
1229
+ ) {
1230
+ throw new MermanInvalidTransportError(
1231
+ "Merman transport returned invalid diagnostic error details.",
1232
+ );
1233
+ }
1234
+ }
1235
+ if (details.cancellation !== undefined) {
1236
+ const cancellation = details.cancellation;
1237
+ if (
1238
+ !isPlainJsonObject(cancellation) ||
1239
+ !CANCELLATION_REASONS.has(cancellation.reason) ||
1240
+ typeof cancellation.phase !== "string" ||
1241
+ !OPERATION_PHASE_IDENTIFIER.test(cancellation.phase)
1242
+ ) {
1243
+ throw new MermanInvalidTransportError(
1244
+ "Merman transport returned invalid cancellation error details.",
1245
+ );
1246
+ }
1247
+ }
1135
1248
  if (details.icon_registry !== undefined) {
1136
1249
  const iconRegistry = details.icon_registry;
1137
1250
  if (
@@ -1161,6 +1274,12 @@ function isBindingResourceCount(value) {
1161
1274
  compareCanonicalUnsignedDecimals(value, U64_MAX_DECIMAL) <= 0;
1162
1275
  }
1163
1276
 
1277
+ function isOptionalDiagnosticUnsignedInteger(value) {
1278
+ return value === undefined ||
1279
+ value === null ||
1280
+ (Number.isSafeInteger(value) && value >= 0);
1281
+ }
1282
+
1164
1283
  function compareCanonicalUnsignedDecimals(left, right) {
1165
1284
  if (left.length !== right.length) return left.length < right.length ? -1 : 1;
1166
1285
  if (left === right) return 0;
@@ -68,6 +68,14 @@ export const CAPABILITY_SPECS = [
68
68
  "id": "png",
69
69
  "implication_ids": []
70
70
  },
71
+ {
72
+ "id": "rustdoc",
73
+ "implication_ids": [
74
+ "layout-cytoscape",
75
+ "markdown",
76
+ "math"
77
+ ]
78
+ },
71
79
  {
72
80
  "id": "shell-completions",
73
81
  "implication_ids": []
@@ -142,6 +150,158 @@ export const BINDING_OPERATION_METADATA_CONTRACT = {
142
150
  }
143
151
  ],
144
152
  "output_plans": [
153
+ {
154
+ "kind": "ascii",
155
+ "fields": [
156
+ {
157
+ "name": "kind",
158
+ "json_type": "string",
159
+ "required": true,
160
+ "integer_width_bits": null,
161
+ "open_value": false
162
+ },
163
+ {
164
+ "name": "schema_version",
165
+ "json_type": "unsigned-integer",
166
+ "required": true,
167
+ "integer_width_bits": 16,
168
+ "open_value": false
169
+ },
170
+ {
171
+ "name": "family",
172
+ "json_type": "string",
173
+ "required": true,
174
+ "integer_width_bits": null,
175
+ "open_value": true
176
+ },
177
+ {
178
+ "name": "projection",
179
+ "json_type": "string",
180
+ "required": true,
181
+ "integer_width_bits": null,
182
+ "open_value": false
183
+ },
184
+ {
185
+ "name": "encoding",
186
+ "json_type": "string",
187
+ "required": true,
188
+ "integer_width_bits": null,
189
+ "open_value": false
190
+ },
191
+ {
192
+ "name": "primary_width",
193
+ "json_type": "unsigned-integer",
194
+ "required": true,
195
+ "integer_width_bits": 64,
196
+ "open_value": false
197
+ },
198
+ {
199
+ "name": "primary_height",
200
+ "json_type": "unsigned-integer",
201
+ "required": true,
202
+ "integer_width_bits": 64,
203
+ "open_value": false
204
+ },
205
+ {
206
+ "name": "emitted_width",
207
+ "json_type": "unsigned-integer",
208
+ "required": true,
209
+ "integer_width_bits": 64,
210
+ "open_value": false
211
+ },
212
+ {
213
+ "name": "emitted_height",
214
+ "json_type": "unsigned-integer",
215
+ "required": true,
216
+ "integer_width_bits": 64,
217
+ "open_value": false
218
+ },
219
+ {
220
+ "name": "width_profile",
221
+ "json_type": "string",
222
+ "required": true,
223
+ "integer_width_bits": null,
224
+ "open_value": false
225
+ },
226
+ {
227
+ "name": "layout_profile",
228
+ "json_type": "string",
229
+ "required": true,
230
+ "integer_width_bits": null,
231
+ "open_value": false
232
+ },
233
+ {
234
+ "name": "requested_layout_profile",
235
+ "json_type": "string",
236
+ "required": true,
237
+ "integer_width_bits": null,
238
+ "open_value": false
239
+ },
240
+ {
241
+ "name": "compact_attempted",
242
+ "json_type": "boolean",
243
+ "required": true,
244
+ "integer_width_bits": null,
245
+ "open_value": false
246
+ },
247
+ {
248
+ "name": "requested_max_width",
249
+ "json_type": "unsigned-integer",
250
+ "required": false,
251
+ "integer_width_bits": 64,
252
+ "open_value": false
253
+ },
254
+ {
255
+ "name": "overflowed",
256
+ "json_type": "boolean",
257
+ "required": true,
258
+ "integer_width_bits": null,
259
+ "open_value": false
260
+ },
261
+ {
262
+ "name": "outcome",
263
+ "json_type": "string",
264
+ "required": true,
265
+ "integer_width_bits": null,
266
+ "open_value": false
267
+ },
268
+ {
269
+ "name": "fallback_capability",
270
+ "json_type": "string",
271
+ "required": true,
272
+ "integer_width_bits": null,
273
+ "open_value": false
274
+ },
275
+ {
276
+ "name": "fallback_attempted",
277
+ "json_type": "boolean",
278
+ "required": true,
279
+ "integer_width_bits": null,
280
+ "open_value": false
281
+ },
282
+ {
283
+ "name": "fallback_reason",
284
+ "json_type": "string",
285
+ "required": false,
286
+ "integer_width_bits": null,
287
+ "open_value": false
288
+ },
289
+ {
290
+ "name": "trimmed",
291
+ "json_type": "boolean",
292
+ "required": true,
293
+ "integer_width_bits": null,
294
+ "open_value": false
295
+ },
296
+ {
297
+ "name": "lossiness",
298
+ "json_type": "string",
299
+ "required": true,
300
+ "integer_width_bits": null,
301
+ "open_value": false
302
+ }
303
+ ]
304
+ },
145
305
  {
146
306
  "kind": "raster",
147
307
  "fields": [
@@ -611,23 +771,23 @@ export const BINDING_OPTION_GROUP_SPECS = [
611
771
  ];
612
772
 
613
773
  export const TEXT_MEASUREMENT_PROVIDER_SPECS = [
774
+ {
775
+ "id": "deterministic",
776
+ "source": "svg-pipeline",
777
+ "constructor_service_id": null
778
+ },
614
779
  {
615
780
  "id": "host-callback",
616
781
  "source": "constructor-service",
617
782
  "constructor_service_id": "host-text-measurement"
618
- },
619
- {
620
- "id": "vendored",
621
- "source": "svg-pipeline",
622
- "constructor_service_id": null
623
783
  }
624
784
  ];
625
785
 
626
786
  export const TEXT_MEASUREMENT_PROVIDER_IDS = [
627
- "host-callback",
628
- "vendored"
787
+ "deterministic",
788
+ "host-callback"
629
789
  ];
630
- export const VENDORED_TEXT_MEASUREMENT_PROVIDER_ID = "vendored";
790
+ export const DETERMINISTIC_TEXT_MEASUREMENT_PROVIDER_ID = "deterministic";
631
791
  export const HOST_CALLBACK_TEXT_MEASUREMENT_PROVIDER_ID = "host-callback";
632
792
 
633
793
  export const CONSTRUCTOR_SERVICE_SPECS = [