@docx-pages/viewer
v0.2.0
Published
React components that draw a page laid out by @docx-pages/core.
Maintainers
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:
substitutionsnames every face another one answered for. Lines drawn in it may break where Word did not break them.fallbackCharactersnames 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.missingGlyphsnames every character nothing could draw, which is painted as a box.unhonourednames 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.
