Skip to content

PDF

The pdf package renders PDF documents with pdf.js inside a Lattice-styled viewer: pages render onto canvases as you scroll, a text layer makes the document selectable and copyable, and the toolbar provides page navigation, zoom, full-text search with match highlighting, and a download action.

config()->set(
'pdf.worker_url',
'https://cdn.jsdelivr.net/npm/[email protected]/build/pdf.worker.min.mjs',
);
PdfViewer::make('manual')
->url('https://raw.githubusercontent.com/lattice-php/lattice/main/workbench/fixtures/sample.pdf')
->filename('sample.pdf')
->maxHeight(480);

The docs are a static site, so this preview uses a version-matched CDN worker and a CORS-enabled sample file. A Laravel application should omit the worker_url override and use the package’s built-in worker route described below.

Terminal window
composer require lattice-php/pdf

Composer is the only install. The lattice() Vite plugin discovers the package through Composer and adds only a small entry to the app bundle: the viewer shell loads from source, and the pdf.js engine ships as a precompiled renderer — with pdf.js bundled inside it, while React and the Lattice runtime remain shared with the host app — that loads on demand the first time a page renders a document, so pages without a PDF never pay for pdf.js. For a no-build app, run php artisan lattice:assets after installation; it publishes the same self-contained renderer as a single standalone plugin.

In an app with a build step, import the viewer stylesheet in your application CSS — it themes the toolbar and pages with the Lattice design tokens and positions the transparent text layer over each canvas:

@import "@lattice-php/pdf/css";
use Lattice\Pdf\Components\PdfViewer;
PdfViewer::make('manual')
->url(fn () => $product->firstMediaUrl('manuals'))
->filename('manual.pdf')
->height(720);

->url() accepts a string or a closure resolved at serialization time — the natural place to produce a signed or temporary URL on every render instead of storing one. The browser fetches the document directly from that URL, so it must be reachable from the client and same-origin or CORS-enabled.

->filename() names the file the download button saves; without it the browser derives a name from the URL. Individual toolbar sections can be turned off, and the initial zoom is configurable:

PdfViewer::make('terms')
->url('/documents/terms.pdf')
->searchable(false)
->downloadable(false)
->zoom(1.5);

Without an explicit ->zoom() the document fits the viewer’s width and keeps tracking it as the container resizes; ->fitWidth() restores that behavior explicitly. Zoom values cover 0.25 to 4.0. The viewer virtualizes rendering — only pages near the viewport hold a live canvas — so long documents stay responsive.

->height() fixes the viewer’s height; ->maxHeight() lets it grow with the document instead and only caps it — a single A4 page then shows without dead space below, while longer documents scroll inside the cap:

PdfViewer::make('manual')
->url('/documents/manual.pdf')
->maxHeight(900);

Link annotations in the document are interactive: external URI links open in a new tab, and internal links (a table of contents, cross references) scroll their target page into view inside the viewer.

The toolbar’s leftmost button opens a sidebar with page thumbnails — click one to jump to its page. When the document carries embedded file attachments, the sidebar gains an Attachments tab listing them for download. ->sidebar(false) removes the toggle, which suits compact embeds such as modals.

With lattice-php/media installed, ->media() sources the document straight from an attachment — the signed URL and the filename resolve freshly on every render:

PdfViewer::make('manual')->media($product->media('manuals')->first());

It accepts a media id or model instance. An explicit ->filename() wins over the media’s stored name.

The “view document” flow of a record table is a server action that ships a modal containing a viewer — the action already has the row’s media id, so it builds the Modal and opens it in the same response:

use Lattice\Ui\Components\Modal;
use Lattice\Ui\Enums\ModalWidth;
// In the row action's handle():
return ActionResult::success()->openModal(
Modal::make('invoice-modal')
->title('Invoice')
->width(ModalWidth::Xl)
->schema([
PdfViewer::make('invoice')
->media($invoiceMediaId)
->sidebar(false)
->height(560),
]),
);

See Modals for the trigger-embedded alternative, when the viewer’s content is already known at render time instead of resolved inside the action.

pdf.js parses documents in a Web Worker whose version must match the bundled library exactly. The package ships the matching worker script and serves it itself; the service provider registers a GET route (named lattice.pdf.worker) that streams the artifact with immutable cache headers, and the component puts that URL on the wire. There is nothing to publish or configure for the default setup.

In config/pdf.php:

return [
'middleware' => ['web'],
'asset_route' => 'lattice/pdf/worker.js',
'worker_url' => null,
'cmap_url' => null,
'standard_font_data_url' => null,
'wasm_url' => null,
];

worker_url overrides the package route entirely — point it at your CDN when you serve static assets from one. middleware and asset_route reshape the built-in route.

Typical PDFs with embedded fonts render out of the box. Three optional pdf.js asset sets cover the long tail, and each has a config passthrough that flows into the viewer:

  • cmap_url — CMap tables for CJK documents without embedded fonts.
  • standard_font_data_url — font data for documents relying on the standard 14 PDF fonts.
  • wasm_url — wasm codecs for JPEG 2000 images and ICC color profiles.

The package does not bundle these (they total several megabytes); host the matching directories from the pdfjs-dist npm package yourself or on a CDN and point the config at them. The versions must match the pinned pdfjs-dist release in the package.

The report measures the precompiled pdf plugin. It includes pdf.js but externalizes React and the framework runtime, so it represents the package’s additional JavaScript — loaded only when a page actually renders a document. The worker script (~1.2 MB) loads separately through the asset route with immutable caching.

145.8 KB gzipped · 566.7 KB raw JavaScript

DependencyRawGzipShare
@lattice-php/pdf21.5 KB8.6 KB5.9%
pdfjs-dist488.2 KB135.4 KB92.8%
Bundler runtime57.1 KB1.9 KB1.3%

Emitted JavaScript files

FileRawGzip
plugin.js566.7 KB145.8 KB

Open the full interactive treemap ↗ · Generated by Sonda 0.14.0 on Sat, 22 Aug 2026 17:50:37 GMT.