@libpdf/core 0.2.2 → 0.2.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/README.md +1 -1
- package/dist/index.d.mts +171 -1
- package/dist/index.mjs +986 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { createCMSECDSASignature } from "pkijs";
|
|
|
9
9
|
import { base64 } from "@scure/base";
|
|
10
10
|
|
|
11
11
|
//#region package.json
|
|
12
|
-
var version = "0.2.
|
|
12
|
+
var version = "0.2.3";
|
|
13
13
|
|
|
14
14
|
//#endregion
|
|
15
15
|
//#region src/objects/pdf-array.ts
|
|
@@ -22717,7 +22717,7 @@ function validateFollowingOperator(scanner) {
|
|
|
22717
22717
|
const tokenStart = pos + i;
|
|
22718
22718
|
const firstCh = scanner.peekAt(tokenStart);
|
|
22719
22719
|
if (firstCh === -1) return false;
|
|
22720
|
-
if (isNumberStart(firstCh)) {
|
|
22720
|
+
if (isNumberStart$1(firstCh)) {
|
|
22721
22721
|
i++;
|
|
22722
22722
|
while (i < lookahead) {
|
|
22723
22723
|
const ch = scanner.peekAt(pos + i);
|
|
@@ -22772,7 +22772,7 @@ function validateFollowingOperator(scanner) {
|
|
|
22772
22772
|
}
|
|
22773
22773
|
return false;
|
|
22774
22774
|
}
|
|
22775
|
-
function isNumberStart(ch) {
|
|
22775
|
+
function isNumberStart$1(ch) {
|
|
22776
22776
|
return ch >= DIGIT_0 && ch <= DIGIT_9 || ch === CHAR_PLUS || ch === CHAR_MINUS || ch === CHAR_PERIOD;
|
|
22777
22777
|
}
|
|
22778
22778
|
/**
|
|
@@ -23722,6 +23722,854 @@ function getPaintOp(hasFill, hasStroke) {
|
|
|
23722
23722
|
return stroke();
|
|
23723
23723
|
}
|
|
23724
23724
|
|
|
23725
|
+
//#endregion
|
|
23726
|
+
//#region src/svg/arc-to-bezier.ts
|
|
23727
|
+
/**
|
|
23728
|
+
* Convert an SVG arc to one or more cubic bezier curves.
|
|
23729
|
+
*
|
|
23730
|
+
* Handles edge cases according to SVG spec:
|
|
23731
|
+
* - If rx=0 or ry=0, returns a line to the endpoint
|
|
23732
|
+
* - If start and end points are the same, returns empty array
|
|
23733
|
+
* - If radii are too small, they're scaled up automatically
|
|
23734
|
+
*
|
|
23735
|
+
* @param arc - Arc parameters in endpoint form
|
|
23736
|
+
* @returns Array of bezier curves that approximate the arc
|
|
23737
|
+
*/
|
|
23738
|
+
function arcToBezier(arc) {
|
|
23739
|
+
const { x1, y1, x2, y2, xAxisRotation, largeArcFlag, sweepFlag } = arc;
|
|
23740
|
+
let { rx, ry } = arc;
|
|
23741
|
+
if (x1 === x2 && y1 === y2) return [];
|
|
23742
|
+
if (rx === 0 || ry === 0) return [{
|
|
23743
|
+
cp1x: x1,
|
|
23744
|
+
cp1y: y1,
|
|
23745
|
+
cp2x: x2,
|
|
23746
|
+
cp2y: y2,
|
|
23747
|
+
x: x2,
|
|
23748
|
+
y: y2
|
|
23749
|
+
}];
|
|
23750
|
+
rx = Math.abs(rx);
|
|
23751
|
+
ry = Math.abs(ry);
|
|
23752
|
+
const phi = xAxisRotation * Math.PI / 180;
|
|
23753
|
+
const cosPhi = Math.cos(phi);
|
|
23754
|
+
const sinPhi = Math.sin(phi);
|
|
23755
|
+
const dx = (x1 - x2) / 2;
|
|
23756
|
+
const dy = (y1 - y2) / 2;
|
|
23757
|
+
const x1p = cosPhi * dx + sinPhi * dy;
|
|
23758
|
+
const y1p = -sinPhi * dx + cosPhi * dy;
|
|
23759
|
+
const lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
|
|
23760
|
+
if (lambda > 1) {
|
|
23761
|
+
const sqrtLambda = Math.sqrt(lambda);
|
|
23762
|
+
rx = sqrtLambda * rx;
|
|
23763
|
+
ry = sqrtLambda * ry;
|
|
23764
|
+
}
|
|
23765
|
+
const rx2 = rx * rx;
|
|
23766
|
+
const ry2 = ry * ry;
|
|
23767
|
+
const x1p2 = x1p * x1p;
|
|
23768
|
+
const y1p2 = y1p * y1p;
|
|
23769
|
+
let sq = (rx2 * ry2 - rx2 * y1p2 - ry2 * x1p2) / (rx2 * y1p2 + ry2 * x1p2);
|
|
23770
|
+
if (sq < 0) sq = 0;
|
|
23771
|
+
const coef = (largeArcFlag === sweepFlag ? -1 : 1) * Math.sqrt(sq);
|
|
23772
|
+
const cxp = coef * rx * y1p / ry;
|
|
23773
|
+
const cyp = -coef * ry * x1p / rx;
|
|
23774
|
+
const midX = (x1 + x2) / 2;
|
|
23775
|
+
const midY = (y1 + y2) / 2;
|
|
23776
|
+
const cx = cosPhi * cxp - sinPhi * cyp + midX;
|
|
23777
|
+
const cy = sinPhi * cxp + cosPhi * cyp + midY;
|
|
23778
|
+
const ux = (x1p - cxp) / rx;
|
|
23779
|
+
const uy = (y1p - cyp) / ry;
|
|
23780
|
+
const vx = (-x1p - cxp) / rx;
|
|
23781
|
+
const vy = (-y1p - cyp) / ry;
|
|
23782
|
+
const theta1 = angleBetween(1, 0, ux, uy);
|
|
23783
|
+
let dTheta = angleBetween(ux, uy, vx, vy);
|
|
23784
|
+
if (!sweepFlag && dTheta > 0) dTheta -= 2 * Math.PI;
|
|
23785
|
+
if (sweepFlag && dTheta < 0) dTheta += 2 * Math.PI;
|
|
23786
|
+
const numSegments = Math.ceil(Math.abs(dTheta) / (Math.PI / 2));
|
|
23787
|
+
const segmentAngle = dTheta / numSegments;
|
|
23788
|
+
const curves = [];
|
|
23789
|
+
let currentAngle = theta1;
|
|
23790
|
+
for (let i = 0; i < numSegments; i++) {
|
|
23791
|
+
const nextAngle = currentAngle + segmentAngle;
|
|
23792
|
+
const curve = arcSegmentToBezier(cx, cy, rx, ry, phi, currentAngle, nextAngle);
|
|
23793
|
+
curves.push(curve);
|
|
23794
|
+
currentAngle = nextAngle;
|
|
23795
|
+
}
|
|
23796
|
+
return curves;
|
|
23797
|
+
}
|
|
23798
|
+
/**
|
|
23799
|
+
* Compute the angle between two vectors.
|
|
23800
|
+
*/
|
|
23801
|
+
function angleBetween(ux, uy, vx, vy) {
|
|
23802
|
+
const sign = ux * vy - uy * vx < 0 ? -1 : 1;
|
|
23803
|
+
let cos = (ux * vx + uy * vy) / (Math.sqrt(ux * ux + uy * uy) * Math.sqrt(vx * vx + vy * vy));
|
|
23804
|
+
if (cos < -1) cos = -1;
|
|
23805
|
+
if (cos > 1) cos = 1;
|
|
23806
|
+
return sign * Math.acos(cos);
|
|
23807
|
+
}
|
|
23808
|
+
/**
|
|
23809
|
+
* Convert a single arc segment (up to 90 degrees) to a cubic bezier.
|
|
23810
|
+
*
|
|
23811
|
+
* Uses the standard arc approximation formula:
|
|
23812
|
+
* For an arc of angle theta centered at origin:
|
|
23813
|
+
* CP1 = P0 + alpha * tangent at P0
|
|
23814
|
+
* CP2 = P1 - alpha * tangent at P1
|
|
23815
|
+
*
|
|
23816
|
+
* Where alpha = (4/3) * tan(theta/4)
|
|
23817
|
+
*/
|
|
23818
|
+
function arcSegmentToBezier(cx, cy, rx, ry, phi, theta1, theta2) {
|
|
23819
|
+
const dTheta = theta2 - theta1;
|
|
23820
|
+
const t = Math.tan(dTheta / 4);
|
|
23821
|
+
const alpha = Math.sin(dTheta) * (Math.sqrt(4 + 3 * t * t) - 1) / 3;
|
|
23822
|
+
const cosPhi = Math.cos(phi);
|
|
23823
|
+
const sinPhi = Math.sin(phi);
|
|
23824
|
+
const cos1 = Math.cos(theta1);
|
|
23825
|
+
const sin1 = Math.sin(theta1);
|
|
23826
|
+
const cos2 = Math.cos(theta2);
|
|
23827
|
+
const sin2 = Math.sin(theta2);
|
|
23828
|
+
const p0x = cx + cosPhi * rx * cos1 - sinPhi * ry * sin1;
|
|
23829
|
+
const p0y = cy + sinPhi * rx * cos1 + cosPhi * ry * sin1;
|
|
23830
|
+
const p1x = cx + cosPhi * rx * cos2 - sinPhi * ry * sin2;
|
|
23831
|
+
const p1y = cy + sinPhi * rx * cos2 + cosPhi * ry * sin2;
|
|
23832
|
+
const t0x = -cosPhi * rx * sin1 - sinPhi * ry * cos1;
|
|
23833
|
+
const t0y = -sinPhi * rx * sin1 + cosPhi * ry * cos1;
|
|
23834
|
+
const t1x = -cosPhi * rx * sin2 - sinPhi * ry * cos2;
|
|
23835
|
+
const t1y = -sinPhi * rx * sin2 + cosPhi * ry * cos2;
|
|
23836
|
+
return {
|
|
23837
|
+
cp1x: p0x + alpha * t0x,
|
|
23838
|
+
cp1y: p0y + alpha * t0y,
|
|
23839
|
+
cp2x: p1x - alpha * t1x,
|
|
23840
|
+
cp2y: p1y - alpha * t1y,
|
|
23841
|
+
x: p1x,
|
|
23842
|
+
y: p1y
|
|
23843
|
+
};
|
|
23844
|
+
}
|
|
23845
|
+
|
|
23846
|
+
//#endregion
|
|
23847
|
+
//#region src/svg/path-parser.ts
|
|
23848
|
+
/**
|
|
23849
|
+
* Number of parameters required for each command type.
|
|
23850
|
+
*/
|
|
23851
|
+
const COMMAND_PARAMS = {
|
|
23852
|
+
M: 2,
|
|
23853
|
+
m: 2,
|
|
23854
|
+
L: 2,
|
|
23855
|
+
l: 2,
|
|
23856
|
+
H: 1,
|
|
23857
|
+
h: 1,
|
|
23858
|
+
V: 1,
|
|
23859
|
+
v: 1,
|
|
23860
|
+
C: 6,
|
|
23861
|
+
c: 6,
|
|
23862
|
+
S: 4,
|
|
23863
|
+
s: 4,
|
|
23864
|
+
Q: 4,
|
|
23865
|
+
q: 4,
|
|
23866
|
+
T: 2,
|
|
23867
|
+
t: 2,
|
|
23868
|
+
A: 7,
|
|
23869
|
+
a: 7,
|
|
23870
|
+
Z: 0,
|
|
23871
|
+
z: 0
|
|
23872
|
+
};
|
|
23873
|
+
/**
|
|
23874
|
+
* Check if a character is a command letter.
|
|
23875
|
+
*/
|
|
23876
|
+
function isCommandLetter(char) {
|
|
23877
|
+
return /^[MmLlHhVvCcSsQqTtAaZz]$/.test(char);
|
|
23878
|
+
}
|
|
23879
|
+
/**
|
|
23880
|
+
* Check if a character can start a number.
|
|
23881
|
+
*/
|
|
23882
|
+
function isNumberStart(char) {
|
|
23883
|
+
return /^[0-9.+-]$/.test(char);
|
|
23884
|
+
}
|
|
23885
|
+
/**
|
|
23886
|
+
* Tokenizer for SVG path strings.
|
|
23887
|
+
* Extracts command letters and numbers from the path string.
|
|
23888
|
+
*/
|
|
23889
|
+
var PathTokenizer = class {
|
|
23890
|
+
path;
|
|
23891
|
+
pos = 0;
|
|
23892
|
+
constructor(path) {
|
|
23893
|
+
this.path = path;
|
|
23894
|
+
}
|
|
23895
|
+
/**
|
|
23896
|
+
* Skip whitespace and commas.
|
|
23897
|
+
*/
|
|
23898
|
+
skipWhitespaceAndCommas() {
|
|
23899
|
+
while (this.pos < this.path.length) {
|
|
23900
|
+
const char = this.path[this.pos];
|
|
23901
|
+
if (char === " " || char === " " || char === "\n" || char === "\r" || char === ",") this.pos++;
|
|
23902
|
+
else break;
|
|
23903
|
+
}
|
|
23904
|
+
}
|
|
23905
|
+
/**
|
|
23906
|
+
* Read a number from the current position.
|
|
23907
|
+
* Handles integers, decimals, negative numbers, and scientific notation.
|
|
23908
|
+
*/
|
|
23909
|
+
readNumber() {
|
|
23910
|
+
this.skipWhitespaceAndCommas();
|
|
23911
|
+
if (this.pos >= this.path.length) return null;
|
|
23912
|
+
const char = this.path[this.pos];
|
|
23913
|
+
if (isCommandLetter(char)) return null;
|
|
23914
|
+
if (!isNumberStart(char)) {
|
|
23915
|
+
this.pos++;
|
|
23916
|
+
return null;
|
|
23917
|
+
}
|
|
23918
|
+
let numStr = "";
|
|
23919
|
+
let hasDecimal = false;
|
|
23920
|
+
let hasExponent = false;
|
|
23921
|
+
if (char === "+" || char === "-") {
|
|
23922
|
+
numStr += char;
|
|
23923
|
+
this.pos++;
|
|
23924
|
+
}
|
|
23925
|
+
while (this.pos < this.path.length) {
|
|
23926
|
+
const c = this.path[this.pos];
|
|
23927
|
+
if (c >= "0" && c <= "9") {
|
|
23928
|
+
numStr += c;
|
|
23929
|
+
this.pos++;
|
|
23930
|
+
} else if (c === "." && !hasDecimal && !hasExponent) {
|
|
23931
|
+
numStr += c;
|
|
23932
|
+
hasDecimal = true;
|
|
23933
|
+
this.pos++;
|
|
23934
|
+
} else if ((c === "e" || c === "E") && !hasExponent && numStr.length > 0) {
|
|
23935
|
+
numStr += c;
|
|
23936
|
+
hasExponent = true;
|
|
23937
|
+
this.pos++;
|
|
23938
|
+
if (this.pos < this.path.length) {
|
|
23939
|
+
const signChar = this.path[this.pos];
|
|
23940
|
+
if (signChar === "+" || signChar === "-") {
|
|
23941
|
+
numStr += signChar;
|
|
23942
|
+
this.pos++;
|
|
23943
|
+
}
|
|
23944
|
+
}
|
|
23945
|
+
} else break;
|
|
23946
|
+
}
|
|
23947
|
+
if (numStr === "" || numStr === "+" || numStr === "-" || numStr === ".") return null;
|
|
23948
|
+
const value = Number.parseFloat(numStr);
|
|
23949
|
+
return Number.isNaN(value) ? null : value;
|
|
23950
|
+
}
|
|
23951
|
+
/**
|
|
23952
|
+
* Read a command letter from the current position.
|
|
23953
|
+
*/
|
|
23954
|
+
readCommand() {
|
|
23955
|
+
this.skipWhitespaceAndCommas();
|
|
23956
|
+
if (this.pos >= this.path.length) return null;
|
|
23957
|
+
const char = this.path[this.pos];
|
|
23958
|
+
if (isCommandLetter(char)) {
|
|
23959
|
+
this.pos++;
|
|
23960
|
+
return char;
|
|
23961
|
+
}
|
|
23962
|
+
return null;
|
|
23963
|
+
}
|
|
23964
|
+
/**
|
|
23965
|
+
* Peek at the next character without consuming it.
|
|
23966
|
+
*/
|
|
23967
|
+
peek() {
|
|
23968
|
+
this.skipWhitespaceAndCommas();
|
|
23969
|
+
if (this.pos >= this.path.length) return null;
|
|
23970
|
+
return this.path[this.pos];
|
|
23971
|
+
}
|
|
23972
|
+
/**
|
|
23973
|
+
* Check if there's more content to parse.
|
|
23974
|
+
*/
|
|
23975
|
+
hasMore() {
|
|
23976
|
+
this.skipWhitespaceAndCommas();
|
|
23977
|
+
return this.pos < this.path.length;
|
|
23978
|
+
}
|
|
23979
|
+
/**
|
|
23980
|
+
* Read a single flag digit (0 or 1) for arc commands.
|
|
23981
|
+
* SVG arc flags are special - they're single digits that can be
|
|
23982
|
+
* concatenated without separators: "00" means two flags, both 0.
|
|
23983
|
+
*/
|
|
23984
|
+
readFlag() {
|
|
23985
|
+
this.skipWhitespaceAndCommas();
|
|
23986
|
+
if (this.pos >= this.path.length) return null;
|
|
23987
|
+
const char = this.path[this.pos];
|
|
23988
|
+
if (char === "0" || char === "1") {
|
|
23989
|
+
this.pos++;
|
|
23990
|
+
return char === "1" ? 1 : 0;
|
|
23991
|
+
}
|
|
23992
|
+
return null;
|
|
23993
|
+
}
|
|
23994
|
+
};
|
|
23995
|
+
/**
|
|
23996
|
+
* Parse an SVG path string into an array of commands.
|
|
23997
|
+
*
|
|
23998
|
+
* @param pathData - The SVG path `d` attribute string
|
|
23999
|
+
* @returns Array of parsed path commands
|
|
24000
|
+
*
|
|
24001
|
+
* @example
|
|
24002
|
+
* ```typescript
|
|
24003
|
+
* const commands = parseSvgPath("M 10 10 L 100 10 L 100 100 Z");
|
|
24004
|
+
* // [
|
|
24005
|
+
* // { type: "M", x: 10, y: 10 },
|
|
24006
|
+
* // { type: "L", x: 100, y: 10 },
|
|
24007
|
+
* // { type: "L", x: 100, y: 100 },
|
|
24008
|
+
* // { type: "Z" },
|
|
24009
|
+
* // ]
|
|
24010
|
+
* ```
|
|
24011
|
+
*/
|
|
24012
|
+
function parseSvgPath(pathData) {
|
|
24013
|
+
const commands = [];
|
|
24014
|
+
const tokenizer = new PathTokenizer(pathData);
|
|
24015
|
+
let currentCommand = null;
|
|
24016
|
+
let isFirstInSequence = true;
|
|
24017
|
+
while (tokenizer.hasMore()) {
|
|
24018
|
+
const nextChar = tokenizer.peek();
|
|
24019
|
+
if (nextChar && isCommandLetter(nextChar)) {
|
|
24020
|
+
currentCommand = tokenizer.readCommand();
|
|
24021
|
+
isFirstInSequence = true;
|
|
24022
|
+
}
|
|
24023
|
+
if (!currentCommand) break;
|
|
24024
|
+
const paramCount = COMMAND_PARAMS[currentCommand];
|
|
24025
|
+
if (currentCommand === "Z" || currentCommand === "z") {
|
|
24026
|
+
commands.push({ type: currentCommand });
|
|
24027
|
+
currentCommand = null;
|
|
24028
|
+
continue;
|
|
24029
|
+
}
|
|
24030
|
+
const params = [];
|
|
24031
|
+
const isArc = currentCommand === "A" || currentCommand === "a";
|
|
24032
|
+
for (let i = 0; i < paramCount; i++) {
|
|
24033
|
+
let num;
|
|
24034
|
+
if (isArc && (i === 3 || i === 4)) num = tokenizer.readFlag();
|
|
24035
|
+
else num = tokenizer.readNumber();
|
|
24036
|
+
if (num === null) break;
|
|
24037
|
+
params.push(num);
|
|
24038
|
+
}
|
|
24039
|
+
if (params.length !== paramCount) continue;
|
|
24040
|
+
const command = createCommand(currentCommand, params);
|
|
24041
|
+
if (command) commands.push(command);
|
|
24042
|
+
if (isFirstInSequence) {
|
|
24043
|
+
isFirstInSequence = false;
|
|
24044
|
+
if (currentCommand === "M") currentCommand = "L";
|
|
24045
|
+
else if (currentCommand === "m") currentCommand = "l";
|
|
24046
|
+
}
|
|
24047
|
+
const nextPeek = tokenizer.peek();
|
|
24048
|
+
if (!nextPeek || isCommandLetter(nextPeek)) {
|
|
24049
|
+
if (!nextPeek) break;
|
|
24050
|
+
}
|
|
24051
|
+
}
|
|
24052
|
+
return commands;
|
|
24053
|
+
}
|
|
24054
|
+
/**
|
|
24055
|
+
* Create a command object from type and parameters.
|
|
24056
|
+
*/
|
|
24057
|
+
function createCommand(type, params) {
|
|
24058
|
+
switch (type) {
|
|
24059
|
+
case "M":
|
|
24060
|
+
case "m": return {
|
|
24061
|
+
type,
|
|
24062
|
+
x: params[0],
|
|
24063
|
+
y: params[1]
|
|
24064
|
+
};
|
|
24065
|
+
case "L":
|
|
24066
|
+
case "l": return {
|
|
24067
|
+
type,
|
|
24068
|
+
x: params[0],
|
|
24069
|
+
y: params[1]
|
|
24070
|
+
};
|
|
24071
|
+
case "H":
|
|
24072
|
+
case "h": return {
|
|
24073
|
+
type,
|
|
24074
|
+
x: params[0]
|
|
24075
|
+
};
|
|
24076
|
+
case "V":
|
|
24077
|
+
case "v": return {
|
|
24078
|
+
type,
|
|
24079
|
+
y: params[0]
|
|
24080
|
+
};
|
|
24081
|
+
case "C":
|
|
24082
|
+
case "c": return {
|
|
24083
|
+
type,
|
|
24084
|
+
x1: params[0],
|
|
24085
|
+
y1: params[1],
|
|
24086
|
+
x2: params[2],
|
|
24087
|
+
y2: params[3],
|
|
24088
|
+
x: params[4],
|
|
24089
|
+
y: params[5]
|
|
24090
|
+
};
|
|
24091
|
+
case "S":
|
|
24092
|
+
case "s": return {
|
|
24093
|
+
type,
|
|
24094
|
+
x2: params[0],
|
|
24095
|
+
y2: params[1],
|
|
24096
|
+
x: params[2],
|
|
24097
|
+
y: params[3]
|
|
24098
|
+
};
|
|
24099
|
+
case "Q":
|
|
24100
|
+
case "q": return {
|
|
24101
|
+
type,
|
|
24102
|
+
x1: params[0],
|
|
24103
|
+
y1: params[1],
|
|
24104
|
+
x: params[2],
|
|
24105
|
+
y: params[3]
|
|
24106
|
+
};
|
|
24107
|
+
case "T":
|
|
24108
|
+
case "t": return {
|
|
24109
|
+
type,
|
|
24110
|
+
x: params[0],
|
|
24111
|
+
y: params[1]
|
|
24112
|
+
};
|
|
24113
|
+
case "A":
|
|
24114
|
+
case "a": return {
|
|
24115
|
+
type,
|
|
24116
|
+
rx: params[0],
|
|
24117
|
+
ry: params[1],
|
|
24118
|
+
xAxisRotation: params[2],
|
|
24119
|
+
largeArcFlag: params[3] !== 0,
|
|
24120
|
+
sweepFlag: params[4] !== 0,
|
|
24121
|
+
x: params[5],
|
|
24122
|
+
y: params[6]
|
|
24123
|
+
};
|
|
24124
|
+
case "Z":
|
|
24125
|
+
case "z": return { type };
|
|
24126
|
+
default: return null;
|
|
24127
|
+
}
|
|
24128
|
+
}
|
|
24129
|
+
|
|
24130
|
+
//#endregion
|
|
24131
|
+
//#region src/svg/path-executor.ts
|
|
24132
|
+
/**
|
|
24133
|
+
* SVG Path Executor
|
|
24134
|
+
*
|
|
24135
|
+
* Executes parsed SVG path commands via a callback interface.
|
|
24136
|
+
* Handles:
|
|
24137
|
+
* - Relative to absolute coordinate conversion
|
|
24138
|
+
* - Smooth curve control point reflection
|
|
24139
|
+
* - Arc to bezier conversion
|
|
24140
|
+
* - Y-axis flipping for PDF coordinate system (optional, default: true)
|
|
24141
|
+
*/
|
|
24142
|
+
/**
|
|
24143
|
+
* Execute SVG path commands via a callback interface.
|
|
24144
|
+
*
|
|
24145
|
+
* The executor handles all coordinate transformations and command
|
|
24146
|
+
* normalization, so the sink receives only absolute coordinates
|
|
24147
|
+
* and standard path operations.
|
|
24148
|
+
*
|
|
24149
|
+
* @param options - Execution options
|
|
24150
|
+
* @param options.commands - Parsed SVG path commands
|
|
24151
|
+
* @param options.sink - Callback interface for path operations
|
|
24152
|
+
* @param options.initialX - Initial X coordinate (default: 0)
|
|
24153
|
+
* @param options.initialY - Initial Y coordinate (default: 0)
|
|
24154
|
+
* @param options.flipY - Flip Y coordinates for PDF (default: true)
|
|
24155
|
+
* @param options.scale - Scale factor (default: 1)
|
|
24156
|
+
* @param options.translateX - X offset after transform (default: 0)
|
|
24157
|
+
* @param options.translateY - Y offset after transform (default: 0)
|
|
24158
|
+
* @returns Final position {x, y} after executing all commands
|
|
24159
|
+
*/
|
|
24160
|
+
function executeSvgPath(options) {
|
|
24161
|
+
const { commands, sink, initialX = 0, initialY = 0, flipY = true, scale = 1, translateX = 0, translateY = 0 } = options;
|
|
24162
|
+
const yFlip = flipY ? -1 : 1;
|
|
24163
|
+
const initialOutputX = initialX + translateX;
|
|
24164
|
+
const initialOutputY = initialY + translateY;
|
|
24165
|
+
const state = {
|
|
24166
|
+
currentX: initialOutputX,
|
|
24167
|
+
currentY: initialOutputY,
|
|
24168
|
+
subpathStartX: initialOutputX,
|
|
24169
|
+
subpathStartY: initialOutputY,
|
|
24170
|
+
lastControlX: initialOutputX,
|
|
24171
|
+
lastControlY: initialOutputY,
|
|
24172
|
+
lastCommand: null,
|
|
24173
|
+
yFlip,
|
|
24174
|
+
scale,
|
|
24175
|
+
translateX,
|
|
24176
|
+
translateY
|
|
24177
|
+
};
|
|
24178
|
+
for (const cmd of commands) executeCommand(cmd, state, sink);
|
|
24179
|
+
return {
|
|
24180
|
+
x: state.currentX,
|
|
24181
|
+
y: state.currentY
|
|
24182
|
+
};
|
|
24183
|
+
}
|
|
24184
|
+
/**
|
|
24185
|
+
* Transform an SVG coordinate to output space.
|
|
24186
|
+
* Applies: scale, Y-flip, then translate.
|
|
24187
|
+
*/
|
|
24188
|
+
function transformX(x, state) {
|
|
24189
|
+
return x * state.scale + state.translateX;
|
|
24190
|
+
}
|
|
24191
|
+
function transformY(y, state) {
|
|
24192
|
+
return y * state.yFlip * state.scale + state.translateY;
|
|
24193
|
+
}
|
|
24194
|
+
/**
|
|
24195
|
+
* Execute a single SVG path command.
|
|
24196
|
+
*/
|
|
24197
|
+
function executeCommand(cmd, state, sink) {
|
|
24198
|
+
switch (cmd.type) {
|
|
24199
|
+
case "M":
|
|
24200
|
+
executeMoveTo({
|
|
24201
|
+
x: cmd.x,
|
|
24202
|
+
y: cmd.y,
|
|
24203
|
+
relative: false,
|
|
24204
|
+
state,
|
|
24205
|
+
sink
|
|
24206
|
+
});
|
|
24207
|
+
break;
|
|
24208
|
+
case "m":
|
|
24209
|
+
executeMoveTo({
|
|
24210
|
+
x: cmd.x,
|
|
24211
|
+
y: cmd.y,
|
|
24212
|
+
relative: true,
|
|
24213
|
+
state,
|
|
24214
|
+
sink
|
|
24215
|
+
});
|
|
24216
|
+
break;
|
|
24217
|
+
case "L":
|
|
24218
|
+
executeLineTo({
|
|
24219
|
+
x: cmd.x,
|
|
24220
|
+
y: cmd.y,
|
|
24221
|
+
relative: false,
|
|
24222
|
+
state,
|
|
24223
|
+
sink
|
|
24224
|
+
});
|
|
24225
|
+
break;
|
|
24226
|
+
case "l":
|
|
24227
|
+
executeLineTo({
|
|
24228
|
+
x: cmd.x,
|
|
24229
|
+
y: cmd.y,
|
|
24230
|
+
relative: true,
|
|
24231
|
+
state,
|
|
24232
|
+
sink
|
|
24233
|
+
});
|
|
24234
|
+
break;
|
|
24235
|
+
case "H":
|
|
24236
|
+
executeHorizontalLine({
|
|
24237
|
+
x: cmd.x,
|
|
24238
|
+
relative: false,
|
|
24239
|
+
state,
|
|
24240
|
+
sink
|
|
24241
|
+
});
|
|
24242
|
+
break;
|
|
24243
|
+
case "h":
|
|
24244
|
+
executeHorizontalLine({
|
|
24245
|
+
x: cmd.x,
|
|
24246
|
+
relative: true,
|
|
24247
|
+
state,
|
|
24248
|
+
sink
|
|
24249
|
+
});
|
|
24250
|
+
break;
|
|
24251
|
+
case "V":
|
|
24252
|
+
executeVerticalLine({
|
|
24253
|
+
y: cmd.y,
|
|
24254
|
+
relative: false,
|
|
24255
|
+
state,
|
|
24256
|
+
sink
|
|
24257
|
+
});
|
|
24258
|
+
break;
|
|
24259
|
+
case "v":
|
|
24260
|
+
executeVerticalLine({
|
|
24261
|
+
y: cmd.y,
|
|
24262
|
+
relative: true,
|
|
24263
|
+
state,
|
|
24264
|
+
sink
|
|
24265
|
+
});
|
|
24266
|
+
break;
|
|
24267
|
+
case "C":
|
|
24268
|
+
executeCubicCurve({
|
|
24269
|
+
x1: cmd.x1,
|
|
24270
|
+
y1: cmd.y1,
|
|
24271
|
+
x2: cmd.x2,
|
|
24272
|
+
y2: cmd.y2,
|
|
24273
|
+
x: cmd.x,
|
|
24274
|
+
y: cmd.y,
|
|
24275
|
+
relative: false,
|
|
24276
|
+
state,
|
|
24277
|
+
sink
|
|
24278
|
+
});
|
|
24279
|
+
break;
|
|
24280
|
+
case "c":
|
|
24281
|
+
executeCubicCurve({
|
|
24282
|
+
x1: cmd.x1,
|
|
24283
|
+
y1: cmd.y1,
|
|
24284
|
+
x2: cmd.x2,
|
|
24285
|
+
y2: cmd.y2,
|
|
24286
|
+
x: cmd.x,
|
|
24287
|
+
y: cmd.y,
|
|
24288
|
+
relative: true,
|
|
24289
|
+
state,
|
|
24290
|
+
sink
|
|
24291
|
+
});
|
|
24292
|
+
break;
|
|
24293
|
+
case "S":
|
|
24294
|
+
executeSmoothCubic({
|
|
24295
|
+
x2: cmd.x2,
|
|
24296
|
+
y2: cmd.y2,
|
|
24297
|
+
x: cmd.x,
|
|
24298
|
+
y: cmd.y,
|
|
24299
|
+
relative: false,
|
|
24300
|
+
state,
|
|
24301
|
+
sink
|
|
24302
|
+
});
|
|
24303
|
+
break;
|
|
24304
|
+
case "s":
|
|
24305
|
+
executeSmoothCubic({
|
|
24306
|
+
x2: cmd.x2,
|
|
24307
|
+
y2: cmd.y2,
|
|
24308
|
+
x: cmd.x,
|
|
24309
|
+
y: cmd.y,
|
|
24310
|
+
relative: true,
|
|
24311
|
+
state,
|
|
24312
|
+
sink
|
|
24313
|
+
});
|
|
24314
|
+
break;
|
|
24315
|
+
case "Q":
|
|
24316
|
+
executeQuadratic({
|
|
24317
|
+
x1: cmd.x1,
|
|
24318
|
+
y1: cmd.y1,
|
|
24319
|
+
x: cmd.x,
|
|
24320
|
+
y: cmd.y,
|
|
24321
|
+
relative: false,
|
|
24322
|
+
state,
|
|
24323
|
+
sink
|
|
24324
|
+
});
|
|
24325
|
+
break;
|
|
24326
|
+
case "q":
|
|
24327
|
+
executeQuadratic({
|
|
24328
|
+
x1: cmd.x1,
|
|
24329
|
+
y1: cmd.y1,
|
|
24330
|
+
x: cmd.x,
|
|
24331
|
+
y: cmd.y,
|
|
24332
|
+
relative: true,
|
|
24333
|
+
state,
|
|
24334
|
+
sink
|
|
24335
|
+
});
|
|
24336
|
+
break;
|
|
24337
|
+
case "T":
|
|
24338
|
+
executeSmoothQuadratic({
|
|
24339
|
+
x: cmd.x,
|
|
24340
|
+
y: cmd.y,
|
|
24341
|
+
relative: false,
|
|
24342
|
+
state,
|
|
24343
|
+
sink
|
|
24344
|
+
});
|
|
24345
|
+
break;
|
|
24346
|
+
case "t":
|
|
24347
|
+
executeSmoothQuadratic({
|
|
24348
|
+
x: cmd.x,
|
|
24349
|
+
y: cmd.y,
|
|
24350
|
+
relative: true,
|
|
24351
|
+
state,
|
|
24352
|
+
sink
|
|
24353
|
+
});
|
|
24354
|
+
break;
|
|
24355
|
+
case "A":
|
|
24356
|
+
executeArc({
|
|
24357
|
+
rx: cmd.rx,
|
|
24358
|
+
ry: cmd.ry,
|
|
24359
|
+
xAxisRotation: cmd.xAxisRotation,
|
|
24360
|
+
largeArcFlag: cmd.largeArcFlag,
|
|
24361
|
+
sweepFlag: cmd.sweepFlag,
|
|
24362
|
+
x: cmd.x,
|
|
24363
|
+
y: cmd.y,
|
|
24364
|
+
relative: false,
|
|
24365
|
+
state,
|
|
24366
|
+
sink
|
|
24367
|
+
});
|
|
24368
|
+
break;
|
|
24369
|
+
case "a":
|
|
24370
|
+
executeArc({
|
|
24371
|
+
rx: cmd.rx,
|
|
24372
|
+
ry: cmd.ry,
|
|
24373
|
+
xAxisRotation: cmd.xAxisRotation,
|
|
24374
|
+
largeArcFlag: cmd.largeArcFlag,
|
|
24375
|
+
sweepFlag: cmd.sweepFlag,
|
|
24376
|
+
x: cmd.x,
|
|
24377
|
+
y: cmd.y,
|
|
24378
|
+
relative: true,
|
|
24379
|
+
state,
|
|
24380
|
+
sink
|
|
24381
|
+
});
|
|
24382
|
+
break;
|
|
24383
|
+
case "Z":
|
|
24384
|
+
case "z":
|
|
24385
|
+
executeClose({
|
|
24386
|
+
state,
|
|
24387
|
+
sink
|
|
24388
|
+
});
|
|
24389
|
+
break;
|
|
24390
|
+
}
|
|
24391
|
+
state.lastCommand = cmd.type;
|
|
24392
|
+
}
|
|
24393
|
+
function executeMoveTo(options) {
|
|
24394
|
+
const { x, y, relative, state, sink } = options;
|
|
24395
|
+
const outX = relative ? state.currentX + x * state.scale : transformX(x, state);
|
|
24396
|
+
const outY = relative ? state.currentY + y * state.yFlip * state.scale : transformY(y, state);
|
|
24397
|
+
sink.moveTo(outX, outY);
|
|
24398
|
+
state.currentX = outX;
|
|
24399
|
+
state.currentY = outY;
|
|
24400
|
+
state.subpathStartX = outX;
|
|
24401
|
+
state.subpathStartY = outY;
|
|
24402
|
+
state.lastControlX = outX;
|
|
24403
|
+
state.lastControlY = outY;
|
|
24404
|
+
}
|
|
24405
|
+
function executeLineTo(options) {
|
|
24406
|
+
const { x, y, relative, state, sink } = options;
|
|
24407
|
+
const outX = relative ? state.currentX + x * state.scale : transformX(x, state);
|
|
24408
|
+
const outY = relative ? state.currentY + y * state.yFlip * state.scale : transformY(y, state);
|
|
24409
|
+
sink.lineTo(outX, outY);
|
|
24410
|
+
state.currentX = outX;
|
|
24411
|
+
state.currentY = outY;
|
|
24412
|
+
state.lastControlX = outX;
|
|
24413
|
+
state.lastControlY = outY;
|
|
24414
|
+
}
|
|
24415
|
+
function executeHorizontalLine(options) {
|
|
24416
|
+
const { x, relative, state, sink } = options;
|
|
24417
|
+
const outX = relative ? state.currentX + x * state.scale : transformX(x, state);
|
|
24418
|
+
sink.lineTo(outX, state.currentY);
|
|
24419
|
+
state.currentX = outX;
|
|
24420
|
+
state.lastControlX = outX;
|
|
24421
|
+
state.lastControlY = state.currentY;
|
|
24422
|
+
}
|
|
24423
|
+
function executeVerticalLine(options) {
|
|
24424
|
+
const { y, relative, state, sink } = options;
|
|
24425
|
+
const outY = relative ? state.currentY + y * state.yFlip * state.scale : transformY(y, state);
|
|
24426
|
+
sink.lineTo(state.currentX, outY);
|
|
24427
|
+
state.currentY = outY;
|
|
24428
|
+
state.lastControlX = state.currentX;
|
|
24429
|
+
state.lastControlY = outY;
|
|
24430
|
+
}
|
|
24431
|
+
function executeCubicCurve(options) {
|
|
24432
|
+
const { x1, y1, x2, y2, x, y, relative, state, sink } = options;
|
|
24433
|
+
const outX1 = relative ? state.currentX + x1 * state.scale : transformX(x1, state);
|
|
24434
|
+
const outY1 = relative ? state.currentY + y1 * state.yFlip * state.scale : transformY(y1, state);
|
|
24435
|
+
const outX2 = relative ? state.currentX + x2 * state.scale : transformX(x2, state);
|
|
24436
|
+
const outY2 = relative ? state.currentY + y2 * state.yFlip * state.scale : transformY(y2, state);
|
|
24437
|
+
const outX = relative ? state.currentX + x * state.scale : transformX(x, state);
|
|
24438
|
+
const outY = relative ? state.currentY + y * state.yFlip * state.scale : transformY(y, state);
|
|
24439
|
+
sink.curveTo(outX1, outY1, outX2, outY2, outX, outY);
|
|
24440
|
+
state.currentX = outX;
|
|
24441
|
+
state.currentY = outY;
|
|
24442
|
+
state.lastControlX = outX2;
|
|
24443
|
+
state.lastControlY = outY2;
|
|
24444
|
+
}
|
|
24445
|
+
function executeSmoothCubic(options) {
|
|
24446
|
+
const { x2, y2, x, y, relative, state, sink } = options;
|
|
24447
|
+
let cp1x;
|
|
24448
|
+
let cp1y;
|
|
24449
|
+
if (state.lastCommand === "C" || state.lastCommand === "c" || state.lastCommand === "S" || state.lastCommand === "s") {
|
|
24450
|
+
cp1x = 2 * state.currentX - state.lastControlX;
|
|
24451
|
+
cp1y = 2 * state.currentY - state.lastControlY;
|
|
24452
|
+
} else {
|
|
24453
|
+
cp1x = state.currentX;
|
|
24454
|
+
cp1y = state.currentY;
|
|
24455
|
+
}
|
|
24456
|
+
const outX2 = relative ? state.currentX + x2 * state.scale : transformX(x2, state);
|
|
24457
|
+
const outY2 = relative ? state.currentY + y2 * state.yFlip * state.scale : transformY(y2, state);
|
|
24458
|
+
const outX = relative ? state.currentX + x * state.scale : transformX(x, state);
|
|
24459
|
+
const outY = relative ? state.currentY + y * state.yFlip * state.scale : transformY(y, state);
|
|
24460
|
+
sink.curveTo(cp1x, cp1y, outX2, outY2, outX, outY);
|
|
24461
|
+
state.currentX = outX;
|
|
24462
|
+
state.currentY = outY;
|
|
24463
|
+
state.lastControlX = outX2;
|
|
24464
|
+
state.lastControlY = outY2;
|
|
24465
|
+
}
|
|
24466
|
+
function executeQuadratic(options) {
|
|
24467
|
+
const { x1, y1, x, y, relative, state, sink } = options;
|
|
24468
|
+
const outCpX = relative ? state.currentX + x1 * state.scale : transformX(x1, state);
|
|
24469
|
+
const outCpY = relative ? state.currentY + y1 * state.yFlip * state.scale : transformY(y1, state);
|
|
24470
|
+
const outX = relative ? state.currentX + x * state.scale : transformX(x, state);
|
|
24471
|
+
const outY = relative ? state.currentY + y * state.yFlip * state.scale : transformY(y, state);
|
|
24472
|
+
sink.quadraticCurveTo(outCpX, outCpY, outX, outY);
|
|
24473
|
+
state.currentX = outX;
|
|
24474
|
+
state.currentY = outY;
|
|
24475
|
+
state.lastControlX = outCpX;
|
|
24476
|
+
state.lastControlY = outCpY;
|
|
24477
|
+
}
|
|
24478
|
+
function executeSmoothQuadratic(options) {
|
|
24479
|
+
const { x, y, relative, state, sink } = options;
|
|
24480
|
+
let cpx;
|
|
24481
|
+
let cpy;
|
|
24482
|
+
if (state.lastCommand === "Q" || state.lastCommand === "q" || state.lastCommand === "T" || state.lastCommand === "t") {
|
|
24483
|
+
cpx = 2 * state.currentX - state.lastControlX;
|
|
24484
|
+
cpy = 2 * state.currentY - state.lastControlY;
|
|
24485
|
+
} else {
|
|
24486
|
+
cpx = state.currentX;
|
|
24487
|
+
cpy = state.currentY;
|
|
24488
|
+
}
|
|
24489
|
+
const outX = relative ? state.currentX + x * state.scale : transformX(x, state);
|
|
24490
|
+
const outY = relative ? state.currentY + y * state.yFlip * state.scale : transformY(y, state);
|
|
24491
|
+
sink.quadraticCurveTo(cpx, cpy, outX, outY);
|
|
24492
|
+
state.currentX = outX;
|
|
24493
|
+
state.currentY = outY;
|
|
24494
|
+
state.lastControlX = cpx;
|
|
24495
|
+
state.lastControlY = cpy;
|
|
24496
|
+
}
|
|
24497
|
+
function executeArc(options) {
|
|
24498
|
+
const { rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y, relative, state, sink } = options;
|
|
24499
|
+
const outX = relative ? state.currentX + x * state.scale : transformX(x, state);
|
|
24500
|
+
const outY = relative ? state.currentY + y * state.yFlip * state.scale : transformY(y, state);
|
|
24501
|
+
const scaledRx = rx * state.scale;
|
|
24502
|
+
const scaledRy = ry * state.scale;
|
|
24503
|
+
const effectiveSweepFlag = state.yFlip === -1 ? !sweepFlag : sweepFlag;
|
|
24504
|
+
const curves = arcToBezier({
|
|
24505
|
+
x1: state.currentX,
|
|
24506
|
+
y1: state.currentY,
|
|
24507
|
+
rx: scaledRx,
|
|
24508
|
+
ry: scaledRy,
|
|
24509
|
+
xAxisRotation,
|
|
24510
|
+
largeArcFlag,
|
|
24511
|
+
sweepFlag: effectiveSweepFlag,
|
|
24512
|
+
x2: outX,
|
|
24513
|
+
y2: outY
|
|
24514
|
+
});
|
|
24515
|
+
for (const curve of curves) sink.curveTo(curve.cp1x, curve.cp1y, curve.cp2x, curve.cp2y, curve.x, curve.y);
|
|
24516
|
+
state.currentX = outX;
|
|
24517
|
+
state.currentY = outY;
|
|
24518
|
+
state.lastControlX = outX;
|
|
24519
|
+
state.lastControlY = outY;
|
|
24520
|
+
}
|
|
24521
|
+
function executeClose(options) {
|
|
24522
|
+
const { state, sink } = options;
|
|
24523
|
+
sink.close();
|
|
24524
|
+
state.currentX = state.subpathStartX;
|
|
24525
|
+
state.currentY = state.subpathStartY;
|
|
24526
|
+
state.lastControlX = state.subpathStartX;
|
|
24527
|
+
state.lastControlY = state.subpathStartY;
|
|
24528
|
+
}
|
|
24529
|
+
/**
|
|
24530
|
+
* Parse and execute an SVG path string.
|
|
24531
|
+
*
|
|
24532
|
+
* This is a convenience function that combines parsing and execution.
|
|
24533
|
+
*
|
|
24534
|
+
* By default, Y coordinates are flipped (negated) to convert from SVG's
|
|
24535
|
+
* top-left origin to PDF's bottom-left origin. Set `flipY: false` in
|
|
24536
|
+
* options to disable this behavior.
|
|
24537
|
+
*
|
|
24538
|
+
* @param options - Execution options
|
|
24539
|
+
* @param options.pathData - SVG path d string
|
|
24540
|
+
* @param options.sink - Callback interface for path operations
|
|
24541
|
+
* @param options.initialX - Initial X coordinate (default: 0)
|
|
24542
|
+
* @param options.initialY - Initial Y coordinate (default: 0)
|
|
24543
|
+
* @param options.flipY - Flip Y coordinates for PDF (default: true)
|
|
24544
|
+
* @param options.scale - Scale factor (default: 1)
|
|
24545
|
+
* @param options.translateX - X offset after transform (default: 0)
|
|
24546
|
+
* @param options.translateY - Y offset after transform (default: 0)
|
|
24547
|
+
* @returns Final position {x, y} after executing all commands
|
|
24548
|
+
*
|
|
24549
|
+
* @example
|
|
24550
|
+
* ```typescript
|
|
24551
|
+
* const sink = {
|
|
24552
|
+
* moveTo: (x, y) => console.log(`M ${x} ${y}`),
|
|
24553
|
+
* lineTo: (x, y) => console.log(`L ${x} ${y}`),
|
|
24554
|
+
* curveTo: (cp1x, cp1y, cp2x, cp2y, x, y) => console.log(`C ...`),
|
|
24555
|
+
* quadraticCurveTo: (cpx, cpy, x, y) => console.log(`Q ...`),
|
|
24556
|
+
* close: () => console.log(`Z`),
|
|
24557
|
+
* };
|
|
24558
|
+
*
|
|
24559
|
+
* executeSvgPathString({ pathData: "M 10 10 L 100 10 L 100 100 Z", sink });
|
|
24560
|
+
* ```
|
|
24561
|
+
*/
|
|
24562
|
+
function executeSvgPathString(options) {
|
|
24563
|
+
const { pathData, sink, initialX, initialY, ...executorOptions } = options;
|
|
24564
|
+
return executeSvgPath({
|
|
24565
|
+
commands: parseSvgPath(pathData),
|
|
24566
|
+
sink,
|
|
24567
|
+
initialX,
|
|
24568
|
+
initialY,
|
|
24569
|
+
...executorOptions
|
|
24570
|
+
});
|
|
24571
|
+
}
|
|
24572
|
+
|
|
23725
24573
|
//#endregion
|
|
23726
24574
|
//#region src/api/drawing/path-builder.ts
|
|
23727
24575
|
/**
|
|
@@ -23848,6 +24696,71 @@ var PathBuilder = class {
|
|
|
23848
24696
|
return this.moveTo(cx - rx, cy).curveTo(cx - rx, cy + ky, cx - kx, cy + ry, cx, cy + ry).curveTo(cx + kx, cy + ry, cx + rx, cy + ky, cx + rx, cy).curveTo(cx + rx, cy - ky, cx + kx, cy - ry, cx, cy - ry).curveTo(cx - kx, cy - ry, cx - rx, cy - ky, cx - rx, cy).close();
|
|
23849
24697
|
}
|
|
23850
24698
|
/**
|
|
24699
|
+
* Append an SVG path string to the current path.
|
|
24700
|
+
*
|
|
24701
|
+
* Parses the SVG path `d` attribute string and adds all commands to this path.
|
|
24702
|
+
* Relative commands (lowercase) are converted to absolute coordinates based on
|
|
24703
|
+
* the current point. Smooth curves (S, T) and arcs (A) are converted to
|
|
24704
|
+
* cubic bezier curves.
|
|
24705
|
+
*
|
|
24706
|
+
* By default, this method does NOT transform coordinates (flipY: false).
|
|
24707
|
+
* Use the options to apply scale, translation, and Y-flip for SVG paths.
|
|
24708
|
+
*
|
|
24709
|
+
* @param pathData - SVG path `d` attribute string
|
|
24710
|
+
* @param options - Execution options (flipY, scale, translate)
|
|
24711
|
+
* @returns This PathBuilder for chaining
|
|
24712
|
+
*
|
|
24713
|
+
* @example
|
|
24714
|
+
* ```typescript
|
|
24715
|
+
* // Simple path (no transform)
|
|
24716
|
+
* page.drawPath()
|
|
24717
|
+
* .appendSvgPath("M 10 10 L 100 10 L 55 90 Z")
|
|
24718
|
+
* .fill({ color: rgb(1, 0, 0) });
|
|
24719
|
+
*
|
|
24720
|
+
* // SVG icon with full transform
|
|
24721
|
+
* page.drawPath()
|
|
24722
|
+
* .appendSvgPath(iconPath, {
|
|
24723
|
+
* flipY: true,
|
|
24724
|
+
* scale: 0.1,
|
|
24725
|
+
* translateX: 100,
|
|
24726
|
+
* translateY: 500,
|
|
24727
|
+
* })
|
|
24728
|
+
* .fill({ color: rgb(0, 0, 0) });
|
|
24729
|
+
* ```
|
|
24730
|
+
*/
|
|
24731
|
+
appendSvgPath(pathData, options = {}) {
|
|
24732
|
+
const executorOptions = {
|
|
24733
|
+
flipY: options.flipY ?? false,
|
|
24734
|
+
scale: options.scale,
|
|
24735
|
+
translateX: options.translateX,
|
|
24736
|
+
translateY: options.translateY
|
|
24737
|
+
};
|
|
24738
|
+
executeSvgPathString({
|
|
24739
|
+
pathData,
|
|
24740
|
+
sink: {
|
|
24741
|
+
moveTo: (x, y) => {
|
|
24742
|
+
this.moveTo(x, y);
|
|
24743
|
+
},
|
|
24744
|
+
lineTo: (x, y) => {
|
|
24745
|
+
this.lineTo(x, y);
|
|
24746
|
+
},
|
|
24747
|
+
curveTo: (cp1x, cp1y, cp2x, cp2y, x, y) => {
|
|
24748
|
+
this.curveTo(cp1x, cp1y, cp2x, cp2y, x, y);
|
|
24749
|
+
},
|
|
24750
|
+
quadraticCurveTo: (cpx, cpy, x, y) => {
|
|
24751
|
+
this.quadraticCurveTo(cpx, cpy, x, y);
|
|
24752
|
+
},
|
|
24753
|
+
close: () => {
|
|
24754
|
+
this.close();
|
|
24755
|
+
}
|
|
24756
|
+
},
|
|
24757
|
+
initialX: this.currentX,
|
|
24758
|
+
initialY: this.currentY,
|
|
24759
|
+
...executorOptions
|
|
24760
|
+
});
|
|
24761
|
+
return this;
|
|
24762
|
+
}
|
|
24763
|
+
/**
|
|
23851
24764
|
* Stroke the path with the given options.
|
|
23852
24765
|
*/
|
|
23853
24766
|
stroke(options = {}) {
|
|
@@ -24839,6 +25752,76 @@ var PDFPage = class PDFPage {
|
|
|
24839
25752
|
drawPath() {
|
|
24840
25753
|
return new PathBuilder((content) => this.appendContent(content), (fillOpacity, strokeOpacity) => this.registerGraphicsStateForOpacity(fillOpacity, strokeOpacity));
|
|
24841
25754
|
}
|
|
25755
|
+
/**
|
|
25756
|
+
* Draw an SVG path on the page.
|
|
25757
|
+
*
|
|
25758
|
+
* This is a convenience method that parses an SVG path `d` attribute string
|
|
25759
|
+
* and draws it with the specified options. For more control, use `drawPath()`
|
|
25760
|
+
* with `appendSvgPath()`.
|
|
25761
|
+
*
|
|
25762
|
+
* By default, the path is filled with black. Specify `borderColor` without
|
|
25763
|
+
* `color` to stroke without filling.
|
|
25764
|
+
*
|
|
25765
|
+
* SVG paths are automatically transformed from SVG coordinate space (Y-down)
|
|
25766
|
+
* to PDF coordinate space (Y-up). Use `x`, `y` to position the path, and
|
|
25767
|
+
* `scale` to resize it.
|
|
25768
|
+
*
|
|
25769
|
+
* @param pathData - SVG path `d` attribute string
|
|
25770
|
+
* @param options - Drawing options (x, y, scale, color, etc.)
|
|
25771
|
+
*
|
|
25772
|
+
* @example
|
|
25773
|
+
* ```typescript
|
|
25774
|
+
* // Draw a Font Awesome heart icon at position (100, 500)
|
|
25775
|
+
* // Icon is 512x512 in SVG, scale to ~50pt
|
|
25776
|
+
* page.drawSvgPath(faHeartPath, {
|
|
25777
|
+
* x: 100,
|
|
25778
|
+
* y: 500,
|
|
25779
|
+
* scale: 0.1,
|
|
25780
|
+
* color: rgb(1, 0, 0),
|
|
25781
|
+
* });
|
|
25782
|
+
*
|
|
25783
|
+
* // Draw a simple triangle at default position (0, 0)
|
|
25784
|
+
* page.drawSvgPath("M 0 0 L 50 0 L 25 40 Z", {
|
|
25785
|
+
* color: rgb(0, 0, 1),
|
|
25786
|
+
* });
|
|
25787
|
+
*
|
|
25788
|
+
* // Stroke a curve
|
|
25789
|
+
* page.drawSvgPath("M 0 0 C 10 10, 30 10, 40 0", {
|
|
25790
|
+
* x: 200,
|
|
25791
|
+
* y: 300,
|
|
25792
|
+
* borderColor: rgb(0, 0, 0),
|
|
25793
|
+
* borderWidth: 2,
|
|
25794
|
+
* });
|
|
25795
|
+
* ```
|
|
25796
|
+
*/
|
|
25797
|
+
drawSvgPath(pathData, options = {}) {
|
|
25798
|
+
const x = options.x ?? 0;
|
|
25799
|
+
const y = options.y ?? 0;
|
|
25800
|
+
const scale = options.scale ?? 1;
|
|
25801
|
+
const flipY = options.flipY ?? true;
|
|
25802
|
+
const builder = this.drawPath();
|
|
25803
|
+
builder.appendSvgPath(pathData, {
|
|
25804
|
+
flipY,
|
|
25805
|
+
scale,
|
|
25806
|
+
translateX: x,
|
|
25807
|
+
translateY: y
|
|
25808
|
+
});
|
|
25809
|
+
const hasFill = options.color !== void 0;
|
|
25810
|
+
const hasStroke = options.borderColor !== void 0;
|
|
25811
|
+
if (hasFill && hasStroke) {
|
|
25812
|
+
builder.fillAndStroke(options);
|
|
25813
|
+
return;
|
|
25814
|
+
}
|
|
25815
|
+
if (hasStroke) {
|
|
25816
|
+
builder.stroke(options);
|
|
25817
|
+
return;
|
|
25818
|
+
}
|
|
25819
|
+
const fillOptions = hasFill ? options : {
|
|
25820
|
+
...options,
|
|
25821
|
+
color: black
|
|
25822
|
+
};
|
|
25823
|
+
builder.fill(fillOptions);
|
|
25824
|
+
}
|
|
24842
25825
|
/** Cached annotations for this page */
|
|
24843
25826
|
_annotationCache = null;
|
|
24844
25827
|
/**
|