@vyaz/core 0.0.9 → 0.0.11

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.
@@ -18200,21 +18200,55 @@ function normaliseWeight(weight) {
18200
18200
  return mapped;
18201
18201
  return weight;
18202
18202
  }
18203
- function cacheKey2(family, weight, style) {
18204
- return `${family}_${normaliseWeight(weight)}_${style}`;
18203
+ function weightToNumber(weight) {
18204
+ const num = parseInt(weight, 10);
18205
+ return isNaN(num) ? 400 : num;
18206
+ }
18207
+ function variantKey(weight, style) {
18208
+ return `${normaliseWeight(weight)}_${style}`;
18209
+ }
18210
+ function nearestWeight(registeredWeights, requested) {
18211
+ if (registeredWeights.length === 0)
18212
+ return null;
18213
+ let best = registeredWeights[0];
18214
+ let bestDist = Math.abs(best - requested);
18215
+ for (let i = 1;i < registeredWeights.length; i++) {
18216
+ const w = registeredWeights[i];
18217
+ const dist = Math.abs(w - requested);
18218
+ if (dist < bestDist || dist === bestDist && w > best) {
18219
+ best = w;
18220
+ bestDist = dist;
18221
+ }
18222
+ }
18223
+ return best;
18205
18224
  }
18206
18225
 
18207
18226
  class FontMetricsProvider {
18208
- cache = new Map;
18227
+ registry = new Map;
18209
18228
  metricsCache = new Map;
18210
18229
  mode = "browser";
18230
+ pendingRegistrations = new Set;
18231
+ _measureCanvas = null;
18232
+ _measureCtx = null;
18233
+ _getMeasureContext() {
18234
+ if (!this._measureCtx) {
18235
+ if (typeof OffscreenCanvas !== "undefined") {
18236
+ this._measureCanvas = new OffscreenCanvas(1, 1);
18237
+ this._measureCtx = this._measureCanvas.getContext("2d");
18238
+ } else {
18239
+ this._measureCanvas = document.createElement("canvas");
18240
+ this._measureCtx = this._measureCanvas.getContext("2d");
18241
+ }
18242
+ }
18243
+ return this._measureCtx;
18244
+ }
18211
18245
  setMode(mode) {
18212
18246
  if (this.mode === mode)
18213
18247
  return;
18214
18248
  this.mode = mode;
18215
18249
  this.metricsCache.clear();
18216
18250
  if (mode === "office") {
18217
- enableOfficeTextMeasure(this.cache);
18251
+ enableOfficeTextMeasure(this._flattenCache());
18218
18252
  } else {
18219
18253
  disableOfficeTextMeasure();
18220
18254
  }
@@ -18222,7 +18256,25 @@ class FontMetricsProvider {
18222
18256
  getMode() {
18223
18257
  return this.mode;
18224
18258
  }
18259
+ _flattenCache() {
18260
+ const flat = new Map;
18261
+ for (const [family, variants] of this.registry) {
18262
+ for (const [vKey, font] of variants) {
18263
+ flat.set(`${family}_${vKey}`, font);
18264
+ }
18265
+ }
18266
+ return flat;
18267
+ }
18225
18268
  async registerFont(family, options, source, sourcePath) {
18269
+ const promise = this._registerFontInternal(family, options, source, sourcePath);
18270
+ this.pendingRegistrations.add(promise);
18271
+ try {
18272
+ await promise;
18273
+ } finally {
18274
+ this.pendingRegistrations.delete(promise);
18275
+ }
18276
+ }
18277
+ async _registerFontInternal(family, options, source, sourcePath) {
18226
18278
  const { createFontFace: createFontFace2 } = await Promise.resolve().then(() => exports_FontEngine);
18227
18279
  const { registerCanvasFont: registerCanvasFont2 } = await init_canvas_polyfill().then(() => exports_canvas_polyfill);
18228
18280
  if (typeof source === "string") {
@@ -18230,25 +18282,107 @@ class FontMetricsProvider {
18230
18282
  source = await getFontBuffer2(source);
18231
18283
  }
18232
18284
  const font = await createFontFace2(source);
18233
- const key = cacheKey2(family, options.weight || "normal", options.style || "normal");
18234
- this.cache.set(key, font);
18235
- this.metricsCache.delete(key);
18285
+ const w = options.weight || "normal";
18286
+ const s = options.style || "normal";
18287
+ const vKey = variantKey(w, s);
18288
+ if (!this.registry.has(family)) {
18289
+ this.registry.set(family, new Map);
18290
+ }
18291
+ this.registry.get(family).set(vKey, font);
18292
+ for (const key of this.metricsCache.keys()) {
18293
+ if (key.startsWith(family)) {
18294
+ this.metricsCache.delete(key);
18295
+ }
18296
+ }
18236
18297
  if (sourcePath) {
18237
18298
  registerCanvasFont2(sourcePath, family);
18238
18299
  }
18239
18300
  }
18301
+ async waitForPendingRegistrations() {
18302
+ await Promise.all(this.pendingRegistrations);
18303
+ }
18304
+ getRegisteredFamilies() {
18305
+ return Array.from(this.registry.keys());
18306
+ }
18307
+ getFamilyVariants(family) {
18308
+ const variants = this.registry.get(family);
18309
+ return variants ? Array.from(variants.keys()) : [];
18310
+ }
18240
18311
  getFont(family, weight = "normal", style = "normal") {
18241
- const key = cacheKey2(family, weight, style);
18242
- return this.cache.get(key);
18312
+ const resolved = this._resolveFont(family, weight, style);
18313
+ if (!resolved)
18314
+ return;
18315
+ return resolved.font;
18316
+ }
18317
+ _resolveFont(family, weight, style) {
18318
+ const variants = this.registry.get(family);
18319
+ if (!variants || variants.size === 0)
18320
+ return null;
18321
+ const normalisedW = normaliseWeight(weight);
18322
+ const normalisedS = style;
18323
+ let key = variantKey(normalisedW, normalisedS);
18324
+ let font = variants.get(key);
18325
+ if (font)
18326
+ return { font, resolvedWeight: normalisedW, resolvedStyle: normalisedS };
18327
+ if (normalisedS === "italic") {
18328
+ key = variantKey(normalisedW, "normal");
18329
+ font = variants.get(key);
18330
+ if (font)
18331
+ return { font, resolvedWeight: normalisedW, resolvedStyle: "normal" };
18332
+ }
18333
+ const sameStyleWeights = [];
18334
+ for (const vKey of variants.keys()) {
18335
+ const [_w, _s] = vKey.split("_");
18336
+ if (_s === normalisedS) {
18337
+ sameStyleWeights.push(weightToNumber(_w));
18338
+ }
18339
+ }
18340
+ const sameStyleNearest = nearestWeight(sameStyleWeights, weightToNumber(normalisedW));
18341
+ if (sameStyleNearest !== null) {
18342
+ key = variantKey(String(sameStyleNearest), normalisedS);
18343
+ font = variants.get(key);
18344
+ if (font)
18345
+ return { font, resolvedWeight: String(sameStyleNearest), resolvedStyle: normalisedS };
18346
+ }
18347
+ if (normalisedS === "italic") {
18348
+ const normalWeights = [];
18349
+ for (const vKey of variants.keys()) {
18350
+ const [_w, _s] = vKey.split("_");
18351
+ if (_s === "normal") {
18352
+ normalWeights.push(weightToNumber(_w));
18353
+ }
18354
+ }
18355
+ const normalNearest = nearestWeight(normalWeights, weightToNumber(normalisedW));
18356
+ if (normalNearest !== null) {
18357
+ key = variantKey(String(normalNearest), "normal");
18358
+ font = variants.get(key);
18359
+ if (font)
18360
+ return { font, resolvedWeight: String(normalNearest), resolvedStyle: "normal" };
18361
+ }
18362
+ }
18363
+ const firstKey = variants.keys().next().value;
18364
+ if (firstKey) {
18365
+ font = variants.get(firstKey);
18366
+ if (font) {
18367
+ const [_w, _s] = firstKey.split("_");
18368
+ return { font, resolvedWeight: _w, resolvedStyle: _s };
18369
+ }
18370
+ }
18371
+ return null;
18243
18372
  }
18244
18373
  getMetrics(fontFamily, fontSize, weight = "normal", style = "normal") {
18245
- const key = cacheKey2(fontFamily, weight, style);
18246
- const metricsKey = `${key}_${fontSize}_${this.mode}`;
18374
+ const _process2 = typeof globalThis !== "undefined" ? globalThis.process : undefined;
18375
+ if (this.pendingRegistrations.size > 0 && _process2?.env?.NODE_ENV !== "production") {
18376
+ console.warn("[vyaz] getMetrics() called while registerFont() promises are still pending. Wait for registerFont() to resolve before layout to avoid inaccurate metrics. Use fontMetricsProvider.waitForPendingRegistrations() if needed.");
18377
+ }
18378
+ const normalisedW = normaliseWeight(weight);
18379
+ const metricsKey = `${fontFamily}_${normalisedW}_${style}_${fontSize}_${this.mode}`;
18247
18380
  const cached = this.metricsCache.get(metricsKey);
18248
18381
  if (cached)
18249
18382
  return cached;
18250
- const font = this.cache.get(key);
18251
- if (font) {
18383
+ const resolved = this._resolveFont(fontFamily, weight, style);
18384
+ if (resolved) {
18385
+ const font = resolved.font;
18252
18386
  const scale = fontSize / font.unitsPerEm;
18253
18387
  let ascent;
18254
18388
  let descent;
@@ -18272,13 +18406,12 @@ class FontMetricsProvider {
18272
18406
  this.metricsCache.set(metricsKey, metrics);
18273
18407
  return metrics;
18274
18408
  }
18275
- if (this.cache.size > 0) {
18409
+ if (this.registry.has(fontFamily)) {
18276
18410
  throw new FontNotFoundError(fontFamily, weight, style);
18277
18411
  }
18278
18412
  if (typeof document !== "undefined") {
18279
18413
  try {
18280
- const canvas = document.createElement("canvas");
18281
- const ctx = canvas.getContext("2d");
18414
+ const ctx = this._getMeasureContext();
18282
18415
  ctx.font = `${style} ${weight} ${fontSize}px ${fontFamily}`;
18283
18416
  const m = ctx.measureText("M");
18284
18417
  const metrics = {
@@ -24338,15 +24471,15 @@ function isLineStartCursor(cursor) {
24338
24471
  return cursor.segmentIndex === 0 && cursor.graphemeIndex === 0;
24339
24472
  }
24340
24473
  function getCollapsedSpaceWidth(font, letterSpacing, cache) {
24341
- const cacheKey3 = `${font}\x00${letterSpacing}`;
24342
- const cached = cache.get(cacheKey3);
24474
+ const cacheKey2 = `${font}\x00${letterSpacing}`;
24475
+ const cached = cache.get(cacheKey2);
24343
24476
  if (cached !== undefined)
24344
24477
  return cached;
24345
24478
  const options = letterSpacing === 0 ? undefined : { letterSpacing };
24346
24479
  const joinedWidth = measureNaturalWidth(prepareWithSegments("A A", font, options));
24347
24480
  const compactWidth = measureNaturalWidth(prepareWithSegments("AA", font, options));
24348
24481
  const collapsedWidth = Math.max(0, joinedWidth - compactWidth);
24349
- cache.set(cacheKey3, collapsedWidth);
24482
+ cache.set(cacheKey2, collapsedWidth);
24350
24483
  return collapsedWidth;
24351
24484
  }
24352
24485
  function prepareWholeItemLine(prepared) {
@@ -24591,11 +24724,11 @@ class ParagraphLayoutEngine {
24591
24724
  layout(paragraph, maxWidth, yOffset = 0, fontProvider, listStyle, listIndex, listMarkerWidth) {
24592
24725
  const provider = fontProvider || fontMetricsProvider;
24593
24726
  const items = compileParagraph(paragraph);
24594
- const cacheKey3 = JSON.stringify(paragraph);
24595
- let prepared = this.preparedCache.get(cacheKey3);
24727
+ const cacheKey2 = JSON.stringify(paragraph);
24728
+ let prepared = this.preparedCache.get(cacheKey2);
24596
24729
  if (!prepared) {
24597
24730
  prepared = prepareRichInline(items);
24598
- this.preparedCache.set(cacheKey3, prepared);
24731
+ this.preparedCache.set(cacheKey2, prepared);
24599
24732
  }
24600
24733
  const effectiveMaxWidth = paragraph.style.whiteSpace === "nowrap" ? Infinity : maxWidth;
24601
24734
  const pretextLines = [];
package/dist/index.d.ts CHANGED
@@ -5,7 +5,46 @@
5
5
  * layout engines, font metric providers, renderers, and utilities.
6
6
  */
7
7
  export type { TextFrame, Paragraph, ParagraphStyle, TextRun, InlineWidget, AutofitConfig, TextAlignment, WritingMode, TextOrientation, VerticalAlignment, ScriptType, WhiteSpace, MultiColumnConfig, DominantBaseline, LineFitEdge, TextAlignLast, WordBreak, LineBreak, OverflowWrap, TextDecorationStyle, TextTransform, ListType, NumberFormat, ListStylePosition, ListStyle, } from './types/Document.js';
8
- export { DEFAULT_PARAGRAPH_STYLE, DEFAULT_TEXT_STYLE, } from './types/Document.js';
8
+ export declare const DEFAULT_PARAGRAPH_STYLE: {
9
+ alignment: import("./index.js").TextAlignment;
10
+ lineHeight: number;
11
+ spaceBefore: number;
12
+ spaceAfter: number;
13
+ indent?: number;
14
+ leftIndent?: number;
15
+ rightIndent?: number;
16
+ textIndent?: number;
17
+ letterSpacing?: number;
18
+ textAlignLast?: import("./index.js").TextAlignLast;
19
+ wordBreak?: import("./index.js").WordBreak;
20
+ lineBreak?: import("./index.js").LineBreak;
21
+ overflowWrap?: import("./index.js").OverflowWrap;
22
+ hyphens?: boolean;
23
+ whiteSpace?: import("./index.js").WhiteSpace;
24
+ listStyle?: import("./index.js").ListStyle;
25
+ listRestart?: boolean;
26
+ };
27
+ export declare const DEFAULT_TEXT_STYLE: {
28
+ type?: "text" | "inline-box" | undefined;
29
+ text?: string | undefined;
30
+ inlineWidget?: import("./index.js").InlineWidget | undefined;
31
+ fontFamily?: string | undefined;
32
+ fontSize?: number | undefined;
33
+ fontWeight?: number | "bold" | "normal" | undefined;
34
+ fontStyle?: "normal" | "italic" | undefined;
35
+ color?: string | undefined;
36
+ backgroundColor?: string | undefined;
37
+ letterSpacing?: number | undefined;
38
+ script?: import("./index.js").ScriptType | undefined;
39
+ underline?: boolean | undefined;
40
+ strikethrough?: boolean | undefined;
41
+ overline?: boolean | undefined;
42
+ textDecorationStyle?: import("./index.js").TextDecorationStyle | undefined;
43
+ textDecorationColor?: string | undefined;
44
+ textTransform?: import("./index.js").TextTransform | undefined;
45
+ fullWidth?: boolean | undefined;
46
+ fullSizeKana?: boolean | undefined;
47
+ };
9
48
  export type { ParagraphLayoutResult, Line, Span, SpanFontMetrics, SemanticParagraph, SemanticLine, SemanticFragment, } from './types/LayoutTypes.js';
10
49
  export type { FontMetrics, IFontMetricsProvider, GlyphData, } from './types/FontTypes.js';
11
50
  export { ParagraphLayoutEngine, paragraphLayoutEngine } from './layout/ParagraphLayoutEngine.js';
@@ -19,7 +58,12 @@ export type { AutoFitOptions, AutoFitResult } from './layout/AutoFitEngine.js';
19
58
  export { groupLinesByParagraph } from './utils/groupLinesByParagraph.js';
20
59
  export type { ParagraphGroup } from './utils/groupLinesByParagraph.js';
21
60
  export { transformText } from './utils/textTransform.js';
22
- export { formatListNumber, defaultBulletChar, BULLET_CHARACTERS } from './utils/list.js';
61
+ import { formatListNumber as _fln, defaultBulletChar as _dbc } from './utils/list.js';
62
+ export declare const formatListNumber: typeof _fln;
63
+ export declare const defaultBulletChar: typeof _dbc;
64
+ export declare const BULLET_CHARACTERS: {
65
+ [x: number]: string;
66
+ };
23
67
  export { compileParagraph, getParagraphText, makeFontToken, splitParagraphByHardBreaks, collapseSegmentWhitespace } from './compile/DocumentCompiler.js';
24
68
  export type { PreparedRichInlineItem } from './compile/DocumentCompiler.js';
25
69
  export type { FontFace } from './measure/FontEngine.js';