Files
tiennm99 d17a79445f feat(pdf): export a novel as a phone-readable PDF
Add a pdfout package that renders the fetched chapters to a single PDF
and an export package holding the whole pipeline, so the CLI and an
embedding program share one code path and reject the same inputs.

The default page is 90x160mm rather than A4 with large type: a viewer
scales a whole page to fit the screen, so on a phone the page shape
decides legibility and a wide page just gets shrunk.

Embed DejaVu Sans in the binary. Vietnamese needs the Latin Extended
Additional block, and a headless host often has no fonts installed at
all, so rendering must not depend on finding one. An explicitly named
font is never silently substituted. Font data is passed as bytes because
fpdf resolves a font path against its own font directory.

Keep the original per-chapter text output available behind -txt.
2026-08-24 20:40:01 +07:00

56 lines
1.4 KiB
Go

package pdfout
import (
"os"
"path/filepath"
"testing"
)
// Write must produce a real PDF with the bundled font, since the host this runs
// on has no fonts installed.
func TestWrite(t *testing.T) {
path := filepath.Join(t.TempDir(), "book.pdf")
opts := Options{
Page: Presets["phone"],
Margin: 6,
Font: BundledFont(),
FontSize: 10,
LineSpacing: 1.55,
Title: "Truyện Thử",
SourceURL: "https://ln.hako.vn/sang-tac/1-x",
}
chapters := []Chapter{
{Heading: "Chương 01", Volume: "Tập 01", Paragraphs: []string{"Trời hôm nay đẹp lắm."}},
{Heading: "Chương 02", Volume: "Tập 01", Paragraphs: []string{"Người anh trai mỉm cười."}},
{Heading: "Minh Họa 01", Volume: "Minh Họa", Paragraphs: nil},
}
if err := Write(path, opts, chapters); err != nil {
t.Fatalf("write: %v", err)
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read back: %v", err)
}
if len(data) < 1024 {
t.Errorf("pdf is %d bytes, which is too small to hold an embedded font", len(data))
}
if string(data[:5]) != "%PDF-" {
t.Errorf("missing PDF header, got %q", data[:5])
}
}
func TestPresetNamesAreStable(t *testing.T) {
want := []string{"a4", "a5", "phone"}
got := PresetNames()
if len(got) != len(want) {
t.Fatalf("got %v, want %v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("got %v, want %v", got, want)
}
}
}