Communitygithub.com

mariosakka/report-to-pdf

Renderiza um relatório em Markdown (cabeçalhos, parágrafos, listas com marcadores/numeradas, blocos de código com destaque de sintaxe Pygments, tabelas, negrito/itálico/código inline, frontmatter opcional) em um PDF polido e com estilo determinístico — página de título com barra de destaque e logotipo opcional, cabeçalho/rodapé, sumário gerado automaticamente com links clicáveis e hierarquia de cabeçalhos de três níveis, tabelas com bordas e listras zebradas, e um tema escuro opcional.

O que é report-to-pdf?

Esta skill permite converter automaticamente arquivos Markdown (revisões, relatórios, documentação técnica) em PDFs com aparência profissional e consistente, sem depender de formatação improvisada por um LLM. Ela é útil para desenvolvedores que desejam exportar relatórios de revisão de código, documentação de projetos ou notas de estudo para um formato pronto para cliente ou mentor, com layout previsível e repetível. A skill é compatível com qualquer sistema que tenha Python 3 e as bibliotecas `fpdf2`, `Pillow` e `Pygments` instaladas. Ela funciona analisando um subconjunto específico de Markdown (cabeçalhos, listas, blocos de código, tabelas, imagens, blocos de citação, math inline/display) e renderizando cada elemento em um PDF com um estilo visual fixo, incluindo página de título, cabeçalho/rodapé, sumário com links para páginas, destaque de sintaxe Pygments para blocos de código, e temas claro e escuro. O processo é totalmente determinístico: a mesma entrada sempre produz a mesma saída.

Funciona com✓Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/mariosakka/my-agent-skills/tree/HEAD/plugins/general/skills/report-to-pdf

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Prévia

Do README da skill

alt
alt

Documentação

report-to-pdf — Relatório Markdown para PDF polido

Renderizador determinístico de Markdown para PDF, controlado por script. Ele analisa um subconjunto restrito de Markdown de "relatório" com um pequeno analisador de blocos feito à mão (nenhuma biblioteca Markdown externa) e o distribui através do fpdf2 com as mesmas convenções visuais do gerador build-user-guide.py do módulo ColeteOnline Magento — página de título, cabeçalho/rodapé, sumário (TOC), cabeçalhos de seção com barra de destaque, blocos de código sombreados, tabelas com bordas e listras zebradas. A lógica de layout nunca muda entre execuções na mesma entrada: mesmo relatório de entrada, mesmo PDF de saída, todas as vezes — sem LLM no loop de renderização.

Quando usar

  • "Transforme esta revisão em markdown em um PDF para o mentor."
  • "Exporte meu relatório como um PDF polido."
  • "Quero que isso tenha sempre a mesma aparência, não reformatado por um LLM a cada execução."
  • Qualquer relatório/escrita .md que tenha cabeçalhos, listas, blocos de código ou tabelas e precise de uma versão em PDF de qualidade para cliente/mentor/marca.

Como executar

python3 <diretório-da-skill>/scripts/build_report_pdf.py <entrada.md> [opções]

(<diretório-da-skill> é o diretório base desta skill, a pasta que contém este SKILL.md.)

Opções:

  • -o / --output <caminho> — caminho do PDF de saída (padrão: <entrada>.pdf ao lado do arquivo de origem).
  • --logo <caminho> — imagem de logotipo opcional (PNG/WebP/JPEG, qualquer coisa que o Pillow possa abrir) na página de título. Repita a flag para vários logotipos, desenhados lado a lado em uma linha (também configurável via frontmatter logo: a.png, b.png, caminhos relativos ao arquivo .md).
  • --title, --subtitle, --version, --vendor, --author — substituem os valores correspondentes do frontmatter (veja abaixo); as flags da CLI vencem.
  • --cover center|left — layout da página de capa. Todo o bloco de título (logotipo, título, subtítulo, autor, versão) é centralizado verticalmente em ambos os modos; center (padrão) também o centraliza horizontalmente, left o alinha à esquerda. Também configurável via frontmatter cover:.
  • --no-header — omite o cabeçalho corrente e o espaço que ele reserva, para que o conteúdo de cada página comece na mesma margem superior (também configurável via frontmatter header: false).
  • --accent "R,G,B" — substitui a cor de destaque usada para a barra superior da página de título e as barras de destaque dos cabeçalhos de seção (padrão 220,50,50, vermelho ColeteOnline).
  • --dark — renderiza com o tema de cores escuro em vez do tema claro padrão (também configurável via frontmatter theme: dark; --dark vence se ambos estiverem presentes). Veja "Tema escuro" abaixo.

