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