전체 글

gpdf에서 굵게와 기울임을 동시에 쓰는 방법

같은 span에 template.Bold()와 template.Italic()을 함께 전달하면 된다. 다만 TrueType 글꼴은 네 개 변형을 모두 등록하지 않으면 BoldItalic 조회가 조용히 기본 패밀리로 폴백한다.

질문을 다시 쓰면

PDF 속 단어 하나 또는 한 줄을 굵게이면서 기울임으로 만들고 싶다. 둘을 한 번에 지정하려면 어떻게 쓰며, 왜 가끔 둘 다 아닌 것처럼 보이는 출력이 나오는가?

짧은 답

같은 c.Text 호출에 두 옵션을 모두 전달한다:

c.Text("WARNING", template.Bold(), template.Italic())

gpdf는 변형 ID Family-BoldItalic을 만들어 등록된 글꼴에서 찾는다. Adobe Standard 14 패밀리(Helvetica, Courier, Times)는 이대로 동작한다—gpdf가 -BoldItalic을 정식 이름 -BoldOblique로 내부 별칭 처리하고 내장 AFM 지표를 쓴다. 직접 등록하는 TrueType 글꼴의 경우 네 변형을 모두 등록해야 하며, 그렇지 않으면 조회가 조용히 기본 패밀리로 폴백한다.

버그 대부분은 두 번째 지점에서 생긴다.

동작하는 코드 (Helvetica, 글꼴 등록 없음)

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(gpdf.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("Regular Helvetica.")
            c.Text("Bold only.", template.Bold())
            c.Text("Italic only.", template.Italic())
            c.Text("Bold and italic.", template.Bold(), template.Italic())
        })
    })

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

네 줄에 네 스타일. WithFont 호출은 전혀 없다. 생성된 PDF는 Helvetica, Helvetica-Bold, Helvetica-Oblique, Helvetica-BoldOblique를 비임베드 Type 1 항목으로 참조한다. 모든 PDF 뷰어가 이미 가지고 있는 글꼴들이다.

gpdf가 실제로 하는 일

리졸버는 스타일 플래그에서 변형 ID를 만든다:

Bold()Italic()조회하는 변형 ID
아니오아니오Helvetica
아니오Helvetica-Bold
아니오Helvetica-ItalicHelvetica-Oblique로 별칭
Helvetica-BoldItalicHelvetica-BoldOblique로 별칭

별칭 단계가 Helvetica의 유일한 특수 처리다. buildFontVariantID는 패밀리와 무관하게 일반 -Italic / -BoldItalic 접미사를 항상 붙이고, 이후 Standard 14의 init 훅이 Helvetica-ItalicHelvetica-Oblique로, Helvetica-BoldItalicHelvetica-BoldOblique로 매핑하여 지표가 뷰어의 출력과 일치하도록 한다. Courier도 같은 방식. Times는 정식 이름 자체가 Times-Italic / Times-BoldItalic이라 별칭이 필요 없다.

함정: TrueType 글꼴은 네 개를 모두 등록해야 한다

CJK 문서가 조용히 깨지는 지점이 여기다. Noto Sans JP를 등록해도 변형 중 하나를 빼먹으면, 빠진 슬롯은 Bold나 Italic을 거치지 않고 곧바로 기본 패밀리로 떨어진다.

// 그럴싸해 보이지만 아니다.
doc := gpdf.NewDocument(
    gpdf.WithFont("NotoSansJP", regular),
    gpdf.WithFont("NotoSansJP-Bold", bold),
    gpdf.WithDefaultFont("NotoSansJP", 12),
)

// 여기는 평범한 NotoSansJP로 렌더링된다 — 굵지도 기울지도 않다.
c.Text("강조 텍스트", template.Bold(), template.Italic())

이유는 리졸버 구현에 있다. 먼저 NotoSansJP-BoldItalic을 찾고, 없으면 딱 한 가지, 기본 패밀리 NotoSansJP로만 떨어진다. "아쉬운 대로 Bold 버전"이라는 중간 단계가 존재하지 않는다. bold-italic을 요청했지만 일반을 받게 된다.

해결은 쓸 변형을 전부 등록하는 것이다:

package main

import (
    "log"
    "os"

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

func main() {
    regular := mustRead("NotoSansJP-Regular.ttf")
    bold := mustRead("NotoSansJP-Bold.ttf")
    italic := mustRead("NotoSansJP-Italic.ttf")
    boldItalic := mustRead("NotoSansJP-BoldItalic.ttf")

    doc := gpdf.NewDocument(
        gpdf.WithPageSize(gpdf.A4),
        gpdf.WithFont("NotoSansJP", regular),
        gpdf.WithFont("NotoSansJP-Bold", bold),
        gpdf.WithFont("NotoSansJP-Italic", italic),
        gpdf.WithFont("NotoSansJP-BoldItalic", boldItalic),
        gpdf.WithDefaultFont("NotoSansJP", 12),
    )

    page := doc.AddPage()
    page.AutoRow(func(r *template.RowBuilder) {
        r.Col(12, func(c *template.ColBuilder) {
            c.Text("일반 텍스트")
            c.Text("강조", template.Bold(), template.Italic())
        })
    })

    data, _ := doc.Generate()
    os.WriteFile("jp-emphasis.pdf", data, 0o644)
}

func mustRead(path string) []byte {
    b, err := os.ReadFile(path)
    if err != nil { log.Fatal(err) }
    return b
}

덧붙이자면, Noto Sans JP 공식 배포에는 기울임(slanted) 컷이 애초에 없다—일본어 조판에서 기울임 자체가 흔치 않다—그래서 실제 일본어 문서 대부분은 regular와 bold만 등록하고 일본어 span에는 template.Italic()을 쓰지 않는다. 그래도 아무 문제 없다. 규칙은 이렇다: 그 패밀리에 한 번도 Italic()을 호출하지 않는다면 기울임 변형은 등록할 필요가 없다. Italic()을 호출하면서 파일을 등록하지 않았을 때만 함정이 된다.

한 문단 안에서 굵게와 기울임 섞기

c.Text는 문자열 전체에 하나의 스타일을 적용한다. 문장 가운데를 강조하려면 c.RichText를 쓴다:

c.RichText(func(rt *template.RichTextBuilder) {
    rt.Span("The ")
    rt.Span("quick brown fox", template.Bold(), template.Italic())
    rt.Span(" jumps over the lazy dog.")
})

rt.Span은 고유 스타일 플래그를 갖고, 레이아웃 엔진은 워드 프로세서처럼 span 간 줄바꿈을 처리한다. 하나의 SpanBold() + Italic()을 함께 주는 것은 c.Text에서와 동일한 -BoldItalic 변형 조회를 거친다 — 같은 코드 경로다.

짚어둘 점 하나: Bold()Italic()은 가환(commutative)이다. template.Italic(), template.Bold()template.Bold(), template.Italic()은 동일한 출력을 낸다. 같은 document.Style의 서로 다른 필드(FontWeightFontStyle)를 설정하는 것뿐이라 순서는 영향이 없다.

관련 레시피

gpdf 써보기

gpdf는 Go용 PDF 생성 라이브러리다. MIT 라이선스, 외부 의존성 없음, CJK 네이티브 지원.

go get github.com/gpdf-dev/gpdf

⭐ Star on GitHub · 문서 읽기