Todas as publicações

Como uso negrito e itálico juntos no gpdf?

Basta passar template.Bold() e template.Italic() no mesmo span. Mas fontes TrueType exigem as quatro variantes registradas, senão a busca por BoldItalic cai silenciosamente para a família base.

A pergunta, em outras palavras

Quero que uma palavra — ou uma linha inteira — apareça ao mesmo tempo em negrito e em itálico dentro do PDF. Como aplicar os dois estilos de uma vez, e por que às vezes o resultado não parece nem um nem outro?

A resposta curta

Passe as duas opções na mesma chamada c.Text:

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

O gpdf monta o ID de variante Family-BoldItalic e procura isso nas fontes registradas. Para as famílias Adobe Standard 14 (Helvetica, Courier, Times) funciona direto — o gpdf aliaseia internamente -BoldItalic para o nome canônico -BoldOblique e usa as métricas AFM embutidas. Para uma fonte TrueType que você mesmo registra, é preciso registrar as quatro variantes, senão a busca cai silenciosamente na família base.

É nesse segundo ponto que moram a maioria dos bugs.

Código funcional (Helvetica, sem registrar fontes)

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)
    }
}

Quatro linhas, quatro estilos visíveis. Nenhuma chamada WithFont. O PDF referencia Helvetica, Helvetica-Bold, Helvetica-Oblique e Helvetica-BoldOblique como entradas Type 1 não-embutidas, que todo visualizador de PDF já tem.

O que o gpdf realmente faz

O resolver monta o ID de variante a partir das flags de estilo:

Bold()Italic()ID de variante pesquisado
nãonãoHelvetica
simnãoHelvetica-Bold
nãosimHelvetica-Italic → alias para Helvetica-Oblique
simsimHelvetica-BoldItalic → alias para Helvetica-BoldOblique

O passo de alias é a única coisa que torna Helvetica especial. buildFontVariantID sempre emite os sufixos genéricos -Italic / -BoldItalic, independentemente da família; depois o hook de init do Standard 14 aponta Helvetica-Italic para Helvetica-Oblique e Helvetica-BoldItalic para Helvetica-BoldOblique, fazendo com que as métricas batam com o que o visualizador desenha. Courier tem o mesmo tratamento. Times não precisa de alias porque o nome canônico já é Times-Italic / Times-BoldItalic.

A armadilha: fontes TrueType exigem quatro registros

É aqui que documentos CJK quebram em silêncio. Se você registra o Noto Sans JP mas esquece uma variante, o slot faltante não passa por Bold ou Italic como consolação — ele cai direto na família base.

// Parece certo. Não está.
doc := gpdf.NewDocument(
    gpdf.WithFont("NotoSansJP", regular),
    gpdf.WithFont("NotoSansJP-Bold", bold),
    gpdf.WithDefaultFont("NotoSansJP", 12),
)

// Aqui renderiza em NotoSansJP normal — nem negrito, nem itálico.
c.Text("強調したい", template.Bold(), template.Italic())

A razão está na implementação do resolver. Primeiro busca NotoSansJP-BoldItalic, não acha, e cai em exatamente uma coisa: a família base NotoSansJP. Não existe etapa intermediária que tente a versão em negrito como prêmio de consolação. Você pediu bold-italic, recebeu regular.

A correção é registrar cada variante que pretende usar:

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("Texto normal")
            c.Text("ênfase", 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
}

A propósito: a distribuição oficial do Noto Sans JP na prática não traz um corte itálico (slanted) — tipografia japonesa raramente usa itálico — então a maioria dos documentos em japonês registra só regular e bold e simplesmente não chama template.Italic() em spans japoneses. Tudo bem assim. A regra é: se você nunca chama Italic() em uma família, não precisa da variante itálica dela. A armadilha só surge quando você chama Italic() sem ter registrado o arquivo.

Misturar negrito e itálico no mesmo parágrafo

c.Text aplica um único estilo a toda a string. Para ênfase no meio da frase use 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.")
})

Cada rt.Span tem suas próprias flags de estilo, e o motor de layout faz as quebras de linha entre spans como um processador de texto faria. Usar Bold() + Italic() em um único Span cai no mesmo lookup da variante -BoldItalic que o c.Text — é o mesmo caminho de código.

Um detalhe que vale nomear: Bold() e Italic() são comutativos. template.Italic(), template.Bold() e template.Bold(), template.Italic() produzem saídas idênticas. Elas setam dois campos diferentes (FontWeight e FontStyle) no mesmo document.Style, então a ordem não importa.

Receitas relacionadas

Experimente o gpdf

O gpdf é uma biblioteca Go para gerar PDFs. Licença MIT, zero dependências externas, suporte CJK nativo.

go get github.com/gpdf-dev/gpdf

⭐ Star on GitHub · Leia a documentação