aboutsummaryrefslogtreecommitdiff
path: root/internal/export/export.go
blob: 96491dbfc7729c1b455eef1ccb194d6afe66c52e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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
}