pa_font 0.3.3 → 0.3.5
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/USAGE.md +4 -2
- package/dist/paFont.cjs +151 -54
- package/dist/paFont.cjs.map +1 -1
- package/dist/paFont.js +151 -54
- package/dist/paFont.js.map +1 -1
- package/paFont.d.ts +1 -1
- package/package.json +1 -1
package/dist/paFont.js
CHANGED
|
@@ -275,7 +275,7 @@ function mod(value, divisor) {
|
|
|
275
275
|
function layoutGlyphs(font, value, opts) {
|
|
276
276
|
const glyphs = [];
|
|
277
277
|
const renderOptions = toRenderOptions(opts);
|
|
278
|
-
return {
|
|
278
|
+
return applyAnchorToGlyphLayout(font, value, {
|
|
279
279
|
text: value,
|
|
280
280
|
glyphs,
|
|
281
281
|
metrics: {
|
|
@@ -292,19 +292,43 @@ function layoutGlyphs(font, value, opts) {
|
|
|
292
292
|
y: opts.y,
|
|
293
293
|
size: opts.size
|
|
294
294
|
}
|
|
295
|
+
}, opts);
|
|
296
|
+
}
|
|
297
|
+
function applyAnchorToGlyphLayout(font, value, layout, opts) {
|
|
298
|
+
if (opts.anchor == null || layout.glyphs.length === 0) return layout;
|
|
299
|
+
const { bbox } = measureText(font, value, {
|
|
300
|
+
...opts,
|
|
301
|
+
anchor: null
|
|
302
|
+
});
|
|
303
|
+
const tx = opts.x - (bbox.x + bbox.w * opts.anchor.x);
|
|
304
|
+
const ty = opts.y - (bbox.y + bbox.h * opts.anchor.y);
|
|
305
|
+
if (tx === 0 && ty === 0) return layout;
|
|
306
|
+
return {
|
|
307
|
+
text: value,
|
|
308
|
+
glyphs: layout.glyphs.map((glyph) => ({
|
|
309
|
+
...glyph,
|
|
310
|
+
x: glyph.x + tx,
|
|
311
|
+
y: glyph.y + ty
|
|
312
|
+
})),
|
|
313
|
+
metrics: {
|
|
314
|
+
...layout.metrics,
|
|
315
|
+
x: layout.metrics.x + tx,
|
|
316
|
+
y: layout.metrics.y + ty
|
|
317
|
+
}
|
|
295
318
|
};
|
|
296
319
|
}
|
|
297
320
|
function measureText(font, value, opts) {
|
|
298
321
|
const renderOptions = toRenderOptions(opts);
|
|
299
|
-
const
|
|
322
|
+
const bbox = resolveMeasuredBBox(font.getPath(value, opts.x, opts.y, opts.size, renderOptions).getBoundingBox(), opts.x, opts.y);
|
|
323
|
+
if (opts.anchor == null) return {
|
|
324
|
+
width: measureAdvanceWidth(font, value, opts),
|
|
325
|
+
bbox
|
|
326
|
+
};
|
|
327
|
+
const tx = opts.x - (bbox.x + bbox.w * opts.anchor.x);
|
|
328
|
+
const ty = opts.y - (bbox.y + bbox.h * opts.anchor.y);
|
|
300
329
|
return {
|
|
301
330
|
width: measureAdvanceWidth(font, value, opts),
|
|
302
|
-
bbox:
|
|
303
|
-
x: box.x1,
|
|
304
|
-
y: box.y1,
|
|
305
|
-
w: box.x2 - box.x1,
|
|
306
|
-
h: box.y2 - box.y1
|
|
307
|
-
}
|
|
331
|
+
bbox: translateRect(bbox, tx, ty)
|
|
308
332
|
};
|
|
309
333
|
}
|
|
310
334
|
function measureAdvanceWidth(font, value, opts) {
|
|
@@ -522,6 +546,7 @@ function translateGlyphGeometry(geometry, tx, ty, glyphPosition) {
|
|
|
522
546
|
}
|
|
523
547
|
function normalizeTextOptions(options = {}) {
|
|
524
548
|
const size = normalizePositive(options.size, 72);
|
|
549
|
+
const anchor = normalizeAnchor(options.anchor);
|
|
525
550
|
return {
|
|
526
551
|
x: normalizeNumber(options.x, 0),
|
|
527
552
|
y: normalizeNumber(options.y, 0),
|
|
@@ -531,11 +556,60 @@ function normalizeTextOptions(options = {}) {
|
|
|
531
556
|
kerning: options.kerning !== false,
|
|
532
557
|
letterSpacing: options.letterSpacing == null ? void 0 : normalizeNumber(options.letterSpacing, 0),
|
|
533
558
|
tracking: options.tracking == null ? void 0 : normalizeNumber(options.tracking, 0),
|
|
559
|
+
anchor,
|
|
534
560
|
script: options.script,
|
|
535
561
|
language: options.language,
|
|
536
562
|
features: options.features
|
|
537
563
|
};
|
|
538
564
|
}
|
|
565
|
+
function normalizeAnchor(value) {
|
|
566
|
+
if (value == null) return null;
|
|
567
|
+
if (Number.isFinite(value)) {
|
|
568
|
+
const next = Number(value);
|
|
569
|
+
return {
|
|
570
|
+
x: next,
|
|
571
|
+
y: next
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
if (Array.isArray(value)) {
|
|
575
|
+
if (value.length === 1 && Number.isFinite(value[0])) {
|
|
576
|
+
const next = Number(value[0]);
|
|
577
|
+
return {
|
|
578
|
+
x: next,
|
|
579
|
+
y: next
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
if (value.length >= 2 && Number.isFinite(value[0]) && Number.isFinite(value[1])) return {
|
|
583
|
+
x: Number(value[0]),
|
|
584
|
+
y: Number(value[1])
|
|
585
|
+
};
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
if (typeof value === "object") {
|
|
589
|
+
const hasX = Number.isFinite(value.x);
|
|
590
|
+
const hasY = Number.isFinite(value.y);
|
|
591
|
+
if (!hasX && !hasY) return null;
|
|
592
|
+
return {
|
|
593
|
+
x: hasX ? Number(value.x) : 0,
|
|
594
|
+
y: hasY ? Number(value.y) : 0
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
function resolveMeasuredBBox(box, fallbackX, fallbackY) {
|
|
600
|
+
if (Number.isFinite(box?.x1) && Number.isFinite(box?.y1) && Number.isFinite(box?.x2) && Number.isFinite(box?.y2)) return {
|
|
601
|
+
x: box.x1,
|
|
602
|
+
y: box.y1,
|
|
603
|
+
w: box.x2 - box.x1,
|
|
604
|
+
h: box.y2 - box.y1
|
|
605
|
+
};
|
|
606
|
+
return {
|
|
607
|
+
x: normalizeNumber(fallbackX, 0),
|
|
608
|
+
y: normalizeNumber(fallbackY, 0),
|
|
609
|
+
w: 0,
|
|
610
|
+
h: 0
|
|
611
|
+
};
|
|
612
|
+
}
|
|
539
613
|
function defaultEdgeEpsilon(size) {
|
|
540
614
|
return Math.min(1, Math.max(.01, size * .0025));
|
|
541
615
|
}
|
|
@@ -3314,7 +3388,7 @@ function layoutParagraph(fontInstance, text, options = {}, state = {}) {
|
|
|
3314
3388
|
const layoutState = pretextState != null && canUsePretextLayout(pretextState, normalized) ? pretextState : layoutParagraphsWithNative(fontInstance, paragraphs, normalized, layoutBox);
|
|
3315
3389
|
const measureWidth = createLazyTextMeasurer(fontInstance, normalized);
|
|
3316
3390
|
const lines = positionLines(fontInstance, applyOverflowClamping(layoutState.lines, normalized, layoutBox, measureWidth), normalized, layoutBox, measureWidth);
|
|
3317
|
-
const textBBox = combineRects(lines.map((line) => line.bbox)) ?? emptyRect();
|
|
3391
|
+
const textBBox = normalized.anchor == null ? combineRects(lines.map((line) => line.bbox)) ?? emptyRect() : measurePositionedTextBBox(fontInstance, lines, normalized);
|
|
3318
3392
|
const textWidth = lines.reduce((max, line) => Math.max(max, line.width), 0);
|
|
3319
3393
|
const textHeight = resolvePositionedTextHeight(lines, layoutBox.contentY);
|
|
3320
3394
|
const anchoredLayout = applyParagraphAnchor(lines, textBBox, finalizeLayoutBox(layoutBox, normalized, textHeight), normalized.anchor);
|
|
@@ -3374,10 +3448,7 @@ function normalizeParagraphOptions(fontInstance, options = {}) {
|
|
|
3374
3448
|
width,
|
|
3375
3449
|
height,
|
|
3376
3450
|
gap: gap ?? DEFAULT_PARAGRAPH_GAP,
|
|
3377
|
-
anchor
|
|
3378
|
-
x: 0,
|
|
3379
|
-
y: 0
|
|
3380
|
-
},
|
|
3451
|
+
anchor,
|
|
3381
3452
|
lineHeight: resolveLineHeight(options.lineHeight, textOptions.size),
|
|
3382
3453
|
align: normalizeEnum(options.align, [
|
|
3383
3454
|
"left",
|
|
@@ -3819,13 +3890,13 @@ function finalizeLayoutBox(layoutBox, options, textHeight) {
|
|
|
3819
3890
|
};
|
|
3820
3891
|
}
|
|
3821
3892
|
function applyParagraphAnchor(lines, textBBox, layoutBox, anchor) {
|
|
3822
|
-
if (anchor == null
|
|
3893
|
+
if (anchor == null) return {
|
|
3823
3894
|
lines,
|
|
3824
3895
|
textBBox,
|
|
3825
3896
|
layoutBox
|
|
3826
3897
|
};
|
|
3827
|
-
const tx =
|
|
3828
|
-
const ty =
|
|
3898
|
+
const tx = layoutBox.contentX - (textBBox.x + textBBox.w * anchor.x);
|
|
3899
|
+
const ty = layoutBox.contentY - (textBBox.y + textBBox.h * anchor.y);
|
|
3829
3900
|
return {
|
|
3830
3901
|
lines: translatePositionedLines(lines, tx, ty),
|
|
3831
3902
|
textBBox: translateRect(textBBox, tx, ty),
|
|
@@ -3856,6 +3927,35 @@ function translateLayoutBox(layoutBox, tx, ty) {
|
|
|
3856
3927
|
clipBox: translateRect(layoutBox.clipBox, tx, ty)
|
|
3857
3928
|
};
|
|
3858
3929
|
}
|
|
3930
|
+
function measurePositionedTextBBox(fontInstance, lines, options) {
|
|
3931
|
+
const visibleBoxes = [];
|
|
3932
|
+
const measureOptions = {
|
|
3933
|
+
x: 0,
|
|
3934
|
+
y: 0,
|
|
3935
|
+
size: options.size,
|
|
3936
|
+
flatten: options.flatten,
|
|
3937
|
+
edgeEpsilon: options.edgeEpsilon,
|
|
3938
|
+
kerning: options.kerning,
|
|
3939
|
+
letterSpacing: options.letterSpacing,
|
|
3940
|
+
tracking: options.tracking,
|
|
3941
|
+
anchor: null,
|
|
3942
|
+
script: options.script,
|
|
3943
|
+
language: options.language,
|
|
3944
|
+
features: options.features
|
|
3945
|
+
};
|
|
3946
|
+
lines.forEach((line) => {
|
|
3947
|
+
line.fragments.forEach((fragment) => {
|
|
3948
|
+
if (fragment.text.length === 0) return;
|
|
3949
|
+
const { bbox } = measureText(fontInstance.font, fragment.text, {
|
|
3950
|
+
...measureOptions,
|
|
3951
|
+
x: fragment.x,
|
|
3952
|
+
y: line.baseline
|
|
3953
|
+
});
|
|
3954
|
+
if (bbox.w > 0 || bbox.h > 0) visibleBoxes.push(bbox);
|
|
3955
|
+
});
|
|
3956
|
+
});
|
|
3957
|
+
return combineRects(visibleBoxes) ?? combineRects(lines.map((line) => line.bbox)) ?? emptyRect();
|
|
3958
|
+
}
|
|
3859
3959
|
function resolveContainerDimension(explicit, fallback) {
|
|
3860
3960
|
if (Number.isFinite(explicit) && explicit > 0) return explicit;
|
|
3861
3961
|
if (Number.isFinite(fallback) && fallback > 0) return fallback;
|
|
@@ -3884,40 +3984,6 @@ function normalizeDimension(value) {
|
|
|
3884
3984
|
function normalizeGap(value) {
|
|
3885
3985
|
return Number.isFinite(value) && value >= 0 ? Number(value) : null;
|
|
3886
3986
|
}
|
|
3887
|
-
function normalizeAnchor(value) {
|
|
3888
|
-
if (value == null) return null;
|
|
3889
|
-
if (Number.isFinite(value)) {
|
|
3890
|
-
const next = Number(value);
|
|
3891
|
-
return {
|
|
3892
|
-
x: next,
|
|
3893
|
-
y: next
|
|
3894
|
-
};
|
|
3895
|
-
}
|
|
3896
|
-
if (Array.isArray(value)) {
|
|
3897
|
-
if (value.length === 1 && Number.isFinite(value[0])) {
|
|
3898
|
-
const next = Number(value[0]);
|
|
3899
|
-
return {
|
|
3900
|
-
x: next,
|
|
3901
|
-
y: next
|
|
3902
|
-
};
|
|
3903
|
-
}
|
|
3904
|
-
if (value.length >= 2 && Number.isFinite(value[0]) && Number.isFinite(value[1])) return {
|
|
3905
|
-
x: Number(value[0]),
|
|
3906
|
-
y: Number(value[1])
|
|
3907
|
-
};
|
|
3908
|
-
return null;
|
|
3909
|
-
}
|
|
3910
|
-
if (typeof value === "object") {
|
|
3911
|
-
const hasX = Number.isFinite(value.x);
|
|
3912
|
-
const hasY = Number.isFinite(value.y);
|
|
3913
|
-
if (!hasX && !hasY) return null;
|
|
3914
|
-
return {
|
|
3915
|
-
x: hasX ? Number(value.x) : 0,
|
|
3916
|
-
y: hasY ? Number(value.y) : 0
|
|
3917
|
-
};
|
|
3918
|
-
}
|
|
3919
|
-
return null;
|
|
3920
|
-
}
|
|
3921
3987
|
function normalizeSpacing(value) {
|
|
3922
3988
|
if (value == null) return zeroSpacing();
|
|
3923
3989
|
if (Number.isFinite(value)) {
|
|
@@ -4528,13 +4594,16 @@ var paFont = class paFont {
|
|
|
4528
4594
|
}
|
|
4529
4595
|
text(value, options = {}) {
|
|
4530
4596
|
const opts = normalizeTextOptions(options);
|
|
4597
|
+
assertValidAnchorOption("font.text()", options, opts.anchor);
|
|
4531
4598
|
return createTextShape(this._layoutText(String(value ?? ""), opts), opts, this);
|
|
4532
4599
|
}
|
|
4533
4600
|
glyph(value, options = {}) {
|
|
4534
4601
|
const opts = normalizeTextOptions(options);
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4602
|
+
assertValidAnchorOption("font.glyph()", options, opts.anchor);
|
|
4603
|
+
const text = Array.from(String(value ?? ""))[0] ?? "";
|
|
4604
|
+
const glyph = this.font.charToGlyph(text);
|
|
4605
|
+
const layout = {
|
|
4606
|
+
text,
|
|
4538
4607
|
glyphs: [{
|
|
4539
4608
|
glyph,
|
|
4540
4609
|
x: opts.x,
|
|
@@ -4548,10 +4617,12 @@ var paFont = class paFont {
|
|
|
4548
4617
|
y: opts.y,
|
|
4549
4618
|
size: opts.size
|
|
4550
4619
|
}
|
|
4551
|
-
}
|
|
4620
|
+
};
|
|
4621
|
+
return createTextShape(anchorSingleGlyphLayout(this.font, text, layout, opts), opts, this);
|
|
4552
4622
|
}
|
|
4553
4623
|
metrics(value, options = {}) {
|
|
4554
4624
|
const opts = normalizeTextOptions(options);
|
|
4625
|
+
assertValidAnchorOption("font.metrics()", options, opts.anchor);
|
|
4555
4626
|
return measureText(this.font, String(value ?? ""), opts);
|
|
4556
4627
|
}
|
|
4557
4628
|
paragraph(value, options = {}) {
|
|
@@ -4606,6 +4677,32 @@ function normalizeShapeVariantValue(value) {
|
|
|
4606
4677
|
function toShapeVariantKey(value) {
|
|
4607
4678
|
return normalizeShapeVariantValue(value).toFixed(6);
|
|
4608
4679
|
}
|
|
4680
|
+
function assertValidAnchorOption(methodName, sourceOptions, anchor) {
|
|
4681
|
+
if (sourceOptions?.anchor != null && anchor == null) throw new TypeError(`${methodName} option "anchor" must be a number, [x, y], or { x, y }.`);
|
|
4682
|
+
}
|
|
4683
|
+
function anchorSingleGlyphLayout(font, text, layout, opts) {
|
|
4684
|
+
if (opts.anchor == null || text.length === 0) return layout;
|
|
4685
|
+
const { bbox } = measureText(font, text, {
|
|
4686
|
+
...opts,
|
|
4687
|
+
anchor: null
|
|
4688
|
+
});
|
|
4689
|
+
const tx = opts.x - (bbox.x + bbox.w * opts.anchor.x);
|
|
4690
|
+
const ty = opts.y - (bbox.y + bbox.h * opts.anchor.y);
|
|
4691
|
+
if (tx === 0 && ty === 0) return layout;
|
|
4692
|
+
return {
|
|
4693
|
+
...layout,
|
|
4694
|
+
glyphs: layout.glyphs.map((glyph) => ({
|
|
4695
|
+
...glyph,
|
|
4696
|
+
x: glyph.x + tx,
|
|
4697
|
+
y: glyph.y + ty
|
|
4698
|
+
})),
|
|
4699
|
+
metrics: {
|
|
4700
|
+
...layout.metrics,
|
|
4701
|
+
x: layout.metrics.x + tx,
|
|
4702
|
+
y: layout.metrics.y + ty
|
|
4703
|
+
}
|
|
4704
|
+
};
|
|
4705
|
+
}
|
|
4609
4706
|
async function fetchFontBytes(source) {
|
|
4610
4707
|
const response = await fetch(source);
|
|
4611
4708
|
if (!response.ok) throw new Error(`Failed to load font from ${source}: ${response.status} ${response.statusText}`);
|