npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zouchengxin/pdf-engine

v1.0.11

Published

A library that supports parsing and editing PDF files

Readme

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

# yarn add @zouchengxin/pdfium-engine
# pnpm install @zouchengxin/pdfium-engine
npm install @zouchengxin/pdfium-engine

Demo

pdf-engine-demo

Usage

Web Worker

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

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

// 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

// 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
// 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
// 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
// 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
// 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

// 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

// 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