PatoLab generates signed pathology report PDFs by rendering a Blade HTML view through a headless Chromium browser using Spatie Browsershot. When a pathologist finalizes a specimen report, the system automatically produces a PDF at letter size (215.9 mm × 279.4 mm), embeds pathologist signatures as inline Base64 images, converts all report images to inline data URIs so Chromium can render them without network requests, and stores the result on the public storage disk. The same pipeline powers the in-editor PDF preview feature, which generates a temporary PDF without altering the stored finalized file.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/lerichardv/patolab-platform/llms.txt
Use this file to discover all available pages before exploring further.
Dependencies
| Dependency | Layer | Purpose |
|---|---|---|
spatie/browsershot | PHP (Composer) | Puppeteer-based HTML → PDF bridge |
| Chromium or Google Chrome | OS binary | Headless browser that renders the HTML |
| Node.js + npm | OS binary | Required by Browsershot’s Puppeteer bridge |
Installing Chromium
Browsershot requires a Chromium-compatible binary accessible on the server. The recommended approach for Ubuntu/Debian:Environment Variables
The following variables configure the paths Browsershot uses to locate Node.js, npm, and Chrome. They are only applied in the production environment — in local development, Browsershot relies on system PATH discovery..env
ReportPdfService::generatePdfContent() via env() calls, applied conditionally:
app/Services/ReportPdfService.php
ReportPdfService
TheApp\Services\ReportPdfService class contains two public methods and a set of private image-processing helpers.
generateAndStoreReport(Specimen $specimen): string
The finalization method — called automatically when a specimen’s status transitions to finalized. It generates the PDF, replaces any previously stored report file, saves the new file on the public storage disk, updates the specimen_reports.report_file column, and cleans up the temporary preview directory.
app/Services/ReportPdfService.php
storage/app/public/reports/report_{sequence_code}_{timestamp}.pdf
Database: The path is stored in specimen_reports.report_file. When a user requests a download, ReportEditorController::downloadPdf() checks this column first and streams the stored file directly, avoiding a re-render.
generatePdfContent(Specimen $specimen, &$pages = null)
The inner rendering method, also called directly by generateTempPdf() during editor preview. It:
- Loads all required relations (
customerRelation,type,examination,category,referrerRelation,report,users.role) - Reads each pathologist’s signature file from the
publicstorage disk and converts it to a Base64 data URI embedded in$user->signature_base64 - Converts all
<img>srcattributes in every HTML report section to inline Base64 data URIs usingconvertImagesToBase64(), so Chromium never needs to make network requests during rendering - Calls
ReportPaginator::paginate()to compute the page layout - Renders the
pdf.report.bodyBlade view to an HTML string - Passes that HTML string to Browsershot and returns the raw PDF binary content
app/Services/ReportPdfService.php
| Argument | Reason |
|---|---|
disable-crash-reporter | Prevents crash reporter processes that cause hangs in server environments |
disable-dev-shm-usage | Avoids /dev/shm exhaustion in Docker and low-memory servers |
no-sandbox | Required for running as root or inside containers |
Pathologist Signatures
When rendering the PDF, each pathologist user assigned to the specimen is checked for auser_signature field. The service reads the signature image from the public storage disk and encodes it as a Base64 data URI before passing it to the Blade view:
app/Services/ReportPdfService.php
finalized status transition is rejected if any assigned pathologist lacks a signature).
Image Optimization Before Upload
When a pathologist uploads images into the report editor (viaReportEditorController::uploadImage()), they pass through App\Services\ImageOptimizerService before being stored. This service compresses images to stay under 300 KB using PHP’s GD extension, reducing PDF file sizes and preventing Browsershot timeouts.
The optimizer:
- Accepts JPEG, PNG, GIF, and WebP uploads
- Iteratively reduces scale (in 0.1 steps down to a minimum of 1000 px on the short edge) then JPEG quality (in steps of 10, minimum 10) until the file is under 300 KB
- Saves the result as a JPEG with a random 40-character filename under
public/report-images/{sequence_code}/
app/Services/ImageOptimizerService.php
storage/app/public/report-images/{sequence_code}/{random}.jpg and served at /storage/report-images/{sequence_code}/{random}.jpg. The convertImagesToBase64() method in ReportPdfService maps these public storage URLs back to their local filesystem paths for inline embedding.
Temporary PDF Preview
The report editor UI offers a preview PDF button that generates a temporary PDF without finalizing the specimen. This callsReportEditorController::generateTempPdf(), which:
- Calls
ReportPdfService::generatePdfContent()(same pipeline as finalization) - Cleans up any previous temp files:
Storage::disk('public')->deleteDirectory("temp_reports/{$specimen->sequence_code}") - Saves the new temp file at
temp_reports/{sequence_code}/report_{timestamp}.pdf - Returns a JSON response with the public URL and total page count
The
temp_reports/{sequence_code}/ directory is automatically deleted in two situations: when a new preview is generated (replaced with fresh content), and when generateAndStoreReport() finalizes the report. You do not need to clean up temp files manually. However, if a server restart or error occurs mid-preview, orphaned temp files may accumulate in storage/app/public/temp_reports/ — these can be safely deleted at any time.PDF Download Flow
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
Could not find chrome exception | Chrome binary not found at configured path | Verify BROWSERSHOT_CHROME_PATH and run which google-chrome-stable |
| PDF generation times out | Heavy report with many images | Increase ->timeout(120) or pre-compress images via ImageOptimizerService |
| Blank PDF / missing images | Images not embedded as Base64 | Ensure storage:link is run (php artisan storage:link) so local path resolution works |
error while loading shared libraries | Chromium missing system libs in Docker | Run apt-get install -y chromium-browser inside the container image |
| Node.js not found in PATH | PATH not propagated to PHP process | Set BROWSERSHOT_INCLUDE_PATH and BROWSERSHOT_NODE_BINARY explicitly |
