Como misturo duas fontes no mesmo parágrafo no gpdf?
Para misturar fontes em um parágrafo no gpdf, use c.RichText e defina template.FontFamily em cada span. c.Text só aplica uma fonte à string inteira.
A pergunta, em outras palavras
Tenho um parágrafo — uma frase, um rótulo, uma célula de tabela — e quero parte dele em uma fonte e parte em outra. Um trecho de código em monoespaçada dentro de uma linha em Helvetica. Um nome em japonês em Noto Sans JP ao lado de um número de pedido ASCII. Como troco de fonte no meio do parágrafo sem quebrar o texto em blocos separados?
A resposta rápida
c.Text é a ferramenta errada aqui. Aplica um único document.Style — uma única família de fonte incluída — à string inteira. A que você quer é c.RichText, onde cada span carrega seu próprio estilo:
c.RichText(func(rt *template.RichTextBuilder) {
rt.Span("Run ")
rt.Span("gofmt ./...", template.FontFamily("Courier"))
rt.Span(" before you commit.")
})
Três spans, duas fontes, um parágrafo. O motor de layout quebra linha atravessando os limites dos spans como um processador de texto, então o trecho monoespaçado flui em linha com o Helvetica ao redor.
Courier funciona sem chamar WithFont porque é uma das fontes Standard 14 do PDF — todo leitor já tem, igual a Helvetica e Times-Roman. Se a sua segunda fonte é um arquivo TrueType que você fornece (uma fonte de marca, uma fonte CJK), você a registra uma vez e a referencia pelo nome. Mais abaixo.
Código que funciona (Helvetica + Courier, sem arquivos de fonte)
package main
import (
"log"
"os"
"github.com/gpdf-dev/gpdf"
"github.com/gpdf-dev/gpdf/document"
"github.com/gpdf-dev/gpdf/pdf"
"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.RichText(func(rt *template.RichTextBuilder) {
rt.Span("Run ")
rt.Span("gofmt ./...", template.FontFamily("Courier"))
rt.Span(" before every commit. ")
rt.Span("It is not optional", template.Bold(), template.Italic())
rt.Span(".")
})
c.RichText(func(rt *template.RichTextBuilder) {
rt.Span("The field is ")
rt.Span("created_at", template.FontFamily("Courier"), template.TextColor(pdf.RGBHex(0xB00020)))
rt.Span(" — not ")
rt.Span("createdAt", template.FontFamily("Courier"))
rt.Span(".")
})
})
})
data, err := doc.Generate()
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("mixed-fonts.pdf", data, 0o644); err != nil {
log.Fatal(err)
}
}
O corpo fica em Helvetica (a fonte padrão), os identificadores em linha trocam para Courier, e um span sobrepõe bold + italic sobre a fonte padrão. Sem WithFont, sem dados de fonte embutidos — o PDF referencia Helvetica, Helvetica-BoldOblique e Courier como entradas Type 1 não embutidas que todo leitor já tem.
O que o RichText faz com os spans
Cada rt.Span vira um document.RichTextFragment com sua própria cópia do estilo. Um span que você chama sem opções herda o estilo de bloco — que no RichText é o da coluna, ou seja, a fonte e o tamanho padrão do documento. Um span que você chama com template.FontFamily("Courier") sobrescreve só aquele campo e mantém todo o resto.
No layout, o gpdf divide cada fragmento em runs de palavra, mede cada run com as métricas da fonte daquele run — é por isso que uma palavra em Courier e outra em Helvetica na mesma linha saem com a largura certa — e então empacota os runs em linhas de forma gulosa. Todos os runs de uma linha compartilham uma linha de base, então um span de 24 pt ao lado de um de 12 pt se alinham embaixo e a altura da linha cresce para caber o alto.
Tem uma distinção que confunde: o segundo argumento de c.RichText é estilo de nível parágrafo, e as opções por span são de nível fragmento:
| Opção | Onde vai |
|---|---|
FontFamily / FontSize / Bold / Italic / TextColor / Underline / Strikethrough | por span — passe para cada rt.Span |
AlignLeft / AlignCenter / AlignRight / AlignJustify, altura de linha, TextIndent | nível parágrafo — passe como segundo argumento de c.RichText |
Pôr AlignRight() num rt.Span individual não faz nada. O alinhamento é uma propriedade da linha, não do fragmento.
O caso de verdade: uma fonte latina ao lado de uma CJK
Monoespaçada dentro de uma frase é a versão fácil. Com a qual o pessoal realmente briga é misturar uma fonte ocidental e uma CJK numa linha — um rótulo em inglês e um valor em japonês, um código de produto e um 商品名. Duas coisas para saber.
Primeira, o gpdf não escolhe fonte por sistema de escrita. Se a família de um span é Helvetica e o texto é 日本語, saem quadradinhos de tofu (□) — Helvetica não tem glifos CJK, e o gpdf não vai puxar em silêncio outra fonte registrada para cobrir. Coloque você mesmo a família CJK no span CJK:
ttf, _ := os.ReadFile("NotoSansJP-Regular.ttf")
doc := gpdf.NewDocument(
gpdf.WithFont("NotoSansJP", ttf),
)
// ...
c.RichText(func(rt *template.RichTextBuilder) {
rt.Span("Customer: ") // padrão → Helvetica
rt.Span("山田 太郎", template.FontFamily("NotoSansJP")) // CJK → Noto Sans JP
rt.Span(" (ID 10293)") // de volta ao Helvetica
})
Segunda — e isso vale dizer em voz alta — a maioria das fontes CJK japonesas já trazem glifos latinos decentes. Noto Sans JP, IPAex, Source Han Sans: todas desenham ID 10293 perfeitamente bem. Então, antes de partir para uma mistura span a span, pergunte-se se você realmente quer duas fontes ou só chegou aí por hábito. Se o documento todo é japonês-com-um-pouco-de-ASCII, o mais simples é gpdf.WithDefaultFont("NotoSansJP", 11) e não misturar nada. Recorra a RichText + FontFamily quando você realmente quiser uma aparência diferente — uma face latina geométrica e limpa para os números, uma CJK humanista para o texto — não só para a escrita renderizar.
Quando c.Text ainda serve
Se a string toda é uma só fonte, continue usando c.Text — é mais leve e lê melhor. c.Text("発行日: 2026-05-11", template.FontFamily("NotoSansJP")) é uma fonte para a linha inteira, e o c.Text resolve. O RichText se justifica só quando o estilo muda dentro da string. Não envolva uma linha de estilo único num callback de RichText só porque dá.
Receitas relacionadas
- Como uso negrito e itálico juntos no gpdf? — o mesmo mecanismo de spans do
RichText, aplicado ao peso e à inclinação em vez da família - Como adiciono uma fonte TrueType personalizada ao gpdf? — registrar a segunda fonte que você quer misturar
- Como incorporo uma fonte japonesa no gpdf? — o passo a passo de
WithFontpara o lado CJK de uma linha com fontes misturadas
Experimente o gpdf
gpdf é uma biblioteca Go para gerar PDF. Licença MIT, zero dependências externas, suporte nativo a CJK.
go get github.com/gpdf-dev/gpdf