@libpdf/core 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # LibPDF
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/@libpdf/core)](https://www.npmjs.com/package/@libpdf/core)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@libpdf/core)](https://www.npmjs.com/package/@libpdf/core)
5
+ [![CI](https://github.com/LibPDF-js/core/actions/workflows/ci.yml/badge.svg)](https://github.com/LibPDF-js/core/actions/workflows/ci.yml)
6
+ [![GitHub stars](https://img.shields.io/github/stars/libpdf-js/core?style=flat)](https://github.com/LibPDF-js/core)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
9
+
3
10
  A modern PDF library for TypeScript. Parse, modify, and generate PDFs with a clean, intuitive API.
4
11
 
5
12
  > **Beta Software**: LibPDF is under active development and APIs may change between minor versions, but we use it in production at [Documenso](https://documenso.com) and consider it ready for real-world use.
@@ -51,7 +58,7 @@ bun add @libpdf/core
51
58
  import { PDF } from "@libpdf/core";
52
59
 
53
60
  const pdf = await PDF.load(bytes);
54
- const pages = await pdf.getPages();
61
+ const pages = pdf.getPages();
55
62
 
56
63
  console.log(`${pages.length} pages`);
57
64
  ```
@@ -66,7 +73,7 @@ const pdf = await PDF.load(bytes, { credentials: "password" });
66
73
 
67
74
  ```typescript
68
75
  const pdf = await PDF.load(bytes);
69
- const form = await pdf.getForm();
76
+ const form = pdf.getForm();
70
77
 
71
78
  form.fill({
72
79
  name: "Jane Doe",
package/dist/index.d.mts CHANGED
@@ -3767,19 +3767,6 @@ interface SvgPathExecutorOptions {
3767
3767
  }
3768
3768
  //#endregion
3769
3769
  //#region src/fonts/standard-14.d.ts
3770
- /**
3771
- * Standard 14 PDF Fonts
3772
- *
3773
- * These are the 14 fonts that every PDF reader must support.
3774
- * They don't require embedding - the reader provides them.
3775
- *
3776
- * Width tables are stored by glyph name (like pdf.js) which works
3777
- * with any encoding. Use getWidthByGlyphName() for lookup.
3778
- *
3779
- * Widths are in glyph units (1000 units = 1 em).
3780
- *
3781
- * Data extracted from pdf.js metrics.js (Mozilla, Apache 2.0 License)
3782
- */
3783
3770
  /**
3784
3771
  * Standard 14 font names.
3785
3772
  */
@@ -4103,8 +4090,9 @@ declare function lineJoinToNumber(join: LineJoin): 0 | 1 | 2;
4103
4090
  //#region src/api/drawing/path-builder.d.ts
4104
4091
  /**
4105
4092
  * Callback type for appending content to a page.
4093
+ * Accepts a string (for ASCII-only content) or raw bytes.
4106
4094
  */
4107
- type ContentAppender = (content: string) => void;
4095
+ type ContentAppender = (content: string | Uint8Array) => void;
4108
4096
  /**
4109
4097
  * Callback type for registering a graphics state and returning its name.
4110
4098
  */
@@ -5662,6 +5650,9 @@ declare class PDFPage {
5662
5650
  private formatNumber;
5663
5651
  /**
5664
5652
  * Create and register a content stream.
5653
+ *
5654
+ * Accepts either a string (for ASCII-only content like operator names and numbers)
5655
+ * or raw bytes (for content that may contain non-ASCII data).
5665
5656
  */
5666
5657
  private createContentStream;
5667
5658
  /**
@@ -5689,6 +5680,9 @@ declare class PDFPage {
5689
5680
  private registerGraphicsStateForOpacity;
5690
5681
  /**
5691
5682
  * Append operators to the page content stream.
5683
+ *
5684
+ * Uses Operator.toBytes() directly to avoid UTF-8 round-trip corruption
5685
+ * of non-ASCII bytes in PdfString operands (e.g., WinAnsi-encoded text).
5692
5686
  */
5693
5687
  private appendOperators;
5694
5688
  /**
@@ -5697,6 +5691,10 @@ declare class PDFPage {
5697
5691
  private addFontResource;
5698
5692
  /**
5699
5693
  * Encode text to a PDF string for the given font.
5694
+ *
5695
+ * Standard 14 fonts use WinAnsiEncoding (or SymbolEncoding/ZapfDingbatsEncoding).
5696
+ * Unencodable characters are substituted with .notdef (byte 0x00).
5697
+ * Embedded fonts use Identity-H encoding with glyph IDs.
5700
5698
  */
5701
5699
  private encodeTextForFont;
5702
5700
  /**
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.3";
12
+ var version = "0.2.5";
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).
@@ -26317,9 +26448,12 @@ var PDFPage = class PDFPage {
26317
26448
  }
26318
26449
  /**
26319
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).
26320
26454
  */
26321
26455
  createContentStream(content) {
26322
- const stream = new PdfStream([], new TextEncoder().encode(content));
26456
+ const stream = new PdfStream([], typeof content === "string" ? new TextEncoder().encode(content) : content);
26323
26457
  if (this.ctx) return this.ctx.register(stream);
26324
26458
  return stream;
26325
26459
  }
@@ -26328,13 +26462,22 @@ var PDFPage = class PDFPage {
26328
26462
  */
26329
26463
  prependContent(content) {
26330
26464
  const existingContents = this.dict.get("Contents");
26331
- 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);
26332
26467
  if (!existingContents) {
26333
26468
  this.dict.set("Contents", newContent);
26334
26469
  return;
26335
26470
  }
26336
26471
  if (existingContents instanceof PdfRef) {
26337
- this.dict.set("Contents", new PdfArray([newContent, existingContents]));
26472
+ const resolved = this.ctx.resolve(existingContents);
26473
+ if (resolved instanceof PdfArray) {
26474
+ const newArray = new PdfArray([newContent]);
26475
+ for (let i = 0; i < resolved.length; i++) {
26476
+ const item = resolved.at(i);
26477
+ if (item) newArray.push(item);
26478
+ }
26479
+ this.dict.set("Contents", newArray);
26480
+ } else this.dict.set("Contents", new PdfArray([newContent, existingContents]));
26338
26481
  this._contentWrapped = true;
26339
26482
  return;
26340
26483
  }
@@ -26360,7 +26503,8 @@ var PDFPage = class PDFPage {
26360
26503
  */
26361
26504
  appendContent(content) {
26362
26505
  const existingContents = this.dict.get("Contents");
26363
- const newContent = this.createContentStream(`\n${content}`);
26506
+ const contentWithNewline = typeof content === "string" ? `\n${content}` : concatBytes([new Uint8Array([10]), content]);
26507
+ const newContent = this.createContentStream(contentWithNewline);
26364
26508
  if (!existingContents) {
26365
26509
  this.dict.set("Contents", newContent);
26366
26510
  return;
@@ -26370,7 +26514,17 @@ var PDFPage = class PDFPage {
26370
26514
  const qStream = this.createContentStream("q\n");
26371
26515
  const QStream = this.createContentStream("\nQ");
26372
26516
  if (existingContents instanceof PdfRef) {
26373
- this.dict.set("Contents", new PdfArray([
26517
+ const resolved = this.ctx.resolve(existingContents);
26518
+ if (resolved instanceof PdfArray) {
26519
+ const newArray = new PdfArray([qStream]);
26520
+ for (let i = 0; i < resolved.length; i++) {
26521
+ const item = resolved.at(i);
26522
+ if (item) newArray.push(item);
26523
+ }
26524
+ newArray.push(QStream);
26525
+ newArray.push(newContent);
26526
+ this.dict.set("Contents", newArray);
26527
+ } else this.dict.set("Contents", new PdfArray([
26374
26528
  qStream,
26375
26529
  existingContents,
26376
26530
  QStream,
@@ -26436,10 +26590,18 @@ var PDFPage = class PDFPage {
26436
26590
  }
26437
26591
  /**
26438
26592
  * Append operators to the page content stream.
26593
+ *
26594
+ * Uses Operator.toBytes() directly to avoid UTF-8 round-trip corruption
26595
+ * of non-ASCII bytes in PdfString operands (e.g., WinAnsi-encoded text).
26439
26596
  */
26440
26597
  appendOperators(ops) {
26441
- const content = ops.map((op) => op.toString()).join("\n");
26442
- this.appendContent(content);
26598
+ const newline = new Uint8Array([10]);
26599
+ const parts = [];
26600
+ for (let i = 0; i < ops.length; i++) {
26601
+ if (i > 0) parts.push(newline);
26602
+ parts.push(ops[i].toBytes());
26603
+ }
26604
+ this.appendContent(concatBytes(parts));
26443
26605
  }
26444
26606
  /**
26445
26607
  * Add a font resource to the page and return its name.
@@ -26457,7 +26619,12 @@ var PDFPage = class PDFPage {
26457
26619
  const baseFont = value.get("BaseFont", this.ctx.resolve.bind(this.ctx));
26458
26620
  if (baseFont instanceof PdfName && baseFont.value === font) return existingName.value;
26459
26621
  }
26460
- const fontDict = PdfDict.of({
26622
+ const fontDict = isWinAnsiStandard14(font) ? PdfDict.of({
26623
+ Type: PdfName.of("Font"),
26624
+ Subtype: PdfName.of("Type1"),
26625
+ BaseFont: PdfName.of(font),
26626
+ Encoding: PdfName.of("WinAnsiEncoding")
26627
+ }) : PdfDict.of({
26461
26628
  Type: PdfName.of("Font"),
26462
26629
  Subtype: PdfName.of("Type1"),
26463
26630
  BaseFont: PdfName.of(font)
@@ -26477,9 +26644,20 @@ var PDFPage = class PDFPage {
26477
26644
  }
26478
26645
  /**
26479
26646
  * Encode text to a PDF string for the given font.
26647
+ *
26648
+ * Standard 14 fonts use WinAnsiEncoding (or SymbolEncoding/ZapfDingbatsEncoding).
26649
+ * Unencodable characters are substituted with .notdef (byte 0x00).
26650
+ * Embedded fonts use Identity-H encoding with glyph IDs.
26480
26651
  */
26481
26652
  encodeTextForFont(text, font) {
26482
- if (typeof font === "string") return PdfString.fromString(text);
26653
+ if (typeof font === "string") {
26654
+ const encoding = getEncodingForStandard14(font);
26655
+ const codes = [];
26656
+ for (const char of text) if (encoding.canEncode(char)) codes.push(encoding.getCode(char.codePointAt(0)));
26657
+ else codes.push(0);
26658
+ const bytes$1 = new Uint8Array(codes);
26659
+ return PdfString.fromBytes(bytes$1);
26660
+ }
26483
26661
  const gids = font.encodeTextToGids(text);
26484
26662
  const bytes = new Uint8Array(gids.length * 2);
26485
26663
  for (let i = 0; i < gids.length; i++) {