pdf.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. package office
  2. /*
  3. pdf.go - shared plumbing for the server-side PDF exporters
  4. (pdf_doc.go / pdf_sheet.go / pdf_slides.go).
  5. Built on github.com/go-pdf/fpdf (MIT). Text is written as REAL text
  6. objects (selectable / editable in PDF editors), not page screenshots.
  7. The core PDF fonts are Latin-1 (cp1252): characters outside that set
  8. are transliterated by fpdf's unicode translator and may degrade -
  9. embedding a full Unicode font is a deliberate non-goal for now (it
  10. would grow the binary by megabytes).
  11. */
  12. import (
  13. "bytes"
  14. "fmt"
  15. "strings"
  16. "github.com/go-pdf/fpdf"
  17. )
  18. const pxToMM = 25.4 / 96.0
  19. // pdfSetFillHex / pdfSetTextHex apply "#rrggbb" (or rgb()) colors
  20. func pdfColor(c string) (int, int, int, bool) {
  21. hexv := cssColorHex(c)
  22. if len(hexv) != 6 {
  23. return 0, 0, 0, false
  24. }
  25. var r, g, b int
  26. if _, err := fmt.Sscanf(hexv, "%02X%02X%02X", &r, &g, &b); err != nil {
  27. return 0, 0, 0, false
  28. }
  29. return r, g, b, true
  30. }
  31. func pdfSetTextHex(pdf *fpdf.Fpdf, c string, defR, defG, defB int) {
  32. if r, g, b, ok := pdfColor(c); ok {
  33. pdf.SetTextColor(r, g, b)
  34. } else {
  35. pdf.SetTextColor(defR, defG, defB)
  36. }
  37. }
  38. func pdfSetFillHex(pdf *fpdf.Fpdf, c string) bool {
  39. if r, g, b, ok := pdfColor(c); ok {
  40. pdf.SetFillColor(r, g, b)
  41. return true
  42. }
  43. return false
  44. }
  45. func pdfSetDrawHex(pdf *fpdf.Fpdf, c string) {
  46. if r, g, b, ok := pdfColor(c); ok {
  47. pdf.SetDrawColor(r, g, b)
  48. } else {
  49. pdf.SetDrawColor(102, 102, 102)
  50. }
  51. }
  52. // pdfStyleStr builds fpdf's font style string
  53. func pdfStyleStr(b, i, u bool) string {
  54. s := ""
  55. if b {
  56. s += "B"
  57. }
  58. if i {
  59. s += "I"
  60. }
  61. if u {
  62. s += "U"
  63. }
  64. return s
  65. }
  66. // pdfImageFromDataURL registers a data-URL image under a unique name and
  67. // returns (name, imageType, ok)
  68. func pdfImageFromDataURL(pdf *fpdf.Fpdf, src string, seq *int) (string, string, bool) {
  69. data, ext, ok := decodeDataURL(src)
  70. if !ok {
  71. return "", "", false
  72. }
  73. imgType := map[string]string{"png": "PNG", "jpeg": "JPG", "gif": "GIF"}[ext]
  74. if imgType == "" {
  75. return "", "", false
  76. }
  77. *seq++
  78. name := fmt.Sprintf("img%d", *seq)
  79. pdf.RegisterImageOptionsReader(name,
  80. fpdf.ImageOptions{ImageType: imgType}, bytes.NewReader(data))
  81. if pdf.Err() {
  82. return "", "", false
  83. }
  84. return name, imgType, true
  85. }
  86. // pdfOutput finalizes the document into bytes
  87. func pdfOutput(pdf *fpdf.Fpdf) ([]byte, error) {
  88. var buf bytes.Buffer
  89. if err := pdf.Output(&buf); err != nil {
  90. return nil, err
  91. }
  92. return buf.Bytes(), nil
  93. }
  94. // pdfNbsp normalizes non-breaking / typographic spaces to plain spaces:
  95. // contenteditable HTML is full of  , and fpdf only wraps lines at
  96. // real spaces, so leaving them in causes early / mid-word line breaks
  97. var pdfNbsp = strings.NewReplacer(
  98. " ", " ", // no-break space ( )
  99. " ", " ", // figure space
  100. " ", " ", // thin space
  101. " ", " ") // narrow no-break space
  102. // pdfTr returns fpdf's UTF-8 -> cp1252 translator for core fonts
  103. func pdfTr(pdf *fpdf.Fpdf) func(string) string {
  104. tr := pdf.UnicodeTranslatorFromDescriptor("")
  105. return func(s string) string {
  106. return tr(pdfNbsp.Replace(s))
  107. }
  108. }