# Serviço de Geração de PDF — FPdfService

> _Atualizado para o **v6** (`update_diario_v5_to_v6`) em 2026-09-11. Alterações desta versão: `DIARIO_OFICIAL_ALTERACOES.md` e `plan/2026-09-11-diario-upgrade-backlog.md`._

## Arquivo

`app/Services/FPdfService.php` — **374 linhas** (serviço único, monolítico)

No v6 o `FPdfService` **não** é um orquestrador: ele concentra cabeçalho, rodapé,
corpo/capítulos, capa e montagem das tags de anexo. Os únicos serviços auxiliares em
`app/Services/Pdf/` são `PdfConverter` e `ImageProcessor`.

## Classe

```php
class FPdfService extends setasign\Fpdi\Tcpdf\Fpdi
```

Estende `setasign\Fpdi\Tcpdf\Fpdi` (que estende `TCPDF`) e mantém as propriedades
públicas de configuração preenchidas pelo repositório.

## Propriedades Públicas

| Propriedade | Tipo | Descrição |
|-------------|------|-----------|
| `$certificate` | array | Certificado A1/A3 (data, key, info) |
| `$published_at` | string | Data de publicação por extenso |
| `$prefecture` | array | Nome e logo da prefeitura |
| `$diario` | Model | Instância do Diario |
| `$city` | string | Cidade |
| `$stateOf` | string | Estado |
| `$publishedYear` | string | Ano |
| `$assigner` | array | Nome e CNPJ do assinante |
| `$address` | string | Endereço completo |
| `$columnsNumber` | int | 1, 2 ou 3 |
| `$hasLine` | bool | Linha separadora |
| `$lineStyle` | string | solid, dashed, dotted |
| `$lineColor` | string | Hexadecimal |
| `$logoPosition` | string | center, left, right |
| `$headerImg` | string | Caminho da imagem de cabeçalho |
| `$coverImagePath` | string | Caminho da imagem de capa |
| `$isPublished` | bool | Assinatura digital habilitada |
| `$progressCallback` | callable\|null | Callback `(currentPage, totalPages)` para progresso |

## Métodos Principais

### Cabeçalho e Rodapé (overrides TCPDF)

```php
public function Header()
    // Página 1 com capa: sem cabeçalho.
    // Página 1 sem capa: renderiza a arte do cabeçalho conforme $logoPosition
    // (center/left/right), QR Code e dados da edição.
    // Sempre: linha superior, "Publicação: ..." à direita, "Página N" à esquerda
    // e o divisor vertical quando columnsNumber > 1 && hasLine.
    // Só chama setSignature(...) quando $isPublished.

public function Footer()
    // Linha + "DOCUMENTO ASSINADO DIGITALMENTE POR ...", nome da prefeitura e endereço.
```

### Conteúdo

```php
public function PrintChapter($content, $columnsNumber = 1)
    // checkIfIsNeedBreakPage() + resetColumns() + setEqualColumns(2,89)/(3,58) + ChapterBody()

public function ChapterHeader($title)
    // Faixa cinza com o nome da ÁREA (posiciona em Y=80 na 1ª página com logo lateral)

public function ChapterTitle($title)
    // Título da matéria em negrito (selectColumn antes)

public function ChapterBody($content)
    // selectColumn() + writeHTML(processImageContent($content)) + Ln(8)

public function checkIfIsNeedBreakPage()
    // y > 240 ou x > 91 → lastPage() + AddPage()
```

### Capa

```php
public function addCoverPage()
    // AddPage() + AutoPageBreak(false) + Image(capa, 0, 0, 210, 297) + setPrintFooter(false)
```

### Processamento de Imagens

```php
public function processImageContent($content)
    // Delega para ImageProcessor::processImageContent()
    // Converte <img src> local em data URI base64 com width/height em mm

public function buildAnexoImageTag($imagePath, $columnWidthMM, $maxHeightMM = 200)
    // Lê a imagem, gera data URI e usa ImageProcessor::buildTagForColumn()
    // Dimensões em width/height (mm), proporcionais à coluna

public function convertPdfToImages($pdfPath, $outputDir, $diarioId, $materiaId)
    // Delega para PdfConverter::convertPdfToImages(), repassando progressCallback
```

### Utilitários

```php
private function hexToRgb($hex)          // #RRGGBB → [r,g,b] (divisor de colunas)
private function contentPageNumber()     // nº da página descontando a capa
public static function createStream($file) // StreamReader::createByString
```

## Fluxo de Geração do PDF

```
DiarioOficialRepository::generatePdfFile($id)
    ↓
FPdfService instanciado + propriedades configuradas (certificado, colunas, diário...)
    ↓
addCoverPage() (se capa) + AddPage() + ln(56)
    ↓
Para cada ÁREA (groupBy('id_area')):
    ├── ChapterHeader(nome da área)
    └── Para cada matéria:
        ├── Texto → acumula HTML
        ├── PDF + file_in=true  → convertPdfToImages() → buildAnexoImageTag()
        │                         → writeHTML/selectColumn (fluxo inline)
        └── PDF + file_in=false → FPDI importPage/useTemplate (página inteira)
    └── PrintChapter(content, columnsNumber) → setEqualColumns + ChapterBody
    ↓
SetTitle + Output (escrita atômica no storage)
```

## Certificação Digital

A assinatura é aplicada no `Header()` **somente quando `$isPublished`**:

```php
if ($this->isPublished) {
    $this->setSignature($certificate, $key, '', '', 2, $info);
}
```

## Dependências

| Pacote | Uso |
|--------|-----|
| `setasign/fpdi` (FPDI + TCPDF) | Geração do PDF, importação de páginas |
| `setasign/fpdf` | Base para FPDI |
| `imagick` (extensão PHP) + **Ghostscript** | Conversão/rasterização de PDF para imagens |
| `simplesoftwareio/simple-qrcode` | Geração de QR Code |

## Tratamento de Erros

A resiliência está concentrada no `DiarioOficialRepository::generatePdfFile()`:

1. **Logging extensivo** — início, sucesso e erro fatal (`Log::error` com exception)
2. **Timeout suspenso** — `@set_time_limit(0)`
3. **Progresso isolado** — `saveGenerationProgress` com try/catch (não interrompe a geração)
4. **Escrita atômica** — nunca deixa PDF parcial/corrompido
5. **Fallbacks de anexo** — imagens ⇄ import FPDI ⇄ grade, preservando o anexo
