pdf-block / laravel
Server-side PDF renderer for pdf-block DSL documents. Converts JSON DSL to native PDF via a local Chromium binary or a remote Browserless (v2) service.
Requires
- php: ^8.2
- ext-curl: *
- ext-json: *
- illuminate/support: ^11.0|^12.0
- illuminate/view: ^11.0|^12.0
- symfony/process: ^6.0|^7.0
- ueberdosis/tiptap-php: ^1.0|^2.0
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0
- phpunit/phpunit: ^11.0
README
Pacote Laravel para renderizar documentos da DSL do pdf-block em PDF nativo. Texto selecionável, links clicáveis, imagens embutidas — PDF de verdade, não screenshot.
Suporta dois drivers de renderização intercambiáveis:
| Driver | Como funciona | Quando usar |
|---|---|---|
local |
Chromium CLI no mesmo container que o PHP, via symfony/process |
Desenvolvimento e ambientes controlados |
browserless |
HTTP POST a um serviço Browserless v2 (auto-hospedado ou gerenciado) via libcurl nativo | Produção, escalabilidade horizontal, separação de responsabilidades |
Instalação
composer require pdf-block/laravel
O service provider é registrado automaticamente via auto-discovery do Laravel.
Publicar config (opcional)
php artisan vendor:publish --tag=pdf-block-config
Cria config/pdf-block.php no projeto com todas as opções disponíveis.
Publicar views (opcional)
php artisan vendor:publish --tag=pdf-block-views
Permite customizar os Blade templates em resources/views/vendor/pdf-block/.
Uso
Injeção de dependência
use PdfBlock\Laravel\PdfBlockRenderer; class ExportController extends Controller { public function pdf(Request $request, PdfBlockRenderer $renderer) { $document = $request->input('document'); return $renderer->toPdf($document)->toResponse('documento.pdf'); } }
API pública
$renderer = app(PdfBlockRenderer::class); // DSL → HTML (útil para debug, e-mail, preview) $html = $renderer->toHtml($document); // DSL → PDF $pdf = $renderer->toPdf($document); // Download (Content-Disposition: attachment) return $pdf->toResponse('fatura.pdf'); // Preview inline no browser (Content-Disposition: inline) return $pdf->toInlineResponse('preview.pdf'); // Salvar em disco $pdf->save(storage_path('exports/fatura.pdf')); // Conteúdo binário raw $binary = $pdf->content(); // Tamanho em bytes $bytes = $pdf->size();
Configuração
// config/pdf-block.php return [ // Driver ativo: 'local' ou 'browserless' 'driver' => env('PDF_BLOCK_DRIVER', 'local'), 'drivers' => [ 'local' => [ 'chrome_path' => env('PDF_BLOCK_CHROME_PATH', '/usr/bin/chromium'), 'timeout' => (int) env('PDF_BLOCK_TIMEOUT', 30), 'chrome_args' => [ /* defaults otimizados — ver config/pdf-block.php */ ], ], 'browserless' => [ 'url' => env('PDF_BLOCK_BROWSERLESS_URL', 'http://browserless:3000'), 'token' => env('PDF_BLOCK_BROWSERLESS_TOKEN'), 'timeout' => (int) env('PDF_BLOCK_BROWSERLESS_TIMEOUT', 30), 'wait_until' => env('PDF_BLOCK_BROWSERLESS_WAIT_UNTIL', 'load'), // Bloqueio de recursos no nível do Puppeteer — cancela requests // ANTES de ir pra rede. Ganho de 200–800ms por PDF. 'reject_resource_types' => ['media', 'websocket', 'eventsource', 'manifest', 'texttrack', 'other'], 'reject_request_pattern' => ['google-analytics\\.com', 'googletagmanager\\.com', /* ... */], ], ], 'fonts' => [ 'local_fonts' => [ /* 20 famílias corporativas — ver config/pdf-block.php */ ], ], ];
Variáveis de ambiente
Driver local:
PDF_BLOCK_DRIVER=local PDF_BLOCK_CHROME_PATH=/usr/bin/chromium PDF_BLOCK_TIMEOUT=30
Driver browserless:
PDF_BLOCK_DRIVER=browserless PDF_BLOCK_BROWSERLESS_URL=http://browserless:3000 PDF_BLOCK_BROWSERLESS_TOKEN= PDF_BLOCK_BROWSERLESS_TIMEOUT=30 PDF_BLOCK_BROWSERLESS_WAIT_UNTIL=load
Driver local
O driver padrão. Invoca o binário do Chromium diretamente via symfony/process com a flag --print-to-pdf. Não requer Node.js, Puppeteer ou Browsershot.
Pré-requisitos
Chromium instalado no servidor ou container:
# Debian/Ubuntu sudo apt install chromium fonts-noto-color-emoji # Alpine (Docker) apk add chromium font-noto-emoji
fonts-noto-color-emoji: necessário para renderizar emojis. Sem ela, emojis aparecem como quadrados vazios no PDF.
Dockerfile (desenvolvimento)
FROM php:8.2-cli-bookworm RUN apt-get update && apt-get install -y --no-install-recommends \ chromium \ fonts-noto-color-emoji \ libzip-dev unzip git \ && docker-php-ext-install zip \ && rm -rf /var/lib/apt/lists/* COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
Produção com www-data (PHP-FPM)
O chrome_crashpad_handler usa ptrace(PTRACE_ATTACH) no processo pai do Chromium. Quando o PHP roda como www-data sem CAP_SYS_PTRACE, o kernel nega essa chamada e o Chromium recebe SIGTRAP.
Os flags --disable-crash-reporter e --disable-breakpad (já incluídos nos defaults) reduzem a frequência desse problema, mas não o eliminam em todos os builds do Chromium no Debian/Ubuntu.
Solução definitiva via setcap (requer libcap2-bin):
RUN apt-get install -y libcap2-bin \
&& setcap cap_sys_ptrace+ep /usr/lib/chromium/chrome_crashpad_handler
Alternativa recomendada para produção: use o driver browserless, onde o Chromium roda isolado em um container próprio — o problema de permissão não existe nesse contexto.
Driver browserless
Envia o HTML via POST /pdf para qualquer serviço compatível com a API Browserless v2. Elimina o Chromium do container PHP por completo.
O driver é implementado sobre libcurl nativo (ext-curl) — sem Guzzle. Motivos:
- Roteamento confiável. libcurl emite sempre request-line em origin-form (
POST /pdf HTTP/1.1), que é o único formato aceito pelo router do Browserless v2. Clientes de alto nível podem serializar em absolute-form para hosts sem TLD (ex.:browserlessem rede Docker), resultando em HTTP 404 "No matching HTTP route handler". - Menor overhead. Sem wrappers PSR-7 / streams / middlewares — ~5 ms a menos por request e pegada de memória menor (útil em containers com
mem_limitbaixo). - Menos superfície de erro. Zero dependência transitiva.
O HTML gerado pelo Blade já carrega toda a lógica de dimensionamento: um script injeta @page { size: Wmm Hmm; margin: 0 } via beforeprint, medindo o scrollHeight real após fontes e imagens carregadas. O driver envia preferCSSPageSize: true para que o Puppeteer obedeça esse @page — tamanho, margens e orientação vêm direto da DSL via CSS, sem duplicação no JSON da requisição.
Otimizações ativas por padrão
| Otimização | Efeito |
|---|---|
waitUntil: 'load' |
Fontes são 100% locais (fontconfig), imagens já são aguardadas pelo load. Usar networkidle0 só é necessário se o template dispara XHR/fetch após o load. |
preferCSSPageSize: true |
Tamanho do papel lido do @page do HTML — evita duplicar config na request. |
rejectResourceTypes |
Cancela media, websocket, eventsource, manifest, texttrack, other antes da rede. |
rejectRequestPattern |
Bloqueia trackers conhecidos (Google Analytics, GTM, DoubleClick, Facebook Pixel, Hotjar, Mixpanel, Segment, FullStory, Clarity). |
CURLOPT_TCP_NODELAY |
Desliga Nagle — request chega ao Browserless sem o delay de 40 ms. |
Expect: header removido |
Evita handshake 100-continue que alguns proxies travam. |
| HTTP/1.1 forçado | Garante request-line em origin-form compatível com o router do Browserless. |
Docker Compose (desenvolvimento)
services: api: build: . env_file: .env depends_on: - browserless mem_limit: 64m browserless: image: ghcr.io/uotz/pdf-block-browserless:latest ports: - "3001:3000" environment: TOKEN: "" TIMEOUT: 30000 CONCURRENT: "1" PREBOOT_CHROME: "false" ENABLE_DEBUGGER: "false" ENABLE_API_GET: "false" DEFAULT_BLOCK_ADS: "false" DEFAULT_STEALTH: "false" DEBUG: "" NODE_OPTIONS: "--max-old-space-size=96" mem_limit: 336m
# .env PDF_BLOCK_DRIVER=browserless PDF_BLOCK_BROWSERLESS_URL=http://browserless:3000
Budget de memória: com esse stack o pico observado é
api ~50 MB+browserless ~150 MBpor PDF de 1–5 páginas. Cabe confortavelmente em 400 MB totais.
DEFAULT_BLOCK_ADS=falseé intencional. O ad-blocker embutido do Browserless baixa listas de filtros em background e pode travar osetContent. O bloqueio de trackers é feito no nível do driver viareject_request_pattern— mais previsível e sem dependência de DNS externo.
Serviço gerenciado (browserless.io)
PDF_BLOCK_DRIVER=browserless PDF_BLOCK_BROWSERLESS_URL=https://production-sfo.browserless.io PDF_BLOCK_BROWSERLESS_TOKEN=seu-token PDF_BLOCK_BROWSERLESS_TIMEOUT=30
Driver customizado
Implemente a interface PdfBlock\Laravel\Contracts\PdfDriver e registre o binding no seu service provider:
use PdfBlock\Laravel\Contracts\PdfDriver; // AppServiceProvider::register() $this->app->bind(PdfDriver::class, MyCustomDriver::class);
Fontes locais
O pacote opera com fontes 100% locais — zero tráfego para fonts.googleapis.com em runtime. A imagem Docker do Browserless (ghcr.io/uotz/pdf-block-browserless:latest) já inclui as 20 famílias curadas abaixo, instaladas no fontconfig do sistema:
Sans-serif: Inter, Roboto, Open Sans, Lato, Source Sans 3, Noto Sans, Work Sans, DM Sans, Montserrat Serif: Spectral (padrão), Merriweather, Lora, Source Serif 4, PT Serif, EB Garamond, Libre Baskerville, Playfair Display, Noto Serif Display: Oswald Mono: Roboto Mono
A lista vive em config/pdf-block.php → fonts.local_fonts e está sincronizada com o FontPicker do pacote React.
Para adicionar uma família: atualize docker/install-fonts.sh (baixa os arquivos no build), config/pdf-block.php → local_fonts e o FontPicker do React. O workflow publish-browserless.yml detecta mudanças em install-fonts.sh e rebuilda automaticamente a imagem.
Imagem Docker pronta
A imagem ghcr.io/uotz/pdf-block-browserless é a maneira recomendada de rodar o driver browserless. Ela estende ghcr.io/browserless/chromium:latest com:
- Noto Color Emoji como família de emoji (mesmo visual do driver local).
- As 20 fontes corporativas instaladas no fontconfig.
- Sem alteração de entrypoint — todas as variáveis de ambiente do Browserless v2 continuam funcionando.
Tags disponíveis:
| Tag | Uso |
|---|---|
latest |
Última build da branch main |
vX.Y.Z |
Vinculada ao release correspondente do pacote Composer |
vX.Y |
Major.minor (atualiza em novos patches) |
Veja docs/image-build.md para detalhes do pipeline de build e como gerar uma nova versão da imagem.
Diagnóstico
O sandbox inclui dois endpoints para validar a configuração de cada driver sem gerar um PDF completo:
Verificar driver local
GET /api/diagnostics/local
Executa chromium --version e retorna:
{
"driver": "local",
"status": "ok",
"chrome_path": "/usr/bin/chromium",
"version": "Chromium 124.0.6367.82 built on Debian 12.5, running on Debian 12.5"
}
Verificar driver browserless
GET /api/diagnostics/browserless
Faz um request de health check ao serviço e retorna:
{
"driver": "browserless",
"status": "ok",
"url": "http://browserless:3000",
"response_ms": 42,
"service": { "Browser": "Chrome/124...", "Protocol": "1.3" }
}
Em caso de erro, ambos os endpoints retornam HTTP 500/502 com "status": "error" e uma "message" descritiva.
Arquitetura
DSL JSON (do editor React)
│
▼
PdfBlockRenderer::toHtml()
│ Blade templates renderizam cada bloco
│ com inline styles idênticos ao editor
▼
HTML string standalone
│
├─── driver: local ──────────────────────┐
│ LocalDriver::render() │
│ Chromium CLI --print-to-pdf │
│ (symfony/process) │
│ │
└─── driver: browserless ────────────────┤
BrowserlessDriver::render() │
POST {url}/pdf (libcurl nativo) │
HTTP/1.1 origin-form · TCP_NODELAY │
▼
PdfResult (PDF nativo)
Paridade visual
Os Blade templates replicam 1:1 os renderers React. Ambos usam 100% inline styles via as mesmas funções CSS helper (portadas de TypeScript para PHP em StyleHelpers.php).
DSL — Formato do documento
O pacote espera receber a mesma DSL JSON produzida pelo editor @pdf-block/react:
{
"id": "uuid",
"version": "2.0.0",
"meta": { "title": "Meu documento", "locale": "pt-BR", "tags": [] },
"pageSettings": {
"paperSize": { "preset": "a4", "width": 210, "height": 297 },
"orientation": "portrait",
"margins": { "top": 20, "right": 20, "bottom": 20, "left": 20 },
"defaultFontFamily": "Spectral, serif"
},
"globalStyles": {
"pageBackground": "#ffffff",
"contentBackground": "#ffffff",
"defaultFontColor": "#333333"
},
"blocks": [...]
}
Blocos suportados
| Bloco | Descrição |
|---|---|
text |
Rich text via TipTap JSON → HTML (ueberdosis/tiptap-php) |
image |
Imagem com alignment, objectFit, bordas |
button |
Link estilizado como botão (clicável no PDF) |
divider |
Linha horizontal |
spacer |
Espaço vertical |
banner |
Imagem de fundo com overlay e textos |
table |
Tabela com header, zebra, bordas |
qrcode |
QR code |
chart |
Gráfico de barras (CSS puro) |
pagebreak |
Quebra de página forçada |
Customização de templates
Após publicar as views (--tag=pdf-block-views), edite os templates em resources/views/vendor/pdf-block/:
resources/views/vendor/pdf-block/
├── document.blade.php # Layout principal
├── stripe.blade.php # Stripe (banda horizontal)
├── structure.blade.php # Structure (row de colunas)
├── block.blade.php # Router de blocos
└── blocks/
├── text.blade.php
├── image.blade.php
├── button.blade.php
├── divider.blade.php
├── spacer.blade.php
├── banner.blade.php
├── table.blade.php
├── qrcode.blade.php
├── chart.blade.php
└── pagebreak.blade.php
Integração com o editor React
Endpoint Laravel
// routes/api.php Route::post('/export/pdf', function (Request $request, PdfBlockRenderer $renderer) { $document = $request->validate([ 'document' => 'required|array', 'document.blocks' => 'required|array', 'document.pageSettings' => 'required|array', 'document.globalStyles' => 'required|array', ])['document']; return $renderer->toPdf($document)->toResponse('documento.pdf'); });
No React
const editorRef = useRef<PDFBuilderRef>(null); const exportPdf = async () => { const doc = editorRef.current?.getDocument(); if (!doc) return; const res = await fetch('/api/export/pdf', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ document: doc }), }); const blob = await res.blob(); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'documento.pdf'; a.click(); };
Requisitos
- PHP ≥ 8.2
- Extensões PHP:
ext-curl,ext-json - Laravel 11.x ou 12.x
- Driver local: Chromium instalado no servidor
- Driver browserless: Serviço Browserless v2 acessível via HTTP
Licença
MIT
pdf-block/laravel
Pacote Laravel para renderizar documentos da DSL do pdf-block em PDF nativo. Texto selecionável, links clicáveis, imagens embutidas — PDF de verdade, não screenshot.
Suporta dois drivers de renderização intercambiáveis:
| Driver | Como funciona | Quando usar |
|---|---|---|
local |
Chromium CLI no mesmo container que o PHP | Desenvolvimento e ambientes controlados |
browserless |
HTTP POST a um serviço Browserless v1 (auto-hospedado ou gerenciado) | Produção, servidores com www-data sem CAP_SYS_PTRACE |
Instalação
composer require pdf-block/laravel
O service provider é registrado automaticamente via auto-discovery do Laravel.
Publicar config (opcional)
php artisan vendor:publish --tag=pdf-block-config
Cria config/pdf-block.php no projeto com todas as opções disponíveis.
Publicar views (opcional)
php artisan vendor:publish --tag=pdf-block-views
Permite customizar os Blade templates em resources/views/vendor/pdf-block/.
Uso
Injeção de dependência
use PdfBlock\Laravel\PdfBlockRenderer; class ExportController extends Controller { public function pdf(Request $request, PdfBlockRenderer $renderer) { $document = $request->input('document'); return $renderer->toPdf($document)->toResponse('documento.pdf'); } }
API pública
$renderer = app(PdfBlockRenderer::class); // DSL → HTML (útil para debug, e-mail, preview) $html = $renderer->toHtml($document); // DSL → PDF $pdf = $renderer->toPdf($document); // Download (Content-Disposition: attachment) return $pdf->toResponse('fatura.pdf'); // Preview inline no browser (Content-Disposition: inline) return $pdf->toInlineResponse('preview.pdf'); // Salvar em disco $pdf->save(storage_path('exports/fatura.pdf')); // Conteúdo binário raw $binary = $pdf->content(); // Tamanho em bytes $bytes = $pdf->size();
Configuração
// config/pdf-block.php return [ // Driver ativo: 'local' ou 'browserless' 'driver' => env('PDF_BLOCK_DRIVER', 'local'), 'drivers' => [ 'local' => [ 'chrome_path' => env('PDF_BLOCK_CHROME_PATH', '/usr/bin/chromium'), 'timeout' => (int) env('PDF_BLOCK_TIMEOUT', 30), 'chrome_args' => [ '--headless', '--ozone-platform=headless', '--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage', '--run-all-compositor-stages-before-draw', '--disable-crash-reporter', '--disable-breakpad', ], ], 'browserless' => [ 'url' => env('PDF_BLOCK_BROWSERLESS_URL', 'http://browserless:3000'), 'timeout' => (int) env('PDF_BLOCK_BROWSERLESS_TIMEOUT', 30), ], ], ];
Variáveis de ambiente
Driver local:
PDF_BLOCK_DRIVER=local PDF_BLOCK_CHROME_PATH=/usr/bin/chromium PDF_BLOCK_TIMEOUT=30
Driver browserless:
PDF_BLOCK_DRIVER=browserless PDF_BLOCK_BROWSERLESS_URL=http://browserless:3000 PDF_BLOCK_BROWSERLESS_TIMEOUT=30
Driver local
O driver padrão. Invoca o binário do Chromium diretamente via symfony/process com a flag --print-to-pdf. Não requer Node.js, Puppeteer ou Browsershot.
Pré-requisitos
Chromium instalado no servidor ou container:
# Debian/Ubuntu sudo apt install chromium fonts-noto-color-emoji # Alpine (Docker) apk add chromium font-noto-emoji
fonts-noto-color-emoji: necessário para renderizar emojis. Sem ela, emojis aparecem como quadrados vazios no PDF.
Dockerfile (desenvolvimento)
FROM php:8.2-cli-bookworm RUN apt-get update && apt-get install -y --no-install-recommends \ chromium \ fonts-noto-color-emoji \ libzip-dev unzip git \ && docker-php-ext-install zip \ && rm -rf /var/lib/apt/lists/* COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
Produção com www-data (PHP-FPM)
O chrome_crashpad_handler usa ptrace(PTRACE_ATTACH) no processo pai do Chromium. Quando o PHP roda como www-data sem CAP_SYS_PTRACE, o kernel nega essa chamada e o Chromium recebe SIGTRAP.
Os flags --disable-crash-reporter e --disable-breakpad (já incluídos nos defaults) reduzem a frequência desse problema, mas não o eliminam em todos os builds do Chromium no Debian/Ubuntu.
Solução definitiva via setcap (requer libcap2-bin):
RUN apt-get install -y libcap2-bin \
&& setcap cap_sys_ptrace+ep /usr/lib/chromium/chrome_crashpad_handler
Alternativa recomendada para produção: use o driver browserless, onde o Chromium roda como root em container isolado — o problema de permissão não existe nesse contexto.
Driver browserless
Envia o HTML via POST /pdf para qualquer serviço compatível com a API Browserless v1. Elimina o Chromium do container PHP por completo.
O HTML gerado pelo Blade já carrega toda a lógica de dimensionamento: um script injeta @page { size: Wmm Hmm; margin: 0 } via beforeprint, medindo o scrollHeight real após fontes e imagens carregadas. O driver envia preferCSSPageSize: true para que o Puppeteer obedeça esse @page — tamanho, margens e orientação vêm diretamente da DSL via CSS, sem duplicação no JSON da requisição.
Docker Compose (desenvolvimento)
# docker-compose.yml services: api: build: . env_file: .env depends_on: - browserless browserless: image: ghcr.io/browserless/chromium:latest ports: - "3000:3000" environment: TOKEN: "" # sem autenticação (desenvolvimento) TIMEOUT: 30000 CONCURRENT: 5
# .env PDF_BLOCK_DRIVER=browserless PDF_BLOCK_BROWSERLESS_URL=http://browserless:3000
Serviço gerenciado (browserless.io)
PDF_BLOCK_DRIVER=browserless PDF_BLOCK_BROWSERLESS_URL=https://chrome.browserless.io PDF_BLOCK_BROWSERLESS_TIMEOUT=30
Driver customizado
Implemente a interface PdfBlock\Laravel\Contracts\PdfDriver e registre o binding no seu service provider:
use PdfBlock\Laravel\Contracts\PdfDriver; // AppServiceProvider::register() $this->app->bind(PdfDriver::class, MyCustomDriver::class);
Diagnóstico
O sandbox inclui dois endpoints para validar a configuração de cada driver sem gerar um PDF completo:
Verificar driver local
GET /api/diagnostics/local
Executa chromium --version e retorna:
{
"driver": "local",
"status": "ok",
"chrome_path": "/usr/bin/chromium",
"version": "Chromium 124.0.6367.82 built on Debian 12.5, running on Debian 12.5"
}
Verificar driver browserless
GET /api/diagnostics/browserless
Faz um request de health check ao serviço e retorna:
{
"driver": "browserless",
"status": "ok",
"url": "http://browserless:3000",
"response_ms": 42,
"service": { "Browser": "Chrome/124...", "Protocol": "1.3" }
}
Em caso de erro, ambos os endpoints retornam HTTP 500/502 com "status": "error" e uma "message" descritiva.
Arquitetura
DSL JSON (do editor React)
│
▼
PdfBlockRenderer::toHtml()
│ Blade templates renderizam cada bloco
│ com inline styles idênticos ao editor
▼
HTML string standalone
│
├─── driver: local ──────────────────────┐
│ LocalDriver::render() │
│ Chromium CLI --print-to-pdf │
│ (symfony/process) │
│ │
└─── driver: browserless ────────────────┤
BrowserlessDriver::render() │
POST {url}/pdf com opções Puppeteer │
(illuminate/http) │
▼
PdfResult (PDF nativo)
Paridade visual
Os Blade templates replicam 1:1 os renderers React. Ambos usam 100% inline styles via as mesmas funções CSS helper (portadas de TypeScript para PHP em StyleHelpers.php).
DSL — Formato do documento
O pacote espera receber a mesma DSL JSON produzida pelo editor @pdf-block/react:
{
"id": "uuid",
"version": "2.0.0",
"meta": { "title": "Meu documento", "locale": "pt-BR", "tags": [] },
"pageSettings": {
"paperSize": { "preset": "a4", "width": 210, "height": 297 },
"orientation": "portrait",
"margins": { "top": 20, "right": 20, "bottom": 20, "left": 20 },
"defaultFontFamily": "Inter, sans-serif"
},
"globalStyles": {
"pageBackground": "#ffffff",
"contentBackground": "#ffffff",
"defaultFontColor": "#333333"
},
"blocks": [...]
}
Blocos suportados
| Bloco | Descrição |
|---|---|
text |
Rich text via TipTap JSON → HTML (ueberdosis/tiptap-php) |
image |
Imagem com alignment, objectFit, bordas |
button |
Link estilizado como botão (clicável no PDF) |
divider |
Linha horizontal |
spacer |
Espaço vertical |
banner |
Imagem de fundo com overlay e textos |
table |
Tabela com header, zebra, bordas |
qrcode |
QR code |
chart |
Gráfico de barras (CSS puro) |
pagebreak |
Quebra de página forçada |
Customização de templates
Após publicar as views (--tag=pdf-block-views), edite os templates em resources/views/vendor/pdf-block/:
resources/views/vendor/pdf-block/
├── document.blade.php # Layout principal
├── stripe.blade.php # Stripe (banda horizontal)
├── structure.blade.php # Structure (row de colunas)
├── block.blade.php # Router de blocos
└── blocks/
├── text.blade.php
├── image.blade.php
├── button.blade.php
├── divider.blade.php
├── spacer.blade.php
├── banner.blade.php
├── table.blade.php
├── qrcode.blade.php
├── chart.blade.php
└── pagebreak.blade.php
Integração com o editor React
Endpoint Laravel
// routes/api.php Route::post('/export/pdf', function (Request $request, PdfBlockRenderer $renderer) { $document = $request->validate([ 'document' => 'required|array', 'document.blocks' => 'required|array', 'document.pageSettings' => 'required|array', 'document.globalStyles' => 'required|array', ])['document']; return $renderer->toPdf($document)->toResponse('documento.pdf'); });
No React
const editorRef = useRef<PDFBuilderRef>(null); const exportPdf = async () => { const doc = editorRef.current?.getDocument(); if (!doc) return; const res = await fetch('/api/export/pdf', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ document: doc }), }); const blob = await res.blob(); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'documento.pdf'; a.click(); };
Migração da config anterior
Se você usava uma versão anterior do pacote, a estrutura de configuração foi alterada. Re-publique o arquivo de config e ajuste suas variáveis de ambiente:
Antes (configuração plana):
return [ 'chrome_path' => env('PDF_BLOCK_CHROME_PATH', '/usr/bin/chromium'), 'timeout' => (int) env('PDF_BLOCK_TIMEOUT', 30), 'chrome_args' => [...], ];
Depois (configuração por driver):
return [ 'driver' => env('PDF_BLOCK_DRIVER', 'local'), 'drivers' => [ 'local' => [ 'chrome_path' => env('PDF_BLOCK_CHROME_PATH', '/usr/bin/chromium'), 'timeout' => (int) env('PDF_BLOCK_TIMEOUT', 30), 'chrome_args' => [...], ], 'browserless' => [...], ], ];
As variáveis PDF_BLOCK_CHROME_PATH e PDF_BLOCK_TIMEOUT continuam funcionando — apenas foram movidas para dentro de drivers.local.
# Após atualizar o pacote:
php artisan vendor:publish --tag=pdf-block-config --force
Requisitos
- PHP ≥ 8.2
- Laravel ≥ 11.0
- Driver local: Chromium instalado no servidor
- Driver browserless: Serviço Browserless v1 acessível via HTTP
Licença
MIT