@libpdf/core 0.2.1 → 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/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.1";
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 = {}) {
@@ -23904,7 +24817,7 @@ var PathBuilder = class {
23904
24817
  dashArray: options.dashArray,
23905
24818
  dashPhase: options.dashPhase,
23906
24819
  windingRule: options.windingRule,
23907
- graphicsStateName: gsName ?? void 0
24820
+ graphicsStateName: gsName ? `/${gsName}` : void 0
23908
24821
  });
23909
24822
  this.emitOps(ops);
23910
24823
  }
@@ -24499,7 +25412,7 @@ var PDFPage = class PDFPage {
24499
25412
  dashArray: options.borderDashArray,
24500
25413
  dashPhase: options.borderDashPhase,
24501
25414
  cornerRadius: options.cornerRadius,
24502
- graphicsStateName: gsName ?? void 0,
25415
+ graphicsStateName: gsName ? `/${gsName}` : void 0,
24503
25416
  rotate
24504
25417
  });
24505
25418
  this.appendOperators(ops);
@@ -24538,7 +25451,7 @@ var PDFPage = class PDFPage {
24538
25451
  dashArray: options.dashArray,
24539
25452
  dashPhase: options.dashPhase,
24540
25453
  lineCap: options.lineCap,
24541
- graphicsStateName: gsName ?? void 0
25454
+ graphicsStateName: gsName ? `/${gsName}` : void 0
24542
25455
  });
24543
25456
  this.appendOperators(ops);
24544
25457
  }
@@ -24566,7 +25479,7 @@ var PDFPage = class PDFPage {
24566
25479
  fillColor: options.color,
24567
25480
  strokeColor: options.borderColor,
24568
25481
  strokeWidth: options.borderWidth,
24569
- graphicsStateName: gsName ?? void 0
25482
+ graphicsStateName: gsName ? `/${gsName}` : void 0
24570
25483
  });
24571
25484
  this.appendOperators(ops);
24572
25485
  }
@@ -24613,7 +25526,7 @@ var PDFPage = class PDFPage {
24613
25526
  fillColor: options.color,
24614
25527
  strokeColor: options.borderColor,
24615
25528
  strokeWidth: options.borderWidth,
24616
- graphicsStateName: gsName ?? void 0,
25529
+ graphicsStateName: gsName ? `/${gsName}` : void 0,
24617
25530
  rotate
24618
25531
  });
24619
25532
  this.appendOperators(ops);
@@ -24670,7 +25583,7 @@ var PDFPage = class PDFPage {
24670
25583
  width: measureText(line, font, fontSize)
24671
25584
  }));
24672
25585
  const ops = [pushGraphicsState()];
24673
- if (gsName) ops.push(setGraphicsState(gsName));
25586
+ if (gsName) ops.push(setGraphicsState(`/${gsName}`));
24674
25587
  if (options.rotate) {
24675
25588
  const textWidth = options.maxWidth ?? Math.max(...lines.map((l) => l.width));
24676
25589
  let ascent;
@@ -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
  /**
@@ -25447,8 +26430,8 @@ var PDFPage = class PDFPage {
25447
26430
  registerGraphicsStateForOpacity(fillOpacity, strokeOpacity) {
25448
26431
  if (fillOpacity === void 0 && strokeOpacity === void 0) return null;
25449
26432
  const params = {};
25450
- if (fillOpacity !== void 0) params.CA = Math.max(0, Math.min(1, fillOpacity));
25451
- if (strokeOpacity !== void 0) params.ca = Math.max(0, Math.min(1, strokeOpacity));
26433
+ if (fillOpacity !== void 0) params.ca = Math.max(0, Math.min(1, fillOpacity));
26434
+ if (strokeOpacity !== void 0) params.CA = Math.max(0, Math.min(1, strokeOpacity));
25452
26435
  return this.addGraphicsState(params);
25453
26436
  }
25454
26437
  /**