# PDF Viewer
Standalone PDF viewer built on Mozilla's **pdf.js** library (loaded through the SGApps `thirdparty/convert/pdf/reader` module from `application-prototype-thirdparty`). Opens a PDF from any URL (public or SGApps `fs://`) and renders each page onto its own canvas at device-pixel-ratio resolution, with a simple toolbar for zoom and fit-to-width.
Internally this is the **Code Editor** webapp launched with a chrome-less preset: no sidebar, no top toolbars, no tab bar, light theme, and the PDF tab type pre-selected. Each page renders eagerly on open; large documents may take a moment to finish.
## Live Demo
## Embed
```html
```
### Open with a PDF URL
Encode the PDF URL in base64 and append it as `/url/`:
```js
var pdfUrl = "https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf";
var src = "https://sgapps.io/online/webapp/pdf-browser/url/" + btoa(pdfUrl);
// → opens that PDF in the viewer on first load
```
## API Events
PDF Viewer inherits the full Code Editor API surface — the active tab is a PDF-type tab, so the Monaco-specific calls (`active-file:codeEditor:value`, `:syntax`, etc.) return "No active editor" for a pdf tab. The following work:
### `files:open` -- Open a PDF in a New Tab
```js
socket.fire("webapp::instance::request", "files:open",
"/files/my-document.pdf", "pdf",
function (err, tabId) {
if (err) console.error(err);
else console.log("Opened as tab", tabId);
}
);
```
Equivalent object form:
```js
socket.fire("webapp::instance::request", "files:open",
{ type: "pdf", path: "/files/my-document.pdf" },
function (err, tabId) { /* ... */ }
);
```
### `files:list` -- List Open Tabs
```js
socket.fire("webapp::instance::request", "files:list",
function (err, tabs) {
console.log(tabs);
// [{ index, name, path, type: "pdf", title }, ...]
}
);
```
### `files:active:set` -- Switch Active Tab
```js
socket.fire("webapp::instance::request", "files:active:set", 1,
function (err) { /* ... */ });
```
### `files:close:all` -- Close All Tabs
```js
socket.fire("webapp::instance::request", "files:close:all",
function () { console.log("All tabs closed"); });
```
### `pdf:sidebar` -- Show / Hide the Page Thumbnails
The sidebar lists a thumbnail per page (drawn when it scrolls into view); a click goes to that page and the current page is highlighted. It is shown by default when the viewer is at least 600 px wide. Pass `true` / `false` to show / hide it, or only a callback to read its state. Answers `"The active tab is not a PDF"` when another kind of tab is active.
| Arg | Type | Description |
|-----|------|-------------|
| `show` | boolean (optional) | `true` = show, `false` = hide |
| `callback` | function | `(err, shown: boolean)` |
```js
socket.fire("webapp::instance::request", "pdf:sidebar", false,
function (err, shown) { console.log("thumbnails shown:", shown); });
```
### `fs:write` -- Write a PDF to SGApps fs
Combine with `files:open` to open an in-memory PDF without a public URL:
```js
// `bytes` must be ArrayBuffer -- WindowSocket's encoder doesn't carry Blob/File.
fetch("local-doc.pdf").then(function (r) { return r.arrayBuffer(); }).then(function (bytes) {
socket.fire("webapp::instance::request", "fs:write",
"input://memory/doc.pdf", bytes,
function (err) {
if (err) return console.error(err);
socket.fire("webapp::instance::request", "files:open",
"input://memory/doc.pdf", "pdf");
}
);
});
```
See [Code Editor](/apps/code-editor.md) for the full `fs:*` API, and [Embedding](/embedding.md) for the ArrayBuffer caveat.
## Window Chrome Controls
Hide the chrome entirely for a fullscreen viewer:
```js
socket.fire("webapp::instance::embed-mode", true);
```
Or granular (see [Embedding](/embedding.md)):
```js
socket.fire("webapp::instance::window:controls", false);
socket.fire("webapp::instance::window:maximize", true);
```
## Supported Mime Types
Registered for: `application/pdf`, `application/x-pdf`, `application/acrobat`, `applications/vnd.pdf`, `text/pdf`, `text/x-pdf`.
Opens from the SGApps window manager when a PDF is double-clicked in File Manager.
## Features
- Canvas-based pdfjs render (device-pixel-ratio aware, re-fits on container resize)
- Toolbar: page thumbnails on / off, current page (`Page 3 / 14`), zoom in / zoom out / fit-width, Download
- Sidebar with page thumbnails: click to go to a page, the current page is highlighted while scrolling (`pdf:sidebar` to show / hide it)
- Download saves the PDF under its own file name, from the copy already loaded (no second request, so PDFs from other sites work too)
- Opens from any URL (public web or SGApps `fs://` paths through `fs.readStreamUrl`)
- Multiple PDFs in parallel tabs (same code-editor tab bar — hidden by default, re-enable by removing `--no-files`)
- Launch via URL argument: `/online/webapp/pdf-browser/url/`
- Full inherited `files:*` and `fs:*` API from Code Editor
- pdfjsLib loaded via `thirdparty/convert/pdf/reader` (version-pinned with the rest of the SGApps third-party stack, no raw CDN pulls)