| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- package office
- /*
- pdf.go - shared plumbing for the server-side PDF exporters
- (pdf_doc.go / pdf_sheet.go / pdf_slides.go).
- Built on github.com/go-pdf/fpdf (MIT). Text is written as REAL text
- objects (selectable / editable in PDF editors), not page screenshots.
- The core PDF fonts are Latin-1 (cp1252): characters outside that set
- are transliterated by fpdf's unicode translator and may degrade -
- embedding a full Unicode font is a deliberate non-goal for now (it
- would grow the binary by megabytes).
- */
- import (
- "bytes"
- "fmt"
- "strings"
- "github.com/go-pdf/fpdf"
- )
- const pxToMM = 25.4 / 96.0
- // pdfSetFillHex / pdfSetTextHex apply "#rrggbb" (or rgb()) colors
- func pdfColor(c string) (int, int, int, bool) {
- hexv := cssColorHex(c)
- if len(hexv) != 6 {
- return 0, 0, 0, false
- }
- var r, g, b int
- if _, err := fmt.Sscanf(hexv, "%02X%02X%02X", &r, &g, &b); err != nil {
- return 0, 0, 0, false
- }
- return r, g, b, true
- }
- func pdfSetTextHex(pdf *fpdf.Fpdf, c string, defR, defG, defB int) {
- if r, g, b, ok := pdfColor(c); ok {
- pdf.SetTextColor(r, g, b)
- } else {
- pdf.SetTextColor(defR, defG, defB)
- }
- }
- func pdfSetFillHex(pdf *fpdf.Fpdf, c string) bool {
- if r, g, b, ok := pdfColor(c); ok {
- pdf.SetFillColor(r, g, b)
- return true
- }
- return false
- }
- func pdfSetDrawHex(pdf *fpdf.Fpdf, c string) {
- if r, g, b, ok := pdfColor(c); ok {
- pdf.SetDrawColor(r, g, b)
- } else {
- pdf.SetDrawColor(102, 102, 102)
- }
- }
- // pdfStyleStr builds fpdf's font style string
- func pdfStyleStr(b, i, u bool) string {
- s := ""
- if b {
- s += "B"
- }
- if i {
- s += "I"
- }
- if u {
- s += "U"
- }
- return s
- }
- // pdfImageFromDataURL registers a data-URL image under a unique name and
- // returns (name, imageType, ok)
- func pdfImageFromDataURL(pdf *fpdf.Fpdf, src string, seq *int) (string, string, bool) {
- data, ext, ok := decodeDataURL(src)
- if !ok {
- return "", "", false
- }
- imgType := map[string]string{"png": "PNG", "jpeg": "JPG", "gif": "GIF"}[ext]
- if imgType == "" {
- return "", "", false
- }
- *seq++
- name := fmt.Sprintf("img%d", *seq)
- pdf.RegisterImageOptionsReader(name,
- fpdf.ImageOptions{ImageType: imgType}, bytes.NewReader(data))
- if pdf.Err() {
- return "", "", false
- }
- return name, imgType, true
- }
- // pdfOutput finalizes the document into bytes
- func pdfOutput(pdf *fpdf.Fpdf) ([]byte, error) {
- var buf bytes.Buffer
- if err := pdf.Output(&buf); err != nil {
- return nil, err
- }
- return buf.Bytes(), nil
- }
- // pdfNbsp normalizes non-breaking / typographic spaces to plain spaces:
- // contenteditable HTML is full of , and fpdf only wraps lines at
- // real spaces, so leaving them in causes early / mid-word line breaks
- var pdfNbsp = strings.NewReplacer(
- " ", " ", // no-break space ( )
- " ", " ", // figure space
- " ", " ", // thin space
- " ", " ") // narrow no-break space
- // pdfTr returns fpdf's UTF-8 -> cp1252 translator for core fonts
- func pdfTr(pdf *fpdf.Fpdf) func(string) string {
- tr := pdf.UnicodeTranslatorFromDescriptor("")
- return func(s string) string {
- return tr(pdfNbsp.Replace(s))
- }
- }
|