O script instala automaticamente fpdf2, Pillow e Pygments via pip install --user se alguma importação falhar, espelhando a auto-instalação do Pillow do script de referência. Todos os três foram verificados como importáveis e o script foi executado de ponta a ponta durante o desenvolvimento (veja Testes abaixo).

Convenções Markdown que ele espera

Bloco de frontmatter opcional no topo do arquivo:

---
title: Título do Meu Relatório
subtitle: Um subtítulo de uma linha
version: 1.0
vendor: ColeteOnline SRL
accent: 220,50,50
---

Todas as chaves são opcionais e têm fallback sensato:

  • title — recai para o primeiro cabeçalho # H1 encontrado no corpo, depois para o nome do arquivo de entrada.
  • subtitle — recai para vazio (omitido da página de título).
  • version — recai para a data de hoje (YYYY-MM-DD); defina version: none para ocultar a linha de versão.
  • vendor — recai para vazio (linha de copyright omitida do rodapé da página de título se não definido).
  • author — nome(s) do(s) autor(es), mostrado(s) na capa abaixo do subtítulo (ex.: author: João Silva, Maria Souza). Omitido se não definido.
  • cover — center (padrão) ou left; veja --cover acima.
  • header — false omite o cabeçalho corrente; veja --no-header.
  • justify — true justifica o texto do corpo, cabeçalhos e células de tabela; o padrão é alinhado à esquerda (texto justificado estica os espaços entre palavras em linhas curtas quebradas, ex.: um cabeçalho de duas linhas).
  • accent — recai para o vermelho da marca ColeteOnline 220,50,50.

Markdown do corpo e como cada elemento é renderizado:

