## Features

- Zero dependencies - The wasm file used is embedded in the package.
- Type-safe - TypeScript definitions are included.
- Works in browser and Node.js
- Powerful features - supports parsing and editing

## Installation

```shell
# yarn add @zouchengxin/pdfium-engine
# pnpm install @zouchengxin/pdfium-engine
npm install @zouchengxin/pdfium-engine
```

## Demo

[pdf-engine-demo](https://pdf-engine-9k4.pages.dev)

## Usage

### Web Worker

```javascript
import { PdfEngineWorker } from "@zouchengxin/pdf-engine";
import workerUrl from "@zouchengxin/pdf-engine/worker?worker&url";

// Note: When using the worker version, all APIs return a Promise
const pdfEngine = new PdfEngineWorker();
// Use the Vite build tool to load the worker script from local
pdfEngine.setupWorker(workerUrl);
await pdfEngine.init(API_KEY);

pdfDoc = await pdfEngine.loadPdf(data);
const count = await pdfDoc.getPageCount();
const meta = await pdfDoc.getMetaData();
const bookmarks = await pdfDoc.getBookmarks();
for (let i = 0; i < count; i++) {
  const page = await pdfDoc.getPageProxy(i);
  const linkAnno = await pageProxy.createLinkAnno({
    rect: [100, 100, 100, 40],
    url: "https://www.baidu.com",
  });
  await pageProxy.updateLinkAnno(linkAnno.$id, {
    rect: [101, 101, 100, 40],
    url: "https://www.baidu.cn",
  });
  // Supports all core APIs
}
```

### Initialization

```javascript
import { PdfEngine } from "@zouchengxin/pdfium-engine";

const pdfEngine = new PdfEngine();
// API_KEY not provided, validity period until 2026-01-01, You can adjust the system time for testing.
// Or contact the developer to obtain the API key.
await pdfEngine.init(API_KEY);
```

### Parse

```javascript
// Select PDF file and return uint8Array data
const selectPdfFile = async () => {
  const [fileHandle] = await window.showOpenFilePicker({
    types: [
      {
        description: "Pdf Files",
        accept: {
          "application/pdf": [".pdf"],
        },
      },
    ],
    excludeAcceptAllOption: true,
    multiple: false,
  });
  const file = await fileHandle.getFile();
  const buf = await file.arrayBuffer();
  const bytes = new Uint8Array(buf);
  return bytes;
};
const data = await selectPdfFile();
// Load PDF documents, parameter Uint8Array
const pdfDoc = pdfEngine.loadPdf(data);
// Get the number of pages
const count = pdfDoc.getPageCount();
// Retrieves PDF metadata.
// returning the fields Title, Author, Subject, Keywords, Creator, Producer, CreationDate, and ModDate.
const meta = pdfDoc.getMetaData();
// Retrieve PDF bookmark information and return tree structure
const bookmarks = pdfDoc.getBookmarks();
console.log("Page Count:", count);
console.log("Pdf Meta:", meta);
console.log("Pdf Bookmarks:", bookmarks);
for (let i = 0; i < count; i++) {
  // Obtain the page proxy object and perform operations such as parsing and editing.
  const page = pdfDoc.getPageProxy(i);
  // Get page width
  const width = page.getPageWidth();
  // Get page height
  const height = page.getPageHeight();
  // Retrieves all xobject objects on the page.
  // including those of type TEXT, PATH, IMAGE, SHADING, and FORM.
  const objs = page.getObjects();
  // Retrieves all annotation objects on the page.
  // including those of type TEXT, LINK, FREETEXT, LINE, SQUARE, CIRCLE, HIGHLIGHT, UNDERLINE, STAMP, INK etc.
  const annots = page.getAnnotions();
  console.log("Page Size:", width, height);
  console.log("Page Objects:", objs);
  console.log("Page Annotions:", annots);
}
```

### Rendering

```javascript
// Retrieve the bitmap after page rendering; render only the xobject object, excluding annotations.
// Return value: ImageData object
const data = page.getBitmap();
// Retrieve page thumbnail; return empty if not stored.
// Return value: ImageData object
const data = page.getThumbnail();
```

### Create

###### Annotations

```javascript
// Select Image file and return uint8Array data
const selectImageFile = async () => {
  const [fileHandle] = await window.showOpenFilePicker({
    types: [
      {
        description: "Image Files",
        accept: {
          "image/*": [".png", ".jpeg", ".jpg", ".webp"],
        },
      },
    ],
    excludeAcceptAllOption: true,
    multiple: false,
  });
  const file = await fileHandle.getFile();
  return file;
};
// get ImageData data
const getImageDataFromFile = async (file) => {
  const { promise, resolve } = Promise.withResolvers();
  const img = new Image();
  const url = URL.createObjectURL(file);
  img.onload = () => {
    const canvas = document.createElement("canvas");
    canvas.width = img.width;
    canvas.height = img.height;
    const ctx = canvas.getContext("2d");
    if (!ctx) return null;
    ctx.drawImage(img, 0, 0);
    const data = ctx.getImageData(0, 0, img.width, img.height);
    resolve(data);
    URL.revokeObjectURL(url);
  };
  img.src = url;
  return promise;
};

// Create a link annotation.
// rect: a rectangular area.
// url: the redirect link.
const linkAnno = page.createLinkAnno({
  rect: [100, 100, 100, 40],
  url: "https://www.baidu.com",
});

// Create a FreeText annotation.
const freeAnno = page.createFreeTextAnno({
  rect: [100, 200, 100, 40],
  content: "Free Text Anno",
  color: [255, 0, 0, 255],
  fontSize: 14,
});

// Create a Text annotation.
const textAnno = page.createTextAnno({
  rect: [100, 300, 100, 40],
  content: "Text Anno",
  color: [255, 0, 0, 255],
  fontSize: 14,
});

// Create a Square annotation.
const squareAnno = page.createSquareAnno({
  rect: [100, 400, 100, 60],
  strokeColor: [255, 0, 0, 255],
  fillColor: [0, 255, 0, 255],
});

// Create a Circle annotation.
const circleAnno = page.createCircleAnno({
  rect: [200, 100, 100, 100],
  strokeColor: [255, 0, 0, 255],
  fillColor: [0, 255, 0, 255],
});

// Create a Highlight annotation.
const hlAnno = page.createHighlightAnno({
  rect: [200, 200, 100, 60],
  strokeColor: [255, 0, 0, 255],
  fillColor: [0, 255, 0, 255],
});

// Create a Underline annotation.
const ulAnno = page.createUnderlineAnno({
  rect: [200, 300, 100, 60],
  strokeColor: [255, 0, 0, 255],
  fillColor: [0, 255, 0, 255],
});

// Create a Stamp annotation.
const file = await selectImageFile();
const data = await getImageDataFromFile(file);
const stampAnno = page.createStampAnno({
  rect: [200, 400, 100, 100],
  matrix: { a: 50, b: 0, c: 0, d: 50, e: 200, f: 400 },
  data,
});

// More features are under development.
```

###### XObject

```javascript
// Select Image file and return uint8Array data
const selectFontFile = async () => {
  const [fileHandle] = await window.showOpenFilePicker({
    types: [
      {
        description: "Font Files",
        accept: {
          "font/*": [".ttf"],
        },
      },
    ],
    excludeAcceptAllOption: true,
    multiple: false,
  });
  const file = await fileHandle.getFile();
  const buf = await file.arrayBuffer();
  const bytes = new Uint8Array(buf);
  return bytes;
};

// Add Text XObject(system font)
// system font: Helvetica, Helvetica-Bold, Helvetica-Oblique, Helvetica-BoldOblique,
// Times-Roman, Times-Bold, Times-Italic, Times-BoldItalic,
// Courier, Courier-Bold, Courier-Oblique, Courier-BoldOblique,
// Symbol, ZapfDingbats ( not supported for now )
// Note: system fonts only support English
page.addTextObj({
  x: 300,
  y: 100,
  text: "hello word!",
  fontFamily: "Helvetica-Bold",
  strokeColor: [255, 0, 0, 255],
  color: [0, 255, 0, 255],
});

// Add external fonts, such as Chinese fonts
// Note: Currently supports ttf files only
const data = await selectFontFile();
pdfDoc.addCustomFont("custom-font", data);

// Get registered external fonts
const fonts = pdfDoc.getCustomFonts();
console.log("custom fonts:", fonts);

// Add Text XObject(custom font)
const textObj = page.addTextObj({
  x: 300,
  y: 200,
  text: "你好世界",
  fontFamily: "custom-font",
});

// Add Image XObject
const file = await selectImageFile();
const data = await getImageDataFromFile(file);
const imgObj = page.addImageObj({
  rect: [300, 300, 100, 100],
  matrix: { a: 80, b: 0, c: 0, d: 80, e: 300, f: 300 },
  data,
});
```

#### Update

###### Annotations

```javascript
// Update a link annotation.
// The first parameter can be obtained by taking the $id property from the object returned by createLinkAnno or getAnnotions
page.updateLinkAnno(linkAnno.$id, {
  rect: [101, 101, 100, 40],
  url: "https://www.baidu.cn",
});

// Update a FreeText annotation.
page.updateFreeTextAnno(freeAnno.$id, {
  rect: [101, 201, 100, 40],
  content: "Update Free Text Anno",
  color: [0, 255, 0, 255],
  fontSize: 18,
});

// Update a Text annotation.
page.updateTextAnno(textAnno.$id, {
  rect: [101, 301, 100, 40],
  content: "Update Text Anno",
  color: [0, 255, 0, 255],
  fontSize: 18,
});

// Update a Square annotation.
page.updateSquareAnno(squareAnno.$id, {
  rect: [101, 401, 100, 60],
  strokeColor: [0, 255, 0, 255],
  fillColor: [255, 0, 0, 255],
});

// Update a Circle annotation.
page.updateCircleAnno(circleAnno.$id, {
  rect: [201, 101, 100, 100],
  strokeColor: [0, 255, 0, 255],
  fillColor: [255, 0, 0, 255],
});

// Update a Highlight annotation.
page.updateHighlightAnno(hlAnno.$id, {
  rect: [201, 201, 100, 60],
  strokeColor: [0, 255, 0, 255],
  fillColor: [255, 0, 0, 255],
});

// Update a Underline annotation.
page.updateUnderlineAnno(ulAnno.$id, {
  rect: [201, 301, 100, 60],
  strokeColor: [0, 255, 0, 255],
  fillColor: [255, 0, 0, 255],
});

// Update a Stamp annotation.
page.updateStampAnno(stampAnno.$id, {
  rect: [201, 401, 100, 100],
  matrix: { a: 90, b: 0, c: 0, d: 90, e: 200, f: 400 },
});
```

###### XObject

```javascript
// Update Text XObject
// The first parameter can be obtained by taking the $id property from the object returned by addTextObj or getObjects
page.updateTextObj(textObj.$id, {
  x: 301,
  y: 101,
  text: "update helloword",
  fontFamily: "Helvetica-Bold",
  strokeColor: [0, 255, 0, 255],
  color: [255, 0, 0, 255],
});

// Update Image XObject
page.updateImageObj(imgObj.$id, {
  rect: [301, 301, 100, 100],
  matrix: { a: 90, b: 0, c: 0, d: 90, e: 300, f: 300 },
  data,
});
```

#### Operate

```javascript
// Create a new page and return a PageProxy object.
const page = pdfDoc.createPage();

// Delete the first page, with parameters starting from zero for the page index.
pdfDoc.deletePage(0);
```

#### Save

```javascript
// Save the PDF data and return a Uint8Array.
const uint8Arr = pdfDoc.savePdf();
```

## Note

- color: [r, g, b, a], An array of red, green, blue, and blue
- rect: [x, y, w, h], An array consisting of the x-coordinate of the bottom left corner of the rectangular region, the y-coordinate of the bottom left corner of the rectangular region, the width of the rectangular region, and the height of the rectangular region.
- Coordinate: The origin is at the bottom left corner of the page, the horizontal direction is the x-axis, and the vertical direction is the y-axis.
- API_KEY: Contact the developer for details, or change your system time to before January 1, 2026 for testing purposes.

## Contact

#### 交流群: 877673376

#### API_KEY: [https://ifdian.net](https://ifdian.net/order/create?plan_id=a4d458f0f86b11f0be555254001e7c00)
