전체 글

gpdf에서 이미지를 비율 유지로 컬럼에 맞추려면?

c.Image에 바이트만 넘기면 gpdf가 컬럼 너비에 비율 유지로 맞춘다. 명시적 크기는 FitWidth / FitHeight를 쓴다.

질문을 다시 말하면

로고, 차트, 스크린샷 — 예를 들어 1200×800 PNG — 를 gpdf 컬럼에 넣고 싶다. 가로세로비 계산을 손으로 하고 싶지 않다. 타원처럼 늘어나는 것도 싫고, 옆 컬럼으로 넘쳐 흐르는 것도 싫다. 그냥 비율 유지하면서 줄여서 맞추고 끝내고 싶다.

한 줄 답

c.Image(imgBytes)

대부분의 경우 이게 전부다. c.Image의 기본은 FitContain이고, 가로세로비를 유지한 채 컬럼 너비에 맞춰 축소한다. 이미지가 이미 컬럼보다 작다면 원본 크기로 그대로 그려진다.

컬럼 전체가 아니라 더 작은 한계가 필요하다면 template.FitWidthtemplate.FitHeight를 추가한다:

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

둘 다 원본 비율을 유지한다. 한쪽 차원만 지정하면 나머지는 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() {
    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) {
        // 로고용 좁은 컬럼, 30mm로 고정
        r.Col(3, func(c *template.ColBuilder) {
            c.Image(logo, template.FitWidth(document.Mm(30)))
        })
        // 차트용 넓은 컬럼, 기본 fit으로 가용 폭 전체 사용
        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)
    }
}

3 컬럼 짜리 로고 셀은 FitWidth(30mm)으로 고정한다. 컬럼 폭이 어떻게 변해도 로고는 항상 30mm. 9 컬럼 짜리 차트 셀은 c.Image(chart)만 써서 컬럼이 주는 만큼 다 쓴다. 둘 다 비율 유지. 코드에서 원본 픽셀 수를 알 필요가 없다.

gpdf의 "비율 유지"가 실제로 의미하는 것

fit 모드는 4 개. 그중 기본 1 개로 실용 사례의 90%가 끝난다:

모드동작쓰는 곳
FitContain (기본)비율 유지하면서 박스 안에 맞춰 축소. 한쪽 차원에 여백이 남을 수 있음로고, 차트, 스크린샷 — 거의 전부
FitCover비율 유지하면서 박스 전체를 덮음. 넘치는 부분은 잘림히어로 배너, 프로필 사진 정사각 크롭
FitStretch박스에 정확히 맞춰 늘림. 비율이 망가짐거의 안 씀. 쓰면 보통 버그
FitOriginal72 DPI 환산한 원본 픽셀 크기로 그림인쇄 해상도로 만든 도해를 리샘플링 없이 쓰고 싶을 때

FitWidthFitHeight는 둘 다 한쪽 차원을 고정하고 다른 쪽은 FitContain으로 계산한다. "폭이 신경 쓰임" 또는 "높이가 신경 쓰임"을 자연스럽게 쓰기 위한 단축 형태이고, WithFitMode를 직접 부를 일은 거의 없다.

자주 빠지는 함정

가장 흔한 실수는 비율이 안 맞는 width와 height를 둘 다 지정해놓고 이미지가 짜부라졌다고 불평하는 케이스:

// 진심으로 의도한 게 아니면 쓰지 말 것
c.Image(img,
    template.FitWidth(document.Mm(40)),
    template.FitHeight(document.Mm(40)),
)

PNG가 1200×800인데 40×40 박스에 억지로 넣으면 둘 중 하나는 깨져야 한다 — 가로세로비 (FitStretch 동작) 아니면 이미지 일부 (FitCover 동작). 기본 fit 모드는 FitContain이라 gpdf는 비율을 유지하면서 한쪽을 못 채운 채로 둔다. 결과는 폭 40mm, 높이 약 26mm. 40mm 짜리 슬롯의 아래쪽 14mm가 비게 된다.

해법은 한쪽 차원만 지정하고 계산은 맡기는 것. 정말 정사각으로 잘라내고 싶다면 모순된 두 차원이 아니라 FitCover를 써야 한다:

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

픽셀 수는 거짓말하지 않는다

gpdf는 스케일 결정을 내리기 전에 PNG / JPEG 헤더에서 원본 픽셀 크기를 읽는다. 그래서 4000×3000 사진을 60mm 컬럼에 넣어도 "원본에서 줄여서 들어가는" 게 아니다. gpdf는 원본 바이트 그대로 PDF에 임베드하고, 리샘플링은 PDF 리더가 렌더링할 때 한다. 표시 크기를 어떻게 바꿔도 출력 PDF 크기는 같다.

파일 크기가 인쇄 품질보다 중요하다면 image/draw 같은 걸로 먼저 다운샘플링하고 gpdf에 넘긴다. 라이브러리가 알아서 픽셀을 버리지는 않는다 — 그건 호출자가 정할 일이다.

레이아웃 오버플로 대비

페이지 분할이나 좁아진 테이블 셀 때문에 컬럼이 예상보다 좁아지면, 기본 FitContain은 로고를 우표 크기까지 줄여버린다. 그게 싫으면 하한을 정한다:

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

MinDisplayWidth는 레이아웃 엔진에 "20mm 이하로 줄여야 들어간다면 이 페이지에 그리지 말고 다음 페이지로 보내라"고 말한다. 읽을 수 있는 크기로 그리거나, 아예 그리지 않거나 — 어중간한 중간 상태는 없다.

관련 레시피

gpdf 써보기

gpdf는 Go의 PDF 생성 라이브러리. MIT, 외부 의존성 0, 이미지와 폰트를 순수 Go로 처리.

go get github.com/gpdf-dev/gpdf

⭐ GitHub에서 Star · 문서 보기