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ão | não | Helvetica |
| sim | não | Helvetica-Bold |
| não | sim | Helvetica-Italic → alias para Helvetica-Oblique |
| sim | sim | Helvetica-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
- Como embutir uma fonte japonesa no gpdf — passo-a-passo completo de
WithFontcom o padrão de quatro variantes - Por que meu PDF mostra quadrados vazios (tofu) em japonês? — como o "fallback silencioso" aparece quando nem a família base está registrada
- Como usar Noto Sans JP com gpdf — qual arquivo Noto escolher e como
go:embedsimplifica a distribuição
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