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