unipdf é AGPL ou pago. Como migrar para o gpdf.
O unipdf da UniDoc obriga AGPL v3 ou licença comercial por desenvolvedor. Este guia mapeia a API creator do unipdf para gpdf — MIT, zero deps, sem chave.
TL;DR
gpdf é uma biblioteca PDF em Go puro sob licença MIT, com zero dependências externas e sem etapa de registro de chave de licença. Se você está em unidoc/unipdf porque nada mais resolveu CJK ou aplainamento de AcroForm, mas a cláusula AGPL travou o jurídico para distribuição e o tier comercial é difícil de justificar, este guia mapeia a API creator do unipdf para o gpdf, peça por peça.
No último trimestre, um amigo numa fintech levou github.com/unidoc/unipdf/v3 pelo fluxo de aprovação OSS. O ticket de compliance voltou no dia seguinte com um X vermelho ao lado de AGPL-3.0 e uma nota do jurídico: "Não pode ser linkado em produtos fechados distribuíveis. Adquirir licença comercial ou remover." O orçamento comercial chegou com uma anuidade por desenvolvedor que, para um time de doze, fez todo mundo reabrir os resultados de busca.
Esta é a parte da história do unipdf que não aparece no README. unipdf é tecnicamente excelente — maduro, completo, bem mantido. Também é dual-licensed: AGPL v3 para uso aberto, comercial pago para o resto. AGPL v3 é o copyleft mais forte de uso comum. Se você linka unipdf num serviço acessado por usuários via rede, o §13 obriga publicar todo o código-fonte correspondente. A maioria dos jurídicos diz não.
Se você tem código unipdf em produção e a licença pegou na auditoria ou está chegando na renovação, este é o mapa de migração. Se você está começando e pegou unipdf no reflexo porque a documentação era a mais polida, esta é a alternativa que não vem com relação de cobrança.
O que "AGPL ou pago" significa na prática
Muitas bibliotecas Go ganham um rótulo "AGPL" sem o time pensar de fato no que significa. unipdf não é assim. O arquivo de licença do repositório é AGPL v3 puro, o README é explícito que uso comercial requer chave, e o próprio binário força isso — chame qualquer API do unipdf sem registrar uma licença na inicialização e você recebe um erro ou uma marca d'água em cada página de saída.
Você acaba em um destes três modos:
- Modo AGPL. Você publica o seu código sob AGPL v3. Cada byte do seu serviço que toca unipdf, mais tudo que linka a ele, precisa ficar disponível para qualquer um que interaja com o serviço pela rede. Para a maioria das ferramentas internas e produtos SaaS, isso é inviável.
- Modo comercial. Você paga à UniDoc por desenvolvedor por ano. Os preços variam — orçamentos públicos recentes giraram em torno de quatro dígitos por assento por ano — e incluem uma chamada de registro (medida ou por chave) que cada binário precisa fazer no startup. A chave é tratada como segredo, então mora no seu secret manager e é injetada em cada container.
- Modo trial / avaliação. Grátis por tempo limitado. Saídas com marca d'água. Inviável em produção.
Nenhum desses modos é intrinsecamente errado. UniDoc é uma empresa real com engenheiros reais, e o preço reflete o que custa construir e manter uma biblioteca PDF abrangente. O ponto é que a decisão de licenciamento se infiltra em cada camada: revisão jurídica, rotação de segredos, renovação financeira e a superfície de deploy (cada container precisa da chave). gpdf elimina essa coluna inteira da sua planilha por ser MIT.
O que você perde e o que você mantém
Vale ser honesto antes de entrar na API. unipdf faz coisas que gpdf não faz:
| Capacidade | unipdf | gpdf |
|---|---|---|
| Geração de PDF | ✅ | ✅ |
| TrueType / fontes CJK | ✅ | ✅ (sem CGO, subset automático) |
| Cifragem AES-128/256 | ✅ | ✅ (ISO 32000-2 Rev 6, Go puro) |
| Assinatura PKCS#7 / PAdES | ✅ | ✅ (suporte RFC 3161 TSA) |
| PDF/A-1b/2b | ✅ | ✅ |
| AcroForm — preencher existente | ✅ | ✅ (apenas flatten, sem criar campo novo) |
| AcroForm — criar campos novos | ✅ | ❌ |
| Parsing / extração de texto | ✅ | ❌ (gpdf foca em geração) |
| OCR | ✅ | ❌ |
| Redaction (tarjamento) | ✅ | ❌ |
| Renderização HTML | parcial | ❌ (use renderer separado e faça merge) |
Se você precisa de parsing de PDF, OCR ou redaction, esta migração não te leva até o fim. Ou mantém unipdf só nesses caminhos de código (vai continuar devendo a licença comercial para esses binários) ou escolhe uma biblioteca focada em parsing para o lado de leitura. Para o caminho de geração, cifragem, assinatura, fontes e CJK — para o que a maior parte das faturas de unipdf de fato vai — gpdf é uma troca completa.
Removendo o código de registro de licença
É o menor diff de toda a migração e o que faz o resto parecer real. Binários unipdf precisam registrar uma chave no startup. Há algumas variantes:
// API key (medida)
import "github.com/unidoc/unipdf/v3/common/license"
func init() {
if err := license.SetMeteredKey(os.Getenv("UNIDOC_API_KEY")); err != nil {
log.Fatal(err)
}
}
// Arquivo de licença offline
func init() {
licenseKey, _ := os.ReadFile("/etc/unidoc/license.txt")
if err := license.SetLicenseKey(string(licenseKey), "Acme Corp"); err != nil {
log.Fatal(err)
}
}
No gpdf não existe equivalente. Apague o bloco init() inteiro. Tire UNIDOC_API_KEY do seu secret manager, das variáveis de CI e dos manifests de container. Remova o arquivo de licença da imagem. A única coisa que você importa é github.com/gpdf-dev/gpdf, e a única exigência é chamar gpdf.NewDocument em algum lugar.
Só isso. Também é o teste de que sua migração de fato aterrissou: grep -r unidoc . deve retornar zero ocorrências quando terminar.
Tabela de mapeamento da API
A tabela é a cola. As seções depois andam por cinco pares concretos. unipdf chama o builder de alto nível de Creator; gpdf chama de Document. As formas são parecidas o suficiente para a maioria do código se traduzir por inspeção visual.
| O que você quer fazer | unipdf (creator) | gpdf |
|---|---|---|
| Criar um builder | c := creator.New(); c.SetPageSize(creator.PageSizeA4) | doc := gpdf.NewDocument(gpdf.WithPageSize(document.A4)) |
| Definir margens | c.SetPageMargins(L, R, T, B) | gpdf.WithMargins(document.UniformEdges(document.Mm(20))) |
| Nova página | c.NewPage() | page := doc.AddPage() |
| Linha única de texto | p := c.NewParagraph("hi"); c.Draw(p) | c.Text("hi") (dentro de uma coluna) |
| Texto com quebra | p := c.NewStyledParagraph(); p.SetText(...); c.Draw(p) | c.Text(body) (quebra automática) |
| Registro de fonte | model.NewCompositePdfFontFromTTFFile(path) | gpdf.WithFont("Name", ttfBytes) (no construtor) |
| Aplicar fonte ao texto | style.Font = font; style.FontSize = 12 | template.FontFamily("Name"), template.FontSize(12) por-texto |
| Cor | style.Color = creator.ColorRGBFromHex("#1A237E") | template.TextColor(pdf.RGBHex(0x1A237E)) |
| Tabela | t := c.NewTable(4); t.SetColumnWidths(...); c.Draw(t) | c.Table(headers, rows, template.ColumnWidths(...)) |
| Imagem | img, _ := c.NewImageFromFile(path); img.ScaleToWidth(w); c.Draw(img) | c.Image(imgBytes, template.FitWidth(document.Mm(50))) |
| Cabeçalho / rodapé | c.DrawHeader(fn) / c.DrawFooter(fn) | doc.Header(fn) / doc.Footer(fn) |
| Número de página | manual via args de DrawFooter | c.PageNumber() / c.TotalPages() (placeholders) |
| Cifragem | model.PdfWriter + Encrypt re-encoding | gpdf.WithEncryption(gpdf.AES256, "user", "owner", perms) |
| Assinatura | model.NewPdfAppender(...).Sign(...) | gpdf.SignDocument(pdfBytes, signer, opts) |
| Registro de licença | license.SetMeteredKey(...) em init() | (nenhum — apague) |
| Saída para arquivo | c.WriteToFile("out.pdf") | data, _ := doc.Generate(); os.WriteFile("out.pdf", data, 0o644) |
| Saída para writer | c.Write(w) | doc.Render(w) |
Duas mudanças estruturais para guardar. O creator do unipdf é com estado: você constrói um Paragraph ou uma Table e chama c.Draw(thing) para confirmar. gpdf é declarativo: você descreve uma árvore de linhas e colunas e deixa o motor de layout posicionar. A segunda mudança é que gpdf tem uma grade de 12 colunas como o Bootstrap. Toda linha tem implicitamente 12 unidades de largura; você gasta com r.Col(n, fn). A maioria dos layouts colapsa em duas ou três linhas assim que você para de medir larguras em milímetros.
Antes / Depois 1: o PDF mais simples possível
O par "hello world". A versão unipdf não é longa; só tem mais cerimônia por causa da chamada de licença.
Antes — unipdf:
package main
import (
"log"
"os"
"github.com/unidoc/unipdf/v3/common/license"
"github.com/unidoc/unipdf/v3/creator"
)
func init() {
if err := license.SetMeteredKey(os.Getenv("UNIDOC_API_KEY")); err != nil {
log.Fatal(err)
}
}
func main() {
c := creator.New()
c.SetPageSize(creator.PageSizeA4)
p := c.NewParagraph("Hello, World!")
p.SetFontSize(24)
if err := c.Draw(p); err != nil {
log.Fatal(err)
}
if err := c.WriteToFile("hello.pdf"); err != nil {
log.Fatal(err)
}
}
Depois — gpdf:
package main
import (
"log"
"os"
"github.com/gpdf-dev/gpdf"
"github.com/gpdf-dev/gpdf/document"
"github.com/gpdf-dev/gpdf/template"
)
func main() {
doc := gpdf.NewDocument(
gpdf.WithPageSize(document.A4),
gpdf.WithMargins(document.UniformEdges(document.Mm(20))),
)
page := doc.AddPage()
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("Hello, World!", template.FontSize(24), template.Bold())
})
})
data, err := doc.Generate()
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("hello.pdf", data, 0o644); err != nil {
log.Fatal(err)
}
}
Três coisas para notar. O bloco init() sumiu — sem chave, sem variável de ambiente. A construção usa opções em vez de mutar o builder. O texto vive dentro de uma linha e coluna em vez de ser um Paragraph solto que você desenha depois. A grade está fazendo o posicionamento; você não escolhe coordenadas.
Antes / Depois 2: tabela de itens de uma fatura
Tabelas são onde a API creator do unipdf alonga. Você constrói uma Table, chama SetColumnWidths com frações absolutas, monta células uma a uma com NewCell / SetContent e configura bordas e alinhamento de cada célula à mão.
Antes — unipdf:
table := c.NewTable(4)
table.SetColumnWidths(0.5, 0.15, 0.15, 0.2)
headerStyle := c.NewTextStyle()
headerStyle.Font, _ = model.NewStandard14Font("Helvetica-Bold")
headerStyle.FontSize = 11
headerStyle.Color = creator.ColorWhite
drawHeaderCell := func(text string) {
cell := table.NewCell()
cell.SetBackgroundColor(creator.ColorRGBFromHex("#1A237E"))
cell.SetBorder(creator.CellBorderSideAll, creator.CellBorderStyleSingle, 0.5)
p := c.NewStyledParagraph()
chunk := p.Append(text)
chunk.Style = headerStyle
cell.SetContent(p)
}
for _, h := range []string{"Descrição", "Qtd.", "Unit.", "Total"} {
drawHeaderCell(h)
}
for _, row := range items {
for _, cellText := range row {
cell := table.NewCell()
cell.SetBorder(creator.CellBorderSideAll, creator.CellBorderStyleSingle, 0.3)
p := c.NewParagraph(cellText)
p.SetFontSize(11)
cell.SetContent(p)
}
}
if err := c.Draw(table); err != nil {
log.Fatal(err)
}
Bordas, conteúdo por célula, loop que desenha o cabeçalho — tudo mecânico.
Depois — gpdf:
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Table(
[]string{"Descrição", "Qtd.", "Unit.", "Total"},
[][]string{
{"Desenvolvimento frontend", "40 h", "R$ 750,00", "R$ 30.000,00"},
{"Desenvolvimento backend", "60 h", "R$ 750,00", "R$ 45.000,00"},
{"Design de UI", "20 h", "R$ 600,00", "R$ 12.000,00"},
},
template.ColumnWidths(50, 15, 15, 20),
template.TableHeaderStyle(
template.Bold(),
template.TextColor(pdf.White),
template.BgColor(pdf.RGBHex(0x1A237E)),
),
template.TableStripe(pdf.RGBHex(0xF5F5F5)),
)
})
})
ColumnWidths são porcentagens da coluna em que a tabela vive, não frações absolutas da página. Coloque a mesma tabela dentro de r.Col(6, ...) e os percentuais continuam valendo: a tabela ocupa metade da linha e as colunas se redistribuem na proporção. A quebra de página é automática; se o corpo passar a margem inferior, o cabeçalho repete na próxima página sem você fiar nada.
Um detalhe específico. A Table do unipdf numa rodada de fatura de 100 linhas marca cerca de 8,6 ms por render na nossa suite. A do gpdf faz o mesmo trabalho em 108 µs — cerca de 80× mais rápido — porque o motor de layout mede cada linha uma vez e escreve páginas em uma única passada, em vez de materializar um DOM célula por célula. Numa fatura solta isso é invisível. Num batch em cron, muda se você precisa ou não de uma fila.
Para NF-e / DANFE / PDF/A-3 (Brasil), o lado de layout funciona com gpdf direto. Se precisar de timestamping para conformidade, vai pelo lado de gpdf.SignDocument com a opção RFC 3161 TSA.
Antes / Depois 3: texto com acentos sem a dança da composite font
unipdf suporta texto estendido (incluindo CJK), mas o caminho é verboso. Você constrói uma composite font a partir de um TTF em disco, define como font do style e passa por cada paragraph. Se quiser fallbacks, fia você mesmo.
Antes — unipdf:
font, err := model.NewCompositePdfFontFromTTFFile("NotoSans-Regular.ttf")
if err != nil {
log.Fatal(err)
}
c := creator.New()
c.SetPageSize(creator.PageSizeA4)
style := c.NewTextStyle()
style.Font = font
style.FontSize = 14
p := c.NewStyledParagraph()
p.Append("Olá, mundo. Atenção: ção, ã, é, ô.").Style = style
if err := c.Draw(p); err != nil {
log.Fatal(err)
}
c.WriteToFile("pt.pdf")
O TTF precisa existir no caminho indicado, em runtime, no host que roda o binário. As imagens de container têm que carregar a fonte. NewCompositePdfFontFromTTFFile precisa acontecer antes de qualquer chamada de desenho que use a fonte, então mora em algum lugar global ou é passada como dependência.
Depois — gpdf:
package main
import (
_ "embed"
"log"
"os"
"github.com/gpdf-dev/gpdf"
"github.com/gpdf-dev/gpdf/document"
"github.com/gpdf-dev/gpdf/template"
)
//go:embed NotoSans-Regular.ttf
var noto []byte
func main() {
doc := gpdf.NewDocument(
gpdf.WithPageSize(document.A4),
gpdf.WithMargins(document.UniformEdges(document.Mm(20))),
gpdf.WithFont("Noto", noto),
gpdf.WithDefaultFont("Noto", 14),
)
page := doc.AddPage()
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("Olá, mundo.")
c.Text("Atenção: ção, ã, é, ô — sem escapes.")
c.Text("Av. Paulista, 1578, São Paulo")
})
})
data, _ := doc.Generate()
if err := os.WriteFile("pt.pdf", data, 0o644); err != nil {
log.Fatal(err)
}
}
Três diferenças a observar. A fonte é bytes, não caminho — //go:embed compila ela no binário, então a imagem de runtime deixa de precisar de um diretório de fontes. A fonte é registrada uma vez no construtor; sem encadeamento de style por paragraph. E o subseteador TrueType do gpdf entende formatos cmap CJK (4, 6, 12) e codificação Identity-H, então o PDF de saída só carrega os glifos que você de fato usou. Uma fatura de 200 caracteres produz um subset de fonte de ~30 KB em vez de um embed completo de 4 MB.
Para CJK específico (Source Han Sans, IPAex Gothic, cadeias de fallback), há um post complementar sobre fontes japonesas.
Antes / Depois 4: cabeçalho em todas as páginas, número de página no rodapé
O padrão do unipdf é c.DrawHeader(fn) / c.DrawFooter(fn), ambos recebem um contexto com o block atual e o número da página. Os números vêm dos campos PageNum / TotalPages do contexto.
Antes — unipdf:
c.DrawHeader(func(block *creator.Block, args creator.HeaderFunctionArgs) {
p := c.NewParagraph("ACME Ltda.")
p.SetFontSize(12)
p.SetPos(40, 30)
block.Draw(p)
})
c.DrawFooter(func(block *creator.Block, args creator.FooterFunctionArgs) {
p := c.NewParagraph(fmt.Sprintf("Página %d de %d", args.PageNum, args.TotalPages))
p.SetFontSize(8)
p.SetPos(0, 20)
p.SetTextAlignment(creator.TextAlignmentCenter)
block.Draw(p)
})
Cabeçalho / rodapé são blocks que você desenha em posições absolutas. Coordenada Y errada, margem errada — trabalho de pixel toda vez que muda o tamanho da página.
Depois — gpdf:
doc := gpdf.NewDocument(
gpdf.WithPageSize(document.A4),
gpdf.WithMargins(document.UniformEdges(document.Mm(20))),
)
doc.Header(func(p *template.PageBuilder) {
p.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("ACME Ltda.", template.Bold(), template.FontSize(12))
c.Line(template.LineColor(pdf.Gray(0.7)))
c.Spacer(document.Mm(4))
})
})
})
doc.Footer(func(p *template.PageBuilder) {
p.AutoRow(func(r *template.RowBuilder) {
r.Col(6, func(c *template.ColBuilder) {
c.Text("ACME Ltda.",
template.FontSize(8), template.TextColor(pdf.Gray(0.5)))
})
r.Col(6, func(c *template.ColBuilder) {
c.PageNumber(template.AlignRight(),
template.FontSize(8), template.TextColor(pdf.Gray(0.5)))
})
})
})
for i := 0; i < 10; i++ {
page := doc.AddPage()
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text(fmt.Sprintf("Conteúdo da página %d.", i+1))
})
})
}
PageNumber e TotalPages são placeholders que o motor de layout resolve depois da paginação. Cabeçalho e rodapé são árvores em si, não blocks que você posiciona à mão. O motor reserva espaço para eles em cada página automaticamente; se mudar o tamanho da página de A4 para Letter, nada mais precisa ser movido.
Antes / Depois 5: cifragem com AES-256
O par em que a foto de licenciamento aparece mais nítida. A cifragem do unipdf vai por model.PdfWriter, que conta como uso comercial e dispara o caminho de registro de licença. A do gpdf mora atrás de uma única opção funcional, e a implementação AES-256 (ISO 32000-2 Rev 6) está no core MIT de código aberto.
Antes — unipdf:
// Renderiza o conteúdo via creator e re-codifica com model.PdfWriter
// para anexar cifragem. O check de licença dispara aqui.
c := creator.New()
// ... desenhar conteúdo ...
var buf bytes.Buffer
if err := c.Write(&buf); err != nil {
log.Fatal(err)
}
reader, err := model.NewPdfReader(bytes.NewReader(buf.Bytes()))
if err != nil {
log.Fatal(err)
}
writer := model.NewPdfWriter()
encryptOpts := &model.EncryptOptions{Algorithm: model.RC4_128bit, Permissions: model.PermPrinting}
if err := writer.Encrypt([]byte("user-pwd"), []byte("owner-pwd"), encryptOpts); err != nil {
log.Fatal(err)
}
for i := 1; i <= reader.NumPage; i++ {
page, _ := reader.GetPage(i)
writer.AddPage(page)
}
f, _ := os.Create("encrypted.pdf")
defer f.Close()
writer.Write(f)
Depois — gpdf:
doc := gpdf.NewDocument(
gpdf.WithPageSize(document.A4),
gpdf.WithMargins(document.UniformEdges(document.Mm(20))),
gpdf.WithEncryption(
gpdf.AES256,
"user-pwd",
"owner-pwd",
gpdf.PermPrinting|gpdf.PermCopyContent,
),
)
page := doc.AddPage()
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("Confidencial.")
})
})
data, _ := doc.Generate()
os.WriteFile("encrypted.pdf", data, 0o644)
Uma opção, AES-256 por padrão, sem passada de writer separada. O caminho de cifragem inteiro mora dentro do core MIT — mesmo módulo, mesmo go get. Mesma história para assinatura digital: gpdf.SignDocument(pdfBytes, signer, gpdf.WithTSA("http://timestamp.digicert.com")) pós-processa os bytes com timestamp PKCS#7 + RFC 3161, sem pacote extra e sem registro de chave.
Quão rápido fica?
Benchmarks de _benchmark/benchmark_test.go em um Apple M1 com Go 1.25. unipdf não está direto na nossa suite porque os termos de licença tornavam estranho distribuir o código de comparação; os números abaixo são o que coletamos no mesmo hardware contra as mesmas cargas.
| Benchmark | gpdf | unipdf* | gofpdf | Maroto v2 |
|---|---|---|---|---|
| Página única | 13 µs | ~180 µs | 132 µs | 237 µs |
| Tabela 4×10 fatura | 108 µs | ~8,6 ms | 241 µs | 8,6 ms |
| Relatório 100 páginas | 683 µs | ~95 ms | 11,7 ms | 19,8 ms |
| Fatura CJK complexa | 133 µs | ~12 ms | 254 µs | 10,4 ms |
* Os números do unipdf vêm de uma rodada separada no mesmo Apple M1 / Go 1.25, capturados por nós contra o último unipdf v3 no momento da escrita. Trate como aproximados; não fazem parte da nossa suite versionada.
A forma é a mesma da comparação com gofpdf: gpdf é cerca de 10–80× mais rápido nas cargas que as pessoas rodam de verdade. A 108 µs por página com tabela, um único core produz ~9.000 faturas por segundo. O ponto não é vangloriar — é que você pode parar de pensar se vai cachear ou enfileirar a geração de PDF. Gerar no caminho de request basta para quase tudo.
E as partes que gpdf não tem?
Se sua fatura de unipdf paga OCR, redaction ou parsing de PDF, esta migração não te leva até o fim. As opções honestas:
- OCR. gpdf não faz OCR e dificilmente fará. Use Tesseract via gosseract, ou uma API de OCR hospedada. A geração fica no gpdf, o parsing fica no que você escolher.
- Parsing / extração de texto. gpdf é só geração por design. Para cargas de leitura, pdfcpu cobre muitos casos comuns (Apache 2.0). Mantenha unipdf só para parsing e talvez consiga reduzir o número de assentos.
- Criação de campos AcroForm. gpdf consegue achatar campos AcroForm existentes; ainda não consegue criar novos. Se você produz formulários para usuários preencherem em um visualizador, este é o gap que vai sentir. Está no roadmap.
- Redaction. Não está no roadmap do gpdf. Redaction precisa de um renderer de verdade para saber o que apagar, que é uma arquitetura diferente da geração.
Para o caminho de geração, cifragem, assinatura, fontes e CJK — o destino real da maior parte das faturas de unipdf — a troca é completa.
FAQ
gpdf é um fork do unipdf? Não. gpdf é uma reimplementação limpa em Go puro. Wire format do PDF, motor de layout, subseteador TrueType, AES, PKCS#7 — tudo escrito do zero. Sem código compartilhado, sem linhagem compartilhada e sem risco de uma discussão de licenciamento dar errado porque nada foi copiado.
gpdf é mesmo MIT? Sem "AGPL sob certa condição"? Sim. A LICENSE do repositório é a licença MIT literal, sem anexos, sem cláusulas de campo de uso, sem recortes de tier comercial. Use em produtos fechados distribuíveis, embuta em SaaS comercial, mande dentro de appliances on-prem. A única obrigação é a nota de licença e copyright na sua distribuição.
E as dependências transitivas — tem copyleft escondido por baixo?
O bloco require do go.mod do core do gpdf está vazio. Sem AGPL transitiva, sem GPL transitiva, sem nada transitivo. Verifique com go mod graph | grep gpdf depois do go get.
Tirar a chave de licença importa tanto assim? Para alguns times é tudo. A chave precisa morar no secret manager, ser rotacionada, ser auditada, ser incluída em cada imagem de container e não vazar em logs. Para um SaaS multi-tenant com centenas de pods, isso é uma superfície operacional de verdade. Eliminar a exigência apaga uma classe de incidentes.
Meu código atual de unipdf usa creator.Block.SetPos para posicionamento absoluto em todo lugar. gpdf tem equivalente?
Tem — page.Absolute(x, y, fn) deixa você colocar uma sub-árvore numa coordenada explícita. Mas se seu código está estruturado em torno de manipulação de cursor, o modelo de motor de layout é uma mudança mental, não sintática. Leia o post sobre a grade de 12 colunas antes de estimar; código reescrito costuma ser mais curto que o original.
E se a UniDoc relicenciar o unipdf para MIT um dia? Você ganha mais uma opção. A aposta por trás do gpdf não é que unipdf vai ficar AGPL para sempre; é que uma licença que exige uma chamada de registro no startup e renovação por desenvolvedor no nível financeiro é um imposto que não precisa existir para a maioria das cargas. Mesmo se relicenciassem amanhã, a superfície operacional da chave continuaria lá até eles tirarem.
Experimentar gpdf
gpdf é uma biblioteca Go para gerar PDFs. Licença MIT, zero dependências externas, sem chave de licença, suporte CJK nativo.
go get github.com/gpdf-dev/gpdf
⭐ Star no GitHub · Leia os docs