Todas as publicações

Como escalo uma imagem proporcionalmente para caber em uma coluna?

Passe os bytes para c.Image. gpdf já escala mantendo a proporção pela largura da coluna. Use FitWidth ou FitHeight quando precisar de um limite explícito.

A pergunta, em outras palavras

Tenho um logo, um gráfico ou um screenshot — digamos um PNG 1200×800 — e quero colocar dentro de uma das colunas do gpdf. Não quero fazer a conta da proporção na mão. Não quero que a imagem fique ovalada. Não quero que estoure para a coluna ao lado. Reduzir mantendo a proporção, e pronto.

TL;DR

c.Image(imgBytes)

No caso mais comum, essa é a receita inteira. c.Image usa FitContain por padrão, que reduz a imagem até a largura da coluna preservando a proporção original. Se a imagem já é menor que a coluna, o gpdf desenha no tamanho natural.

Precisa de um limite menor que a coluna inteira? Acrescente template.FitWidth ou template.FitHeight:

c.Image(imgBytes, template.FitWidth(document.Mm(40)))
c.Image(imgBytes, template.FitHeight(document.Mm(20)))

As duas opções preservam a proporção do original. Você especifica só uma dimensão; o gpdf calcula a outra.

Exemplo completo

package main

import (
    "log"
    "os"

    "github.com/gpdf-dev/gpdf"
    "github.com/gpdf-dev/gpdf/document"
    "github.com/gpdf-dev/gpdf/template"
)

func main() {
    logo, err := os.ReadFile("logo.png")
    if err != nil {
        log.Fatal(err)
    }
    chart, err := os.ReadFile("chart.png")
    if err != nil {
        log.Fatal(err)
    }

    doc := gpdf.NewDocument(
        gpdf.WithPageSize(gpdf.A4),
        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),
    )

    page := doc.AddPage()

    page.AutoRow(func(r *template.RowBuilder) {
        // Coluna estreita para o logo, fixada em 30mm
        r.Col(3, func(c *template.ColBuilder) {
            c.Image(logo, template.FitWidth(document.Mm(30)))
        })
        // Coluna larga para o gráfico, fit padrão usa toda a largura
        r.Col(9, func(c *template.ColBuilder) {
            c.Image(chart)
        })
    })

    data, err := doc.Generate()
    if err != nil {
        log.Fatal(err)
    }
    if err := os.WriteFile("report.pdf", data, 0o644); err != nil {
        log.Fatal(err)
    }
}

A célula do logo (3 colunas) usa FitWidth(30mm) porque queremos um logo pequeno e consistente independente do espaço da coluna. A do gráfico (9 colunas) leva um c.Image(chart) puro para usar toda a largura disponível. As duas mantêm a proporção. Nenhuma precisa conhecer as dimensões em píxeis do arquivo de origem.

O que "proporcional" significa exatamente no gpdf

São 4 modos de fit; um é o padrão e cobre uns 90% dos casos reais:

ModoO que fazQuando usar
FitContain (padrão)Reduz para caber dentro da caixa, preserva a proporção, pode deixar espaço sobrandoLogos, gráficos, screenshots — quase tudo
FitCoverEscala até cobrir toda a caixa preservando a proporção, corta o que sobraBanners hero, cortes quadrados de fotos de perfil
FitStretchEscala até preencher exatamente a caixa, distorce a proporçãoQuase nunca — quando você usa, normalmente é bug
FitOriginalRenderiza no tamanho original em píxeis convertido a 72 DPIDiagramas feitos em resolução de impressão que não devem ser reamostrados

FitWidth e FitHeight fixam uma dimensão e usam FitContain para a outra. São o atalho ergonômico para "me importa a largura" ou "me importa a altura" — quase nunca é necessário chamar WithFitMode direto.

A pegadinha clássica

O erro mais comum é informar width e height ao mesmo tempo, com valores que não batem com a proporção do original, e depois reclamar que a imagem ficou amassada. Tipo isto:

// Não faça isso a menos que seja realmente intencional
c.Image(img,
    template.FitWidth(document.Mm(40)),
    template.FitHeight(document.Mm(40)),
)

Se o PNG é 1200×800, forçar dentro de uma caixa 40×40 obriga a sacrificar uma de duas coisas: a proporção (comportamento FitStretch) ou parte da imagem (comportamento FitCover). O fit padrão é FitContain, então o gpdf mantém a proporção e deixa uma das dimensões sem preencher — a imagem sai 40mm de largura e ~26mm de altura, com espaço vazio embaixo na caixa de 40mm.

A solução é escolher uma dimensão e confiar no cálculo. Se você realmente precisa de um corte quadrado de uma imagem não quadrada, o que quer é FitCover, não duas dimensões em conflito:

c.Image(img,
    template.FitWidth(document.Mm(40)),
    template.FitHeight(document.Mm(40)),
    template.WithFitMode(document.FitCover),
)

Píxeis não mentem

O gpdf lê as dimensões intrínsecas em píxeis do header do PNG ou JPEG antes de qualquer decisão de escala. Então uma foto 4000×3000 jogada em uma coluna de 60mm não é "reduzida no original" — o gpdf incorpora os bytes inteiros e o leitor PDF faz a reamostragem na hora de renderizar. O PDF de saída tem o mesmo tamanho de arquivo independente do tamanho de exibição.

Se o tamanho do arquivo importa mais que a qualidade máxima de impressão, reduza a imagem você mesmo com algo como image/draw antes de passar para o gpdf. A biblioteca não vai jogar píxeis fora silenciosamente. Essa decisão é sua.

E se a coluna ficar estreita demais?

Se uma coluna acaba mais estreita do que o esperado na hora de renderizar — porque a página quebrou de forma inesperada, ou uma célula de tabela encolheu para caber no conteúdo — o FitContain padrão vai reduzir alegremente o seu logo até ficar do tamanho de um selo. Se isso incomoda, defina um piso:

c.Image(logo,
    template.FitWidth(document.Mm(30)),
    template.MinDisplayWidth(document.Mm(20)),
)

MinDisplayWidth diz ao motor de layout: se for preciso reduzir essa imagem abaixo de 20mm para caber, manda para a próxima página. A imagem sai legível ou não sai — nunca o pior dos dois mundos no meio.

Receitas relacionadas

Experimente o gpdf

gpdf é uma biblioteca Go para gerar PDFs. MIT, zero dependências externas, manuseio de imagens e fontes em Go puro.

go get github.com/gpdf-dev/gpdf

⭐ Star no GitHub · Leia a documentação