@embedpdf/core-annotation 3.0.0-next.3 → 3.0.0-next.4

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/index.cjs CHANGED
@@ -1464,9 +1464,12 @@ const DEFAULT_CHROME_GEOM = {
1464
1464
  };
1465
1465
  /** Normalize degrees into `[0, 360)`. */
1466
1466
  const normalizeDeg = (d) => (d % 360 + 360) % 360;
1467
- /** A geom's applied rotation (deg), or 0 for the non-rotatable kinds. */
1467
+ /** A geom's applied rotation (deg), or 0 for the non-rotatable kinds. A
1468
+ * caret's `rot` is AUTHORING metadata (its text's baseline tilt): reported
1469
+ * here so the renderer and selection chrome follow it, while the caret's
1470
+ * caps (not movable/resizable) keep every rotate gesture away from it. */
1468
1471
  function geomRotation(g) {
1469
- if (g.t === "rect" || g.t === "line" || g.t === "poly" || g.t === "ink" || g.t === "text") return g.rot ?? 0;
1472
+ if (g.t === "rect" || g.t === "line" || g.t === "poly" || g.t === "ink" || g.t === "text" || g.t === "caret") return g.rot ?? 0;
1470
1473
  return 0;
1471
1474
  }
1472
1475
  const rectCenter = (r) => ({
@@ -1488,7 +1491,7 @@ function centroidOf(g) {
1488
1491
  x: (g.a.x + g.b.x) / 2,
1489
1492
  y: (g.a.y + g.b.y) / 2
1490
1493
  };
1491
- const pts = g.t === "poly" ? g.points : g.t === "ink" ? g.strokes.flat() : g.quads.flat();
1494
+ const pts = g.t === "poly" ? g.points : g.t === "ink" ? g.strokes.flat() : g.quads.flatMap(_embedpdf_core_geometry.textQuadPoints);
1492
1495
  let sx = 0;
1493
1496
  let sy = 0;
1494
1497
  for (const p of pts) {
@@ -1501,9 +1504,12 @@ function centroidOf(g) {
1501
1504
  y: sy / n
1502
1505
  };
1503
1506
  }
1504
- /** Is this kind rotatable (the geometry carries a meaningful `rot`)? */
1507
+ /** Does the geometry carry a meaningful `rot` (an oriented local box exists)?
1508
+ * ORIENTATION is a geometry fact; whether the USER may rotate is the separate
1509
+ * `caps.rotatable` gate — a caret is oriented (it rides its text's tilt) yet
1510
+ * offers no rotate gesture. */
1505
1511
  function isRotatableGeom(g) {
1506
- return g.t === "rect" || g.t === "line" || g.t === "poly" || g.t === "ink" || g.t === "text" && !g.callout;
1512
+ return g.t === "rect" || g.t === "line" || g.t === "poly" || g.t === "ink" || g.t === "caret" || g.t === "text" && !g.callout;
1507
1513
  }
1508
1514
  /**
1509
1515
  * Rotate a geom by `deltaDeg` (clockwise) about `pivot`.
@@ -1512,7 +1518,10 @@ function isRotatableGeom(g) {
1512
1518
  * pivot IS the box centre this is a pure `rot += delta`.
1513
1519
  * - VERTEX (`line`/`poly`/`ink`): map every point through the rotation AND bump
1514
1520
  * the advisory `rot` (the points stay the authoritative visual).
1515
- * Non-rotatable kinds (caret/quads, callouts) are returned unchanged.
1521
+ * Kinds without a rotate VERB are returned unchanged: quads and callouts, and
1522
+ * also the caret — oriented (`isRotatableGeom`) but text-anchored, so its tilt
1523
+ * is authoring metadata that no gesture edits (`geomResetRotation` still
1524
+ * clears it).
1516
1525
  */
1517
1526
  function geomRotateAbout(g, pivot, deltaDeg) {
1518
1527
  if (deltaDeg === 0) return g;
@@ -1572,7 +1581,7 @@ function apFrameSize(g) {
1572
1581
  width: g.rect.width,
1573
1582
  height: g.rect.height
1574
1583
  };
1575
- const b = unionRect(g.t === "line" ? [g.a, g.b] : g.t === "poly" ? g.points : g.t === "ink" ? g.strokes.flat() : g.quads.flat());
1584
+ const b = unionRect(g.t === "line" ? [g.a, g.b] : g.t === "poly" ? g.points : g.t === "ink" ? g.strokes.flat() : g.quads.flatMap(_embedpdf_core_geometry.textQuadPoints));
1576
1585
  return {
1577
1586
  width: b.width,
1578
1587
  height: b.height
@@ -1681,7 +1690,7 @@ function fitStampBox(center, desired, page, rotCW) {
1681
1690
  function geomResetRotation(g) {
1682
1691
  const rot = geomRotation(g);
1683
1692
  if (!rot) return g;
1684
- if (g.t === "rect" || g.t === "text") return {
1693
+ if (g.t === "rect" || g.t === "text" || g.t === "caret") return {
1685
1694
  ...g,
1686
1695
  rot: 0
1687
1696
  };
@@ -1698,7 +1707,7 @@ function geomResetRotation(g) {
1698
1707
  function obbFromGeom(g, strokeWidth, border) {
1699
1708
  if (!isRotatableGeom(g)) return null;
1700
1709
  const rot = geomRotation(g);
1701
- if (g.t === "rect" || g.t === "text") {
1710
+ if (g.t === "rect" || g.t === "text" || g.t === "caret") {
1702
1711
  const c = rectCenter(g.rect);
1703
1712
  return {
1704
1713
  corners: rectCornerPoints(g.rect).map((p) => rotatePoint(p, c, rot)),
@@ -2003,6 +2012,77 @@ function caretRectFromTextEnd(lineRect) {
2003
2012
  height
2004
2013
  };
2005
2014
  }
2015
+ /**
2016
+ * Caret box for a text-edit anchor: half the glyph's ink height, centered on
2017
+ * the TRAILING baseline corner in READING direction (`advance` decides which
2018
+ * end — RTL carets land on the visual left), sitting on the baseline. The box
2019
+ * stays axis-aligned; use {@link caretGeomFromAnchor} for the tilt-carrying
2020
+ * geometry.
2021
+ */
2022
+ function caretRectFromAnchor(anchor) {
2023
+ const q = anchor.glyphQuad;
2024
+ const ink = Math.hypot(q.lowerStart.x - q.upperStart.x, q.lowerStart.y - q.upperStart.y);
2025
+ const size = Math.max(ink / 2, 1);
2026
+ const corner = anchor.advance > 0 ? q.lowerEnd : q.lowerStart;
2027
+ return {
2028
+ x: corner.x - size / 2,
2029
+ y: corner.y - size,
2030
+ width: size,
2031
+ height: size
2032
+ };
2033
+ }
2034
+ /** Rotations closer than ~0.05° to upright stay upright (float noise guard). */
2035
+ const CARET_ROT_EPSILON = .05;
2036
+ /**
2037
+ * Caret GEOMETRY for a text-edit anchor: the box-family pair — an UNROTATED
2038
+ * box whose centre sits half a caret-size ascent-ward of the trailing
2039
+ * baseline corner, plus `rot` = the text's baseline tilt (deg, CW in y-down
2040
+ * content space). Rotating the box about its centre by `rot` lands it
2041
+ * hugging the rotated baseline, symbol pointing at its text. For upright
2042
+ * anchors this degenerates EXACTLY to {@link caretRectFromAnchor} with no
2043
+ * `rot` key — the dominant case is byte-identical.
2044
+ */
2045
+ function caretGeomFromAnchor(anchor) {
2046
+ const q = anchor.glyphQuad;
2047
+ const ink = Math.hypot(q.lowerStart.x - q.upperStart.x, q.lowerStart.y - q.upperStart.y);
2048
+ const size = Math.max(ink / 2, 1);
2049
+ const corner = anchor.advance > 0 ? q.lowerEnd : q.lowerStart;
2050
+ const bx = q.lowerEnd.x - q.lowerStart.x;
2051
+ const by = q.lowerEnd.y - q.lowerStart.y;
2052
+ const rot = Math.hypot(bx, by) > 0 ? normalizeDeg(Math.atan2(by, bx) * 180 / Math.PI) : 0;
2053
+ if (rot < CARET_ROT_EPSILON || rot > 360 - CARET_ROT_EPSILON) return {
2054
+ t: "caret",
2055
+ rect: {
2056
+ x: corner.x - size / 2,
2057
+ y: corner.y - size,
2058
+ width: size,
2059
+ height: size
2060
+ }
2061
+ };
2062
+ const ux = (q.upperStart.x - q.lowerStart.x) / ink;
2063
+ const uy = (q.upperStart.y - q.lowerStart.y) / ink;
2064
+ const cx = corner.x + ux * size / 2;
2065
+ const cy = corner.y + uy * size / 2;
2066
+ return {
2067
+ t: "caret",
2068
+ rect: {
2069
+ x: cx - size / 2,
2070
+ y: cy - size / 2,
2071
+ width: size,
2072
+ height: size
2073
+ },
2074
+ rot
2075
+ };
2076
+ }
2077
+ /** Map a TextQuad's corners through a point function (names ride along). */
2078
+ function mapTextQuad(q, f) {
2079
+ return {
2080
+ upperStart: f(q.upperStart),
2081
+ upperEnd: f(q.upperEnd),
2082
+ lowerStart: f(q.lowerStart),
2083
+ lowerEnd: f(q.lowerEnd)
2084
+ };
2085
+ }
2006
2086
  /** Miter limit shared by the bounds math AND the SVG renderer (`stroke-miterlimit`),
2007
2087
  * so the computed box and the drawn stroke always agree on where a sharp join
2008
2088
  * bevels instead of spiking. 10 = the PDF default (also what the baked /AP uses). */
@@ -2151,7 +2231,7 @@ function geomVisualBounds(g, strokeWidth, border) {
2151
2231
  return expandRect(unionRect(all), strokeWidth / 2);
2152
2232
  }
2153
2233
  if (g.t === "rect" || g.t === "text" || g.t === "caret") return g.rect;
2154
- if (g.t === "quads") return expandRect(unionRect(g.quads.flat()), strokeWidth / 2);
2234
+ if (g.t === "quads") return expandRect(unionRect(g.quads.flatMap(_embedpdf_core_geometry.textQuadPoints)), strokeWidth / 2);
2155
2235
  if (g.t === "ink") return expandRect(unionRect(g.strokes.flat()), strokeWidth / 2);
2156
2236
  const pts = strokeOutlinePoints(g.t === "line" ? [g.a, g.b] : g.points, g.t === "poly" && g.closed, strokeWidth);
2157
2237
  for (const seg of endingSegs(g)) for (const node of endingNodes(seg.tip, seg.angle, seg.ending, strokeWidth)) if (node.kind === "poly") pts.push(...strokeOutlinePoints(node.points, node.closed, strokeWidth));
@@ -2287,7 +2367,7 @@ function geomBounds(g) {
2287
2367
  if (g.t === "line") return rectFromPoints(g.a, g.b);
2288
2368
  if (g.t === "poly") return unionRect(g.points);
2289
2369
  if (g.t === "ink") return unionRect(g.strokes.flat());
2290
- return unionRect(g.quads.flat());
2370
+ return unionRect(g.quads.flatMap(_embedpdf_core_geometry.textQuadPoints));
2291
2371
  }
2292
2372
  /**
2293
2373
  * Is the content point ON the annotation: within `margin` of the stroke, or
@@ -2300,7 +2380,7 @@ function geomBounds(g) {
2300
2380
  */
2301
2381
  function geomHit(g, p, margin, filled, strokeWidth) {
2302
2382
  const tol = margin + strokeWidth / 2;
2303
- if ((g.t === "rect" || g.t === "text" && !g.callout) && (g.rot ?? 0) !== 0) p = rotatePoint(p, rectCenter(g.rect), -(g.rot ?? 0));
2383
+ if ((g.t === "rect" || g.t === "caret" || g.t === "text" && !g.callout) && (g.rot ?? 0) !== 0) p = rotatePoint(p, rectCenter(g.rect), -(g.rot ?? 0));
2304
2384
  if (g.t === "caret") return rectContains(expandRect(g.rect, margin), p);
2305
2385
  if (g.t === "text") {
2306
2386
  const rot = g.callout ? g.rot ?? 0 : 0;
@@ -2375,7 +2455,7 @@ function geomHit(g, p, margin, filled, strokeWidth) {
2375
2455
  for (const stroke of g.strokes) for (let i = 0; i < stroke.length - 1; i++) if (segDist(p, stroke[i], stroke[i + 1]) <= tol) return true;
2376
2456
  return false;
2377
2457
  }
2378
- return g.quads.some((q) => rectContains(unionRect(q), p));
2458
+ return g.quads.some((q) => pointInQuad(p, (0, _embedpdf_core_geometry.textQuadRing)(q)));
2379
2459
  }
2380
2460
  function geomHandles(g) {
2381
2461
  if (g.t === "rect" || g.t === "text") {
@@ -2464,7 +2544,7 @@ function geomTranslate(g, d) {
2464
2544
  };
2465
2545
  return {
2466
2546
  ...g,
2467
- quads: g.quads.map((q) => q.map(mv))
2547
+ quads: g.quads.map((q) => mapTextQuad(q, mv))
2468
2548
  };
2469
2549
  }
2470
2550
  const OPPOSITE_HANDLE = {
@@ -2637,12 +2717,7 @@ function geomScene(g, strokeWidth = 0, border) {
2637
2717
  }));
2638
2718
  return g.quads.map((q) => ({
2639
2719
  kind: "poly",
2640
- points: [
2641
- q[0],
2642
- q[1],
2643
- q[3],
2644
- q[2]
2645
- ],
2720
+ points: (0, _embedpdf_core_geometry.textQuadRing)(q),
2646
2721
  closed: true
2647
2722
  }));
2648
2723
  }
@@ -3665,10 +3740,10 @@ function update(m, msg) {
3665
3740
  case "createPointer": return createPointer(m, msg.phase, msg.subtype, msg.in, msg.preset, msg.intent, msg.deferInkCommit, msg.straightenInk, msg.clickCreate, msg.flags);
3666
3741
  case "finishInkDraft": return finishInkCreate(m);
3667
3742
  case "finishCreationDraft": return finishPolyCreate(m);
3668
- case "createCaret": return createCaret(m, msg.pon, msg.rect, msg.flags);
3669
- case "createReplaceText": return createReplaceText(m, msg.pon, msg.rects, msg.endRect, msg.preset);
3670
- case "createMarkup": return createMarkup(m, msg.subtype, msg.pon, msg.rects, msg.preset, msg.flags);
3671
- case "setMarkupPreview": return setMarkupPreview(m, msg.subtype, msg.rectsByPage, msg.preset);
3743
+ case "createCaret": return createCaret(m, msg.pon, msg.anchor, msg.flags);
3744
+ case "createReplaceText": return createReplaceText(m, msg.pon, msg.quads, msg.anchor, msg.preset);
3745
+ case "createMarkup": return createMarkup(m, msg.subtype, msg.pon, msg.quads, msg.preset, msg.flags);
3746
+ case "setMarkupPreview": return setMarkupPreview(m, msg.subtype, msg.quadsByPage, msg.preset);
3672
3747
  case "clearMarkupPreview": return m.preview ? [{
3673
3748
  ...m,
3674
3749
  preview: null
@@ -4469,33 +4544,22 @@ function finishPolyCreate(m) {
4469
4544
  id
4470
4545
  }]];
4471
4546
  }
4472
- /** Per-line selection rects → /QuadPoints quads (content space, y-down: UL, UR,
4473
- * LL, LR). Shared by markup creation and the live preview. */
4474
- const rectsToQuads = (rects) => rects.filter((r) => r.width > 0 && r.height > 0).map((r) => [
4475
- {
4476
- x: r.x,
4477
- y: r.y
4478
- },
4479
- {
4480
- x: r.x + r.width,
4481
- y: r.y
4482
- },
4483
- {
4484
- x: r.x,
4485
- y: r.y + r.height
4486
- },
4487
- {
4488
- x: r.x + r.width,
4489
- y: r.y + r.height
4490
- }
4491
- ]);
4547
+ /** Drop degenerate segment quads (zero-length baseline or ink extent). Area is
4548
+ * the cross product of the two edge vectors — orientation-safe. */
4549
+ const usableQuads = (quads) => quads.filter((q) => {
4550
+ const ux = q.upperEnd.x - q.upperStart.x;
4551
+ const uy = q.upperEnd.y - q.upperStart.y;
4552
+ const sx = q.lowerStart.x - q.upperStart.x;
4553
+ const sy = q.lowerStart.y - q.upperStart.y;
4554
+ return Math.abs(ux * sy - uy * sx) > 0;
4555
+ });
4492
4556
  /**
4493
4557
  * Build a text-markup annotation from the selection's per-line rects. The new
4494
4558
  * annotation is `vector` (rendered live by the overlay) and selected, mirroring
4495
4559
  * `createPointer`. One call per page the selection spans. Clears any live preview.
4496
4560
  */
4497
- function createMarkup(m, subtype, pon, rects, preset = subtype, flags) {
4498
- const quads = rectsToQuads(rects);
4561
+ function createMarkup(m, subtype, pon, segmentQuads, preset = subtype, flags) {
4562
+ const quads = usableQuads(segmentQuads);
4499
4563
  if (!quads.length) return [m, []];
4500
4564
  const id = `tmp:${m.seq + 1}`;
4501
4565
  const annot = {
@@ -4536,9 +4600,9 @@ function createMarkup(m, subtype, pon, rects, preset = subtype, flags) {
4536
4600
  * (`/IT /StrikeOutTextEdit`, `/IRT` caret, `/RT /Group`). Persistence performs
4537
4601
  * the two ordered writes and rolls the primary back if the subordinate fails.
4538
4602
  */
4539
- function createReplaceText(m, pon, rects, textEndRect, preset = "replace-text") {
4540
- const quads = rectsToQuads(rects);
4541
- if (!quads.length || textEndRect.width <= 0 || textEndRect.height <= 0) return [m, []];
4603
+ function createReplaceText(m, pon, segmentQuads, anchor, preset = "replace-text") {
4604
+ const quads = usableQuads(segmentQuads);
4605
+ if (!quads.length) return [m, []];
4542
4606
  const primaryId = `tmp:${m.seq + 1}`;
4543
4607
  const strikeoutId = `tmp:${m.seq + 2}`;
4544
4608
  const style = styleFromProps(defaultsFor(m, preset));
@@ -4548,10 +4612,7 @@ function createReplaceText(m, pon, rects, textEndRect, preset = "replace-text")
4548
4612
  pon,
4549
4613
  subtype: "caret",
4550
4614
  intent: "replace",
4551
- geom: {
4552
- t: "caret",
4553
- rect: caretRectFromTextEnd(textEndRect)
4554
- },
4615
+ geom: caretGeomFromAnchor(anchor),
4555
4616
  style,
4556
4617
  flags: DRAWN_FLAGS,
4557
4618
  source: "vector"
@@ -4594,20 +4655,17 @@ function createReplaceText(m, pon, rects, textEndRect, preset = "replace-text")
4594
4655
  members: [strikeoutId]
4595
4656
  }]];
4596
4657
  }
4597
- function createCaret(m, pon, textEndRect, flags) {
4598
- if (textEndRect.width <= 0 || textEndRect.height <= 0) return [m, []];
4658
+ function createCaret(m, pon, anchor, flags) {
4659
+ const caretGeom = caretGeomFromAnchor(anchor);
4660
+ if (caretGeom.rect.width <= 0 || caretGeom.rect.height <= 0) return [m, []];
4599
4661
  const id = `tmp:${m.seq + 1}`;
4600
- const def = defaultsFor(m, "caret");
4601
4662
  const annot = {
4602
4663
  id,
4603
4664
  ref: null,
4604
4665
  pon,
4605
4666
  subtype: "caret",
4606
- geom: {
4607
- t: "caret",
4608
- rect: caretRectFromTextEnd(textEndRect)
4609
- },
4610
- style: styleFromProps(def),
4667
+ geom: caretGeom,
4668
+ style: styleFromProps(defaultsFor(m, "caret")),
4611
4669
  flags: {
4612
4670
  ...DRAWN_FLAGS,
4613
4671
  ...flags
@@ -4630,11 +4688,11 @@ function createCaret(m, pon, textEndRect, flags) {
4630
4688
  id
4631
4689
  }]];
4632
4690
  }
4633
- /** Set / replace the live markup preview from the selection's per-page rects. */
4634
- function setMarkupPreview(m, subtype, rectsByPage, preset = subtype) {
4691
+ /** Set / replace the live markup preview from the selection's per-page quads. */
4692
+ function setMarkupPreview(m, subtype, quadsByPage, preset = subtype) {
4635
4693
  const byPage = {};
4636
- for (const k in rectsByPage) {
4637
- const quads = rectsToQuads(rectsByPage[k]);
4694
+ for (const k in quadsByPage) {
4695
+ const quads = usableQuads(quadsByPage[k]);
4638
4696
  if (quads.length) byPage[Number(k)] = quads;
4639
4697
  }
4640
4698
  return [{
@@ -4973,70 +5031,99 @@ function shapePaint(style, closed) {
4973
5031
  ...style.border.kind === "cloudy" ? { join: "round" } : {}
4974
5032
  };
4975
5033
  }
4976
- /** A smooth squiggle (quadratic-bezier wave) along a baseline, adapted from v2's
4977
- * tile: one `Q` hump then reflected `T` segments alternate up/down across the run. */
4978
- function squigglePath(x, y, w, amp) {
5034
+ /** A smooth squiggle (quadratic-bezier wave) along an ARBITRARY baseline,
5035
+ * generated in the (û, n̂) basis: one `Q` hump then reflected `T` segments
5036
+ * alternate across the run. Reflection is affine-invariant, so an upright
5037
+ * quad reproduces the old axis-aligned wave byte-for-byte. `n̂` points from
5038
+ * the baseline toward the ascent side; humps rise toward the text. */
5039
+ function squigglePath(start, u, n, w, amp) {
4979
5040
  const half = Math.max(2, amp * 1.5);
4980
- let d = `M ${num(x)} ${num(y)} Q ${num(x + half / 2)} ${num(y - amp)} ${num(x + half)} ${num(y)}`;
4981
- for (let px = x + half; px + half <= x + w + .5; px += half) d += ` T ${num(px + half)} ${num(y)}`;
5041
+ const at = (t, off) => ({
5042
+ x: start.x + u.x * t + n.x * off,
5043
+ y: start.y + u.y * t + n.y * off
5044
+ });
5045
+ const p = (v) => `${num(v.x)} ${num(v.y)}`;
5046
+ const hump = at(half / 2, amp);
5047
+ let d = `M ${p(at(0, 0))} Q ${p(hump)} ${p(at(half, 0))}`;
5048
+ for (let t = half; t + half <= w + .5; t += half) d += ` T ${p(at(t + half, 0))}`;
4982
5049
  return d;
4983
5050
  }
4984
- /** Per-subtype markup nodes. Quads are axis-aligned per-line rects (UL,UR,LL,LR);
4985
- * the colour is the markup `/C` (our model keeps stroke==fill). */
5051
+ /** Per-subtype markup nodes on the quads' own edges (corner-NAMED TextQuads:
5052
+ * upper = ascent side, lower = baseline side, start → end along the frame).
5053
+ * The colour is the markup `/C` (our model keeps stroke==fill). Rotated and
5054
+ * sheared cells draw along their true baselines; upright output is identical
5055
+ * to the old axis-aligned math. */
4986
5056
  function markupScene(subtype, quads, style) {
4987
5057
  const color = style.color;
4988
5058
  const opacity = style.opacity;
4989
5059
  const nodes = [];
4990
5060
  for (const q of quads) {
4991
- const x = q[0].x;
4992
- const y = q[0].y;
4993
- const w = q[1].x - q[0].x;
4994
- const h = q[2].y - q[0].y;
5061
+ const down = {
5062
+ x: q.lowerStart.x - q.upperStart.x,
5063
+ y: q.lowerStart.y - q.upperStart.y
5064
+ };
5065
+ const h = Math.hypot(down.x, down.y);
5066
+ const wVec = {
5067
+ x: q.lowerEnd.x - q.lowerStart.x,
5068
+ y: q.lowerEnd.y - q.lowerStart.y
5069
+ };
5070
+ const w = Math.hypot(wVec.x, wVec.y);
4995
5071
  if (w <= 0 || h <= 0) continue;
5072
+ const n = {
5073
+ x: down.x / h,
5074
+ y: down.y / h
5075
+ };
4996
5076
  const lw = Math.min(2.5, Math.max(.75, h * .06));
4997
- if (subtype === "underline") {
4998
- const yy = y + h - lw;
4999
- nodes.push({
5000
- kind: "line",
5001
- a: {
5002
- x,
5003
- y: yy
5004
- },
5005
- b: {
5006
- x: x + w,
5007
- y: yy
5008
- },
5009
- paint: {
5010
- stroke: color,
5011
- width: lw,
5012
- opacity,
5013
- blend: blendFor(style)
5014
- }
5015
- });
5016
- } else if (subtype === "strikeout") {
5017
- const yy = y + h / 2;
5018
- nodes.push({
5019
- kind: "line",
5020
- a: {
5021
- x,
5022
- y: yy
5023
- },
5024
- b: {
5025
- x: x + w,
5026
- y: yy
5027
- },
5028
- paint: {
5029
- stroke: color,
5030
- width: lw,
5031
- opacity,
5032
- blend: blendFor(style)
5033
- }
5034
- });
5035
- } else if (subtype === "squiggly") {
5077
+ if (subtype === "underline") nodes.push({
5078
+ kind: "line",
5079
+ a: {
5080
+ x: q.lowerStart.x - n.x * lw,
5081
+ y: q.lowerStart.y - n.y * lw
5082
+ },
5083
+ b: {
5084
+ x: q.lowerEnd.x - n.x * lw,
5085
+ y: q.lowerEnd.y - n.y * lw
5086
+ },
5087
+ paint: {
5088
+ stroke: color,
5089
+ width: lw,
5090
+ opacity,
5091
+ blend: blendFor(style)
5092
+ }
5093
+ });
5094
+ else if (subtype === "strikeout") nodes.push({
5095
+ kind: "line",
5096
+ a: {
5097
+ x: (q.upperStart.x + q.lowerStart.x) / 2,
5098
+ y: (q.upperStart.y + q.lowerStart.y) / 2
5099
+ },
5100
+ b: {
5101
+ x: (q.upperEnd.x + q.lowerEnd.x) / 2,
5102
+ y: (q.upperEnd.y + q.lowerEnd.y) / 2
5103
+ },
5104
+ paint: {
5105
+ stroke: color,
5106
+ width: lw,
5107
+ opacity,
5108
+ blend: blendFor(style)
5109
+ }
5110
+ });
5111
+ else if (subtype === "squiggly") {
5036
5112
  const amp = Math.min(2, Math.max(1, h * .08));
5113
+ const u = {
5114
+ x: wVec.x / w,
5115
+ y: wVec.y / w
5116
+ };
5117
+ const start = {
5118
+ x: q.lowerStart.x - n.x * amp,
5119
+ y: q.lowerStart.y - n.y * amp
5120
+ };
5037
5121
  nodes.push({
5038
5122
  kind: "path",
5039
- d: squigglePath(x, y + h - amp, w, amp),
5123
+ d: squigglePath(start, u, {
5124
+ x: -n.x,
5125
+ y: -n.y
5126
+ }, w, amp),
5040
5127
  paint: {
5041
5128
  stroke: color,
5042
5129
  width: lw,
@@ -5045,13 +5132,9 @@ function markupScene(subtype, quads, style) {
5045
5132
  }
5046
5133
  });
5047
5134
  } else nodes.push({
5048
- kind: "rect",
5049
- rect: {
5050
- x,
5051
- y,
5052
- width: w,
5053
- height: h
5054
- },
5135
+ kind: "poly",
5136
+ points: (0, _embedpdf_core_geometry.textQuadRing)(q),
5137
+ closed: true,
5055
5138
  paint: {
5056
5139
  fill: color,
5057
5140
  opacity,
@@ -5061,23 +5144,40 @@ function markupScene(subtype, quads, style) {
5061
5144
  }
5062
5145
  return nodes;
5063
5146
  }
5064
- /** A redact mark's regions: per-quad boxes (text marks) or the rect (area). */
5147
+ const rectRing = (r) => [
5148
+ {
5149
+ x: r.x,
5150
+ y: r.y
5151
+ },
5152
+ {
5153
+ x: r.x + r.width,
5154
+ y: r.y
5155
+ },
5156
+ {
5157
+ x: r.x + r.width,
5158
+ y: r.y + r.height
5159
+ },
5160
+ {
5161
+ x: r.x,
5162
+ y: r.y + r.height
5163
+ }
5164
+ ];
5065
5165
  function redactRegions(geom) {
5066
5166
  if (geom.t === "quads") {
5067
5167
  const out = [];
5068
5168
  for (const q of geom.quads) {
5069
- const w = q[1].x - q[0].x;
5070
- const h = q[2].y - q[0].y;
5071
- if (w > 0 && h > 0) out.push({
5072
- x: q[0].x,
5073
- y: q[0].y,
5074
- width: w,
5075
- height: h
5169
+ const bounds = (0, _embedpdf_core_geometry.textQuadBounds)(q);
5170
+ if (bounds.width > 0 && bounds.height > 0) out.push({
5171
+ ring: (0, _embedpdf_core_geometry.textQuadRing)(q),
5172
+ bounds
5076
5173
  });
5077
5174
  }
5078
5175
  return out;
5079
5176
  }
5080
- if (geom.t === "rect") return [geom.rect];
5177
+ if (geom.t === "rect") return [{
5178
+ ring: rectRing(geom.rect),
5179
+ bounds: geom.rect
5180
+ }];
5081
5181
  return [];
5082
5182
  }
5083
5183
  /**
@@ -5143,9 +5243,10 @@ function redactScene(item) {
5143
5243
  width: item.style.strokeWidth || 1.5,
5144
5244
  opacity: item.style.opacity
5145
5245
  };
5146
- return regions.map((rect) => ({
5147
- kind: "rect",
5148
- rect,
5246
+ return regions.map((region) => ({
5247
+ kind: "poly",
5248
+ points: region.ring,
5249
+ closed: true,
5149
5250
  paint
5150
5251
  }));
5151
5252
  }
@@ -5155,13 +5256,14 @@ function redactScene(item) {
5155
5256
  fill: item.style.interiorColor,
5156
5257
  opacity: 1
5157
5258
  };
5158
- for (const rect of regions) nodes.push({
5159
- kind: "rect",
5160
- rect,
5259
+ for (const region of regions) nodes.push({
5260
+ kind: "poly",
5261
+ points: region.ring,
5262
+ closed: true,
5161
5263
  paint: fill
5162
5264
  });
5163
5265
  }
5164
- if (item.label) for (const region of regions) nodes.push(...layoutRedactLabel(region, item.label, item.text));
5266
+ if (item.label) for (const region of regions) nodes.push(...layoutRedactLabel(region.bounds, item.label, item.text));
5165
5267
  return nodes;
5166
5268
  }
5167
5269
  /** The full painted scene for one annotation. */
@@ -5585,7 +5687,7 @@ function chrome(m, pon, pageBox, knobOffset = 24, view) {
5585
5687
  const style = effStyle(a, view);
5586
5688
  const caps = capsFor(a.subtype);
5587
5689
  const rot = geomRotation(g);
5588
- const obb = caps.rotatable ? obbFromGeom(g, style.strokeWidth, a.style.border) : null;
5690
+ const obb = obbFromGeom(g, style.strokeWidth, a.style.border);
5589
5691
  if (obb && rot !== 0) nodes.push({
5590
5692
  kind: "obb",
5591
5693
  corners: obb.corners,
@@ -5698,6 +5800,8 @@ exports.calloutConnection = calloutConnection;
5698
5800
  exports.calloutLinePoints = calloutLinePoints;
5699
5801
  exports.canMove = canMove;
5700
5802
  exports.capsFor = capsFor;
5803
+ exports.caretGeomFromAnchor = caretGeomFromAnchor;
5804
+ exports.caretRectFromAnchor = caretRectFromAnchor;
5701
5805
  exports.caretRectFromTextEnd = caretRectFromTextEnd;
5702
5806
  exports.centroidOf = centroidOf;
5703
5807
  exports.chrome = chrome;