@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 +244 -140
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -13
- package/dist/index.d.ts +54 -13
- package/dist/index.js +244 -142
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
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.
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
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.
|
|
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.
|
|
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.
|
|
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) =>
|
|
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
|
|
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.
|
|
3669
|
-
case "createReplaceText": return createReplaceText(m, msg.pon, msg.
|
|
3670
|
-
case "createMarkup": return createMarkup(m, msg.subtype, msg.pon, msg.
|
|
3671
|
-
case "setMarkupPreview": return setMarkupPreview(m, msg.subtype, msg.
|
|
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
|
-
/**
|
|
4473
|
-
*
|
|
4474
|
-
const
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
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,
|
|
4498
|
-
const quads =
|
|
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,
|
|
4540
|
-
const quads =
|
|
4541
|
-
if (!quads.length
|
|
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,
|
|
4598
|
-
|
|
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
|
-
|
|
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
|
|
4634
|
-
function setMarkupPreview(m, 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
|
|
4637
|
-
const quads =
|
|
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
|
|
4977
|
-
*
|
|
4978
|
-
|
|
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
|
-
|
|
4981
|
-
|
|
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
|
|
4985
|
-
*
|
|
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
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
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
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
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(
|
|
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: "
|
|
5049
|
-
|
|
5050
|
-
|
|
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
|
-
|
|
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
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
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 [
|
|
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((
|
|
5147
|
-
kind: "
|
|
5148
|
-
|
|
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
|
|
5159
|
-
kind: "
|
|
5160
|
-
|
|
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 =
|
|
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;
|