mirror of
https://github.com/tiennm99/monkeyd-crawler.git
synced 2026-09-09 02:17:28 +00:00
PDF rendering failed on Linux with "stat usr/share/fonts/...: no such file or directory": fpdf joins the font path onto its own font directory, which it defaults to ".", so path.Join turns an absolute path into a working-directory-relative one. It only resolved when the process happened to run from the filesystem root, which is why Windows was unaffected. Font data is now read by pdfout and handed over as bytes. LoadFont also falls back to a bundled DejaVu Sans, so a host with no fonts installed still renders. An explicitly requested font remains a hard error when unreadable rather than being silently substituted. Tests verify the bundled font parses and covers Vietnamese, which is the reason it exists.
109 lines
3.5 KiB
Go
109 lines
3.5 KiB
Go
package pdfout
|
|
|
|
import (
|
|
_ "embed"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
)
|
|
|
|
// bundledTTF is the font used when nothing else is available, so rendering never
|
|
// depends on the host having fonts installed — a minimal container typically has
|
|
// none. See fonts/NOTICE.md for provenance and licensing.
|
|
//
|
|
//go:embed fonts/DejaVuSans.ttf
|
|
var bundledTTF []byte
|
|
|
|
// BundledFontName labels the embedded font in diagnostics. It is not a path;
|
|
// the font is compiled into the binary.
|
|
const BundledFontName = "DejaVu Sans (bundled)"
|
|
|
|
// Font is font data ready to embed in a PDF.
|
|
//
|
|
// The data is carried as bytes rather than as a path because fpdf joins a font
|
|
// path onto its own font directory, which it defaults to "." — turning an
|
|
// absolute path into a working-directory-relative one that resolves only when
|
|
// the process happens to run from the filesystem root.
|
|
type Font struct {
|
|
// Name identifies the font for diagnostics: the file path it was read
|
|
// from, or BundledFontName.
|
|
Name string
|
|
Data []byte
|
|
}
|
|
|
|
// Vietnamese text needs the Latin Extended Additional block (ư, ạ, ế, ộ …).
|
|
// Every font listed here ships with its platform and covers it; fonts with only
|
|
// basic Latin would silently drop the diacritics.
|
|
func fontCandidates() []string {
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
dir := filepath.Join(os.Getenv("SystemRoot"), "Fonts")
|
|
if os.Getenv("SystemRoot") == "" {
|
|
dir = `C:\Windows\Fonts`
|
|
}
|
|
return []string{
|
|
filepath.Join(dir, "segoeui.ttf"),
|
|
filepath.Join(dir, "arial.ttf"),
|
|
filepath.Join(dir, "calibri.ttf"),
|
|
filepath.Join(dir, "tahoma.ttf"),
|
|
filepath.Join(dir, "times.ttf"),
|
|
}
|
|
case "darwin":
|
|
return []string{
|
|
"/System/Library/Fonts/Supplemental/Arial.ttf",
|
|
"/Library/Fonts/Arial.ttf",
|
|
"/System/Library/Fonts/Supplemental/Times New Roman.ttf",
|
|
}
|
|
default:
|
|
return []string{
|
|
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
|
"/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf",
|
|
"/usr/share/fonts/TTF/DejaVuSans.ttf",
|
|
"/usr/share/fonts/dejavu/DejaVuSans.ttf",
|
|
"/usr/share/fonts/liberation/LiberationSans-Regular.ttf",
|
|
}
|
|
}
|
|
}
|
|
|
|
// FindFont returns the first available system font suitable for Vietnamese.
|
|
// Callers that just need something that works should use LoadFont, which falls
|
|
// back to the bundled font instead of failing.
|
|
func FindFont() (string, error) {
|
|
candidates := fontCandidates()
|
|
for _, path := range candidates {
|
|
if info, err := os.Stat(path); err == nil && !info.IsDir() {
|
|
return path, nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("no Vietnamese-capable system font found (looked for %v)", candidates)
|
|
}
|
|
|
|
// LoadFont resolves the font to embed.
|
|
//
|
|
// An explicit path wins, and is a hard error when it cannot be read: a caller
|
|
// that named a font wants that font, not a substitute. Otherwise a system font
|
|
// is used, and when none can be read the bundled font is returned — so with an
|
|
// empty path LoadFont always succeeds.
|
|
func LoadFont(path string) (Font, error) {
|
|
if path != "" {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return Font{}, fmt.Errorf("read font: %w", err)
|
|
}
|
|
return Font{Name: path, Data: data}, nil
|
|
}
|
|
if found, err := FindFont(); err == nil {
|
|
if data, err := os.ReadFile(found); err == nil {
|
|
return Font{Name: found, Data: data}, nil
|
|
}
|
|
// A listed font that cannot be read is no better than a missing one.
|
|
}
|
|
return BundledFont(), nil
|
|
}
|
|
|
|
// BundledFont returns the font compiled into the binary.
|
|
func BundledFont() Font {
|
|
return Font{Name: BundledFontName, Data: bundledTTF}
|
|
}
|