// Package export renders a day's readings (or a passage) to shareable formats: // plain text, Markdown, and PDF -- plus a printable A4 month calendar. The PDF // uses an embedded DejaVu Sans (regular + bold) so Polish, Latin and Greek all // render; it is pure Go (no cgo), keeping the single-binary cross-compile. package export import ( "bytes" _ "embed" "fmt" "strings" "github.com/go-pdf/fpdf" "github.com/lukaszkasprzak/lectio/internal/i18n" "github.com/lukaszkasprzak/lectio/internal/liturgy" "github.com/lukaszkasprzak/lectio/internal/render" ) // versionLabel is the localized name of a version (i18n), or the bare code. func versionLabel(version, lang string) string { if l, ok := i18n.Get(lang).Version[version]; ok { return l } return version } // title is the document heading: the celebration name if the source carries // one, else a plain "readings for DATE" style line. func title(date string, info liturgy.DayInfo) string { if info.Name != "" { return info.Name } return date } // Text renders a day's readings as plain UTF-8 text: a title, the day-info, // then each section's localized heading and its version blocks. func Text(date string, info liturgy.DayInfo, secs []liturgy.Section, version, lectionary, lang string) string { var b strings.Builder fmt.Fprintf(&b, "%s\n", title(date, info)) if info.Season != "" { fmt.Fprintf(&b, "%s\n", info.Season) } fmt.Fprintf(&b, "%s · %s\n\n", date, versionLabel(version, lang)) for _, sec := range secs { fmt.Fprintf(&b, "%s\n\n", render.HeadingWithRef(sec, lang)) _, blocks := render.GatherVersion(version, sec, lectionary, lang) for _, blk := range blocks { fmt.Fprintf(&b, "%s\n", blk) } b.WriteString("\n") } return b.String() } // Markdown renders a day's readings as Markdown (# title, ## section headings, // verse/paragraph blocks). func Markdown(date string, info liturgy.DayInfo, secs []liturgy.Section, version, lectionary, lang string) string { var b strings.Builder fmt.Fprintf(&b, "# %s\n\n", title(date, info)) if info.Season != "" { fmt.Fprintf(&b, "*%s*\n\n", info.Season) } fmt.Fprintf(&b, "**%s · %s**\n\n", date, versionLabel(version, lang)) for _, sec := range secs { fmt.Fprintf(&b, "## %s\n\n", render.HeadingWithRef(sec, lang)) _, blocks := render.GatherVersion(version, sec, lectionary, lang) for _, blk := range blocks { fmt.Fprintf(&b, "%s\n\n", blk) } } return b.String() } // ReadingsPDF renders a day's readings as a clean printable PDF. func ReadingsPDF(date string, info liturgy.DayInfo, secs []liturgy.Section, version, lectionary, lang string) ([]byte, error) { pdf := newPDF() pdf.AddPage() pdf.SetFont(fontFamily, "B", 18) pdf.MultiCell(0, 9, title(date, info), "", "L", false) if info.Season != "" { pdf.SetFont(fontFamily, "", 10) pdf.MultiCell(0, 6, info.Season, "", "L", false) } pdf.SetFont(fontFamily, "", 10) pdf.MultiCell(0, 6, date+" · "+versionLabel(version, lang), "", "L", false) pdf.Ln(3) for _, sec := range secs { pdf.Ln(2) pdf.SetFont(fontFamily, "B", 13) pdf.MultiCell(0, 7, render.HeadingWithRef(sec, lang), "", "L", false) pdf.Ln(1) pdf.SetFont(fontFamily, "", 11) _, blocks := render.GatherVersion(version, sec, lectionary, lang) for _, blk := range blocks { pdf.MultiCell(0, 6, blk, "", "L", false) pdf.Ln(1) } } var buf bytes.Buffer if err := pdf.Output(&buf); err != nil { return nil, err } return buf.Bytes(), nil } //go:embed fonts/DejaVuSans.ttf var fontRegular []byte //go:embed fonts/DejaVuSans-Bold.ttf var fontBold []byte const fontFamily = "dejavu" // newPDF returns an A4 portrait document with the embedded Unicode font // registered (regular + bold) and sensible margins/auto page break. func newPDF() *fpdf.Fpdf { pdf := fpdf.New("P", "mm", "A4", "") pdf.AddUTF8FontFromBytes(fontFamily, "", fontRegular) pdf.AddUTF8FontFromBytes(fontFamily, "B", fontBold) pdf.SetMargins(18, 16, 18) pdf.SetAutoPageBreak(true, 16) return pdf }