Tesserae.Pdf
26.9.4916
dotnet add package Tesserae.Pdf --version 26.9.4916
NuGet\Install-Package Tesserae.Pdf -Version 26.9.4916
<PackageReference Include="Tesserae.Pdf" Version="26.9.4916" />
<PackageVersion Include="Tesserae.Pdf" Version="26.9.4916" />
<PackageReference Include="Tesserae.Pdf" />
paket add Tesserae.Pdf --version 26.9.4916
#r "nuget: Tesserae.Pdf, 26.9.4916"
#:package Tesserae.Pdf@26.9.4916
#addin nuget:?package=Tesserae.Pdf&version=26.9.4916
#tool nuget:?package=Tesserae.Pdf&version=26.9.4916
Tesserae.Pdf
A Tesserae wrapper around Mozilla's pdf.js, for Transpose C#-to-JavaScript apps.
PdfJs.Viewer()
.Url("/api/files/42.pdf")
.FitWidth()
.OnPageChanged(page => label.Text = $"Page {page} of {viewer.PageCount}")
pdf.js ships with the package - there is nothing to add to your page, no CDN to configure, and no worker path to keep in sync.
What is in it
PdfJs.Viewer() - a scrollable, searchable, linkable document viewer. Pages, links, text
selection, form fields, keyboard scrolling and full-text search all work with no further wiring.
It draws no toolbar: that is the part that has to look like the rest of your application, so the
component exposes the methods a toolbar calls (NextPage, FitWidth, Rotate, Search, ...) and
leaves the buttons to you.
PdfJs.ViewerChrome() - the same viewer with the toolbar already on it. Panel toggles, page
controls, a zoom stepper whose menu holds the fit modes, rotate and spread, an always-visible search
box with a Fuzzy | Precise switch (whose two meanings a host can redefine with SearchOptions, down
to "any of these words" through FindOptions.AnyWord), and a side panel showing the outline or the page
thumbnails. For
an application that wants a document reader and does not want to have an opinion about what one looks
like. It is a composition of PdfJs.Viewer()'s public surface and nothing else, and chrome.Viewer
hands that component back - so starting here and replacing the toolbar later costs the toolbar and
nothing more. It is built from Tesserae's own components - Button, TextBox, SearchBox, Tree,
Grid, ContextMenu, HStack/VStack - so it looks and behaves like the rest of your application,
and every colour resolves to a --tss-* theme variable, so UI.Theme.Dark() and your own
Theme.Build() come through with no work. It sheds controls into an overflow menu as it narrows
rather than clipping them, wraps its search box onto a second row on a phone, and grows its touch
targets on a coarse pointer. Border() frames it in the theme's border colour for the case where
nothing around it draws the edge - off by default, since a chrome filling a window or sitting in a
Card has one already.
PdfJs.PageCanvas() - one page painted into a canvas. A thumbnail, a preview tile, a page in a
contact sheet. Give it a URL and it opens its own document; give it a PdfDocument and it borrows
one, which is how a rail of thumbnails shares a single document rather than opening twelve.
PdfJs.OpenAsync(source) - a document with nothing on screen, for the things that need no
viewer: extracting text, reading metadata and permissions, listing an outline, rendering a page into
a canvas of your own.
Alongside those: encrypted documents (OnPassword), embedded JavaScript (EnableScripting),
localization through Tesserae's TNT table, typed failures (PdfError.Kind), and SaveAsync for
getting a filled form's bytes back out.
The sample gallery has a page per feature.
Getting started
Add the package. Its build copies pdf.js into your app's output under assets/js/pdf, and the
components load it from there on first use - nothing is fetched until a viewer mounts.
The package's own JavaScript is on demand too, all the way down: index.html does not script it, so
an application fetches the entry itself, just before the first PdfJs call - one line, and a
natural place for it is the route or view that shows documents:
await Transpose.Require.RequireAsync(Transpose.RequireKind.Module, "./Tesserae.Pdf.js");
Until that has run, nothing in the Tesserae.Pdf namespace exists. An application that shows a PDF on
every page can make the call first thing in Main, which is what the sample gallery does.
var viewer = PdfJs.Viewer();
viewer
.Url("report.pdf")
.FitWidth()
.OnDocumentLoaded(document => Console.WriteLine($"{document.PageCount} pages"))
.OnError(error => ShowMessage(error.Message));
// Give it a height, or a parent that has one: the viewer fills its container and scrolls inside it.
MountToBody(viewer.H(600).WS());
Serving pdf.js from somewhere else - a CDN, a shared static host - is one setting, and it moves the worker and every asset directory with it:
PdfJs.AssetsPath = "https://static.example.com/pdfjs";
Things worth knowing
The chrome is the shortcut, not the replacement. PdfJs.ViewerChrome() and PdfJs.Viewer() are
the same component with and without a toolbar. Reach for the chrome when a reader is what you want;
reach for the viewer when the controls have to be yours, or when there is barely a control at all - a
preview pane, a print dialog, a thumbnail with a click-to-zoom. The chrome can also be pared back
(ShowZoom(false), Tabs(thumbnails: false), ShowSearch(false)) rather than swapped out.
The chrome owns the viewer's event slots. OnPageChanged and friends on PdfViewer are single
slots - a second call replaces the first - so register on the chrome (OnPanelChanged,
OnSearchModeChanged) or on the shared event bus. The chrome deliberately uses the bus itself so
chrome.Viewer.OnPageChanged(...) stays free for you.
Give the viewer a height. It fills its container and scrolls inside it, so in a container of no height it renders nothing - which looks like a document that failed to load.
Prefer the fit modes to an explicit zoom. FitWidth() and its siblings are re-applied when the
container resizes; Zoom(1.4) is a number and stays one. (pdf.js resolves a fit mode once, into a
number, and does not re-resolve it - the component re-applies it for you.)
A component owns what it opened. A viewer releases its document when it is torn down, including
the teardown that happens when it leaves the DOM; being re-added rebuilds it and restores the page,
zoom, rotation and layout. A document you opened yourself with PdfJs.OpenAsync is yours to release
with DestroyAsync.
AnnotationMode.EnableStorage is not "EnableForms and more". In a viewer it makes the form
non-interactive, silently - pdf.js tests for exactly EnableForms when deciding whether to build
real inputs. EnableForms is the default and the right choice for a viewer; EnableStorage belongs
on a page render, where it means "include the values already entered".
Whether a viewer has an annotation editor is decided before it is built. Call
AnnotationEditor(AnnotationEditorMode.None) while configuring the component to build the editor
layer; afterwards tools switch freely, but a viewer built without it cannot grow one.
A search scrolls the viewer, not your page. pdf.js 6 brings a match into view with the native
element.scrollIntoView, which scrolls every scrollable ancestor up to the window - so in a viewer
embedded in a scrolling page it moves your scrollbar as well as the document's. The component
replaces that one call with the bounded equivalent, so you do not have to do anything about it.
Watch for "Setting up fake worker" in the console. It means the worker could not be loaded and pdf.js is parsing on the main thread - documents still render, and the UI freezes while they do.
Localizing pdf.js's own strings
pdf.js puts data-l10n-id attributes on the elements it builds - page landmarks a screen reader
announces, alt text on annotation icons, tooltips on the editor's buttons - and expects something to
turn them into text. This package answers them through TNT, the same translation table Tesserae
itself uses, so a German application gets a German viewer from the dictionary that already
translates its own buttons. There is nothing to configure.
There is one gap a package cannot close by itself: your tnt extract scans your source, and these
strings live in a NuGet package it never sees. So add the keys below to your translation source
(or to whatever merges into TNT.T.SetTranslation). They are the English text of every message
pdf.js can ask for, and {0} is TNT's own placeholder convention.
PdfJs.Language tells pdf.js which language it is looking at - which decides text direction, and
how dates inside annotations are formatted. L10n(customObject) replaces the bridge entirely, and
WithoutOwnLocalization() falls back to pdf.js's built-in English.
PdfJs.ViewerChrome() labels its own controls through the same table, and they are in the same
position - add these too if you use it.
<details> <summary>The 38 strings the chrome uses</summary>
| Key |
|---|
({0} of {1}) |
Actual size |
Automatic |
Clear |
Continued from the start of the document |
Document outline |
Find in document |
Fit content |
Fit page |
Fuzzy |
Ignore case, accents and word boundaries |
Match case, whole words, diacritics respected |
More controls |
Next match |
Next page |
No document. |
No matches |
No matches - try Fuzzy |
of {0} |
Page |
Page {0} |
Page {0} of {1} |
pages {0} |
pages {0} +{1} more |
Precise |
Previous match |
Previous page |
Rotate right |
Searching... |
Show in outline |
This document has no outline. |
Thumbnails |
Two-page spread |
Zoom and fit |
Zoom in |
Zoom out |
{0} matches |
{0} pages |
</details>
<details> <summary>The 45 translatable strings pdf.js can ask for</summary>
| Key |
|---|
[{0} Annotation] |
Add comment |
Alt text |
Alt text added |
Blue |
Bottom left corner — resize |
Bottom middle — resize |
Bottom right corner — resize |
Change color |
Change drawing color |
Change text color |
Color choices |
Comment |
Created automatically: {0} |
Drawing added |
Drawing editor |
Edit alt text |
Green |
Highlight |
Highlight added |
Highlight editor |
Image added |
Image editor |
Marked as decorative |
Middle left — resize |
Middle right — resize |
Missing alt text |
Page {0} |
Pink |
Red |
Remove drawing |
Remove highlight |
Remove image |
Remove signature |
Remove text |
Review alt text |
Show comment |
Signature added |
Signature editor: {0} |
Text added |
Text Editor |
Top left corner — resize |
Top middle — resize |
Top right corner — resize |
Yellow |
</details>
Requirements
- .NET SDK 10 and the Transpose compiler.
- Node, for a build from source: pdf.js is not vendored, it is bundled from the pinned
pdfjs-distnpm package on every build.
Licensing
This package is MIT. pdf.js is Apache-2.0, and the bundled distribution carries its license plus the
separate licenses of the WebAssembly decoders, the substitute fonts and the ICC profile it ships -
all of them in assets/js/pdf/LICENSE.txt and beside the files they cover.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Tesserae (>= 2026.9.70414)
- Transpose.BCL (>= 26.9.4872)
- Transpose.Core (>= 26.9.4869)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 26.9.4916 | 88 | 9/9/2026 |
| 26.9.4915 | 84 | 9/9/2026 |
| 26.9.4912 | 88 | 9/9/2026 |
| 26.9.4908 | 85 | 9/9/2026 |
| 26.9.4901 | 89 | 9/9/2026 |
| 26.9.4900 | 95 | 9/9/2026 |
| 26.9.4886 | 91 | 9/9/2026 |
| 26.9.4885 | 91 | 9/9/2026 |
| 26.8.4634 | 103 | 8/28/2026 |
| 26.8.4623 | 86 | 8/28/2026 |
| 26.8.4622 | 91 | 8/28/2026 |
| 26.8.4620 | 99 | 8/28/2026 |
| 26.8.4525 | 103 | 8/24/2026 |
| 26.8.4524 | 105 | 8/24/2026 |
| 26.8.4360 | 96 | 8/21/2026 |
| 26.8.4359 | 95 | 8/21/2026 |
| 26.8.4319 | 117 | 8/21/2026 |