pa_font 0.3.4 → 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 +147 -47
- package/dist/paFont.cjs.map +1 -1
- package/dist/paFont.js +147 -47
- 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);
|
|
@@ -3853,6 +3927,35 @@ function translateLayoutBox(layoutBox, tx, ty) {
|
|
|
3853
3927
|
clipBox: translateRect(layoutBox.clipBox, tx, ty)
|
|
3854
3928
|
};
|
|
3855
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
|
+
}
|
|
3856
3959
|
function resolveContainerDimension(explicit, fallback) {
|
|
3857
3960
|
if (Number.isFinite(explicit) && explicit > 0) return explicit;
|
|
3858
3961
|
if (Number.isFinite(fallback) && fallback > 0) return fallback;
|
|
@@ -3881,40 +3984,6 @@ function normalizeDimension(value) {
|
|
|
3881
3984
|
function normalizeGap(value) {
|
|
3882
3985
|
return Number.isFinite(value) && value >= 0 ? Number(value) : null;
|
|
3883
3986
|
}
|
|
3884
|
-
function normalizeAnchor(value) {
|
|
3885
|
-
if (value == null) return null;
|
|
3886
|
-
if (Number.isFinite(value)) {
|
|
3887
|
-
const next = Number(value);
|
|
3888
|
-
return {
|
|
3889
|
-
x: next,
|
|
3890
|
-
y: next
|
|
3891
|
-
};
|
|
3892
|
-
}
|
|
3893
|
-
if (Array.isArray(value)) {
|
|
3894
|
-
if (value.length === 1 && Number.isFinite(value[0])) {
|
|
3895
|
-
const next = Number(value[0]);
|
|
3896
|
-
return {
|
|
3897
|
-
x: next,
|
|
3898
|
-
y: next
|
|
3899
|
-
};
|
|
3900
|
-
}
|
|
3901
|
-
if (value.length >= 2 && Number.isFinite(value[0]) && Number.isFinite(value[1])) return {
|
|
3902
|
-
x: Number(value[0]),
|
|
3903
|
-
y: Number(value[1])
|
|
3904
|
-
};
|
|
3905
|
-
return null;
|
|
3906
|
-
}
|
|
3907
|
-
if (typeof value === "object") {
|
|
3908
|
-
const hasX = Number.isFinite(value.x);
|
|
3909
|
-
const hasY = Number.isFinite(value.y);
|
|
3910
|
-
if (!hasX && !hasY) return null;
|
|
3911
|
-
return {
|
|
3912
|
-
x: hasX ? Number(value.x) : 0,
|
|
3913
|
-
y: hasY ? Number(value.y) : 0
|
|
3914
|
-
};
|
|
3915
|
-
}
|
|
3916
|
-
return null;
|
|
3917
|
-
}
|
|
3918
3987
|
function normalizeSpacing(value) {
|
|
3919
3988
|
if (value == null) return zeroSpacing();
|
|
3920
3989
|
if (Number.isFinite(value)) {
|
|
@@ -4525,13 +4594,16 @@ var paFont = class paFont {
|
|
|
4525
4594
|
}
|
|
4526
4595
|
text(value, options = {}) {
|
|
4527
4596
|
const opts = normalizeTextOptions(options);
|
|
4597
|
+
assertValidAnchorOption("font.text()", options, opts.anchor);
|
|
4528
4598
|
return createTextShape(this._layoutText(String(value ?? ""), opts), opts, this);
|
|
4529
4599
|
}
|
|
4530
4600
|
glyph(value, options = {}) {
|
|
4531
4601
|
const opts = normalizeTextOptions(options);
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
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,
|
|
4535
4607
|
glyphs: [{
|
|
4536
4608
|
glyph,
|
|
4537
4609
|
x: opts.x,
|
|
@@ -4545,10 +4617,12 @@ var paFont = class paFont {
|
|
|
4545
4617
|
y: opts.y,
|
|
4546
4618
|
size: opts.size
|
|
4547
4619
|
}
|
|
4548
|
-
}
|
|
4620
|
+
};
|
|
4621
|
+
return createTextShape(anchorSingleGlyphLayout(this.font, text, layout, opts), opts, this);
|
|
4549
4622
|
}
|
|
4550
4623
|
metrics(value, options = {}) {
|
|
4551
4624
|
const opts = normalizeTextOptions(options);
|
|
4625
|
+
assertValidAnchorOption("font.metrics()", options, opts.anchor);
|
|
4552
4626
|
return measureText(this.font, String(value ?? ""), opts);
|
|
4553
4627
|
}
|
|
4554
4628
|
paragraph(value, options = {}) {
|
|
@@ -4603,6 +4677,32 @@ function normalizeShapeVariantValue(value) {
|
|
|
4603
4677
|
function toShapeVariantKey(value) {
|
|
4604
4678
|
return normalizeShapeVariantValue(value).toFixed(6);
|
|
4605
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
|
+
}
|
|
4606
4706
|
async function fetchFontBytes(source) {
|
|
4607
4707
|
const response = await fetch(source);
|
|
4608
4708
|
if (!response.ok) throw new Error(`Failed to load font from ${source}: ${response.status} ${response.statusText}`);
|