summaryrefslogtreecommitdiff
path: root/internal/export/export.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-24 16:50:45 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-24 16:50:45 +0200
commit29e244c48222278c2f255ff2adf45e3af7c9f895 (patch)
treeafa11818de1d8115eee1777f7bfc70757f218ac7 /internal/export/export.go
parent102bf0c522c94d36f8363c38711621fe5ad18555 (diff)
downloadlectio-29e244c48222278c2f255ff2adf45e3af7c9f895.tar.gz
lectio-29e244c48222278c2f255ff2adf45e3af7c9f895.zip
export: internal/export (Markdown/Text/ReadingsPDF via pure-Go go-pdf/fpdf + embedded DejaVu Sans, covers pl/la/gr); cli --md/--pdf/--out; web /export + download links; v0.22.0
Diffstat (limited to 'internal/export/export.go')
-rw-r--r--internal/export/export.go128
1 files changed, 128 insertions, 0 deletions
diff --git a/internal/export/export.go b/internal/export/export.go
new file mode 100644
index 0000000..96491db
--- /dev/null
+++ b/internal/export/export.go
@@ -0,0 +1,128 @@
+// 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
+}