pa_font 0.3.0 → 0.3.1

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 CHANGED
@@ -119,6 +119,7 @@ console.log(metrics.width, metrics.bbox);
119
119
  - `overflowWrap`: `normal | break-word | anywhere`
120
120
  - `wordBreak`: `normal | break-all | keep-all`
121
121
  - `overflow`: `visible | hidden`
122
+ - `overflowY`: `visible | hidden | scroll`
122
123
  - `textOverflow`: `clip | ellipsis`
123
124
  - `fontFamily`, `fontWeight`, `fontStyle`, `font`
124
125
  - `engine`: `pretext | native`
@@ -137,6 +138,7 @@ console.log(metrics.width, metrics.bbox);
137
138
  - `gap`: 문단 사이 간격. `lineHeight` 배수이며 기본값은 `0.5`
138
139
  - `whiteSpace: "normal"`이면 문단 안 단일 줄바꿈은 공백으로 정리
139
140
  - `whiteSpace: "pre-wrap"`이면 문단 안 단일 줄바꿈 유지
141
+ - `overflowY: "scroll"`을 쓰려면 `height`가 필요
140
142
 
141
143
  ## paParagraph API
142
144
 
@@ -182,6 +184,28 @@ paragraph.drawText(ctx, {
182
184
  - `strokeStyle`
183
185
  - `fill`
184
186
  - `stroke`
187
+ - `scrollTop`
188
+
189
+ 스크롤 가능한 viewport를 만들고 싶다면:
190
+
191
+ ```js
192
+ const paragraph = font.paragraph(text, {
193
+ width: 320,
194
+ height: 220,
195
+ overflowY: "scroll",
196
+ lineHeight: 1.5,
197
+ gap: 0.5,
198
+ });
199
+
200
+ paragraph.drawText(ctx, {
201
+ fillStyle: "#111",
202
+ scrollTop: 48,
203
+ });
204
+ ```
205
+
206
+ 전체 콘텐츠 높이는 `paragraph.metrics.height`,
207
+ 현재 viewport 높이는 `paragraph.metrics.viewportHeight`,
208
+ 최대 스크롤 값은 `paragraph.metrics.maxScrollTop`에서 확인할 수 있습니다.
185
209
 
186
210
  ### `paragraph.toShape(options?)`
187
211
 
package/dist/paFont.cjs CHANGED
@@ -3332,6 +3332,8 @@ function layoutParagraph(fontInstance, text, options = {}, state = {}) {
3332
3332
  y: finalLayoutBox.contentBox.y,
3333
3333
  width: textWidth,
3334
3334
  height: textHeight,
3335
+ viewportHeight: finalLayoutBox.contentBox.h,
3336
+ maxScrollTop: Math.max(0, textHeight - finalLayoutBox.contentBox.h),
3335
3337
  lineCount: lines.length,
3336
3338
  bbox: textBBox,
3337
3339
  contentBox: { ...finalLayoutBox.contentBox },
@@ -3358,9 +3360,12 @@ function normalizeParagraphOptions(fontInstance, options = {}) {
3358
3360
  const width = normalizeDimension(options.width);
3359
3361
  const height = normalizeDimension(options.height);
3360
3362
  const gap = normalizeGap(options.gap);
3363
+ const overflow = normalizeEnum(options.overflow, ["visible", "hidden"], "visible");
3364
+ const overflowY = resolveOverflowY(options.overflowY, overflow);
3361
3365
  if (options.width != null && width == null) throw new TypeError("font.paragraph() option \"width\" must be a positive number.");
3362
3366
  if (options.height != null && height == null) throw new TypeError("font.paragraph() option \"height\" must be a positive number.");
3363
3367
  if (options.gap != null && gap == null) throw new TypeError("font.paragraph() option \"gap\" must be a non-negative number.");
3368
+ if (overflowY === "scroll" && height == null) throw new TypeError("font.paragraph() option \"height\" is required when \"overflowY\" is \"scroll\".");
3364
3369
  const font = resolveCanvasFont(fontInstance, textOptions.size, options);
3365
3370
  return {
3366
3371
  ...textOptions,
@@ -3398,7 +3403,8 @@ function normalizeParagraphOptions(fontInstance, options = {}) {
3398
3403
  "break-all",
3399
3404
  "keep-all"
3400
3405
  ], wrapDefaults.wordBreak),
3401
- overflow: normalizeEnum(options.overflow, ["visible", "hidden"], "visible"),
3406
+ overflow,
3407
+ overflowY,
3402
3408
  textOverflow: normalizeNullableEnum(options.textOverflow, ["clip", "ellipsis"], null),
3403
3409
  maxLines: normalizeMaxLines(options.maxLines),
3404
3410
  ellipsis: normalizeEllipsis(options.ellipsis),
@@ -3587,7 +3593,7 @@ function applyOverflowClamping(lines, options, layoutBox, measureWidth) {
3587
3593
  const contentWidth = layoutBox.contentWidth;
3588
3594
  const result = lines.map((line) => ({ ...line }));
3589
3595
  let lineLimit = options.maxLines;
3590
- if (options.overflow === "hidden" && layoutBox.clipContentHeight != null) {
3596
+ if (shouldClampLinesToHeight(options) && layoutBox.clipContentHeight != null) {
3591
3597
  const visibleLineCount = countVisibleLinesForHeight(result, options, layoutBox.clipContentHeight);
3592
3598
  lineLimit = lineLimit == null ? visibleLineCount : Math.min(lineLimit, visibleLineCount);
3593
3599
  }
@@ -3812,7 +3818,7 @@ function finalizeLayoutBox(layoutBox, options, textHeight) {
3812
3818
  x: layoutBox.contentX,
3813
3819
  y: layoutBox.contentY,
3814
3820
  w: layoutBox.contentWidth,
3815
- h: options.overflow === "hidden" ? contentHeight : textHeight
3821
+ h: shouldClipToContentHeight(options) ? contentHeight : textHeight
3816
3822
  }
3817
3823
  };
3818
3824
  }
@@ -3828,6 +3834,19 @@ function normalizeNullableEnum(value, supported, fallback) {
3828
3834
  if (value == null) return fallback;
3829
3835
  return typeof value === "string" && supported.includes(value) ? value : fallback;
3830
3836
  }
3837
+ function resolveOverflowY(value, overflow) {
3838
+ return normalizeEnum(value, [
3839
+ "visible",
3840
+ "hidden",
3841
+ "scroll"
3842
+ ], overflow === "hidden" ? "hidden" : "visible");
3843
+ }
3844
+ function shouldClampLinesToHeight(options) {
3845
+ return options.overflowY === "hidden";
3846
+ }
3847
+ function shouldClipToContentHeight(options) {
3848
+ return options.overflow === "hidden" || options.overflowY === "hidden" || options.overflowY === "scroll";
3849
+ }
3831
3850
  function normalizeEllipsis(value) {
3832
3851
  if (value === false) return false;
3833
3852
  if (typeof value === "string") return value;
@@ -4264,6 +4283,7 @@ var paParagraph = class paParagraph {
4264
4283
  if (!ctx || typeof ctx.fillText !== "function") throw new TypeError("drawText() expects a CanvasRenderingContext2D.");
4265
4284
  const fill = options.fill !== false;
4266
4285
  const stroke = options.stroke === true;
4286
+ const scrollTop = clampScrollTop(options.scrollTop, this.metrics.maxScrollTop);
4267
4287
  if (!fill && !stroke) return;
4268
4288
  this._syncLayoutWithContext(ctx);
4269
4289
  ctx.save();
@@ -4272,12 +4292,13 @@ var paParagraph = class paParagraph {
4272
4292
  ctx.textBaseline = "alphabetic";
4273
4293
  if (options.fillStyle != null) ctx.fillStyle = options.fillStyle;
4274
4294
  if (options.strokeStyle != null) ctx.strokeStyle = options.strokeStyle;
4275
- if (this.options.overflow === "hidden" && this._state.layoutBox?.clipBox) {
4295
+ if (shouldClipParagraph(this.options) && this._state.layoutBox?.clipBox) {
4276
4296
  const clipBox = this._state.layoutBox.clipBox;
4277
4297
  ctx.beginPath();
4278
4298
  ctx.rect(clipBox.x, clipBox.y, clipBox.w, clipBox.h);
4279
4299
  ctx.clip();
4280
4300
  }
4301
+ if (scrollTop > 0) ctx.translate(0, -scrollTop);
4281
4302
  this._state.lines.forEach((line) => {
4282
4303
  line.fragments.forEach((fragment) => {
4283
4304
  if (fill) ctx.fillText(fragment.text, fragment.x, line.baseline);
@@ -4328,6 +4349,8 @@ var paParagraph = class paParagraph {
4328
4349
  this.metrics = {
4329
4350
  ...state.metrics,
4330
4351
  bbox: { ...state.metrics.bbox },
4352
+ viewportHeight: state.metrics.viewportHeight,
4353
+ maxScrollTop: state.metrics.maxScrollTop,
4331
4354
  contentBox: state.metrics.contentBox ? { ...state.metrics.contentBox } : void 0,
4332
4355
  paddingBox: state.metrics.paddingBox ? { ...state.metrics.paddingBox } : void 0,
4333
4356
  marginBox: state.metrics.marginBox ? { ...state.metrics.marginBox } : void 0,
@@ -4422,6 +4445,13 @@ function normalizeParagraphPointOptions(options = {}) {
4422
4445
  layout: options.layout ?? "current"
4423
4446
  };
4424
4447
  }
4448
+ function shouldClipParagraph(options) {
4449
+ return options.overflow === "hidden" || options.overflowY === "hidden" || options.overflowY === "scroll";
4450
+ }
4451
+ function clampScrollTop(value, maxScrollTop) {
4452
+ if (!Number.isFinite(value) || value <= 0) return 0;
4453
+ return Math.min(Number(value), maxScrollTop ?? 0);
4454
+ }
4425
4455
  //#endregion
4426
4456
  //#region src/paFont/paFont.js
4427
4457
  var browserFontRegistrationId = 0;