| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- package office
- /*
- pdf_sheet.go - Build a PDF from a Sheets workbook with REAL text.
- Formula evaluation and number formatting live in the web client, so
- the client posts a "print model": per sheet, the used-range grid of
- formatted display strings plus the visual styles that affect print
- (bold/italic/underline, font and fill colors, alignment) and the
- column widths in CSS pixels. The server lays that grid out on A4
- landscape pages, scaling the columns down when the sheet is wider
- than the printable area and splitting rows across pages.
- */
- import (
- "encoding/json"
- "errors"
- "github.com/go-pdf/fpdf"
- )
- // SheetPrintCell is one formatted cell in the print model
- type SheetPrintCell struct {
- T string `json:"t"` // display text
- B bool `json:"b,omitempty"` // bold
- I bool `json:"i,omitempty"` // italic
- U bool `json:"u,omitempty"` // underline
- Fc string `json:"fc,omitempty"` // font color (#rrggbb)
- Bg string `json:"bg,omitempty"` // fill color (#rrggbb)
- Al string `json:"al,omitempty"` // l | c | r
- }
- // SheetPrintSheet is one sheet's used-range grid
- type SheetPrintSheet struct {
- Name string `json:"name"`
- ColW []float64 `json:"colW"` // column widths, css px
- RowH []float64 `json:"rowH,omitempty"` // row heights, css px
- Rows [][]*SheetPrintCell `json:"rows"`
- }
- // SheetPrintModel is the whole workbook print model
- type SheetPrintModel struct {
- Sheets []*SheetPrintSheet `json:"sheets"`
- }
- // ParseSheetPrintJSON decodes the print model posted by sheets.js
- func ParseSheetPrintJSON(jsonStr string) (*SheetPrintModel, error) {
- m := SheetPrintModel{}
- if err := json.Unmarshal([]byte(jsonStr), &m); err != nil {
- return nil, errors.New("invalid print model JSON: " + err.Error())
- }
- if len(m.Sheets) == 0 {
- return nil, errors.New("print model has no sheets")
- }
- return &m, nil
- }
- // BuildSheetPdf renders the print model into PDF bytes
- func BuildSheetPdf(m *SheetPrintModel) ([]byte, error) {
- const marginMM = 12.0
- pdf := fpdf.New("L", "mm", "A4", "")
- pdf.SetMargins(marginMM, marginMM, marginMM)
- pdf.SetAutoPageBreak(false, marginMM)
- tr := pdfTr(pdf)
- pageW, pageH := pdf.GetPageSize()
- usableW := pageW - 2*marginMM
- for _, sheet := range m.Sheets {
- pdf.AddPage()
- // sheet name heading
- pdf.SetFont("Arial", "B", 12)
- pdf.SetTextColor(31, 35, 40)
- pdf.SetXY(marginMM, marginMM)
- pdf.CellFormat(usableW, 6, tr(sheet.Name), "", 1, "L", false, 0, "")
- pdf.SetY(pdf.GetY() + 2)
- if len(sheet.Rows) == 0 {
- continue
- }
- cols := 0
- for _, row := range sheet.Rows {
- if len(row) > cols {
- cols = len(row)
- }
- }
- if cols == 0 {
- continue
- }
- // column widths: px -> mm, scaled to fit the page when too wide
- widths := make([]float64, cols)
- total := 0.0
- for i := 0; i < cols; i++ {
- w := 100.0 * pxToMM
- if i < len(sheet.ColW) && sheet.ColW[i] > 0 {
- w = sheet.ColW[i] * pxToMM
- }
- widths[i] = w
- total += w
- }
- scale := 1.0
- if total > usableW {
- scale = usableW / total
- for i := range widths {
- widths[i] *= scale
- }
- }
- fontPt := 9.0 * scale
- if fontPt < 5 {
- fontPt = 5
- }
- for r, row := range sheet.Rows {
- rowH := 24.0 * pxToMM * scale
- if r < len(sheet.RowH) && sheet.RowH[r] > 0 {
- rowH = sheet.RowH[r] * pxToMM * scale
- }
- minH := fontPt*0.3528 + 1.6
- if rowH < minH {
- rowH = minH
- }
- if pdf.GetY()+rowH > pageH-marginMM {
- pdf.AddPage()
- pdf.SetY(marginMM)
- }
- x := marginMM
- y := pdf.GetY()
- pdf.SetDrawColor(190, 194, 200)
- pdf.SetLineWidth(0.15)
- for c := 0; c < cols; c++ {
- var cell *SheetPrintCell
- if c < len(row) {
- cell = row[c]
- }
- fill := false
- if cell != nil && cell.Bg != "" {
- fill = pdfSetFillHex(pdf, cell.Bg)
- }
- pdf.Rect(x, y, widths[c], rowH, map[bool]string{true: "FD", false: "D"}[fill])
- if cell != nil && cell.T != "" {
- pdf.SetFont("Arial", pdfStyleStr(cell.B, cell.I, cell.U), fontPt)
- pdfSetTextHex(pdf, cell.Fc, 31, 35, 40)
- align := map[string]string{"c": "C", "r": "R"}[cell.Al]
- if align == "" {
- align = "L"
- }
- pdf.SetXY(x+0.8, y)
- pdf.CellFormat(widths[c]-1.6, rowH, tr(cell.T), "", 0, align, false, 0, "")
- }
- x += widths[c]
- }
- pdf.SetY(y + rowH)
- }
- }
- if pdf.Err() {
- return nil, pdf.Error()
- }
- return pdfOutput(pdf)
- }
|