pa_font 0.2.1 → 0.2.2
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/README.md +39 -0
- package/USAGE.md +294 -0
- package/dist/paFont.cjs +453 -108
- package/dist/paFont.cjs.map +1 -1
- package/dist/paFont.js +453 -108
- package/dist/paFont.js.map +1 -1
- package/paFont.d.ts +36 -2
- package/package.json +3 -2
package/dist/paFont.js
CHANGED
|
@@ -3186,61 +3186,95 @@ var sharedGraphemeSegmenter = null;
|
|
|
3186
3186
|
function layoutParagraph(fontInstance, text, options = {}, state = {}) {
|
|
3187
3187
|
const normalized = normalizeParagraphOptions(fontInstance, options);
|
|
3188
3188
|
const textValue = String(text ?? "");
|
|
3189
|
-
const
|
|
3190
|
-
const
|
|
3191
|
-
const
|
|
3192
|
-
const
|
|
3189
|
+
const layoutBox = resolveLayoutBox(normalized, state);
|
|
3190
|
+
const retainedPreparedState = resolveRetainedPreparedState(state, normalized);
|
|
3191
|
+
const pretextState = shouldAttemptPretextLayout(normalized) ? layoutWithPretext(textValue, normalized, retainedPreparedState, layoutBox) : null;
|
|
3192
|
+
const layoutState = pretextState != null && canUsePretextLayout(pretextState, normalized) ? pretextState : layoutWithNative(fontInstance, textValue, normalized, layoutBox);
|
|
3193
|
+
const measureWidth = createLazyTextMeasurer(fontInstance, normalized);
|
|
3194
|
+
const lines = positionLines(fontInstance, applyOverflowClamping(layoutState.lines, normalized, layoutBox, measureWidth), normalized, layoutBox, measureWidth);
|
|
3195
|
+
const textBBox = combineRects(lines.map((line) => line.bbox)) ?? emptyRect();
|
|
3196
|
+
const textWidth = lines.reduce((max, line) => Math.max(max, line.width), 0);
|
|
3197
|
+
const textHeight = lines.length * normalized.lineHeight;
|
|
3198
|
+
const finalLayoutBox = finalizeLayoutBox(layoutBox, normalized, textHeight);
|
|
3199
|
+
const cachedPrepared = pretextState?.prepared ?? retainedPreparedState.prepared ?? null;
|
|
3200
|
+
const cachedPreparedWhiteSpace = pretextState?.preparedWhiteSpace ?? retainedPreparedState.preparedWhiteSpace ?? null;
|
|
3193
3201
|
return {
|
|
3194
3202
|
options: normalized,
|
|
3195
3203
|
lines,
|
|
3196
3204
|
metrics: {
|
|
3197
|
-
x:
|
|
3198
|
-
y:
|
|
3199
|
-
width,
|
|
3200
|
-
height:
|
|
3205
|
+
x: finalLayoutBox.contentBox.x,
|
|
3206
|
+
y: finalLayoutBox.contentBox.y,
|
|
3207
|
+
width: textWidth,
|
|
3208
|
+
height: textHeight,
|
|
3201
3209
|
lineCount: lines.length,
|
|
3202
|
-
bbox
|
|
3210
|
+
bbox: textBBox,
|
|
3211
|
+
contentBox: { ...finalLayoutBox.contentBox },
|
|
3212
|
+
paddingBox: { ...finalLayoutBox.paddingBox },
|
|
3213
|
+
marginBox: { ...finalLayoutBox.marginBox },
|
|
3214
|
+
clipBox: { ...finalLayoutBox.clipBox }
|
|
3203
3215
|
},
|
|
3204
|
-
prepared:
|
|
3205
|
-
preparedWhiteSpace:
|
|
3206
|
-
layoutEngine: layoutState.layoutEngine
|
|
3216
|
+
prepared: cachedPrepared,
|
|
3217
|
+
preparedWhiteSpace: cachedPreparedWhiteSpace,
|
|
3218
|
+
layoutEngine: layoutState.layoutEngine,
|
|
3219
|
+
layoutBox: finalLayoutBox,
|
|
3220
|
+
containerWidth: layoutBox.containerWidth,
|
|
3221
|
+
containerHeight: layoutBox.containerHeight
|
|
3207
3222
|
};
|
|
3208
3223
|
}
|
|
3209
3224
|
function normalizeParagraphOptions(fontInstance, options = {}) {
|
|
3210
3225
|
if (options == null || typeof options !== "object" || Array.isArray(options)) throw new TypeError("font.paragraph() options must be an object.");
|
|
3211
3226
|
const textOptions = normalizeTextOptions(options);
|
|
3212
|
-
const
|
|
3213
|
-
|
|
3227
|
+
const wrapDefaults = resolveWrapDefaults(normalizeNullableEnum(options.wrap, [
|
|
3228
|
+
"word",
|
|
3229
|
+
"char",
|
|
3230
|
+
"keep"
|
|
3231
|
+
], null));
|
|
3232
|
+
const width = normalizeDimension(options.width);
|
|
3233
|
+
const height = normalizeDimension(options.height);
|
|
3234
|
+
if (options.width != null && width == null) throw new TypeError("font.paragraph() option \"width\" must be a positive number.");
|
|
3235
|
+
if (options.height != null && height == null) throw new TypeError("font.paragraph() option \"height\" must be a positive number.");
|
|
3214
3236
|
const font = resolveCanvasFont(fontInstance, textOptions.size, options);
|
|
3215
|
-
const lineHeight = resolveLineHeight(options.lineHeight, textOptions.size);
|
|
3216
|
-
const align = normalizeEnum(options.align, [
|
|
3217
|
-
"left",
|
|
3218
|
-
"center",
|
|
3219
|
-
"right",
|
|
3220
|
-
"justify"
|
|
3221
|
-
], "left");
|
|
3222
|
-
const whiteSpace = normalizeEnum(options.whiteSpace, [
|
|
3223
|
-
"normal",
|
|
3224
|
-
"pre-wrap",
|
|
3225
|
-
"nowrap"
|
|
3226
|
-
], "normal");
|
|
3227
|
-
const overflowWrap = normalizeEnum(options.overflowWrap, [
|
|
3228
|
-
"normal",
|
|
3229
|
-
"break-word",
|
|
3230
|
-
"anywhere"
|
|
3231
|
-
], "break-word");
|
|
3232
|
-
const engine = normalizeEnum(options.engine, ["pretext", "native"], "pretext");
|
|
3233
|
-
const maxLines = normalizeMaxLines(options.maxLines);
|
|
3234
|
-
const ellipsis = normalizeEllipsis(options.ellipsis);
|
|
3235
3237
|
return {
|
|
3236
3238
|
...textOptions,
|
|
3239
|
+
wrap: resolveWrapPreset(normalizeEnum(options.wordBreak, [
|
|
3240
|
+
"normal",
|
|
3241
|
+
"break-all",
|
|
3242
|
+
"keep-all"
|
|
3243
|
+
], wrapDefaults.wordBreak), normalizeEnum(options.overflowWrap, [
|
|
3244
|
+
"normal",
|
|
3245
|
+
"break-word",
|
|
3246
|
+
"anywhere"
|
|
3247
|
+
], wrapDefaults.overflowWrap)),
|
|
3237
3248
|
width,
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3249
|
+
height,
|
|
3250
|
+
lineHeight: resolveLineHeight(options.lineHeight, textOptions.size),
|
|
3251
|
+
align: normalizeEnum(options.align, [
|
|
3252
|
+
"left",
|
|
3253
|
+
"center",
|
|
3254
|
+
"right",
|
|
3255
|
+
"justify"
|
|
3256
|
+
], "left"),
|
|
3257
|
+
whiteSpace: normalizeEnum(options.whiteSpace, [
|
|
3258
|
+
"normal",
|
|
3259
|
+
"pre-wrap",
|
|
3260
|
+
"nowrap"
|
|
3261
|
+
], "normal"),
|
|
3262
|
+
overflowWrap: normalizeEnum(options.overflowWrap, [
|
|
3263
|
+
"normal",
|
|
3264
|
+
"break-word",
|
|
3265
|
+
"anywhere"
|
|
3266
|
+
], wrapDefaults.overflowWrap),
|
|
3267
|
+
wordBreak: normalizeEnum(options.wordBreak, [
|
|
3268
|
+
"normal",
|
|
3269
|
+
"break-all",
|
|
3270
|
+
"keep-all"
|
|
3271
|
+
], wrapDefaults.wordBreak),
|
|
3272
|
+
overflow: normalizeEnum(options.overflow, ["visible", "hidden"], "visible"),
|
|
3273
|
+
textOverflow: normalizeNullableEnum(options.textOverflow, ["clip", "ellipsis"], null),
|
|
3274
|
+
maxLines: normalizeMaxLines(options.maxLines),
|
|
3275
|
+
ellipsis: normalizeEllipsis(options.ellipsis),
|
|
3276
|
+
margin: normalizeSpacing(options.margin),
|
|
3277
|
+
padding: normalizeSpacing(options.padding),
|
|
3244
3278
|
fontStyle: normalizeEnum(options.fontStyle, [
|
|
3245
3279
|
"normal",
|
|
3246
3280
|
"italic",
|
|
@@ -3249,7 +3283,7 @@ function normalizeParagraphOptions(fontInstance, options = {}) {
|
|
|
3249
3283
|
fontWeight: typeof options.fontWeight === "string" || Number.isFinite(options.fontWeight) ? options.fontWeight : 400,
|
|
3250
3284
|
fontFamily: resolveFontFamily(fontInstance, options.fontFamily),
|
|
3251
3285
|
font,
|
|
3252
|
-
engine
|
|
3286
|
+
engine: normalizeEnum(options.engine, ["pretext", "native"], "pretext")
|
|
3253
3287
|
};
|
|
3254
3288
|
}
|
|
3255
3289
|
function resolveCanvasFont(fontInstance, size, options = {}) {
|
|
@@ -3263,11 +3297,12 @@ function resolveCanvasFont(fontInstance, size, options = {}) {
|
|
|
3263
3297
|
function canReusePreparedParagraphState(previousState, previousOptions, nextOptions) {
|
|
3264
3298
|
return previousState?.prepared != null && previousState.preparedWhiteSpace === resolvePretextWhiteSpace(nextOptions.whiteSpace) && previousOptions?.font === nextOptions.font;
|
|
3265
3299
|
}
|
|
3266
|
-
function layoutWithPretext(
|
|
3300
|
+
function layoutWithPretext(text, options, state, layoutBox) {
|
|
3267
3301
|
const preparedWhiteSpace = resolvePretextWhiteSpace(options.whiteSpace);
|
|
3268
3302
|
const prepared = state.prepared != null && state.preparedWhiteSpace === preparedWhiteSpace && state.font === options.font ? state.prepared : prepareWithSegments(text, options.font, { whiteSpace: preparedWhiteSpace });
|
|
3303
|
+
const layout = layoutWithLines(prepared, options.whiteSpace === "nowrap" ? HUGE_LAYOUT_WIDTH : layoutBox.contentWidth, options.lineHeight);
|
|
3269
3304
|
return {
|
|
3270
|
-
lines:
|
|
3305
|
+
lines: layout.lines.map((line) => ({
|
|
3271
3306
|
text: line.text,
|
|
3272
3307
|
width: line.width,
|
|
3273
3308
|
start: null,
|
|
@@ -3277,10 +3312,10 @@ function layoutWithPretext(fontInstance, text, options, state) {
|
|
|
3277
3312
|
prepared,
|
|
3278
3313
|
preparedWhiteSpace,
|
|
3279
3314
|
layoutEngine: "pretext",
|
|
3280
|
-
|
|
3315
|
+
usedOverflowWrapFallbackBreaks: layout.lines.some((line) => line.end.graphemeIndex > 0)
|
|
3281
3316
|
};
|
|
3282
3317
|
}
|
|
3283
|
-
function layoutWithNative(fontInstance, text, options) {
|
|
3318
|
+
function layoutWithNative(fontInstance, text, options, layoutBox) {
|
|
3284
3319
|
const measuredWidth = createOpenTypeMeasurer(fontInstance, options);
|
|
3285
3320
|
const source = normalizeNativeText(text, options.whiteSpace);
|
|
3286
3321
|
if (source.length === 0) return {
|
|
@@ -3301,7 +3336,7 @@ function layoutWithNative(fontInstance, text, options) {
|
|
|
3301
3336
|
preparedWhiteSpace: null,
|
|
3302
3337
|
layoutEngine: "native"
|
|
3303
3338
|
};
|
|
3304
|
-
const tokens = tokenizeNativeText(source, options.whiteSpace, measuredWidth);
|
|
3339
|
+
const tokens = tokenizeNativeText(source, options.whiteSpace, measuredWidth, options.wordBreak);
|
|
3305
3340
|
const lines = [];
|
|
3306
3341
|
let currentText = "";
|
|
3307
3342
|
let currentWidth = 0;
|
|
@@ -3329,12 +3364,12 @@ function layoutWithNative(fontInstance, text, options) {
|
|
|
3329
3364
|
currentEnd = piece.end;
|
|
3330
3365
|
};
|
|
3331
3366
|
const appendWrappedToken = (token) => {
|
|
3332
|
-
if (options.overflowWrap === "normal") {
|
|
3367
|
+
if (options.overflowWrap === "normal" && options.wordBreak !== "break-all") {
|
|
3333
3368
|
appendPiece(token);
|
|
3334
3369
|
return;
|
|
3335
3370
|
}
|
|
3336
3371
|
splitIntoGraphemePieces(token, measuredWidth).forEach((piece) => {
|
|
3337
|
-
if (currentText.length > 0 && currentWidth + piece.width >
|
|
3372
|
+
if (currentText.length > 0 && currentWidth + piece.width > layoutBox.contentWidth + JUSTIFY_EPSILON) pushCurrentLine(false, piece.start);
|
|
3338
3373
|
appendPiece(piece);
|
|
3339
3374
|
});
|
|
3340
3375
|
};
|
|
@@ -3344,7 +3379,7 @@ function layoutWithNative(fontInstance, text, options) {
|
|
|
3344
3379
|
return;
|
|
3345
3380
|
}
|
|
3346
3381
|
if (token.type === "space" && currentText.length === 0 && options.whiteSpace !== "pre-wrap") return;
|
|
3347
|
-
if (currentText.length === 0 || currentWidth + token.width <=
|
|
3382
|
+
if (currentText.length === 0 || currentWidth + token.width <= layoutBox.contentWidth + JUSTIFY_EPSILON) {
|
|
3348
3383
|
appendPiece(token);
|
|
3349
3384
|
return;
|
|
3350
3385
|
}
|
|
@@ -3381,45 +3416,69 @@ function splitIntoGraphemePieces(token, measureWidth) {
|
|
|
3381
3416
|
}
|
|
3382
3417
|
return pieces.length > 0 ? pieces : [token];
|
|
3383
3418
|
}
|
|
3384
|
-
function
|
|
3385
|
-
|
|
3386
|
-
const
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
const
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3419
|
+
function applyOverflowClamping(lines, options, layoutBox, measureWidth) {
|
|
3420
|
+
const contentWidth = layoutBox.contentWidth;
|
|
3421
|
+
const result = lines.map((line) => ({ ...line }));
|
|
3422
|
+
let lineLimit = options.maxLines;
|
|
3423
|
+
if (options.overflow === "hidden" && layoutBox.clipContentHeight != null) {
|
|
3424
|
+
const visibleLineCount = Math.max(0, Math.floor((layoutBox.clipContentHeight + JUSTIFY_EPSILON) / options.lineHeight));
|
|
3425
|
+
lineLimit = lineLimit == null ? visibleLineCount : Math.min(lineLimit, visibleLineCount);
|
|
3426
|
+
}
|
|
3427
|
+
let clippedByCount = false;
|
|
3428
|
+
if (lineLimit != null) {
|
|
3429
|
+
if (lineLimit <= 0) return [];
|
|
3430
|
+
if (result.length > lineLimit) {
|
|
3431
|
+
result.length = lineLimit;
|
|
3432
|
+
clippedByCount = true;
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
if (result.length === 0) return result;
|
|
3436
|
+
if (options.overflow === "hidden" && options.whiteSpace === "nowrap" && result[0].width > contentWidth + JUSTIFY_EPSILON) result[0] = truncateLineToWidth(result[0], contentWidth, measureWidth, options.textOverflow === "ellipsis" ? options.ellipsis : false);
|
|
3437
|
+
else if (clippedByCount && shouldEllipsizeClampedLines(options)) {
|
|
3438
|
+
const lastIndex = result.length - 1;
|
|
3439
|
+
result[lastIndex] = truncateLineToWidth(result[lastIndex], contentWidth, measureWidth, options.ellipsis);
|
|
3440
|
+
result[lastIndex].hardBreak = false;
|
|
3441
|
+
}
|
|
3442
|
+
return result;
|
|
3443
|
+
}
|
|
3444
|
+
function truncateLineToWidth(line, maxWidth, measureWidth, suffix) {
|
|
3445
|
+
const suffixText = suffix === false ? "" : suffix;
|
|
3446
|
+
if ((suffixText.length > 0 ? measureWidth(suffixText) : 0) > maxWidth + JUSTIFY_EPSILON) return {
|
|
3447
|
+
...line,
|
|
3448
|
+
text: "",
|
|
3449
|
+
width: 0,
|
|
3450
|
+
hardBreak: false
|
|
3451
|
+
};
|
|
3452
|
+
let nextText = trimTrailingWhitespace(line.text);
|
|
3453
|
+
while (nextText.length > 0 && measureWidth(`${nextText}${suffixText}`) > maxWidth + JUSTIFY_EPSILON) nextText = trimLastGrapheme(nextText);
|
|
3454
|
+
const text = `${nextText}${suffixText}`;
|
|
3455
|
+
return {
|
|
3456
|
+
...line,
|
|
3457
|
+
text,
|
|
3458
|
+
width: measureWidth(text),
|
|
3459
|
+
hardBreak: false
|
|
3460
|
+
};
|
|
3461
|
+
}
|
|
3462
|
+
function shouldEllipsizeClampedLines(options) {
|
|
3463
|
+
return options.textOverflow === "ellipsis" || options.textOverflow == null && options.maxLines != null && options.ellipsis !== false;
|
|
3464
|
+
}
|
|
3465
|
+
function positionLines(fontInstance, lines, options, layoutBox, measureWidth) {
|
|
3406
3466
|
const ascent = getFontAscender(fontInstance, options.size);
|
|
3407
3467
|
const lineBoxHeight = options.lineHeight;
|
|
3408
|
-
const measureWidth = createTextMeasurer(fontInstance, options);
|
|
3409
3468
|
let cursor = 0;
|
|
3410
3469
|
return lines.map((line, index) => {
|
|
3411
3470
|
const justified = shouldJustifyLine(line, index, lines.length, options);
|
|
3412
|
-
const offsetX = justified ? 0 : resolveAlignOffset(options.align, line.width,
|
|
3413
|
-
const x =
|
|
3414
|
-
const y =
|
|
3471
|
+
const offsetX = justified ? 0 : resolveAlignOffset(options.align, line.width, layoutBox.contentWidth);
|
|
3472
|
+
const x = layoutBox.contentX + offsetX;
|
|
3473
|
+
const y = layoutBox.contentY + index * lineBoxHeight;
|
|
3415
3474
|
const baseline = y + ascent;
|
|
3416
|
-
const fragments = justified ? buildJustifiedFragments(line,
|
|
3475
|
+
const fragments = justified ? buildJustifiedFragments(line, layoutBox.contentX, layoutBox.contentWidth, measureWidth) : [{
|
|
3417
3476
|
text: line.text,
|
|
3418
3477
|
x,
|
|
3419
3478
|
width: line.width,
|
|
3420
3479
|
isWhitespace: false
|
|
3421
3480
|
}];
|
|
3422
|
-
const width = justified ?
|
|
3481
|
+
const width = justified ? layoutBox.contentWidth : line.width;
|
|
3423
3482
|
const bbox = {
|
|
3424
3483
|
x,
|
|
3425
3484
|
y,
|
|
@@ -3444,7 +3503,7 @@ function positionLines(fontInstance, lines, options) {
|
|
|
3444
3503
|
return positioned;
|
|
3445
3504
|
});
|
|
3446
3505
|
}
|
|
3447
|
-
function buildJustifiedFragments(line,
|
|
3506
|
+
function buildJustifiedFragments(line, contentX, contentWidth, measureWidth) {
|
|
3448
3507
|
const tokens = splitPreservingWhitespace(line.text);
|
|
3449
3508
|
const expandable = tokens.reduce((count, token, index) => {
|
|
3450
3509
|
if (index > 0 && index < tokens.length - 1 && /\s/u.test(token)) return count + 1;
|
|
@@ -3452,13 +3511,13 @@ function buildJustifiedFragments(line, options, measureWidth) {
|
|
|
3452
3511
|
}, 0);
|
|
3453
3512
|
if (expandable === 0) return [{
|
|
3454
3513
|
text: line.text,
|
|
3455
|
-
x:
|
|
3514
|
+
x: contentX,
|
|
3456
3515
|
width: line.width,
|
|
3457
3516
|
isWhitespace: false
|
|
3458
3517
|
}];
|
|
3459
|
-
const extraPerGap = Math.max(0,
|
|
3518
|
+
const extraPerGap = Math.max(0, contentWidth - line.width) / expandable;
|
|
3460
3519
|
const fragments = [];
|
|
3461
|
-
let cursorX =
|
|
3520
|
+
let cursorX = contentX;
|
|
3462
3521
|
tokens.forEach((token, index) => {
|
|
3463
3522
|
const isWhitespace = /\s/u.test(token);
|
|
3464
3523
|
const isExpandable = isWhitespace && index > 0 && index < tokens.length - 1;
|
|
@@ -3486,20 +3545,169 @@ function resolveLineHeight(value, size) {
|
|
|
3486
3545
|
if (value <= 10) return size * value;
|
|
3487
3546
|
return value;
|
|
3488
3547
|
}
|
|
3548
|
+
function resolveLayoutBox(options, state = {}) {
|
|
3549
|
+
const margin = options.margin;
|
|
3550
|
+
const padding = options.padding;
|
|
3551
|
+
const containerWidth = resolveContainerDimension(state.containerWidth, typeof window !== "undefined" ? window.innerWidth : null);
|
|
3552
|
+
const containerHeight = resolveContainerDimension(state.containerHeight, typeof window !== "undefined" ? window.innerHeight : null);
|
|
3553
|
+
const contentWidth = options.width ?? Math.max(1, (containerWidth ?? 0) - options.x - margin.left - margin.right - padding.left - padding.right);
|
|
3554
|
+
const contentX = options.x + margin.left + padding.left;
|
|
3555
|
+
const contentY = options.y + margin.top + padding.top;
|
|
3556
|
+
return {
|
|
3557
|
+
containerWidth,
|
|
3558
|
+
containerHeight,
|
|
3559
|
+
contentWidth,
|
|
3560
|
+
clipContentHeight: options.height,
|
|
3561
|
+
contentX,
|
|
3562
|
+
contentY,
|
|
3563
|
+
margin,
|
|
3564
|
+
padding,
|
|
3565
|
+
contentBox: {
|
|
3566
|
+
x: contentX,
|
|
3567
|
+
y: contentY,
|
|
3568
|
+
w: contentWidth,
|
|
3569
|
+
h: options.height ?? 0
|
|
3570
|
+
},
|
|
3571
|
+
paddingBox: {
|
|
3572
|
+
x: options.x + margin.left,
|
|
3573
|
+
y: options.y + margin.top,
|
|
3574
|
+
w: contentWidth + padding.left + padding.right,
|
|
3575
|
+
h: (options.height ?? 0) + padding.top + padding.bottom
|
|
3576
|
+
},
|
|
3577
|
+
marginBox: {
|
|
3578
|
+
x: options.x,
|
|
3579
|
+
y: options.y,
|
|
3580
|
+
w: contentWidth + padding.left + padding.right + margin.left + margin.right,
|
|
3581
|
+
h: (options.height ?? 0) + padding.top + padding.bottom + margin.top + margin.bottom
|
|
3582
|
+
},
|
|
3583
|
+
clipBox: {
|
|
3584
|
+
x: contentX,
|
|
3585
|
+
y: contentY,
|
|
3586
|
+
w: contentWidth,
|
|
3587
|
+
h: options.height ?? 0
|
|
3588
|
+
}
|
|
3589
|
+
};
|
|
3590
|
+
}
|
|
3591
|
+
function finalizeLayoutBox(layoutBox, options, textHeight) {
|
|
3592
|
+
const contentHeight = options.height ?? textHeight;
|
|
3593
|
+
return {
|
|
3594
|
+
...layoutBox,
|
|
3595
|
+
contentBox: {
|
|
3596
|
+
x: layoutBox.contentX,
|
|
3597
|
+
y: layoutBox.contentY,
|
|
3598
|
+
w: layoutBox.contentWidth,
|
|
3599
|
+
h: contentHeight
|
|
3600
|
+
},
|
|
3601
|
+
paddingBox: {
|
|
3602
|
+
x: layoutBox.paddingBox.x,
|
|
3603
|
+
y: layoutBox.paddingBox.y,
|
|
3604
|
+
w: layoutBox.paddingBox.w,
|
|
3605
|
+
h: contentHeight + layoutBox.padding.top + layoutBox.padding.bottom
|
|
3606
|
+
},
|
|
3607
|
+
marginBox: {
|
|
3608
|
+
x: layoutBox.marginBox.x,
|
|
3609
|
+
y: layoutBox.marginBox.y,
|
|
3610
|
+
w: layoutBox.marginBox.w,
|
|
3611
|
+
h: contentHeight + layoutBox.padding.top + layoutBox.padding.bottom + layoutBox.margin.top + layoutBox.margin.bottom
|
|
3612
|
+
},
|
|
3613
|
+
clipBox: {
|
|
3614
|
+
x: layoutBox.contentX,
|
|
3615
|
+
y: layoutBox.contentY,
|
|
3616
|
+
w: layoutBox.contentWidth,
|
|
3617
|
+
h: options.overflow === "hidden" ? contentHeight : textHeight
|
|
3618
|
+
}
|
|
3619
|
+
};
|
|
3620
|
+
}
|
|
3621
|
+
function resolveContainerDimension(explicit, fallback) {
|
|
3622
|
+
if (Number.isFinite(explicit) && explicit > 0) return explicit;
|
|
3623
|
+
if (Number.isFinite(fallback) && fallback > 0) return fallback;
|
|
3624
|
+
return null;
|
|
3625
|
+
}
|
|
3489
3626
|
function normalizeEnum(value, supported, fallback) {
|
|
3490
3627
|
return typeof value === "string" && supported.includes(value) ? value : fallback;
|
|
3491
3628
|
}
|
|
3629
|
+
function normalizeNullableEnum(value, supported, fallback) {
|
|
3630
|
+
if (value == null) return fallback;
|
|
3631
|
+
return typeof value === "string" && supported.includes(value) ? value : fallback;
|
|
3632
|
+
}
|
|
3492
3633
|
function normalizeEllipsis(value) {
|
|
3493
|
-
if (value === false
|
|
3494
|
-
|
|
3634
|
+
if (value === false) return false;
|
|
3635
|
+
if (typeof value === "string") return value;
|
|
3636
|
+
return "…";
|
|
3495
3637
|
}
|
|
3496
3638
|
function normalizeMaxLines(value) {
|
|
3497
3639
|
if (!Number.isFinite(value)) return null;
|
|
3498
3640
|
const rounded = Math.floor(value);
|
|
3499
3641
|
return rounded > 0 ? rounded : null;
|
|
3500
3642
|
}
|
|
3643
|
+
function normalizeDimension(value) {
|
|
3644
|
+
return Number.isFinite(value) && value > 0 ? value : null;
|
|
3645
|
+
}
|
|
3646
|
+
function normalizeSpacing(value) {
|
|
3647
|
+
if (value == null) return zeroSpacing();
|
|
3648
|
+
if (Number.isFinite(value)) {
|
|
3649
|
+
const next = Number(value);
|
|
3650
|
+
return {
|
|
3651
|
+
top: next,
|
|
3652
|
+
right: next,
|
|
3653
|
+
bottom: next,
|
|
3654
|
+
left: next
|
|
3655
|
+
};
|
|
3656
|
+
}
|
|
3657
|
+
if (Array.isArray(value)) {
|
|
3658
|
+
const numbers = value.map((entry) => Number.isFinite(entry) ? Number(entry) : 0);
|
|
3659
|
+
if (numbers.length === 2) return {
|
|
3660
|
+
top: numbers[0],
|
|
3661
|
+
right: numbers[1],
|
|
3662
|
+
bottom: numbers[0],
|
|
3663
|
+
left: numbers[1]
|
|
3664
|
+
};
|
|
3665
|
+
if (numbers.length === 3) return {
|
|
3666
|
+
top: numbers[0],
|
|
3667
|
+
right: numbers[1],
|
|
3668
|
+
bottom: numbers[2],
|
|
3669
|
+
left: numbers[1]
|
|
3670
|
+
};
|
|
3671
|
+
if (numbers.length === 4) return {
|
|
3672
|
+
top: numbers[0],
|
|
3673
|
+
right: numbers[1],
|
|
3674
|
+
bottom: numbers[2],
|
|
3675
|
+
left: numbers[3]
|
|
3676
|
+
};
|
|
3677
|
+
if (numbers.length === 1) return {
|
|
3678
|
+
top: numbers[0],
|
|
3679
|
+
right: numbers[0],
|
|
3680
|
+
bottom: numbers[0],
|
|
3681
|
+
left: numbers[0]
|
|
3682
|
+
};
|
|
3683
|
+
}
|
|
3684
|
+
if (typeof value === "string") {
|
|
3685
|
+
const tokens = parseSpacingString(value);
|
|
3686
|
+
if (tokens != null) return normalizeSpacing(tokens);
|
|
3687
|
+
}
|
|
3688
|
+
if (typeof value === "object") {
|
|
3689
|
+
const horizontal = normalizeNumber(value.x, 0);
|
|
3690
|
+
const vertical = normalizeNumber(value.y, 0);
|
|
3691
|
+
return {
|
|
3692
|
+
top: normalizeNumber(value.top, vertical),
|
|
3693
|
+
right: normalizeNumber(value.right, horizontal),
|
|
3694
|
+
bottom: normalizeNumber(value.bottom, vertical),
|
|
3695
|
+
left: normalizeNumber(value.left, horizontal)
|
|
3696
|
+
};
|
|
3697
|
+
}
|
|
3698
|
+
return zeroSpacing();
|
|
3699
|
+
}
|
|
3700
|
+
function zeroSpacing() {
|
|
3701
|
+
return {
|
|
3702
|
+
top: 0,
|
|
3703
|
+
right: 0,
|
|
3704
|
+
bottom: 0,
|
|
3705
|
+
left: 0
|
|
3706
|
+
};
|
|
3707
|
+
}
|
|
3501
3708
|
function resolveFontFamily(fontInstance, fontFamily) {
|
|
3502
3709
|
if (typeof fontFamily === "string" && fontFamily.trim().length > 0) return fontFamily.trim();
|
|
3710
|
+
if (typeof fontInstance?.canvasFamily === "string" && fontInstance.canvasFamily.trim().length > 0) return fontInstance.canvasFamily.trim();
|
|
3503
3711
|
const preferred = fontInstance?.font?.names?.fullName?.en ?? fontInstance?.font?.names?.fontFamily?.en ?? fontInstance?.font?.familyName;
|
|
3504
3712
|
return typeof preferred === "string" && preferred.trim().length > 0 ? preferred.trim() : "sans-serif";
|
|
3505
3713
|
}
|
|
@@ -3510,6 +3718,42 @@ function formatFontFamily(value) {
|
|
|
3510
3718
|
function resolvePretextWhiteSpace(whiteSpace) {
|
|
3511
3719
|
return whiteSpace === "pre-wrap" ? "pre-wrap" : "normal";
|
|
3512
3720
|
}
|
|
3721
|
+
function resolveWrapDefaults(wrap) {
|
|
3722
|
+
if (wrap === "char") return {
|
|
3723
|
+
overflowWrap: "break-word",
|
|
3724
|
+
wordBreak: "break-all"
|
|
3725
|
+
};
|
|
3726
|
+
if (wrap === "keep") return {
|
|
3727
|
+
overflowWrap: "normal",
|
|
3728
|
+
wordBreak: "keep-all"
|
|
3729
|
+
};
|
|
3730
|
+
return {
|
|
3731
|
+
overflowWrap: "break-word",
|
|
3732
|
+
wordBreak: "normal"
|
|
3733
|
+
};
|
|
3734
|
+
}
|
|
3735
|
+
function resolveWrapPreset(wordBreak, overflowWrap) {
|
|
3736
|
+
if (wordBreak === "break-all") return "char";
|
|
3737
|
+
if (wordBreak === "keep-all" && overflowWrap === "normal") return "keep";
|
|
3738
|
+
if (wordBreak === "normal" && overflowWrap === "break-word") return "word";
|
|
3739
|
+
return null;
|
|
3740
|
+
}
|
|
3741
|
+
function shouldAttemptPretextLayout(options) {
|
|
3742
|
+
return options.engine === "pretext" && options.wordBreak === "normal";
|
|
3743
|
+
}
|
|
3744
|
+
function canUsePretextLayout(layoutState, options) {
|
|
3745
|
+
return !(options.overflowWrap === "normal" && layoutState.usedOverflowWrapFallbackBreaks);
|
|
3746
|
+
}
|
|
3747
|
+
function resolveRetainedPreparedState(state, options) {
|
|
3748
|
+
if (state.prepared != null && state.preparedWhiteSpace === resolvePretextWhiteSpace(options.whiteSpace) && state.font === options.font) return {
|
|
3749
|
+
prepared: state.prepared,
|
|
3750
|
+
preparedWhiteSpace: state.preparedWhiteSpace
|
|
3751
|
+
};
|
|
3752
|
+
return {
|
|
3753
|
+
prepared: null,
|
|
3754
|
+
preparedWhiteSpace: null
|
|
3755
|
+
};
|
|
3756
|
+
}
|
|
3513
3757
|
function isHardBreak(prepared, line) {
|
|
3514
3758
|
return prepared.kinds?.[line.end.segmentIndex] === "hard-break";
|
|
3515
3759
|
}
|
|
@@ -3517,7 +3761,7 @@ function normalizeNativeText(text, whiteSpace) {
|
|
|
3517
3761
|
if (whiteSpace === "pre-wrap") return String(text ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
3518
3762
|
return String(text ?? "").replace(/\s+/gu, " ").trim();
|
|
3519
3763
|
}
|
|
3520
|
-
function tokenizeNativeText(text, whiteSpace, measureWidth) {
|
|
3764
|
+
function tokenizeNativeText(text, whiteSpace, measureWidth, wordBreak) {
|
|
3521
3765
|
const tokens = [];
|
|
3522
3766
|
let index = 0;
|
|
3523
3767
|
while (index < text.length) {
|
|
@@ -3536,12 +3780,13 @@ function tokenizeNativeText(text, whiteSpace, measureWidth) {
|
|
|
3536
3780
|
if ((char === " " || char === " ") && whiteSpace === "pre-wrap") {
|
|
3537
3781
|
const start = index;
|
|
3538
3782
|
while (index < text.length && (text[index] === " " || text[index] === " ")) index += 1;
|
|
3783
|
+
const segment = text.slice(start, index);
|
|
3539
3784
|
tokens.push({
|
|
3540
|
-
text:
|
|
3785
|
+
text: segment,
|
|
3541
3786
|
type: "space",
|
|
3542
3787
|
start,
|
|
3543
3788
|
end: index,
|
|
3544
|
-
width: measureWidth(
|
|
3789
|
+
width: measureWidth(segment)
|
|
3545
3790
|
});
|
|
3546
3791
|
continue;
|
|
3547
3792
|
}
|
|
@@ -3558,7 +3803,7 @@ function tokenizeNativeText(text, whiteSpace, measureWidth) {
|
|
|
3558
3803
|
}
|
|
3559
3804
|
const nextStop = findNextStop(text, index, whiteSpace);
|
|
3560
3805
|
const chunk = text.slice(index, nextStop);
|
|
3561
|
-
|
|
3806
|
+
tokens.push(...segmentChunk(chunk, index, measureWidth, wordBreak));
|
|
3562
3807
|
index = nextStop;
|
|
3563
3808
|
}
|
|
3564
3809
|
return tokens;
|
|
@@ -3573,6 +3818,24 @@ function findNextStop(text, start, whiteSpace) {
|
|
|
3573
3818
|
}
|
|
3574
3819
|
return index;
|
|
3575
3820
|
}
|
|
3821
|
+
function segmentChunk(text, baseOffset, measureWidth, wordBreak) {
|
|
3822
|
+
if (text.length === 0) return [];
|
|
3823
|
+
if (wordBreak === "keep-all") return [{
|
|
3824
|
+
text,
|
|
3825
|
+
type: "text",
|
|
3826
|
+
start: baseOffset,
|
|
3827
|
+
end: baseOffset + text.length,
|
|
3828
|
+
width: measureWidth(text)
|
|
3829
|
+
}];
|
|
3830
|
+
if (wordBreak === "break-all") return splitIntoGraphemePieces({
|
|
3831
|
+
text,
|
|
3832
|
+
type: "text",
|
|
3833
|
+
start: baseOffset,
|
|
3834
|
+
end: baseOffset + text.length,
|
|
3835
|
+
width: measureWidth(text)
|
|
3836
|
+
}, measureWidth);
|
|
3837
|
+
return segmentWords(text, baseOffset, measureWidth);
|
|
3838
|
+
}
|
|
3576
3839
|
function segmentWords(text, baseOffset, measureWidth) {
|
|
3577
3840
|
const pieces = [];
|
|
3578
3841
|
const segmenter = getWordSegmenter();
|
|
@@ -3611,6 +3874,13 @@ function createTextMeasurer(fontInstance, options) {
|
|
|
3611
3874
|
return width;
|
|
3612
3875
|
};
|
|
3613
3876
|
}
|
|
3877
|
+
function createLazyTextMeasurer(fontInstance, options) {
|
|
3878
|
+
let measureWidth = null;
|
|
3879
|
+
return (value) => {
|
|
3880
|
+
if (measureWidth == null) measureWidth = createTextMeasurer(fontInstance, options);
|
|
3881
|
+
return measureWidth(value);
|
|
3882
|
+
};
|
|
3883
|
+
}
|
|
3614
3884
|
function createOpenTypeMeasurer(fontInstance, options) {
|
|
3615
3885
|
const cache = /* @__PURE__ */ new Map();
|
|
3616
3886
|
const widthOptions = {
|
|
@@ -3666,6 +3936,15 @@ function trimLastGrapheme(value) {
|
|
|
3666
3936
|
function splitPreservingWhitespace(value) {
|
|
3667
3937
|
return value.split(/(\s+)/u).filter((token) => token.length > 0);
|
|
3668
3938
|
}
|
|
3939
|
+
function parseSpacingString(value) {
|
|
3940
|
+
const tokens = value.trim().split(/[\s,]+/u).filter(Boolean).map(parseSpacingToken);
|
|
3941
|
+
if (tokens.length >= 1 && tokens.length <= 4 && tokens.every(Number.isFinite)) return tokens;
|
|
3942
|
+
return null;
|
|
3943
|
+
}
|
|
3944
|
+
function parseSpacingToken(value) {
|
|
3945
|
+
const match = value.match(/^(-?\d+(?:\.\d+)?)(px)?$/u);
|
|
3946
|
+
return match ? Number(match[1]) : NaN;
|
|
3947
|
+
}
|
|
3669
3948
|
function getFontAscender(fontInstance, size) {
|
|
3670
3949
|
return normalizeNumber(fontInstance?.font?.ascender, fontInstance?.unitsPerEm ?? 1e3) / normalizePositive(fontInstance?.unitsPerEm, 1e3) * size;
|
|
3671
3950
|
}
|
|
@@ -3674,29 +3953,12 @@ function getFontAscender(fontInstance, size) {
|
|
|
3674
3953
|
var paParagraph = class paParagraph {
|
|
3675
3954
|
constructor(fontInstance, text, options, state) {
|
|
3676
3955
|
this._font = fontInstance;
|
|
3677
|
-
this._state = state;
|
|
3678
3956
|
this.text = text;
|
|
3679
|
-
this.options = options;
|
|
3680
|
-
this.lines = state.lines.map((line) => ({
|
|
3681
|
-
index: line.index,
|
|
3682
|
-
text: line.text,
|
|
3683
|
-
start: line.start,
|
|
3684
|
-
end: line.end,
|
|
3685
|
-
x: line.x,
|
|
3686
|
-
y: line.y,
|
|
3687
|
-
width: line.width,
|
|
3688
|
-
baseline: line.baseline,
|
|
3689
|
-
height: line.height,
|
|
3690
|
-
bbox: { ...line.bbox }
|
|
3691
|
-
}));
|
|
3692
|
-
this.metrics = {
|
|
3693
|
-
...state.metrics,
|
|
3694
|
-
bbox: { ...state.metrics.bbox }
|
|
3695
|
-
};
|
|
3696
3957
|
this._cache = {
|
|
3697
3958
|
baseShapes: /* @__PURE__ */ new Map(),
|
|
3698
3959
|
layoutParagraphs: /* @__PURE__ */ new Map()
|
|
3699
3960
|
};
|
|
3961
|
+
this._applySnapshot(options, state);
|
|
3700
3962
|
}
|
|
3701
3963
|
relayout(next = {}) {
|
|
3702
3964
|
const normalized = normalizeParagraphOptions(this._font, {
|
|
@@ -3706,7 +3968,9 @@ var paParagraph = class paParagraph {
|
|
|
3706
3968
|
const state = layoutParagraph(this._font, this.text, normalized, {
|
|
3707
3969
|
prepared: canReusePreparedParagraphState(this._state, this.options, normalized) ? this._state.prepared : null,
|
|
3708
3970
|
preparedWhiteSpace: this._state.preparedWhiteSpace,
|
|
3709
|
-
font: this.options.font
|
|
3971
|
+
font: this.options.font,
|
|
3972
|
+
containerWidth: this._state.containerWidth,
|
|
3973
|
+
containerHeight: this._state.containerHeight
|
|
3710
3974
|
});
|
|
3711
3975
|
return new paParagraph(this._font, this.text, normalized, state);
|
|
3712
3976
|
}
|
|
@@ -3718,12 +3982,19 @@ var paParagraph = class paParagraph {
|
|
|
3718
3982
|
const fill = options.fill !== false;
|
|
3719
3983
|
const stroke = options.stroke === true;
|
|
3720
3984
|
if (!fill && !stroke) return;
|
|
3985
|
+
this._syncLayoutWithContext(ctx);
|
|
3721
3986
|
ctx.save();
|
|
3722
3987
|
ctx.font = resolveCanvasFont(this._font, this.options.size, this.options);
|
|
3723
3988
|
ctx.textAlign = "left";
|
|
3724
3989
|
ctx.textBaseline = "alphabetic";
|
|
3725
3990
|
if (options.fillStyle != null) ctx.fillStyle = options.fillStyle;
|
|
3726
3991
|
if (options.strokeStyle != null) ctx.strokeStyle = options.strokeStyle;
|
|
3992
|
+
if (this.options.overflow === "hidden" && this._state.layoutBox?.clipBox) {
|
|
3993
|
+
const clipBox = this._state.layoutBox.clipBox;
|
|
3994
|
+
ctx.beginPath();
|
|
3995
|
+
ctx.rect(clipBox.x, clipBox.y, clipBox.w, clipBox.h);
|
|
3996
|
+
ctx.clip();
|
|
3997
|
+
}
|
|
3727
3998
|
this._state.lines.forEach((line) => {
|
|
3728
3999
|
line.fragments.forEach((fragment) => {
|
|
3729
4000
|
if (fill) ctx.fillText(fragment.text, fragment.x, line.baseline);
|
|
@@ -3751,6 +4022,48 @@ var paParagraph = class paParagraph {
|
|
|
3751
4022
|
paragraph._cache.baseShapes.set("base", baseShape);
|
|
3752
4023
|
return baseShape;
|
|
3753
4024
|
}
|
|
4025
|
+
_applySnapshot(options, state) {
|
|
4026
|
+
this.options = options;
|
|
4027
|
+
this.layoutEngine = state.layoutEngine;
|
|
4028
|
+
this._state = state;
|
|
4029
|
+
this.lines = state.lines.map((line) => ({
|
|
4030
|
+
index: line.index,
|
|
4031
|
+
text: line.text,
|
|
4032
|
+
start: line.start,
|
|
4033
|
+
end: line.end,
|
|
4034
|
+
x: line.x,
|
|
4035
|
+
y: line.y,
|
|
4036
|
+
width: line.width,
|
|
4037
|
+
baseline: line.baseline,
|
|
4038
|
+
height: line.height,
|
|
4039
|
+
bbox: { ...line.bbox }
|
|
4040
|
+
}));
|
|
4041
|
+
this.metrics = {
|
|
4042
|
+
...state.metrics,
|
|
4043
|
+
bbox: { ...state.metrics.bbox },
|
|
4044
|
+
contentBox: state.metrics.contentBox ? { ...state.metrics.contentBox } : void 0,
|
|
4045
|
+
paddingBox: state.metrics.paddingBox ? { ...state.metrics.paddingBox } : void 0,
|
|
4046
|
+
marginBox: state.metrics.marginBox ? { ...state.metrics.marginBox } : void 0,
|
|
4047
|
+
clipBox: state.metrics.clipBox ? { ...state.metrics.clipBox } : void 0
|
|
4048
|
+
};
|
|
4049
|
+
this._cache.baseShapes.clear();
|
|
4050
|
+
this._cache.layoutParagraphs.clear();
|
|
4051
|
+
}
|
|
4052
|
+
_syncLayoutWithContext(ctx) {
|
|
4053
|
+
const canvas = ctx?.canvas;
|
|
4054
|
+
if (!canvas || this.options.width != null) return;
|
|
4055
|
+
const containerWidth = Number.isFinite(canvas.width) ? canvas.width : null;
|
|
4056
|
+
const containerHeight = Number.isFinite(canvas.height) ? canvas.height : null;
|
|
4057
|
+
if (containerWidth === this._state.containerWidth && containerHeight === this._state.containerHeight) return;
|
|
4058
|
+
const state = layoutParagraph(this._font, this.text, this.options, {
|
|
4059
|
+
prepared: canReusePreparedParagraphState(this._state, this.options, this.options) ? this._state.prepared : null,
|
|
4060
|
+
preparedWhiteSpace: this._state.preparedWhiteSpace,
|
|
4061
|
+
font: this.options.font,
|
|
4062
|
+
containerWidth,
|
|
4063
|
+
containerHeight
|
|
4064
|
+
});
|
|
4065
|
+
this._applySnapshot(this.options, state);
|
|
4066
|
+
}
|
|
3754
4067
|
_resolveParagraph(layout) {
|
|
3755
4068
|
if (layout === "current") return this;
|
|
3756
4069
|
if (layout === "native") return this._getLayoutParagraph("native");
|
|
@@ -3824,17 +4137,30 @@ function normalizeParagraphPointOptions(options = {}) {
|
|
|
3824
4137
|
}
|
|
3825
4138
|
//#endregion
|
|
3826
4139
|
//#region src/paFont/paFont.js
|
|
4140
|
+
var browserFontRegistrationId = 0;
|
|
3827
4141
|
var paFont = class paFont {
|
|
3828
4142
|
constructor(font) {
|
|
3829
4143
|
this.font = font;
|
|
3830
4144
|
this.unitsPerEm = font.unitsPerEm ?? 1e3;
|
|
4145
|
+
this.family = resolveLoadedFontFamily(font);
|
|
4146
|
+
this.canvasFamily = this.family;
|
|
3831
4147
|
this._glyphTopologyCache = /* @__PURE__ */ new Map();
|
|
3832
4148
|
this._glyphFlatCache = /* @__PURE__ */ new Map();
|
|
3833
4149
|
}
|
|
3834
4150
|
static async load(source, options = {}) {
|
|
3835
4151
|
const opts = normalizeLoadOptions(options);
|
|
3836
|
-
if (source instanceof ArrayBuffer || ArrayBuffer.isView(source))
|
|
3837
|
-
|
|
4152
|
+
if (source instanceof ArrayBuffer || ArrayBuffer.isView(source)) {
|
|
4153
|
+
const bytes = toArrayBuffer(source);
|
|
4154
|
+
const instance = new paFont(parse(bytes));
|
|
4155
|
+
await instance._registerBrowserFont(bytes);
|
|
4156
|
+
return instance;
|
|
4157
|
+
}
|
|
4158
|
+
if (typeof window !== "undefined") {
|
|
4159
|
+
const bytes = await fetchFontBytes(resolveBrowserFontSource(source, opts.base));
|
|
4160
|
+
const instance = new paFont(parse(bytes));
|
|
4161
|
+
await instance._registerBrowserFont(bytes);
|
|
4162
|
+
return instance;
|
|
4163
|
+
}
|
|
3838
4164
|
const { target, loadOptions } = await resolveNodeFontSource(source, opts.base);
|
|
3839
4165
|
return new paFont(await load(target, void 0, loadOptions));
|
|
3840
4166
|
}
|
|
@@ -3869,6 +4195,18 @@ var paFont = class paFont {
|
|
|
3869
4195
|
paragraph(value, options = {}) {
|
|
3870
4196
|
return createParagraph(this, String(value ?? ""), options);
|
|
3871
4197
|
}
|
|
4198
|
+
async _registerBrowserFont(source) {
|
|
4199
|
+
if (typeof window === "undefined" || typeof FontFace === "undefined" || typeof document === "undefined" || document.fonts == null) return;
|
|
4200
|
+
try {
|
|
4201
|
+
const family = createBrowserFontFamily(this.family);
|
|
4202
|
+
const face = new FontFace(family, source);
|
|
4203
|
+
await face.load();
|
|
4204
|
+
document.fonts.add(face);
|
|
4205
|
+
this.canvasFamily = family;
|
|
4206
|
+
} catch {
|
|
4207
|
+
this.canvasFamily = this.family;
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
3872
4210
|
_getGlyphTopology(glyph) {
|
|
3873
4211
|
const key = String(glyph.index);
|
|
3874
4212
|
if (!this._glyphTopologyCache.has(key)) this._glyphTopologyCache.set(key, buildGlyphTopology(glyph, this.unitsPerEm));
|
|
@@ -3953,6 +4291,13 @@ function readSignature(bytes) {
|
|
|
3953
4291
|
function isHtmlSignature(signature) {
|
|
3954
4292
|
return signature === "<!do" || signature === "<htm";
|
|
3955
4293
|
}
|
|
4294
|
+
function resolveLoadedFontFamily(font) {
|
|
4295
|
+
return font?.names?.fontFamily?.en ?? font?.names?.preferredFamily?.en ?? font?.names?.fullName?.en ?? font?.familyName ?? "paFont";
|
|
4296
|
+
}
|
|
4297
|
+
function createBrowserFontFamily(family) {
|
|
4298
|
+
browserFontRegistrationId += 1;
|
|
4299
|
+
return `${String(family ?? "paFont").trim().replace(/\s+/g, " ").replace(/[^a-zA-Z0-9 _-]/g, "") || "paFont"}__pa_${browserFontRegistrationId}`;
|
|
4300
|
+
}
|
|
3956
4301
|
//#endregion
|
|
3957
4302
|
export { PAShape, paFont as default, paFont, paParagraph };
|
|
3958
4303
|
|