r2soft/docx-toolkit

Manipulação de DOCX por tags e exportação para DOCX/PDF (PHP puro).

v1.000.033 2025-08-29 21:16 UTC

README

Biblioteca PHP para preencher templates DOCX (variáveis, tabelas e blocos) e converter DOCX em PDF, usando PhpOffice/PhpWord por baixo dos panos. Foco em simplicidade, zero dependências de framework e compatível com Docker.

  • Preenchimento de variáveis simples (setValue)
  • Preenchimento de tabelas (cloneRowAndSetValues)
  • Preenchimento de blocos (cloneBlock com índices #1, #2, ...)
  • Suporte a textos ricos básicos via setComplexValue a partir de tags HTML: , , , ,
  • Conversão de DOCX para PDF com DomPDF, mPDF, TCPDF ou LibreOffice (alta fidelidade)
  • Aceita template como binário ou Base64

Requisitos

  • PHP >= 8.0
  • Extensão ext-zip habilitada
  • PhpOffice/phpword ^1.4
  • Para PDF via renderizadores PHP (opcional): dompdf/dompdf, mpdf/mpdf ou tecnickcom/tcpdf
  • Para PDF via LibreOffice (opcional): é necessário ter o LibreOffice instalado na máquina (binário soffice acessível no PATH ou informe o caminho completo)

Consulte composer.json para detalhes de dependências e sugestões.

Instalação

Via Composer (dentro do seu projeto):

composer require r2soft/docx-toolkit
# Para PDF (opcional escolha um):
composer require dompdf/dompdf
# ou
composer require mpdf/mpdf
# ou
composer require tecnickcom/tcpdf

Uso rápido

Exemplo mínimo (baseado em test.php):

<?php
require __DIR__ . '/vendor/autoload.php';

use R2Soft\Docx\DocxEngine;

$engine = new DocxEngine(pdfRenderer: 'DomPDF'); // ou 'mPDF', 'TCPDF' ou null

$templateBinary = file_get_contents('exemplo.docx');

$docx = $engine->renderDocx($templateBinary, [
    'nome' => 'Fulano',
]);
file_put_contents('saida.docx', $docx);

Converter para PDF (renderizadores PHP: DomPDF/mPDF/TCPDF)

Por padrão, toPdf() retorna um Data URL Base64 (data:application/pdf;base64,...). Para obter o binário diretamente, passe false no segundo parâmetro.

// Retorno como Data URL Base64 (padrão)
$pdfDataUrl = $engine->toPdf($docx); // $docx pode ser binário ou base64

// Se quiser gravar o PDF a partir do Data URL:
[$meta, $b64] = explode(',', $pdfDataUrl, 2);
file_put_contents('saida.pdf', base64_decode($b64));

// OU, mais simples: peça binário diretamente
$pdfBinary = $engine->toPdf($docx, false);
file_put_contents('saida.pdf', $pdfBinary);

Observações importantes:

  • Se você passar Base64 no renderDocx() ou toPdf(), a biblioteca detecta e converte automaticamente.
  • Para toPdf(), é obrigatório informar pdfRenderer no construtor e ter a dependência do motor instalada.

Converter para PDF com LibreOffice (alta fidelidade)

Em muitos casos, a melhor fidelidade de conversão de DOCX para PDF é obtida via LibreOffice. Este projeto expõe o método toPdfLibreOffice() na DocxEngine, que usa internamente R2Soft\Docx\Support\LibreOfficePdfConverter.

Requisitos obrigatórios:

  • Ter o LibreOffice instalado na máquina/contêiner.
  • O binário soffice deve estar acessível no PATH do sistema. Caso contrário, informe o caminho completo criando o conversor manualmente.

Uso básico:

use R2Soft\Docx\DocxEngine;

$engine = new DocxEngine(); // não precisa configurar pdfRenderer
$pdfBin = $engine->toPdfLibreOffice($docx, false); // $docx binário ou base64
file_put_contents('saida_libreoffice.pdf', $pdfBin);

Definindo um caminho customizado do binário (avançado):

use R2Soft\Docx\Support\LibreOfficePdfConverter;

$converter = new LibreOfficePdfConverter('/usr/bin/soffice');
$pdfBin = $converter->convert($docxBin);

Atualizações recentes do conversor (internos importantes):

  • Diretório temporário otimizado: quando disponível, o conversor utiliza /dev/shm (tmpfs) para reduzir I/O e acelerar a conversão. Caso contrário, usa sys_get_temp_dir().
  • Perfil dedicado do LibreOffice: cada execução usa um perfil limpo. Opcionalmente, você pode habilitar um perfil persistente para melhorar performance e estabilidade entre execuções (ver LO_PROFILE_DIR abaixo).
  • Execução headless e estável: usamos --headless --invisible --nologo --nodefault --nolockcheck --norestore --nofirststartwizard, desativamos Java e OpenCL e fixamos variáveis de ambiente para evitar problemas de UI/dconf.
  • Limpeza segura: somente os diretórios temporários criados com prefixo controlado são removidos ao final da execução.

Variável de ambiente LO_PROFILE_DIR (recomendado em produção):

  • O que é: caminho de um diretório no qual o LibreOffice manterá seu "perfil de usuário" (cache/config) de forma persistente.
  • Para que serve: evita o custo de primeira execução do LibreOffice a cada conversão, reaproveita cache de fontes e configurações e tende a reduzir falhas intermitentes em ambientes headless.
  • Requisitos: o caminho precisa existir (ou ser criável) e ser gravável pelo processo PHP/contêiner. Pode ser montado como volume em Docker.
  • Exemplo de valor: LO_PROFILE_DIR=/var/lib/lo-profile.
  • Como funciona aqui: se LO_PROFILE_DIR estiver definido, o conversor usará esse diretório; caso contrário, criará um perfil temporário isolado por execução.

Exemplos de uso:

  • Linux/macOS (shell):
    export LO_PROFILE_DIR=/var/lib/lo-profile
    php seu_script.php
    
  • Docker Compose (persistindo o perfil):
    environment:
    LO_PROFILE_DIR: /var/lib/lo-profile
    volumes:
    - lo-profile:/var/lib/lo-profile
    
  • Dockerfile desta imagem: define LO_PROFILE_DIR=/var/lib/lo-profile e realiza um "warm-up" chamando o soffice uma vez para preparar cache de fontes.

Notas técnicas (variáveis de ambiente usadas internamente durante a conversão):

  • HOME, XDG_CACHE_HOME, XDG_CONFIG_HOME são apontados para o perfil (persistente ou temporário).
  • SAL_USE_VCLPLUGIN=headless, NO_AT_BRIDGE=1, SAL_DISABLE_OPENCL=1, OMP_THREAD_LIMIT=2 para estabilidade/performance em headless.
  • LANG herda do ambiente ou usa pt_BR.UTF-8 por padrão.

Dicas:

  • Em Docker (imagem deste repo), o LibreOffice já é instalado no Dockerfile.
  • Em Ubuntu/Debian, instale com: sudo apt-get update && sudo apt-get install -y libreoffice.
  • Em Alpine, prefira imagens base Debian/Ubuntu para melhor compatibilidade; em Alpine puro, use apk add libreoffice (pode resultar em uma instalação pesada).
  • Em Windows, instale o LibreOffice e garanta que soffice.exe esteja no PATH (ou forneça o caminho, ex.: C:\\Program Files\\LibreOffice\\program\\soffice.exe).
  • Em macOS, via Homebrew: brew install --cask libreoffice. O binário geralmente fica em /Applications/LibreOffice.app/Contents/MacOS/soffice.

Como preparar o template DOCX

Você pode criar seu DOCX no Word/LibreOffice e inserir marcadores entre chaves. Exemplos:

  • Variáveis simples: ${nome}
  • Tabela (cloneRowAndSetValues): defina uma linha com marcadores como ${item_nome}, ${item_preco} e repita a linha. Ao chamar preencherTabela('item', $linhas) a engine multiplicará as linhas.
  • Blocos (cloneBlock): crie um bloco nomeado, por exemplo, tabela_lotes no conteúdo e use marcadores com sufixos #1, #2, ... ex.: ${campo#1}, ${campo#2}.

A engine já contempla alguns nomes especiais de chave para facilitar:

  • tabela_lotes (array): usa cloneBlock com índices (#1, #2, ...)
  • tabela_atrasadas (array): aplica formatação de dinheiro e datas automaticamente e clona uma tabela com referência numero
  • tabela_lotes_lista (array): se a variável quadra_q existir no template, clona a tabela com essa referência
  • dados_tabela_simples (array): clona tabela com referência item

Se você não usar esses nomes especiais, ainda pode preencher variáveis simples diretamente com o mesmo nome das chaves no array $termos.

Texto rico básico

Quando o valor tiver HTML simples com <b>, <strong>, <i>, <u> ou <br>, a engine converte para um TextRun e usa setComplexValue(), preservando estilo e quebras de linha. Caso contrário, usa setValue() com limpeza de tags.

Há um tratamento especial para chaves contendo "assinatura": nelas o HTML não é removido na limpeza de variáveis simples.

API principal

R2Soft\Docx\DocxEngine

Construtor:

new DocxEngine(
    ?string $pdfRenderer = null,     // 'DomPDF' | 'mPDF' | 'TCPDF' | null
    ?string $pdfRendererPath = null, // opcional; normalmente não é necessário
    $moneyFormatter = null           // callable|null para formatar dinheiro
)
  • $pdfRenderer: define o motor para gerar PDF. Obrigatório para usar toPdf().
  • $pdfRendererPath: caminho do renderer (casos avançados). Normalmente omita.
  • $moneyFormatter: callback para formatar valores monetários. Se não informado, a engine utiliza Format::money().

Métodos:

  • renderDocx(string $templateBase64OrBinary, array $termos): string
    • Recebe o template como binário ou Base64 e um array de termos.
    • Entrega o DOCX gerado (binário).
  • toPdf(string $docxBinaryOrBase64, bool $base64 = true): string
    • Converte um DOCX (binário/Base64) para PDF usando o renderer configurado (DomPDF/mPDF/TCPDF). Requer $pdfRenderer.
  • toPdfLibreOffice(string $docxBinaryOrBase64, bool $base64 = true): string
    • Converte um DOCX (binário/Base64) para PDF usando o LibreOffice (requer LibreOffice instalado e soffice acessível).
  • DocxEngine::toBase64(string $binary): string e DocxEngine::fromBase64(string $b64): string

R2Soft\Docx\Support\LibreOfficePdfConverter

  • __construct(string $binary = 'soffice')
    • Permite definir o caminho do binário do LibreOffice.
  • convert(string $docxBinary): string
    • Recebe o DOCX binário e retorna o PDF binário.

R2Soft\Docx\Support\Templating

  • toTextRun(string $html): TextRun — converte HTML básico para TextRun.
  • preencherTabela(TemplateProcessor $template, string $tagReferencia, array $dados): void — clona linhas de tabela baseadas na tag de referência (ex.: item). O array $dados deve ser uma lista de arrays associativos.
  • preencherBloco(TemplateProcessor $template, string $nomeBloco, array $dados): void — clona blocos usando sufixos #1, #2, etc.

R2Soft\Docx\Support\Format

  • toDate(mixed $v): ?DateTime — tenta converter para data.
  • money(mixed $v, $moneyFormatter = null): string — formata dinheiro usando callback customizado (se houver) ou rs_dinheiro().
  • formatTabelaAtrasadas(array $linhas, $moneyFormatter = null): array — formata números (dinheiro) e datas (dd/mm/YYYY) para a tabela especial tabela_atrasadas.
  • rs_dinheiro($value, int $decimal=2, string $dec=",", string $mil="."): string — formatação brasileira: R$ 1.234,56.

Estrutura esperada dos dados

Exemplos práticos:

$termos = [
    'nome' => 'Fulano',

    // Tabela simples com referência 'item' no template:
    'dados_tabela_simples' => [
        ['item' => 'Produto A', 'preco' => 10.5],
        ['item' => 'Produto B', 'preco' => 22],
    ],

    // Tabela atrasadas (formatação automática)
    'tabela_atrasadas' => [
        ['numero' => 1, 'vencimento' => '2025-08-10', 'valor' => 140.9],
        ['numero' => 2, 'vencimento' => '2025-09-10', 'valor' => 200],
    ],

    // Bloco 'tabela_lotes' com índices #1, #2 no template
    'tabela_lotes' => [
        ['quadra' => 'Q1', 'lote' => 'L10'],
        ['quadra' => 'Q2', 'lote' => 'L20'],
    ],
];

No template, você usaria por exemplo:

  • ${item} e ${preco} na linha da tabela de referência item.
  • Para blocos, ${quadra#1}, ${lote#1}, ${quadra#2}, ${lote#2}, etc.

Fluxo de Teste

Este projeto inclui um fluxo de teste simples dentro da pasta tests/ para validar o preenchimento de um template DOCX e a geração do arquivo de saída.

Arquivos relevantes:

  • tests/test.php: script que orquestra o teste.
  • tests/tags.php: retorna um array associativo com as chaves e valores que serão injetados no template.
  • tests/exemplo.docx: template de exemplo contendo as variáveis e estruturas de tabela/bloco.
  • tests/saida.docx: arquivo de saída gerado pelo teste (será sobrescrito a cada execução).

Como o teste funciona:

  1. O script tests/test.php carrega o autoloader do Composer: vendor/autoload.php.
  2. Instancia R2Soft\Docx\DocxEngine com pdfRenderer: 'DomPDF' (opcional, apenas necessário se for converter para PDF em seguida).
  3. Lê o template binário de tests/exemplo.docx via file_get_contents.
  4. Carrega os termos (tags) a partir de tests/tags.php (o arquivo retorna um array com chaves que precisam existir no template).
  5. Chama $engine->renderDocx($template, $tags), que:
    • Substitui variáveis simples (ex.: ${nome}),
    • Preenche tabelas e blocos quando os nomes convencionados são encontrados (ex.: dados_tabela_simples, tabela_atrasadas, tabela_lotes),
    • Converte HTML básico em TextRun quando apropriado (para setComplexValue).
  6. Escreve o resultado em tests/saida.docx e exibe uma mensagem [SUCESSO] com o caminho de saída.

Executando localmente:

  • Pré-requisitos: PHP 8.0+, ext-zip, Composer, e (opcional) um renderer de PDF se for testar PDF.

Passos:

composer install
php tests/test.php

Saída esperada no terminal:

[SUCESSO] Arquivo gerado em: /caminho/para/o/projeto/tests/saida.docx

Em caso de erro, será exibido:

[ERRO] <mensagem>

Executando em Docker/Docker Compose:

  1. Suba o ambiente:
    docker compose up --build -d
    
  2. Entre no contêiner, instale dependências e execute:
    docker compose exec app bash
    composer install
    php tests/test.php
    

Gerando PDF (opcional):

  • O test.php de exemplo demonstra geração de PDF com dois caminhos: 1) via renderizador PHP (toPdf()), 2) via LibreOffice (toPdfLibreOffice()).

Exemplo (trecho do teste):

$pdf = $engine->toPdf($docx, false);
$pdfLibreOffice = $engine->toPdfLibreOffice($docx, false);
file_put_contents(__DIR__ . '/results/saida.pdf', $pdf);
file_put_contents(__DIR__ . '/results/saida_libreoffice.pdf', $pdfLibreOffice);

Para o caminho 1, instale um renderer, por exemplo:

composer require dompdf/dompdf

Para o caminho 2 (LibreOffice), é obrigatório ter o LibreOffice instalado e soffice acessível.

Personalizando o teste:

  • Edite tests/tags.php para alterar valores e estruturas. As chaves devem corresponder às variáveis do template DOCX.
  • Substitua tests/exemplo.docx pelo seu template contendo os marcadores ${chave} esperados.

Limpeza:

  • Remova os arquivos gerados:
    rm -f tests/saida.docx tests/saida.pdf
    

Solução de problemas específicos do teste:

  • "Renderer de PDF não configurado": só necessário se você tentar converter para PDF. Configure pdfRenderer e instale o pacote do renderer.
  • "Variável não substituída": verifique se o nome da chave em tags.php coincide exatamente com a variável no template (${chave}).
  • Problemas de formatação HTML: limite-se a <b>, <strong>, <i>, <u>, <br>.

Docker e Docker Compose

O projeto inclui Dockerfile e docker-compose.yaml para desenvolvimento.

Subindo o contêiner de desenvolvimento:

docker compose up --build -d

Entrar no contêiner e instalar dependências:

docker compose exec app bash
composer install

Rodar o exemplo tests/test.php dentro do contêiner:

php tests/test.php

Arquivos são montados via volume em /app. Ajuste PHP_MEMORY_LIMIT em docker-compose.yaml se necessário.

Boas práticas e dicas

  • Use apenas HTML simples suportado para setComplexValue (, , , ,
    ).
  • Garanta que os nomes das chaves no array $termos existam como variáveis no DOCX.
  • Para tabelas com cloneRowAndSetValues, defina uma única linha modelo com os marcadores e deixe a engine multiplicar.
  • Valores não escalares em variáveis simples são convertidos para texto padrão "NÃO DEFINIDO".
  • Campos contendo "assinatura" não têm o HTML removido automaticamente.

Solução de problemas

  • Erro "Renderer de PDF não configurado": forneça pdfRenderer no construtor e instale o pacote do renderer (DomPDF/mPDF/TCPDF).
  • Problemas com fontes no PDF: pode ser necessário instalar fontes adicionais no contêiner/sistema ou configurar o renderer.
  • Quebras de linha: use <br> ou \r\n no valor para quebrar linhas adequadamente.

Solução de problemas (LibreOffice)

  • "Falha ao iniciar processo do LibreOffice" ou "LibreOffice falhou":
    • Verifique se o binário soffice está instalado e acessível (which soffice).
    • Tente informar o caminho completo via new LibreOfficePdfConverter('/caminho/para/soffice') e usar o conversor diretamente.
    • Em ambientes headless (servidores), o conversor já usa --headless e um perfil temporário. Ainda assim, garanta permissões de escrita em /tmp.
  • "Command not found" (soffice): instale o LibreOffice conforme sua plataforma (veja dicas na seção de LibreOffice acima).
  • Problemas de acentuação/locale: garanta um locale UTF-8 (ex.: pt_BR.UTF-8).

Licença

MIT.

Autores

  • Ricardo de Paula Coelho — ricardo@r2soft.com.br
  • R2 Soft - Informática e Softwares Ltda-ME — r2soft@r2soft.com.br