MarkdownRenderiza como
# TítuloO primeiro # no arquivo é o título da página de título apenas (consumido, não repetido no corpo). Qualquer # posterior é um cabeçalho principal real (veja abaixo).
# Seção Principal (após o primeiro #)Cabeçalho principal. Sempre começa sua própria página nova (a menos que o cursor já esteja no topo de uma página em branco), grande barra colorida à esquerda, negrito 16pt, texto preto — e ganha uma entrada no Sumário. Use apenas para divisões genuínas de nível superior; cada uma custa uma página.
## SubseçãoSubcabeçalho. Negrito 13pt, texto na cor de destaque com um sublinhado curto na cor de destaque (visualmente distinto dos dois níveis acima e abaixo) — não força uma quebra de página, apenas continua o fluxo. Ainda ganha uma entrada no Sumário. Tem sua própria verificação de "não deixar um cabeçalho preso no final de uma página".
### Sub-subseção (e ####+)Cabeçalho terciário. Negrito 12pt, cinza neutro, linha fina de largura total, sem quebra de página, sem entrada no Sumário (mantém o Sumário escaneável).
Texto de parágrafoParágrafo de corpo justificado à esquerda, 10pt.
- item / * itemLista com marcadores (•).
1. itemLista numerada.
```linguagem ... ```Bloco de código monoespaçado sombreado e com borda; a tag de linguagem (se presente) é impressa como uma pequena etiqueta em itálico acima do bloco e aciona o destaque de sintaxe Pygments real — veja abaixo.
`a
--- / *** / ___ em sua própria linhaEspaçador vertical (não uma quebra de página).
<br> em sua própria linhaUma linha de corpo em branco explícita (5,5mm). Empilhe várias para mais espaço. O renderizador não adiciona espaços próprios no topo de uma página, então use isso quando quiser espaço extra.
<!-- pagebreak --> em sua própria linhaIniciar uma nova página (sem efeito se já estiver no topo de uma).
![alt](caminho) em sua própria linhaImagem centralizada, com borda, redimensionada para caber; caminhos relativos são resolvidos em relação à pasta do arquivo .md. PNG/JPEG/WebP e SVG (veja "Figuras, caixas e matemática"). Uma imagem raster nua com menos de 900 px de largura é limitada a ~130x105mm; uma figura (imagem seguida por uma legenda), qualquer SVG e qualquer imagem com 900 px ou mais de largura pode usar a largura total do texto (altura máxima 120mm). Uma figura que não cabe no espaço restante na página é reduzida para caber (até 70% de sua altura) em vez de pular para a próxima página e deixar um espaço.
*Figura: ...* (uma linha inteira em itálico logo após uma imagem)A legenda da imagem: pequena, centralizada, em itálico, mantida na mesma página que a imagem. O texto alternativo nunca é impresso.
> textoCaixa de definição: uma caixa com borda e levemente sombreada por blockquote (termo em negrito e math inline funcionam dentro; uma linha > sem nada após inicia um novo parágrafo na mesma caixa).
$...$Math inline, convertido para texto Unicode (letras gregas, ≤ ≥ × ·, sub/sobrescritos, \frac{a}{b} -> (a)/b, \sqrt, \text{}, acentos). Um preço como $5 não é math.
$$...$$ (uma linha, ou em suas próprias linhas)Math de exibição, uma imagem centralizada do primeiro backend de math que funcionar (veja "Figuras, caixas e matemática").
Bloco indentado com 4 espaços após uma linha em brancoBloco de código sem tag de linguagem (exportações de notebook imprimem a saída de célula dessa forma). Não aplicado logo após uma lista.
**negrito**Texto em negrito real, através do renderizador markdown=True embutido do fpdf2.
[texto](url)Uma anotação de link PDF real e clicável — apenas o texto de exibição é mostrado, não a URL bruta — estilizado em uma cor de link distinta.
*itálico* / _itálico_Itálico renderizado (convertido internamente para a forma nativa __italic__ do fpdf2).
`código`As crases de código inline são removidas — sem suporte a monoespaçado inline, renderiza como texto simples.

Cabeçalhos, parágrafos, marcadores, itens numerados e células de tabela são todos renderizados através do caminho markdown=True do fpdf2, então negrito e links [texto](url) funcionam em todo lugar onde o texto aparece, incluindo dentro de cabeçalhos e células de tabela. Blocos de código com cerca são a única exceção — seu conteúdo é renderizado completamente literalmente (sem análise de markdown), então uma amostra de código contendo **, __ ou []() literais não será mal interpretada.

Figuras, caixas e matemática

Escrito para que o companheiro markdown de um notebook (saída do nbconvert) renderize diretamente: report-to-pdf notebook.md substitui uma etapa separada de PDF baseada em Chrome.

Imagens SVG. ![alt](figure.svg) funciona. O desenho SVG próprio do fpdf2 pula pontas de seta <marker>, filtros e máscaras, desenha texto em uma fonte principal (sem grego) e colore mal o texto sem fill, então todo SVG é rasterizado a 2,5x e incorporado como PNG, usando, nesta ordem: um Chrome/Edge headless (encontrado automaticamente; defina REPORT_PDF_BROWSER para o caminho de um navegador para substituir), depois cairosvg se importável. Com nenhum dos dois, o SVG é desenhado nativamente pelo fpdf2 e um aviso diz que pode estar incompleto. A rasterização é armazenada em cache por execução.

Backends de math de exibição (--math auto|latex|mathtext|unicode, ou frontmatter math:; padrão auto). O script imprime qual backend foi usado.

  1. latex: LaTeX real quando pdflatex, xelatex ou tectonic está no PATH (mais pdftocairo, PyMuPDF ou dvisvgm para fazer a imagem), ou latex + dvipng. Cada fórmula é compilada com standalone, amsmath, amssymb e xcolor a 12 pt, então cortada para um PNG transparente de 300 dpi na cor do texto. Instalação: MiKTeX (Windows), TeX Live (apt install texlive-latex-base texlive-latex-extra poppler-utils, macOS brew install --cask mactex-no-gui), ou tectonic (binário único, baixa pacotes sob demanda; conda install -c conda-forge tectonic ou scoop install tectonic; testado de ponta a ponta no Windows com PyMuPDF como conversor de imagem).
  2. mathtext: subconjunto LaTeX do matplotlib (frações, somas, grego, \text{} é mapeado para \mathrm). Nenhum TeX necessário, apenas matplotlib. Ambientes como aligned não são suportados e falham.
  3. unicode: a mesma conversão de texto que o math inline, centralizado.

Um backend que está faltando ou falha (por exemplo, uma fórmula que o TeX rejeita) cai silenciosamente para o próximo; --math latex ainda degrada da mesma forma.

Teste: python tests/run_checks.py (conversão de math, analisador, roteamento SVG, fallback de backend com PATH simulado, construção de ponta a ponta de tests/fixture.md). Renderize um companheiro: python scripts/build_report_pdf.py notebook.md -o notebook.pdf.

Destaque de sintaxe

Blocos de código com cerca são tokenizados com lexers Pygments reais (não um realçador de regex feito à mão — código é exatamente o tipo de coisa que um pequeno analisador ad hoc erra sutilmente: escapes de string, comentários aninhados, literais de template). A tag de linguagem na cerca (```typescript, ```sql, ```python, ...) seleciona o lexer; um punhado de aliases comuns são normalizados (ts/ tsx → typescript, js/jsx → javascript, sh/shell/zsh → bash, yml → yaml, prisma → graphql — o lexer embutido mais próximo e uma boa correspondência na prática, já que a sintaxe @attribute(...) do Prisma e a sintaxe @directive(...) do GraphQL tokenizam quase de forma idêntica).

