@libpdf/core 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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.2";
12
+ var version = "0.2.4";
13
13
 
14
14
  //#endregion
15
15
  //#region src/objects/pdf-array.ts
@@ -13790,19 +13790,6 @@ var ZapfDingbatsEncoding = class ZapfDingbatsEncoding extends SimpleEncoding {
13790
13790
  //#endregion
13791
13791
  //#region src/fonts/standard-14.ts
13792
13792
  /**
13793
- * Standard 14 PDF Fonts
13794
- *
13795
- * These are the 14 fonts that every PDF reader must support.
13796
- * They don't require embedding - the reader provides them.
13797
- *
13798
- * Width tables are stored by glyph name (like pdf.js) which works
13799
- * with any encoding. Use getWidthByGlyphName() for lookup.
13800
- *
13801
- * Widths are in glyph units (1000 units = 1 em).
13802
- *
13803
- * Data extracted from pdf.js metrics.js (Mozilla, Apache 2.0 License)
13804
- */
13805
- /**
13806
13793
  * Standard 14 font names.
13807
13794
  */
13808
13795
  const STANDARD_14_FONTS = [
@@ -13861,6 +13848,27 @@ function getBaseFontName(name) {
13861
13848
  return name.includes("+") ? name.split("+")[1] : name;
13862
13849
  }
13863
13850
  /**
13851
+ * Get the font encoding for a Standard 14 font.
13852
+ *
13853
+ * - Symbol → SymbolEncoding
13854
+ * - ZapfDingbats → ZapfDingbatsEncoding
13855
+ * - All others (Helvetica, Times, Courier) → WinAnsiEncoding
13856
+ */
13857
+ function getEncodingForStandard14(name) {
13858
+ const baseName = getBaseFontName(name);
13859
+ if (baseName === "Symbol") return SymbolEncoding.instance;
13860
+ if (baseName === "ZapfDingbats") return ZapfDingbatsEncoding.instance;
13861
+ return WinAnsiEncoding.instance;
13862
+ }
13863
+ /**
13864
+ * Check if a Standard 14 font uses WinAnsiEncoding.
13865
+ * Returns false for Symbol and ZapfDingbats (they use built-in encodings).
13866
+ */
13867
+ function isWinAnsiStandard14(name) {
13868
+ const baseName = getBaseFontName(name);
13869
+ return baseName !== "Symbol" && baseName !== "ZapfDingbats";
13870
+ }
13871
+ /**
13864
13872
  * Get basic metrics (ascent, descent, etc.) for a Standard 14 font.
13865
13873
  */
13866
13874
  function getStandard14BasicMetrics(name) {
@@ -13987,7 +13995,130 @@ const CHAR_TO_GLYPH = {
13987
13995
  123: "braceleft",
13988
13996
  124: "bar",
13989
13997
  125: "braceright",
13990
- 126: "asciitilde"
13998
+ 126: "asciitilde",
13999
+ 8364: "Euro",
14000
+ 8218: "quotesinglbase",
14001
+ 402: "florin",
14002
+ 8222: "quotedblbase",
14003
+ 8230: "ellipsis",
14004
+ 8224: "dagger",
14005
+ 8225: "daggerdbl",
14006
+ 710: "circumflex",
14007
+ 8240: "perthousand",
14008
+ 352: "Scaron",
14009
+ 8249: "guilsinglleft",
14010
+ 338: "OE",
14011
+ 381: "Zcaron",
14012
+ 8216: "quoteleft",
14013
+ 8217: "quoteright",
14014
+ 8220: "quotedblleft",
14015
+ 8221: "quotedblright",
14016
+ 8226: "bullet",
14017
+ 8211: "endash",
14018
+ 8212: "emdash",
14019
+ 732: "tilde",
14020
+ 8482: "trademark",
14021
+ 353: "scaron",
14022
+ 8250: "guilsinglright",
14023
+ 339: "oe",
14024
+ 382: "zcaron",
14025
+ 376: "Ydieresis",
14026
+ 160: "space",
14027
+ 161: "exclamdown",
14028
+ 162: "cent",
14029
+ 163: "sterling",
14030
+ 164: "currency",
14031
+ 165: "yen",
14032
+ 166: "brokenbar",
14033
+ 167: "section",
14034
+ 168: "dieresis",
14035
+ 169: "copyright",
14036
+ 170: "ordfeminine",
14037
+ 171: "guillemotleft",
14038
+ 172: "logicalnot",
14039
+ 173: "hyphen",
14040
+ 174: "registered",
14041
+ 175: "macron",
14042
+ 176: "degree",
14043
+ 177: "plusminus",
14044
+ 178: "twosuperior",
14045
+ 179: "threesuperior",
14046
+ 180: "acute",
14047
+ 181: "mu",
14048
+ 182: "paragraph",
14049
+ 183: "periodcentered",
14050
+ 184: "cedilla",
14051
+ 185: "onesuperior",
14052
+ 186: "ordmasculine",
14053
+ 187: "guillemotright",
14054
+ 188: "onequarter",
14055
+ 189: "onehalf",
14056
+ 190: "threequarters",
14057
+ 191: "questiondown",
14058
+ 192: "Agrave",
14059
+ 193: "Aacute",
14060
+ 194: "Acircumflex",
14061
+ 195: "Atilde",
14062
+ 196: "Adieresis",
14063
+ 197: "Aring",
14064
+ 198: "AE",
14065
+ 199: "Ccedilla",
14066
+ 200: "Egrave",
14067
+ 201: "Eacute",
14068
+ 202: "Ecircumflex",
14069
+ 203: "Edieresis",
14070
+ 204: "Igrave",
14071
+ 205: "Iacute",
14072
+ 206: "Icircumflex",
14073
+ 207: "Idieresis",
14074
+ 208: "Eth",
14075
+ 209: "Ntilde",
14076
+ 210: "Ograve",
14077
+ 211: "Oacute",
14078
+ 212: "Ocircumflex",
14079
+ 213: "Otilde",
14080
+ 214: "Odieresis",
14081
+ 215: "multiply",
14082
+ 216: "Oslash",
14083
+ 217: "Ugrave",
14084
+ 218: "Uacute",
14085
+ 219: "Ucircumflex",
14086
+ 220: "Udieresis",
14087
+ 221: "Yacute",
14088
+ 222: "Thorn",
14089
+ 223: "germandbls",
14090
+ 224: "agrave",
14091
+ 225: "aacute",
14092
+ 226: "acircumflex",
14093
+ 227: "atilde",
14094
+ 228: "adieresis",
14095
+ 229: "aring",
14096
+ 230: "ae",
14097
+ 231: "ccedilla",
14098
+ 232: "egrave",
14099
+ 233: "eacute",
14100
+ 234: "ecircumflex",
14101
+ 235: "edieresis",
14102
+ 236: "igrave",
14103
+ 237: "iacute",
14104
+ 238: "icircumflex",
14105
+ 239: "idieresis",
14106
+ 240: "eth",
14107
+ 241: "ntilde",
14108
+ 242: "ograve",
14109
+ 243: "oacute",
14110
+ 244: "ocircumflex",
14111
+ 245: "otilde",
14112
+ 246: "odieresis",
14113
+ 247: "divide",
14114
+ 248: "oslash",
14115
+ 249: "ugrave",
14116
+ 250: "uacute",
14117
+ 251: "ucircumflex",
14118
+ 252: "udieresis",
14119
+ 253: "yacute",
14120
+ 254: "thorn",
14121
+ 255: "ydieresis"
13991
14122
  };
13992
14123
  /**
13993
14124
  * Get glyph name for a character (for Standard 14 font width lookup).
@@ -22717,7 +22848,7 @@ function validateFollowingOperator(scanner) {
22717
22848
  const tokenStart = pos + i;
22718
22849
  const firstCh = scanner.peekAt(tokenStart);
22719
22850
  if (firstCh === -1) return false;
22720
- if (isNumberStart(firstCh)) {
22851
+ if (isNumberStart$1(firstCh)) {
22721
22852
  i++;
22722
22853
  while (i < lookahead) {
22723
22854
  const ch = scanner.peekAt(pos + i);
@@ -22772,7 +22903,7 @@ function validateFollowingOperator(scanner) {
22772
22903
  }
22773
22904
  return false;
22774
22905
  }
22775
- function isNumberStart(ch) {
22906
+ function isNumberStart$1(ch) {
22776
22907
  return ch >= DIGIT_0 && ch <= DIGIT_9 || ch === CHAR_PLUS || ch === CHAR_MINUS || ch === CHAR_PERIOD;
22777
22908
  }
22778
22909
  /**
@@ -23722,6 +23853,854 @@ function getPaintOp(hasFill, hasStroke) {
23722
23853
  return stroke();
23723
23854
  }
23724
23855
 
23856
+ //#endregion
23857
+ //#region src/svg/arc-to-bezier.ts
23858
+ /**
23859
+ * Convert an SVG arc to one or more cubic bezier curves.
23860
+ *
23861
+ * Handles edge cases according to SVG spec:
23862
+ * - If rx=0 or ry=0, returns a line to the endpoint
23863
+ * - If start and end points are the same, returns empty array
23864
+ * - If radii are too small, they're scaled up automatically
23865
+ *
23866
+ * @param arc - Arc parameters in endpoint form
23867
+ * @returns Array of bezier curves that approximate the arc
23868
+ */
23869
+ function arcToBezier(arc) {
23870
+ const { x1, y1, x2, y2, xAxisRotation, largeArcFlag, sweepFlag } = arc;
23871
+ let { rx, ry } = arc;
23872
+ if (x1 === x2 && y1 === y2) return [];
23873
+ if (rx === 0 || ry === 0) return [{
23874
+ cp1x: x1,
23875
+ cp1y: y1,
23876
+ cp2x: x2,
23877
+ cp2y: y2,
23878
+ x: x2,
23879
+ y: y2
23880
+ }];
23881
+ rx = Math.abs(rx);
23882
+ ry = Math.abs(ry);
23883
+ const phi = xAxisRotation * Math.PI / 180;
23884
+ const cosPhi = Math.cos(phi);
23885
+ const sinPhi = Math.sin(phi);
23886
+ const dx = (x1 - x2) / 2;
23887
+ const dy = (y1 - y2) / 2;
23888
+ const x1p = cosPhi * dx + sinPhi * dy;
23889
+ const y1p = -sinPhi * dx + cosPhi * dy;
23890
+ const lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
23891
+ if (lambda > 1) {
23892
+ const sqrtLambda = Math.sqrt(lambda);
23893
+ rx = sqrtLambda * rx;
23894
+ ry = sqrtLambda * ry;
23895
+ }
23896
+ const rx2 = rx * rx;
23897
+ const ry2 = ry * ry;
23898
+ const x1p2 = x1p * x1p;
23899
+ const y1p2 = y1p * y1p;
23900
+ let sq = (rx2 * ry2 - rx2 * y1p2 - ry2 * x1p2) / (rx2 * y1p2 + ry2 * x1p2);
23901
+ if (sq < 0) sq = 0;
23902
+ const coef = (largeArcFlag === sweepFlag ? -1 : 1) * Math.sqrt(sq);
23903
+ const cxp = coef * rx * y1p / ry;
23904
+ const cyp = -coef * ry * x1p / rx;
23905
+ const midX = (x1 + x2) / 2;
23906
+ const midY = (y1 + y2) / 2;
23907
+ const cx = cosPhi * cxp - sinPhi * cyp + midX;
23908
+ const cy = sinPhi * cxp + cosPhi * cyp + midY;
23909
+ const ux = (x1p - cxp) / rx;
23910
+ const uy = (y1p - cyp) / ry;
23911
+ const vx = (-x1p - cxp) / rx;
23912
+ const vy = (-y1p - cyp) / ry;
23913
+ const theta1 = angleBetween(1, 0, ux, uy);
23914
+ let dTheta = angleBetween(ux, uy, vx, vy);
23915
+ if (!sweepFlag && dTheta > 0) dTheta -= 2 * Math.PI;
23916
+ if (sweepFlag && dTheta < 0) dTheta += 2 * Math.PI;
23917
+ const numSegments = Math.ceil(Math.abs(dTheta) / (Math.PI / 2));
23918
+ const segmentAngle = dTheta / numSegments;
23919
+ const curves = [];
23920
+ let currentAngle = theta1;
23921
+ for (let i = 0; i < numSegments; i++) {
23922
+ const nextAngle = currentAngle + segmentAngle;
23923
+ const curve = arcSegmentToBezier(cx, cy, rx, ry, phi, currentAngle, nextAngle);
23924
+ curves.push(curve);
23925
+ currentAngle = nextAngle;
23926
+ }
23927
+ return curves;
23928
+ }
23929
+ /**
23930
+ * Compute the angle between two vectors.
23931
+ */
23932
+ function angleBetween(ux, uy, vx, vy) {
23933
+ const sign = ux * vy - uy * vx < 0 ? -1 : 1;
23934
+ let cos = (ux * vx + uy * vy) / (Math.sqrt(ux * ux + uy * uy) * Math.sqrt(vx * vx + vy * vy));
23935
+ if (cos < -1) cos = -1;
23936
+ if (cos > 1) cos = 1;
23937
+ return sign * Math.acos(cos);
23938
+ }
23939
+ /**
23940
+ * Convert a single arc segment (up to 90 degrees) to a cubic bezier.
23941
+ *
23942
+ * Uses the standard arc approximation formula:
23943
+ * For an arc of angle theta centered at origin:
23944
+ * CP1 = P0 + alpha * tangent at P0
23945
+ * CP2 = P1 - alpha * tangent at P1
23946
+ *
23947
+ * Where alpha = (4/3) * tan(theta/4)
23948
+ */
23949
+ function arcSegmentToBezier(cx, cy, rx, ry, phi, theta1, theta2) {
23950
+ const dTheta = theta2 - theta1;
23951
+ const t = Math.tan(dTheta / 4);
23952
+ const alpha = Math.sin(dTheta) * (Math.sqrt(4 + 3 * t * t) - 1) / 3;
23953
+ const cosPhi = Math.cos(phi);
23954
+ const sinPhi = Math.sin(phi);
23955
+ const cos1 = Math.cos(theta1);
23956
+ const sin1 = Math.sin(theta1);
23957
+ const cos2 = Math.cos(theta2);
23958
+ const sin2 = Math.sin(theta2);
23959
+ const p0x = cx + cosPhi * rx * cos1 - sinPhi * ry * sin1;
23960
+ const p0y = cy + sinPhi * rx * cos1 + cosPhi * ry * sin1;
23961
+ const p1x = cx + cosPhi * rx * cos2 - sinPhi * ry * sin2;
23962
+ const p1y = cy + sinPhi * rx * cos2 + cosPhi * ry * sin2;
23963
+ const t0x = -cosPhi * rx * sin1 - sinPhi * ry * cos1;
23964
+ const t0y = -sinPhi * rx * sin1 + cosPhi * ry * cos1;
23965
+ const t1x = -cosPhi * rx * sin2 - sinPhi * ry * cos2;
23966
+ const t1y = -sinPhi * rx * sin2 + cosPhi * ry * cos2;
23967
+ return {
23968
+ cp1x: p0x + alpha * t0x,
23969
+ cp1y: p0y + alpha * t0y,
23970
+ cp2x: p1x - alpha * t1x,
23971
+ cp2y: p1y - alpha * t1y,
23972
+ x: p1x,
23973
+ y: p1y
23974
+ };
23975
+ }
23976
+
23977
+ //#endregion
23978
+ //#region src/svg/path-parser.ts
23979
+ /**
23980
+ * Number of parameters required for each command type.
23981
+ */
23982
+ const COMMAND_PARAMS = {
23983
+ M: 2,
23984
+ m: 2,
23985
+ L: 2,
23986
+ l: 2,
23987
+ H: 1,
23988
+ h: 1,
23989
+ V: 1,
23990
+ v: 1,
23991
+ C: 6,
23992
+ c: 6,
23993
+ S: 4,
23994
+ s: 4,
23995
+ Q: 4,
23996
+ q: 4,
23997
+ T: 2,
23998
+ t: 2,
23999
+ A: 7,
24000
+ a: 7,
24001
+ Z: 0,
24002
+ z: 0
24003
+ };
24004
+ /**
24005
+ * Check if a character is a command letter.
24006
+ */
24007
+ function isCommandLetter(char) {
24008
+ return /^[MmLlHhVvCcSsQqTtAaZz]$/.test(char);
24009
+ }
24010
+ /**
24011
+ * Check if a character can start a number.
24012
+ */
24013
+ function isNumberStart(char) {
24014
+ return /^[0-9.+-]$/.test(char);
24015
+ }
24016
+ /**
24017
+ * Tokenizer for SVG path strings.
24018
+ * Extracts command letters and numbers from the path string.
24019
+ */
24020
+ var PathTokenizer = class {
24021
+ path;
24022
+ pos = 0;
24023
+ constructor(path) {
24024
+ this.path = path;
24025
+ }
24026
+ /**
24027
+ * Skip whitespace and commas.
24028
+ */
24029
+ skipWhitespaceAndCommas() {
24030
+ while (this.pos < this.path.length) {
24031
+ const char = this.path[this.pos];
24032
+ if (char === " " || char === " " || char === "\n" || char === "\r" || char === ",") this.pos++;
24033
+ else break;
24034
+ }
24035
+ }
24036
+ /**
24037
+ * Read a number from the current position.
24038
+ * Handles integers, decimals, negative numbers, and scientific notation.
24039
+ */
24040
+ readNumber() {
24041
+ this.skipWhitespaceAndCommas();
24042
+ if (this.pos >= this.path.length) return null;
24043
+ const char = this.path[this.pos];
24044
+ if (isCommandLetter(char)) return null;
24045
+ if (!isNumberStart(char)) {
24046
+ this.pos++;
24047
+ return null;
24048
+ }
24049
+ let numStr = "";
24050
+ let hasDecimal = false;
24051
+ let hasExponent = false;
24052
+ if (char === "+" || char === "-") {
24053
+ numStr += char;
24054
+ this.pos++;
24055
+ }
24056
+ while (this.pos < this.path.length) {
24057
+ const c = this.path[this.pos];
24058
+ if (c >= "0" && c <= "9") {
24059
+ numStr += c;
24060
+ this.pos++;
24061
+ } else if (c === "." && !hasDecimal && !hasExponent) {
24062
+ numStr += c;
24063
+ hasDecimal = true;
24064
+ this.pos++;
24065
+ } else if ((c === "e" || c === "E") && !hasExponent && numStr.length > 0) {
24066
+ numStr += c;
24067
+ hasExponent = true;
24068
+ this.pos++;
24069
+ if (this.pos < this.path.length) {
24070
+ const signChar = this.path[this.pos];
24071
+ if (signChar === "+" || signChar === "-") {
24072
+ numStr += signChar;
24073
+ this.pos++;
24074
+ }
24075
+ }
24076
+ } else break;
24077
+ }
24078
+ if (numStr === "" || numStr === "+" || numStr === "-" || numStr === ".") return null;
24079
+ const value = Number.parseFloat(numStr);
24080
+ return Number.isNaN(value) ? null : value;
24081
+ }
24082
+ /**
24083
+ * Read a command letter from the current position.
24084
+ */
24085
+ readCommand() {
24086
+ this.skipWhitespaceAndCommas();
24087
+ if (this.pos >= this.path.length) return null;
24088
+ const char = this.path[this.pos];
24089
+ if (isCommandLetter(char)) {
24090
+ this.pos++;
24091
+ return char;
24092
+ }
24093
+ return null;
24094
+ }
24095
+ /**
24096
+ * Peek at the next character without consuming it.
24097
+ */
24098
+ peek() {
24099
+ this.skipWhitespaceAndCommas();
24100
+ if (this.pos >= this.path.length) return null;
24101
+ return this.path[this.pos];
24102
+ }
24103
+ /**
24104
+ * Check if there's more content to parse.
24105
+ */
24106
+ hasMore() {
24107
+ this.skipWhitespaceAndCommas();
24108
+ return this.pos < this.path.length;
24109
+ }
24110
+ /**
24111
+ * Read a single flag digit (0 or 1) for arc commands.
24112
+ * SVG arc flags are special - they're single digits that can be
24113
+ * concatenated without separators: "00" means two flags, both 0.
24114
+ */
24115
+ readFlag() {
24116
+ this.skipWhitespaceAndCommas();
24117
+ if (this.pos >= this.path.length) return null;
24118
+ const char = this.path[this.pos];
24119
+ if (char === "0" || char === "1") {
24120
+ this.pos++;
24121
+ return char === "1" ? 1 : 0;
24122
+ }
24123
+ return null;
24124
+ }
24125
+ };
24126
+ /**
24127
+ * Parse an SVG path string into an array of commands.
24128
+ *
24129
+ * @param pathData - The SVG path `d` attribute string
24130
+ * @returns Array of parsed path commands
24131
+ *
24132
+ * @example
24133
+ * ```typescript
24134
+ * const commands = parseSvgPath("M 10 10 L 100 10 L 100 100 Z");
24135
+ * // [
24136
+ * // { type: "M", x: 10, y: 10 },
24137
+ * // { type: "L", x: 100, y: 10 },
24138
+ * // { type: "L", x: 100, y: 100 },
24139
+ * // { type: "Z" },
24140
+ * // ]
24141
+ * ```
24142
+ */
24143
+ function parseSvgPath(pathData) {
24144
+ const commands = [];
24145
+ const tokenizer = new PathTokenizer(pathData);
24146
+ let currentCommand = null;
24147
+ let isFirstInSequence = true;
24148
+ while (tokenizer.hasMore()) {
24149
+ const nextChar = tokenizer.peek();
24150
+ if (nextChar && isCommandLetter(nextChar)) {
24151
+ currentCommand = tokenizer.readCommand();
24152
+ isFirstInSequence = true;
24153
+ }
24154
+ if (!currentCommand) break;
24155
+ const paramCount = COMMAND_PARAMS[currentCommand];
24156
+ if (currentCommand === "Z" || currentCommand === "z") {
24157
+ commands.push({ type: currentCommand });
24158
+ currentCommand = null;
24159
+ continue;
24160
+ }
24161
+ const params = [];
24162
+ const isArc = currentCommand === "A" || currentCommand === "a";
24163
+ for (let i = 0; i < paramCount; i++) {
24164
+ let num;
24165
+ if (isArc && (i === 3 || i === 4)) num = tokenizer.readFlag();
24166
+ else num = tokenizer.readNumber();
24167
+ if (num === null) break;
24168
+ params.push(num);
24169
+ }
24170
+ if (params.length !== paramCount) continue;
24171
+ const command = createCommand(currentCommand, params);
24172
+ if (command) commands.push(command);
24173
+ if (isFirstInSequence) {
24174
+ isFirstInSequence = false;
24175
+ if (currentCommand === "M") currentCommand = "L";
24176
+ else if (currentCommand === "m") currentCommand = "l";
24177
+ }
24178
+ const nextPeek = tokenizer.peek();
24179
+ if (!nextPeek || isCommandLetter(nextPeek)) {
24180
+ if (!nextPeek) break;
24181
+ }
24182
+ }
24183
+ return commands;
24184
+ }
24185
+ /**
24186
+ * Create a command object from type and parameters.
24187
+ */
24188
+ function createCommand(type, params) {
24189
+ switch (type) {
24190
+ case "M":
24191
+ case "m": return {
24192
+ type,
24193
+ x: params[0],
24194
+ y: params[1]
24195
+ };
24196
+ case "L":
24197
+ case "l": return {
24198
+ type,
24199
+ x: params[0],
24200
+ y: params[1]
24201
+ };
24202
+ case "H":
24203
+ case "h": return {
24204
+ type,
24205
+ x: params[0]
24206
+ };
24207
+ case "V":
24208
+ case "v": return {
24209
+ type,
24210
+ y: params[0]
24211
+ };
24212
+ case "C":
24213
+ case "c": return {
24214
+ type,
24215
+ x1: params[0],
24216
+ y1: params[1],
24217
+ x2: params[2],
24218
+ y2: params[3],
24219
+ x: params[4],
24220
+ y: params[5]
24221
+ };
24222
+ case "S":
24223
+ case "s": return {
24224
+ type,
24225
+ x2: params[0],
24226
+ y2: params[1],
24227
+ x: params[2],
24228
+ y: params[3]
24229
+ };
24230
+ case "Q":
24231
+ case "q": return {
24232
+ type,
24233
+ x1: params[0],
24234
+ y1: params[1],
24235
+ x: params[2],
24236
+ y: params[3]
24237
+ };
24238
+ case "T":
24239
+ case "t": return {
24240
+ type,
24241
+ x: params[0],
24242
+ y: params[1]
24243
+ };
24244
+ case "A":
24245
+ case "a": return {
24246
+ type,
24247
+ rx: params[0],
24248
+ ry: params[1],
24249
+ xAxisRotation: params[2],
24250
+ largeArcFlag: params[3] !== 0,
24251
+ sweepFlag: params[4] !== 0,
24252
+ x: params[5],
24253
+ y: params[6]
24254
+ };
24255
+ case "Z":
24256
+ case "z": return { type };
24257
+ default: return null;
24258
+ }
24259
+ }
24260
+
24261
+ //#endregion
24262
+ //#region src/svg/path-executor.ts
24263
+ /**
24264
+ * SVG Path Executor
24265
+ *
24266
+ * Executes parsed SVG path commands via a callback interface.
24267
+ * Handles:
24268
+ * - Relative to absolute coordinate conversion
24269
+ * - Smooth curve control point reflection
24270
+ * - Arc to bezier conversion
24271
+ * - Y-axis flipping for PDF coordinate system (optional, default: true)
24272
+ */
24273
+ /**
24274
+ * Execute SVG path commands via a callback interface.
24275
+ *
24276
+ * The executor handles all coordinate transformations and command
24277
+ * normalization, so the sink receives only absolute coordinates
24278
+ * and standard path operations.
24279
+ *
24280
+ * @param options - Execution options
24281
+ * @param options.commands - Parsed SVG path commands
24282
+ * @param options.sink - Callback interface for path operations
24283
+ * @param options.initialX - Initial X coordinate (default: 0)
24284
+ * @param options.initialY - Initial Y coordinate (default: 0)
24285
+ * @param options.flipY - Flip Y coordinates for PDF (default: true)
24286
+ * @param options.scale - Scale factor (default: 1)
24287
+ * @param options.translateX - X offset after transform (default: 0)
24288
+ * @param options.translateY - Y offset after transform (default: 0)
24289
+ * @returns Final position {x, y} after executing all commands
24290
+ */
24291
+ function executeSvgPath(options) {
24292
+ const { commands, sink, initialX = 0, initialY = 0, flipY = true, scale = 1, translateX = 0, translateY = 0 } = options;
24293
+ const yFlip = flipY ? -1 : 1;
24294
+ const initialOutputX = initialX + translateX;
24295
+ const initialOutputY = initialY + translateY;
24296
+ const state = {
24297
+ currentX: initialOutputX,
24298
+ currentY: initialOutputY,
24299
+ subpathStartX: initialOutputX,
24300
+ subpathStartY: initialOutputY,
24301
+ lastControlX: initialOutputX,
24302
+ lastControlY: initialOutputY,
24303
+ lastCommand: null,
24304
+ yFlip,
24305
+ scale,
24306
+ translateX,
24307
+ translateY
24308
+ };
24309
+ for (const cmd of commands) executeCommand(cmd, state, sink);
24310
+ return {
24311
+ x: state.currentX,
24312
+ y: state.currentY
24313
+ };
24314
+ }
24315
+ /**
24316
+ * Transform an SVG coordinate to output space.
24317
+ * Applies: scale, Y-flip, then translate.
24318
+ */
24319
+ function transformX(x, state) {
24320
+ return x * state.scale + state.translateX;
24321
+ }
24322
+ function transformY(y, state) {
24323
+ return y * state.yFlip * state.scale + state.translateY;
24324
+ }
24325
+ /**
24326
+ * Execute a single SVG path command.
24327
+ */
24328
+ function executeCommand(cmd, state, sink) {
24329
+ switch (cmd.type) {
24330
+ case "M":
24331
+ executeMoveTo({
24332
+ x: cmd.x,
24333
+ y: cmd.y,
24334
+ relative: false,
24335
+ state,
24336
+ sink
24337
+ });
24338
+ break;
24339
+ case "m":
24340
+ executeMoveTo({
24341
+ x: cmd.x,
24342
+ y: cmd.y,
24343
+ relative: true,
24344
+ state,
24345
+ sink
24346
+ });
24347
+ break;
24348
+ case "L":
24349
+ executeLineTo({
24350
+ x: cmd.x,
24351
+ y: cmd.y,
24352
+ relative: false,
24353
+ state,
24354
+ sink
24355
+ });
24356
+ break;
24357
+ case "l":
24358
+ executeLineTo({
24359
+ x: cmd.x,
24360
+ y: cmd.y,
24361
+ relative: true,
24362
+ state,
24363
+ sink
24364
+ });
24365
+ break;
24366
+ case "H":
24367
+ executeHorizontalLine({
24368
+ x: cmd.x,
24369
+ relative: false,
24370
+ state,
24371
+ sink
24372
+ });
24373
+ break;
24374
+ case "h":
24375
+ executeHorizontalLine({
24376
+ x: cmd.x,
24377
+ relative: true,
24378
+ state,
24379
+ sink
24380
+ });
24381
+ break;
24382
+ case "V":
24383
+ executeVerticalLine({
24384
+ y: cmd.y,
24385
+ relative: false,
24386
+ state,
24387
+ sink
24388
+ });
24389
+ break;
24390
+ case "v":
24391
+ executeVerticalLine({
24392
+ y: cmd.y,
24393
+ relative: true,
24394
+ state,
24395
+ sink
24396
+ });
24397
+ break;
24398
+ case "C":
24399
+ executeCubicCurve({
24400
+ x1: cmd.x1,
24401
+ y1: cmd.y1,
24402
+ x2: cmd.x2,
24403
+ y2: cmd.y2,
24404
+ x: cmd.x,
24405
+ y: cmd.y,
24406
+ relative: false,
24407
+ state,
24408
+ sink
24409
+ });
24410
+ break;
24411
+ case "c":
24412
+ executeCubicCurve({
24413
+ x1: cmd.x1,
24414
+ y1: cmd.y1,
24415
+ x2: cmd.x2,
24416
+ y2: cmd.y2,
24417
+ x: cmd.x,
24418
+ y: cmd.y,
24419
+ relative: true,
24420
+ state,
24421
+ sink
24422
+ });
24423
+ break;
24424
+ case "S":
24425
+ executeSmoothCubic({
24426
+ x2: cmd.x2,
24427
+ y2: cmd.y2,
24428
+ x: cmd.x,
24429
+ y: cmd.y,
24430
+ relative: false,
24431
+ state,
24432
+ sink
24433
+ });
24434
+ break;
24435
+ case "s":
24436
+ executeSmoothCubic({
24437
+ x2: cmd.x2,
24438
+ y2: cmd.y2,
24439
+ x: cmd.x,
24440
+ y: cmd.y,
24441
+ relative: true,
24442
+ state,
24443
+ sink
24444
+ });
24445
+ break;
24446
+ case "Q":
24447
+ executeQuadratic({
24448
+ x1: cmd.x1,
24449
+ y1: cmd.y1,
24450
+ x: cmd.x,
24451
+ y: cmd.y,
24452
+ relative: false,
24453
+ state,
24454
+ sink
24455
+ });
24456
+ break;
24457
+ case "q":
24458
+ executeQuadratic({
24459
+ x1: cmd.x1,
24460
+ y1: cmd.y1,
24461
+ x: cmd.x,
24462
+ y: cmd.y,
24463
+ relative: true,
24464
+ state,
24465
+ sink
24466
+ });
24467
+ break;
24468
+ case "T":
24469
+ executeSmoothQuadratic({
24470
+ x: cmd.x,
24471
+ y: cmd.y,
24472
+ relative: false,
24473
+ state,
24474
+ sink
24475
+ });
24476
+ break;
24477
+ case "t":
24478
+ executeSmoothQuadratic({
24479
+ x: cmd.x,
24480
+ y: cmd.y,
24481
+ relative: true,
24482
+ state,
24483
+ sink
24484
+ });
24485
+ break;
24486
+ case "A":
24487
+ executeArc({
24488
+ rx: cmd.rx,
24489
+ ry: cmd.ry,
24490
+ xAxisRotation: cmd.xAxisRotation,
24491
+ largeArcFlag: cmd.largeArcFlag,
24492
+ sweepFlag: cmd.sweepFlag,
24493
+ x: cmd.x,
24494
+ y: cmd.y,
24495
+ relative: false,
24496
+ state,
24497
+ sink
24498
+ });
24499
+ break;
24500
+ case "a":
24501
+ executeArc({
24502
+ rx: cmd.rx,
24503
+ ry: cmd.ry,
24504
+ xAxisRotation: cmd.xAxisRotation,
24505
+ largeArcFlag: cmd.largeArcFlag,
24506
+ sweepFlag: cmd.sweepFlag,
24507
+ x: cmd.x,
24508
+ y: cmd.y,
24509
+ relative: true,
24510
+ state,
24511
+ sink
24512
+ });
24513
+ break;
24514
+ case "Z":
24515
+ case "z":
24516
+ executeClose({
24517
+ state,
24518
+ sink
24519
+ });
24520
+ break;
24521
+ }
24522
+ state.lastCommand = cmd.type;
24523
+ }
24524
+ function executeMoveTo(options) {
24525
+ const { x, y, relative, state, sink } = options;
24526
+ const outX = relative ? state.currentX + x * state.scale : transformX(x, state);
24527
+ const outY = relative ? state.currentY + y * state.yFlip * state.scale : transformY(y, state);
24528
+ sink.moveTo(outX, outY);
24529
+ state.currentX = outX;
24530
+ state.currentY = outY;
24531
+ state.subpathStartX = outX;
24532
+ state.subpathStartY = outY;
24533
+ state.lastControlX = outX;
24534
+ state.lastControlY = outY;
24535
+ }
24536
+ function executeLineTo(options) {
24537
+ const { x, y, relative, state, sink } = options;
24538
+ const outX = relative ? state.currentX + x * state.scale : transformX(x, state);
24539
+ const outY = relative ? state.currentY + y * state.yFlip * state.scale : transformY(y, state);
24540
+ sink.lineTo(outX, outY);
24541
+ state.currentX = outX;
24542
+ state.currentY = outY;
24543
+ state.lastControlX = outX;
24544
+ state.lastControlY = outY;
24545
+ }
24546
+ function executeHorizontalLine(options) {
24547
+ const { x, relative, state, sink } = options;
24548
+ const outX = relative ? state.currentX + x * state.scale : transformX(x, state);
24549
+ sink.lineTo(outX, state.currentY);
24550
+ state.currentX = outX;
24551
+ state.lastControlX = outX;
24552
+ state.lastControlY = state.currentY;
24553
+ }
24554
+ function executeVerticalLine(options) {
24555
+ const { y, relative, state, sink } = options;
24556
+ const outY = relative ? state.currentY + y * state.yFlip * state.scale : transformY(y, state);
24557
+ sink.lineTo(state.currentX, outY);
24558
+ state.currentY = outY;
24559
+ state.lastControlX = state.currentX;
24560
+ state.lastControlY = outY;
24561
+ }
24562
+ function executeCubicCurve(options) {
24563
+ const { x1, y1, x2, y2, x, y, relative, state, sink } = options;
24564
+ const outX1 = relative ? state.currentX + x1 * state.scale : transformX(x1, state);
24565
+ const outY1 = relative ? state.currentY + y1 * state.yFlip * state.scale : transformY(y1, state);
24566
+ const outX2 = relative ? state.currentX + x2 * state.scale : transformX(x2, state);
24567
+ const outY2 = relative ? state.currentY + y2 * state.yFlip * state.scale : transformY(y2, state);
24568
+ const outX = relative ? state.currentX + x * state.scale : transformX(x, state);
24569
+ const outY = relative ? state.currentY + y * state.yFlip * state.scale : transformY(y, state);
24570
+ sink.curveTo(outX1, outY1, outX2, outY2, outX, outY);
24571
+ state.currentX = outX;
24572
+ state.currentY = outY;
24573
+ state.lastControlX = outX2;
24574
+ state.lastControlY = outY2;
24575
+ }
24576
+ function executeSmoothCubic(options) {
24577
+ const { x2, y2, x, y, relative, state, sink } = options;
24578
+ let cp1x;
24579
+ let cp1y;
24580
+ if (state.lastCommand === "C" || state.lastCommand === "c" || state.lastCommand === "S" || state.lastCommand === "s") {
24581
+ cp1x = 2 * state.currentX - state.lastControlX;
24582
+ cp1y = 2 * state.currentY - state.lastControlY;
24583
+ } else {
24584
+ cp1x = state.currentX;
24585
+ cp1y = state.currentY;
24586
+ }
24587
+ const outX2 = relative ? state.currentX + x2 * state.scale : transformX(x2, state);
24588
+ const outY2 = relative ? state.currentY + y2 * state.yFlip * state.scale : transformY(y2, state);
24589
+ const outX = relative ? state.currentX + x * state.scale : transformX(x, state);
24590
+ const outY = relative ? state.currentY + y * state.yFlip * state.scale : transformY(y, state);
24591
+ sink.curveTo(cp1x, cp1y, outX2, outY2, outX, outY);
24592
+ state.currentX = outX;
24593
+ state.currentY = outY;
24594
+ state.lastControlX = outX2;
24595
+ state.lastControlY = outY2;
24596
+ }
24597
+ function executeQuadratic(options) {
24598
+ const { x1, y1, x, y, relative, state, sink } = options;
24599
+ const outCpX = relative ? state.currentX + x1 * state.scale : transformX(x1, state);
24600
+ const outCpY = relative ? state.currentY + y1 * state.yFlip * state.scale : transformY(y1, state);
24601
+ const outX = relative ? state.currentX + x * state.scale : transformX(x, state);
24602
+ const outY = relative ? state.currentY + y * state.yFlip * state.scale : transformY(y, state);
24603
+ sink.quadraticCurveTo(outCpX, outCpY, outX, outY);
24604
+ state.currentX = outX;
24605
+ state.currentY = outY;
24606
+ state.lastControlX = outCpX;
24607
+ state.lastControlY = outCpY;
24608
+ }
24609
+ function executeSmoothQuadratic(options) {
24610
+ const { x, y, relative, state, sink } = options;
24611
+ let cpx;
24612
+ let cpy;
24613
+ if (state.lastCommand === "Q" || state.lastCommand === "q" || state.lastCommand === "T" || state.lastCommand === "t") {
24614
+ cpx = 2 * state.currentX - state.lastControlX;
24615
+ cpy = 2 * state.currentY - state.lastControlY;
24616
+ } else {
24617
+ cpx = state.currentX;
24618
+ cpy = state.currentY;
24619
+ }
24620
+ const outX = relative ? state.currentX + x * state.scale : transformX(x, state);
24621
+ const outY = relative ? state.currentY + y * state.yFlip * state.scale : transformY(y, state);
24622
+ sink.quadraticCurveTo(cpx, cpy, outX, outY);
24623
+ state.currentX = outX;
24624
+ state.currentY = outY;
24625
+ state.lastControlX = cpx;
24626
+ state.lastControlY = cpy;
24627
+ }
24628
+ function executeArc(options) {
24629
+ const { rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y, relative, state, sink } = options;
24630
+ const outX = relative ? state.currentX + x * state.scale : transformX(x, state);
24631
+ const outY = relative ? state.currentY + y * state.yFlip * state.scale : transformY(y, state);
24632
+ const scaledRx = rx * state.scale;
24633
+ const scaledRy = ry * state.scale;
24634
+ const effectiveSweepFlag = state.yFlip === -1 ? !sweepFlag : sweepFlag;
24635
+ const curves = arcToBezier({
24636
+ x1: state.currentX,
24637
+ y1: state.currentY,
24638
+ rx: scaledRx,
24639
+ ry: scaledRy,
24640
+ xAxisRotation,
24641
+ largeArcFlag,
24642
+ sweepFlag: effectiveSweepFlag,
24643
+ x2: outX,
24644
+ y2: outY
24645
+ });
24646
+ for (const curve of curves) sink.curveTo(curve.cp1x, curve.cp1y, curve.cp2x, curve.cp2y, curve.x, curve.y);
24647
+ state.currentX = outX;
24648
+ state.currentY = outY;
24649
+ state.lastControlX = outX;
24650
+ state.lastControlY = outY;
24651
+ }
24652
+ function executeClose(options) {
24653
+ const { state, sink } = options;
24654
+ sink.close();
24655
+ state.currentX = state.subpathStartX;
24656
+ state.currentY = state.subpathStartY;
24657
+ state.lastControlX = state.subpathStartX;
24658
+ state.lastControlY = state.subpathStartY;
24659
+ }
24660
+ /**
24661
+ * Parse and execute an SVG path string.
24662
+ *
24663
+ * This is a convenience function that combines parsing and execution.
24664
+ *
24665
+ * By default, Y coordinates are flipped (negated) to convert from SVG's
24666
+ * top-left origin to PDF's bottom-left origin. Set `flipY: false` in
24667
+ * options to disable this behavior.
24668
+ *
24669
+ * @param options - Execution options
24670
+ * @param options.pathData - SVG path d string
24671
+ * @param options.sink - Callback interface for path operations
24672
+ * @param options.initialX - Initial X coordinate (default: 0)
24673
+ * @param options.initialY - Initial Y coordinate (default: 0)
24674
+ * @param options.flipY - Flip Y coordinates for PDF (default: true)
24675
+ * @param options.scale - Scale factor (default: 1)
24676
+ * @param options.translateX - X offset after transform (default: 0)
24677
+ * @param options.translateY - Y offset after transform (default: 0)
24678
+ * @returns Final position {x, y} after executing all commands
24679
+ *
24680
+ * @example
24681
+ * ```typescript
24682
+ * const sink = {
24683
+ * moveTo: (x, y) => console.log(`M ${x} ${y}`),
24684
+ * lineTo: (x, y) => console.log(`L ${x} ${y}`),
24685
+ * curveTo: (cp1x, cp1y, cp2x, cp2y, x, y) => console.log(`C ...`),
24686
+ * quadraticCurveTo: (cpx, cpy, x, y) => console.log(`Q ...`),
24687
+ * close: () => console.log(`Z`),
24688
+ * };
24689
+ *
24690
+ * executeSvgPathString({ pathData: "M 10 10 L 100 10 L 100 100 Z", sink });
24691
+ * ```
24692
+ */
24693
+ function executeSvgPathString(options) {
24694
+ const { pathData, sink, initialX, initialY, ...executorOptions } = options;
24695
+ return executeSvgPath({
24696
+ commands: parseSvgPath(pathData),
24697
+ sink,
24698
+ initialX,
24699
+ initialY,
24700
+ ...executorOptions
24701
+ });
24702
+ }
24703
+
23725
24704
  //#endregion
23726
24705
  //#region src/api/drawing/path-builder.ts
23727
24706
  /**
@@ -23848,6 +24827,71 @@ var PathBuilder = class {
23848
24827
  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
24828
  }
23850
24829
  /**
24830
+ * Append an SVG path string to the current path.
24831
+ *
24832
+ * Parses the SVG path `d` attribute string and adds all commands to this path.
24833
+ * Relative commands (lowercase) are converted to absolute coordinates based on
24834
+ * the current point. Smooth curves (S, T) and arcs (A) are converted to
24835
+ * cubic bezier curves.
24836
+ *
24837
+ * By default, this method does NOT transform coordinates (flipY: false).
24838
+ * Use the options to apply scale, translation, and Y-flip for SVG paths.
24839
+ *
24840
+ * @param pathData - SVG path `d` attribute string
24841
+ * @param options - Execution options (flipY, scale, translate)
24842
+ * @returns This PathBuilder for chaining
24843
+ *
24844
+ * @example
24845
+ * ```typescript
24846
+ * // Simple path (no transform)
24847
+ * page.drawPath()
24848
+ * .appendSvgPath("M 10 10 L 100 10 L 55 90 Z")
24849
+ * .fill({ color: rgb(1, 0, 0) });
24850
+ *
24851
+ * // SVG icon with full transform
24852
+ * page.drawPath()
24853
+ * .appendSvgPath(iconPath, {
24854
+ * flipY: true,
24855
+ * scale: 0.1,
24856
+ * translateX: 100,
24857
+ * translateY: 500,
24858
+ * })
24859
+ * .fill({ color: rgb(0, 0, 0) });
24860
+ * ```
24861
+ */
24862
+ appendSvgPath(pathData, options = {}) {
24863
+ const executorOptions = {
24864
+ flipY: options.flipY ?? false,
24865
+ scale: options.scale,
24866
+ translateX: options.translateX,
24867
+ translateY: options.translateY
24868
+ };
24869
+ executeSvgPathString({
24870
+ pathData,
24871
+ sink: {
24872
+ moveTo: (x, y) => {
24873
+ this.moveTo(x, y);
24874
+ },
24875
+ lineTo: (x, y) => {
24876
+ this.lineTo(x, y);
24877
+ },
24878
+ curveTo: (cp1x, cp1y, cp2x, cp2y, x, y) => {
24879
+ this.curveTo(cp1x, cp1y, cp2x, cp2y, x, y);
24880
+ },
24881
+ quadraticCurveTo: (cpx, cpy, x, y) => {
24882
+ this.quadraticCurveTo(cpx, cpy, x, y);
24883
+ },
24884
+ close: () => {
24885
+ this.close();
24886
+ }
24887
+ },
24888
+ initialX: this.currentX,
24889
+ initialY: this.currentY,
24890
+ ...executorOptions
24891
+ });
24892
+ return this;
24893
+ }
24894
+ /**
23851
24895
  * Stroke the path with the given options.
23852
24896
  */
23853
24897
  stroke(options = {}) {
@@ -24839,6 +25883,76 @@ var PDFPage = class PDFPage {
24839
25883
  drawPath() {
24840
25884
  return new PathBuilder((content) => this.appendContent(content), (fillOpacity, strokeOpacity) => this.registerGraphicsStateForOpacity(fillOpacity, strokeOpacity));
24841
25885
  }
25886
+ /**
25887
+ * Draw an SVG path on the page.
25888
+ *
25889
+ * This is a convenience method that parses an SVG path `d` attribute string
25890
+ * and draws it with the specified options. For more control, use `drawPath()`
25891
+ * with `appendSvgPath()`.
25892
+ *
25893
+ * By default, the path is filled with black. Specify `borderColor` without
25894
+ * `color` to stroke without filling.
25895
+ *
25896
+ * SVG paths are automatically transformed from SVG coordinate space (Y-down)
25897
+ * to PDF coordinate space (Y-up). Use `x`, `y` to position the path, and
25898
+ * `scale` to resize it.
25899
+ *
25900
+ * @param pathData - SVG path `d` attribute string
25901
+ * @param options - Drawing options (x, y, scale, color, etc.)
25902
+ *
25903
+ * @example
25904
+ * ```typescript
25905
+ * // Draw a Font Awesome heart icon at position (100, 500)
25906
+ * // Icon is 512x512 in SVG, scale to ~50pt
25907
+ * page.drawSvgPath(faHeartPath, {
25908
+ * x: 100,
25909
+ * y: 500,
25910
+ * scale: 0.1,
25911
+ * color: rgb(1, 0, 0),
25912
+ * });
25913
+ *
25914
+ * // Draw a simple triangle at default position (0, 0)
25915
+ * page.drawSvgPath("M 0 0 L 50 0 L 25 40 Z", {
25916
+ * color: rgb(0, 0, 1),
25917
+ * });
25918
+ *
25919
+ * // Stroke a curve
25920
+ * page.drawSvgPath("M 0 0 C 10 10, 30 10, 40 0", {
25921
+ * x: 200,
25922
+ * y: 300,
25923
+ * borderColor: rgb(0, 0, 0),
25924
+ * borderWidth: 2,
25925
+ * });
25926
+ * ```
25927
+ */
25928
+ drawSvgPath(pathData, options = {}) {
25929
+ const x = options.x ?? 0;
25930
+ const y = options.y ?? 0;
25931
+ const scale = options.scale ?? 1;
25932
+ const flipY = options.flipY ?? true;
25933
+ const builder = this.drawPath();
25934
+ builder.appendSvgPath(pathData, {
25935
+ flipY,
25936
+ scale,
25937
+ translateX: x,
25938
+ translateY: y
25939
+ });
25940
+ const hasFill = options.color !== void 0;
25941
+ const hasStroke = options.borderColor !== void 0;
25942
+ if (hasFill && hasStroke) {
25943
+ builder.fillAndStroke(options);
25944
+ return;
25945
+ }
25946
+ if (hasStroke) {
25947
+ builder.stroke(options);
25948
+ return;
25949
+ }
25950
+ const fillOptions = hasFill ? options : {
25951
+ ...options,
25952
+ color: black
25953
+ };
25954
+ builder.fill(fillOptions);
25955
+ }
24842
25956
  /** Cached annotations for this page */
24843
25957
  _annotationCache = null;
24844
25958
  /**
@@ -25334,9 +26448,12 @@ var PDFPage = class PDFPage {
25334
26448
  }
25335
26449
  /**
25336
26450
  * Create and register a content stream.
26451
+ *
26452
+ * Accepts either a string (for ASCII-only content like operator names and numbers)
26453
+ * or raw bytes (for content that may contain non-ASCII data).
25337
26454
  */
25338
26455
  createContentStream(content) {
25339
- const stream = new PdfStream([], new TextEncoder().encode(content));
26456
+ const stream = new PdfStream([], typeof content === "string" ? new TextEncoder().encode(content) : content);
25340
26457
  if (this.ctx) return this.ctx.register(stream);
25341
26458
  return stream;
25342
26459
  }
@@ -25345,7 +26462,8 @@ var PDFPage = class PDFPage {
25345
26462
  */
25346
26463
  prependContent(content) {
25347
26464
  const existingContents = this.dict.get("Contents");
25348
- const newContent = this.createContentStream(`${content}\n`);
26465
+ const contentWithNewline = typeof content === "string" ? `${content}\n` : concatBytes([content, new Uint8Array([10])]);
26466
+ const newContent = this.createContentStream(contentWithNewline);
25349
26467
  if (!existingContents) {
25350
26468
  this.dict.set("Contents", newContent);
25351
26469
  return;
@@ -25377,7 +26495,8 @@ var PDFPage = class PDFPage {
25377
26495
  */
25378
26496
  appendContent(content) {
25379
26497
  const existingContents = this.dict.get("Contents");
25380
- const newContent = this.createContentStream(`\n${content}`);
26498
+ const contentWithNewline = typeof content === "string" ? `\n${content}` : concatBytes([new Uint8Array([10]), content]);
26499
+ const newContent = this.createContentStream(contentWithNewline);
25381
26500
  if (!existingContents) {
25382
26501
  this.dict.set("Contents", newContent);
25383
26502
  return;
@@ -25453,10 +26572,18 @@ var PDFPage = class PDFPage {
25453
26572
  }
25454
26573
  /**
25455
26574
  * Append operators to the page content stream.
26575
+ *
26576
+ * Uses Operator.toBytes() directly to avoid UTF-8 round-trip corruption
26577
+ * of non-ASCII bytes in PdfString operands (e.g., WinAnsi-encoded text).
25456
26578
  */
25457
26579
  appendOperators(ops) {
25458
- const content = ops.map((op) => op.toString()).join("\n");
25459
- this.appendContent(content);
26580
+ const newline = new Uint8Array([10]);
26581
+ const parts = [];
26582
+ for (let i = 0; i < ops.length; i++) {
26583
+ if (i > 0) parts.push(newline);
26584
+ parts.push(ops[i].toBytes());
26585
+ }
26586
+ this.appendContent(concatBytes(parts));
25460
26587
  }
25461
26588
  /**
25462
26589
  * Add a font resource to the page and return its name.
@@ -25474,7 +26601,12 @@ var PDFPage = class PDFPage {
25474
26601
  const baseFont = value.get("BaseFont", this.ctx.resolve.bind(this.ctx));
25475
26602
  if (baseFont instanceof PdfName && baseFont.value === font) return existingName.value;
25476
26603
  }
25477
- const fontDict = PdfDict.of({
26604
+ const fontDict = isWinAnsiStandard14(font) ? PdfDict.of({
26605
+ Type: PdfName.of("Font"),
26606
+ Subtype: PdfName.of("Type1"),
26607
+ BaseFont: PdfName.of(font),
26608
+ Encoding: PdfName.of("WinAnsiEncoding")
26609
+ }) : PdfDict.of({
25478
26610
  Type: PdfName.of("Font"),
25479
26611
  Subtype: PdfName.of("Type1"),
25480
26612
  BaseFont: PdfName.of(font)
@@ -25494,9 +26626,20 @@ var PDFPage = class PDFPage {
25494
26626
  }
25495
26627
  /**
25496
26628
  * Encode text to a PDF string for the given font.
26629
+ *
26630
+ * Standard 14 fonts use WinAnsiEncoding (or SymbolEncoding/ZapfDingbatsEncoding).
26631
+ * Unencodable characters are substituted with .notdef (byte 0x00).
26632
+ * Embedded fonts use Identity-H encoding with glyph IDs.
25497
26633
  */
25498
26634
  encodeTextForFont(text, font) {
25499
- if (typeof font === "string") return PdfString.fromString(text);
26635
+ if (typeof font === "string") {
26636
+ const encoding = getEncodingForStandard14(font);
26637
+ const codes = [];
26638
+ for (const char of text) if (encoding.canEncode(char)) codes.push(encoding.getCode(char.codePointAt(0)));
26639
+ else codes.push(0);
26640
+ const bytes$1 = new Uint8Array(codes);
26641
+ return PdfString.fromBytes(bytes$1);
26642
+ }
25500
26643
  const gids = font.encodeTextToGids(text);
25501
26644
  const bytes = new Uint8Array(gids.length * 2);
25502
26645
  for (let i = 0; i < gids.length; i++) {