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

@docx-pages/viewer

v0.2.0

Published

React components that draw a page laid out by @docx-pages/core.

Readme

@docx-pages/viewer

Draws a .docx in React, at the positions @docx-pages/core worked out against Word. React 18 or later.

The layout decides where everything goes; this package only paints it. Every position it draws to is one core computed against Word, so the component's job is to put a glyph where it was told and get out of the way.

Which of the two you want

The faces are the whole question. A document names faces, and something has to answer for the ones you cannot ship, so this package has two entries and they differ only in what answers.

npm install @docx-pages/viewer @docx-pages/fonts   # let the pack answer
npm install @docx-pages/viewer                     # answer for them yourself

@docx-pages/fonts is an optional peer, so the second install really does leave it out, and nothing the root entry reaches names it. The pack carries font files and is around 7.7 MiB; the viewer without it bundles to a couple of hundred kilobytes.

Letting the pack answer

import { DocxDocument } from "@docx-pages/viewer/pack";

<DocxDocument source={bytes} onReport={(report) => console.log(report.substitutions)} />;

That is the whole consumer. It opens the bytes, falls back to a metric twin for every face the document names that you have not supplied, lays it out, and paints it with the same bytes it measured with.

Answering for the faces yourself

The root entry is the same component with nothing behind it, so it asks you what to fall back to. defaults is a FaceDefaults from core: the faces to fall back through, the twin each name maps to, and the shape defaults behind that.

import { DocxDocument } from "@docx-pages/viewer";

<DocxDocument
  source={bytes}
  fonts={[{ name: "Calibri", bytes: calibri }]}
  defaults={{
    faces: myFaces,
    twins: { calibri: "My Sans" },
    sansSerif: "My Sans",
    serif: "My Serif",
    monospace: "My Mono",
    lastResort: "My Serif",
  }}
  defaultBytes={[{ name: "My Sans", bytes: mySans }]}
/>;

defaultBytes is what the browser paints the fallbacks with. Leave it out and a stood-in face is measured here and painted by whatever the browser finds, which is the one way a page is right in its geometry and wrong on the screen.

Supplying the real faces

Either entry takes fonts, and that is what exactness means: a face handed in there is used as given, and a document whose faces are all supplied is laid out as if nothing were behind it. Bold and italic are separate faces, each with its own bytes and its own bold/italic flags.

It is never quiet about a stand-in

A page drawn over a fallback is no longer the page Word would draw. onReport says so, after every layout:

  • substitutions names every face another one answered for. Lines drawn in it may break where Word did not break them.
  • fallbackCharacters names every character drawn out of a face the document never mentioned. The room it takes is Word's, so nothing moves, but the glyph in that room is the borrowed one.
  • missingGlyphs names every character nothing could draw, which is painted as a box.
  • unhonoured names what the document asked for that the layout passed over, each saying whether it moves text or only changes paint.

An application that shows a page can show what was doubtful about it.

Under node, and under a dev server

The pack finds its files beside its own module and reaches them with fetch, which node will not do for a file: url. Under node, read them off the disk and hand them to the root entry:

import { DocxDocument } from "@docx-pages/viewer";
import { defaultFacesFromDisk } from "@docx-pages/fonts/node";

<DocxDocument source={bytes} defaults={await defaultFacesFromDisk()} />;

For the same reason a dev server that prebundles its dependencies moves the pack away from its own files. Vite is the one met so far, and the way out is configuration:

optimizeDeps: {
  exclude: ["@docx-pages/viewer", "@docx-pages/fonts"];
}

vite build needs nothing, and neither does the root entry, which has no files to be moved away from. Without it @docx-pages/viewer/pack reports a blocker saying exactly this.

Drawing a layout laid out by hand

Document paints a layout somebody else made, which is what a caller wanting to lay out once and paint many times reaches for. It needs the resolver the pictures come out of as well as the layout:

import { bestEffortMetrics, layOutDocument, openDocx, readFaceShapes } from "@docx-pages/core";
import { defaultFaces } from "@docx-pages/fonts";
import { Document, imageResolver } from "@docx-pages/viewer";

const pkg = openDocx(bytes);
const faces = bestEffortMetrics(suppliedFaces, await defaultFaces(), readFaceShapes(pkg));
const layout = layOutDocument(pkg, faces);
if (layout.kind !== "laid-out") throw new Error(JSON.stringify(layout.blocker));

<Document layout={layout} imageUrl={imageResolver(pkg, faces.metricsFor)} />;

Nothing registers the faces with the browser on this path, which DocxDocument does for itself.

Page takes the same props and one page besides, for a viewer paginating by hand:

{
  layout.pages.map((page, at) => (
    <Page key={at} page={page} layout={layout} imageUrl={imageResolver(pkg, faces.metricsFor)} />
  ));
}

Pictures

A drawing's bytes live in the .docx, so the component is handed a resolver rather than reaching for them itself. imageResolver builds one over a package. Under it, imageDataUrl builds the url for a single part, and drawablesOf says what a page holds to draw, for a caller writing its own painter.

Frames

frames="outlined" draws the frame of every text box and anchored object. It is for looking at a page beside Word's own and seeing which box a difference is in; "hidden" is the default and is what a reader sees.

Licence

MIT. @docx-pages/fonts, if you install it, carries font files under their own licences, part of them the SIL Open Font License.