pa_font 0.3.1 → 0.3.3

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/dist/paFont.js CHANGED
@@ -3317,30 +3317,28 @@ function layoutParagraph(fontInstance, text, options = {}, state = {}) {
3317
3317
  const textBBox = combineRects(lines.map((line) => line.bbox)) ?? emptyRect();
3318
3318
  const textWidth = lines.reduce((max, line) => Math.max(max, line.width), 0);
3319
3319
  const textHeight = resolvePositionedTextHeight(lines, layoutBox.contentY);
3320
- const finalLayoutBox = finalizeLayoutBox(layoutBox, normalized, textHeight);
3320
+ const anchoredLayout = applyParagraphAnchor(lines, textBBox, finalizeLayoutBox(layoutBox, normalized, textHeight), normalized.anchor);
3321
3321
  const cachedPrepared = pretextState?.prepared ?? retainedPreparedState.prepared ?? null;
3322
3322
  const cachedPreparedWhiteSpace = pretextState?.preparedWhiteSpace ?? retainedPreparedState.preparedWhiteSpace ?? null;
3323
3323
  return {
3324
3324
  options: normalized,
3325
- lines,
3325
+ lines: anchoredLayout.lines,
3326
3326
  metrics: {
3327
- x: finalLayoutBox.contentBox.x,
3328
- y: finalLayoutBox.contentBox.y,
3327
+ x: anchoredLayout.layoutBox.contentBox.x,
3328
+ y: anchoredLayout.layoutBox.contentBox.y,
3329
3329
  width: textWidth,
3330
3330
  height: textHeight,
3331
- viewportHeight: finalLayoutBox.contentBox.h,
3332
- maxScrollTop: Math.max(0, textHeight - finalLayoutBox.contentBox.h),
3333
3331
  lineCount: lines.length,
3334
- bbox: textBBox,
3335
- contentBox: { ...finalLayoutBox.contentBox },
3336
- paddingBox: { ...finalLayoutBox.paddingBox },
3337
- marginBox: { ...finalLayoutBox.marginBox },
3338
- clipBox: { ...finalLayoutBox.clipBox }
3332
+ bbox: anchoredLayout.textBBox,
3333
+ contentBox: { ...anchoredLayout.layoutBox.contentBox },
3334
+ paddingBox: { ...anchoredLayout.layoutBox.paddingBox },
3335
+ marginBox: { ...anchoredLayout.layoutBox.marginBox },
3336
+ clipBox: { ...anchoredLayout.layoutBox.clipBox }
3339
3337
  },
3340
3338
  prepared: cachedPrepared,
3341
3339
  preparedWhiteSpace: cachedPreparedWhiteSpace,
3342
3340
  layoutEngine: layoutState.layoutEngine,
3343
- layoutBox: finalLayoutBox,
3341
+ layoutBox: anchoredLayout.layoutBox,
3344
3342
  containerWidth: layoutBox.containerWidth,
3345
3343
  containerHeight: layoutBox.containerHeight
3346
3344
  };
@@ -3356,12 +3354,11 @@ function normalizeParagraphOptions(fontInstance, options = {}) {
3356
3354
  const width = normalizeDimension(options.width);
3357
3355
  const height = normalizeDimension(options.height);
3358
3356
  const gap = normalizeGap(options.gap);
3359
- const overflow = normalizeEnum(options.overflow, ["visible", "hidden"], "visible");
3360
- const overflowY = resolveOverflowY(options.overflowY, overflow);
3357
+ const anchor = normalizeAnchor(options.anchor);
3361
3358
  if (options.width != null && width == null) throw new TypeError("font.paragraph() option \"width\" must be a positive number.");
3362
3359
  if (options.height != null && height == null) throw new TypeError("font.paragraph() option \"height\" must be a positive number.");
3363
3360
  if (options.gap != null && gap == null) throw new TypeError("font.paragraph() option \"gap\" must be a non-negative number.");
3364
- if (overflowY === "scroll" && height == null) throw new TypeError("font.paragraph() option \"height\" is required when \"overflowY\" is \"scroll\".");
3361
+ if (options.anchor != null && anchor == null) throw new TypeError("font.paragraph() option \"anchor\" must be a number, [x, y], or { x, y }.");
3365
3362
  const font = resolveCanvasFont(fontInstance, textOptions.size, options);
3366
3363
  return {
3367
3364
  ...textOptions,
@@ -3377,6 +3374,10 @@ function normalizeParagraphOptions(fontInstance, options = {}) {
3377
3374
  width,
3378
3375
  height,
3379
3376
  gap: gap ?? DEFAULT_PARAGRAPH_GAP,
3377
+ anchor: anchor ?? {
3378
+ x: 0,
3379
+ y: 0
3380
+ },
3380
3381
  lineHeight: resolveLineHeight(options.lineHeight, textOptions.size),
3381
3382
  align: normalizeEnum(options.align, [
3382
3383
  "left",
@@ -3399,8 +3400,7 @@ function normalizeParagraphOptions(fontInstance, options = {}) {
3399
3400
  "break-all",
3400
3401
  "keep-all"
3401
3402
  ], wrapDefaults.wordBreak),
3402
- overflow,
3403
- overflowY,
3403
+ overflow: normalizeEnum(options.overflow, ["visible", "hidden"], "visible"),
3404
3404
  textOverflow: normalizeNullableEnum(options.textOverflow, ["clip", "ellipsis"], null),
3405
3405
  maxLines: normalizeMaxLines(options.maxLines),
3406
3406
  ellipsis: normalizeEllipsis(options.ellipsis),
@@ -3589,7 +3589,7 @@ function applyOverflowClamping(lines, options, layoutBox, measureWidth) {
3589
3589
  const contentWidth = layoutBox.contentWidth;
3590
3590
  const result = lines.map((line) => ({ ...line }));
3591
3591
  let lineLimit = options.maxLines;
3592
- if (shouldClampLinesToHeight(options) && layoutBox.clipContentHeight != null) {
3592
+ if (options.overflow === "hidden" && layoutBox.clipContentHeight != null) {
3593
3593
  const visibleLineCount = countVisibleLinesForHeight(result, options, layoutBox.clipContentHeight);
3594
3594
  lineLimit = lineLimit == null ? visibleLineCount : Math.min(lineLimit, visibleLineCount);
3595
3595
  }
@@ -3814,10 +3814,48 @@ function finalizeLayoutBox(layoutBox, options, textHeight) {
3814
3814
  x: layoutBox.contentX,
3815
3815
  y: layoutBox.contentY,
3816
3816
  w: layoutBox.contentWidth,
3817
- h: shouldClipToContentHeight(options) ? contentHeight : textHeight
3817
+ h: options.overflow === "hidden" ? contentHeight : textHeight
3818
3818
  }
3819
3819
  };
3820
3820
  }
3821
+ function applyParagraphAnchor(lines, textBBox, layoutBox, anchor) {
3822
+ if (anchor == null || Math.abs(anchor.x) <= JUSTIFY_EPSILON && Math.abs(anchor.y) <= JUSTIFY_EPSILON) return {
3823
+ lines,
3824
+ textBBox,
3825
+ layoutBox
3826
+ };
3827
+ const tx = -layoutBox.contentBox.w * anchor.x;
3828
+ const ty = -layoutBox.contentBox.h * anchor.y;
3829
+ return {
3830
+ lines: translatePositionedLines(lines, tx, ty),
3831
+ textBBox: translateRect(textBBox, tx, ty),
3832
+ layoutBox: translateLayoutBox(layoutBox, tx, ty)
3833
+ };
3834
+ }
3835
+ function translatePositionedLines(lines, tx, ty) {
3836
+ return lines.map((line) => ({
3837
+ ...line,
3838
+ x: line.x + tx,
3839
+ y: line.y + ty,
3840
+ baseline: line.baseline + ty,
3841
+ bbox: translateRect(line.bbox, tx, ty),
3842
+ fragments: line.fragments.map((fragment) => ({
3843
+ ...fragment,
3844
+ x: fragment.x + tx
3845
+ }))
3846
+ }));
3847
+ }
3848
+ function translateLayoutBox(layoutBox, tx, ty) {
3849
+ return {
3850
+ ...layoutBox,
3851
+ contentX: layoutBox.contentX + tx,
3852
+ contentY: layoutBox.contentY + ty,
3853
+ contentBox: translateRect(layoutBox.contentBox, tx, ty),
3854
+ paddingBox: translateRect(layoutBox.paddingBox, tx, ty),
3855
+ marginBox: translateRect(layoutBox.marginBox, tx, ty),
3856
+ clipBox: translateRect(layoutBox.clipBox, tx, ty)
3857
+ };
3858
+ }
3821
3859
  function resolveContainerDimension(explicit, fallback) {
3822
3860
  if (Number.isFinite(explicit) && explicit > 0) return explicit;
3823
3861
  if (Number.isFinite(fallback) && fallback > 0) return fallback;
@@ -3830,19 +3868,6 @@ function normalizeNullableEnum(value, supported, fallback) {
3830
3868
  if (value == null) return fallback;
3831
3869
  return typeof value === "string" && supported.includes(value) ? value : fallback;
3832
3870
  }
3833
- function resolveOverflowY(value, overflow) {
3834
- return normalizeEnum(value, [
3835
- "visible",
3836
- "hidden",
3837
- "scroll"
3838
- ], overflow === "hidden" ? "hidden" : "visible");
3839
- }
3840
- function shouldClampLinesToHeight(options) {
3841
- return options.overflowY === "hidden";
3842
- }
3843
- function shouldClipToContentHeight(options) {
3844
- return options.overflow === "hidden" || options.overflowY === "hidden" || options.overflowY === "scroll";
3845
- }
3846
3871
  function normalizeEllipsis(value) {
3847
3872
  if (value === false) return false;
3848
3873
  if (typeof value === "string") return value;
@@ -3859,6 +3884,40 @@ function normalizeDimension(value) {
3859
3884
  function normalizeGap(value) {
3860
3885
  return Number.isFinite(value) && value >= 0 ? Number(value) : null;
3861
3886
  }
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
+ }
3862
3921
  function normalizeSpacing(value) {
3863
3922
  if (value == null) return zeroSpacing();
3864
3923
  if (Number.isFinite(value)) {
@@ -4279,7 +4338,6 @@ var paParagraph = class paParagraph {
4279
4338
  if (!ctx || typeof ctx.fillText !== "function") throw new TypeError("drawText() expects a CanvasRenderingContext2D.");
4280
4339
  const fill = options.fill !== false;
4281
4340
  const stroke = options.stroke === true;
4282
- const scrollTop = clampScrollTop(options.scrollTop, this.metrics.maxScrollTop);
4283
4341
  if (!fill && !stroke) return;
4284
4342
  this._syncLayoutWithContext(ctx);
4285
4343
  ctx.save();
@@ -4288,13 +4346,12 @@ var paParagraph = class paParagraph {
4288
4346
  ctx.textBaseline = "alphabetic";
4289
4347
  if (options.fillStyle != null) ctx.fillStyle = options.fillStyle;
4290
4348
  if (options.strokeStyle != null) ctx.strokeStyle = options.strokeStyle;
4291
- if (shouldClipParagraph(this.options) && this._state.layoutBox?.clipBox) {
4349
+ if (this.options.overflow === "hidden" && this._state.layoutBox?.clipBox) {
4292
4350
  const clipBox = this._state.layoutBox.clipBox;
4293
4351
  ctx.beginPath();
4294
4352
  ctx.rect(clipBox.x, clipBox.y, clipBox.w, clipBox.h);
4295
4353
  ctx.clip();
4296
4354
  }
4297
- if (scrollTop > 0) ctx.translate(0, -scrollTop);
4298
4355
  this._state.lines.forEach((line) => {
4299
4356
  line.fragments.forEach((fragment) => {
4300
4357
  if (fill) ctx.fillText(fragment.text, fragment.x, line.baseline);
@@ -4345,8 +4402,6 @@ var paParagraph = class paParagraph {
4345
4402
  this.metrics = {
4346
4403
  ...state.metrics,
4347
4404
  bbox: { ...state.metrics.bbox },
4348
- viewportHeight: state.metrics.viewportHeight,
4349
- maxScrollTop: state.metrics.maxScrollTop,
4350
4405
  contentBox: state.metrics.contentBox ? { ...state.metrics.contentBox } : void 0,
4351
4406
  paddingBox: state.metrics.paddingBox ? { ...state.metrics.paddingBox } : void 0,
4352
4407
  marginBox: state.metrics.marginBox ? { ...state.metrics.marginBox } : void 0,
@@ -4441,13 +4496,6 @@ function normalizeParagraphPointOptions(options = {}) {
4441
4496
  layout: options.layout ?? "current"
4442
4497
  };
4443
4498
  }
4444
- function shouldClipParagraph(options) {
4445
- return options.overflow === "hidden" || options.overflowY === "hidden" || options.overflowY === "scroll";
4446
- }
4447
- function clampScrollTop(value, maxScrollTop) {
4448
- if (!Number.isFinite(value) || value <= 0) return 0;
4449
- return Math.min(Number(value), maxScrollTop ?? 0);
4450
- }
4451
4499
  //#endregion
4452
4500
  //#region src/paFont/paFont.js
4453
4501
  var browserFontRegistrationId = 0;