O destaque é um bônus, nunca um requisito difícil para renderizar um bloco de código — uma tag de linguagem ausente, uma linguagem que o Pygments não reconhece (mesmo após a tabela de aliases), ou o próprio Pygments não sendo importável, todos recaem para o comportamento exato anterior ao destaque: o bloco inteiro em uma cor plana, sem falha, sem conteúdo ausente.

Paleta de cores (escolhida para legibilidade contra o fundo cinza claro do bloco de código, não vinculada a nenhum tema Pygments específico):

Categoria de tokenCor
Palavras-chave (interface, async, const, NOT NULL, palavras-chave de tipo como string/number)Magenta
StringsVerde
NúmerosMarrom/laranja
ComentáriosCinza
Nomes de função/classe, decoradores/atributos (@map, @relation)Azul / marrom
Builtins (Promise, etc.)Verde-azulado
Todo o resto (identificadores, pontuação, operadores)A cor base do código

Um tipo de token sem uma entrada exata na paleta resolve percorrendo a hierarquia de tipos de token do Pygments (ex.: Keyword.Declaration não tem entrada própria, mas seu pai Keyword tem) até que uma correspondência seja encontrada — a raiz Token nua sempre corresponde, então isso nunca falha em resolver para alguma cor.

A lógica de ajuste de tamanho monoespaçado (uma linha de código muito larga para a página reduz o tamanho da fonte de todo o bloco uniformemente em vez de transbordar) não é afetada pelo destaque — ela mede larguras de caracteres simples, que não mudam com base na cor em que um token é desenhado.

Tema escuro

Passe --dark, ou defina theme: dark no frontmatter, para renderizar o documento inteiro em um tema escuro em vez do tema claro padrão. Cada cor no documento muda para uma contraparte apropriada para o escuro — não apenas os blocos de código:

  • O fundo da página em si é preenchido escuro (fpdf2 não tem configuração nativa de "cor de fundo da página", então isso é feito preenchendo a página inteira no hook header(), antes que qualquer outra coisa desenhe sobre ele — incluindo na página de título).
  • O texto do corpo, cabeçalhos (todos os três níveis), texto de rodapé/atenuado, linhas de régua, listras zebradas de tabela e o cabeçalho/rodapé corrente usam um conjunto separado de cores do modo escuro, não apenas um claro invertido.
  • O destaque de sintaxe usa sua própria paleta de modo escuro (rosas/verdes/azuis mais brilhantes escolhidos para contraste contra o fundo escuro do código), não as cores do modo claro escurecidas.
  • A cor do link (tanto links markdown [texto](url) quanto os links de salto para página do Sumário) muda para um azul mais claro que permanece legível em um fundo escuro.

A cor --accent (a barra superior, barra esquerda do cabeçalho principal e texto/sublinhado do subcabeçalho) é usada como está em ambos os temas — escolha uma que funcione contra um fundo branco e um quase preto se você planeja gerar o mesmo relatório em ambos os temas, ou substitua --accent por tema se não.

Sumário

O Sumário preenchido posteriormente (veja "Como isso renderiza" acima

...

[truncado para tradução]

Habilidades Relacionadas