Implement AddSubtable method for the Table component (#400)

* Implement AddSubtable method for the table component with test case.
This commit is contained in:
Adrian-George Bostan 2019-04-04 01:04:30 +03:00 committed by Gunnsteinn Hall
parent d21dab045b
commit 3712670413
2 changed files with 150 additions and 6 deletions

View File

@ -7,6 +7,7 @@ package creator
import (
"errors"
"sort"
"github.com/unidoc/unidoc/common"
"github.com/unidoc/unidoc/pdf/contentstream/draw"
@ -64,12 +65,7 @@ func newTable(cols int) *Table {
cells: []*TableCell{},
}
// Initialize column widths as all equal.
colWidth := float64(1.0) / float64(cols)
for i := 0; i < cols; i++ {
t.colWidths = append(t.colWidths, colWidth)
}
t.resetColumnWidths()
return t
}
@ -86,6 +82,16 @@ func (table *Table) SetColumnWidths(widths ...float64) error {
return nil
}
func (table *Table) resetColumnWidths() {
table.colWidths = []float64{}
colWidth := float64(1.0) / float64(table.cols)
// Initialize column widths as all equal.
for i := 0; i < table.cols; i++ {
table.colWidths = append(table.colWidths, colWidth)
}
}
// Height returns the total height of all rows.
func (table *Table) Height() float64 {
sum := float64(0.0)
@ -166,6 +172,53 @@ func (table *Table) SetHeaderRows(startRow, endRow int) error {
return nil
}
// AddSubtable copies the cells of the subtable in the table, starting with the
// specified position. The table row and column indices are 1-based, which
// makes the position of the first cell of the first row of the table 1,1.
// The table is automatically extended if the subtable exceeds its columns.
// This can happen when the subtable has more columns than the table or when
// one or more columns of the subtable starting from the specified position
// exceed the last column of the table.
func (table *Table) AddSubtable(row, col int, subtable *Table) {
for _, cell := range subtable.cells {
c := &TableCell{}
*c = *cell
c.table = table
// Adjust added cell column. Add extra columns to the table to
// accomodate the new cell, if needed.
c.col += col - 1
if colsLeft := table.cols - (c.col - 1); colsLeft < c.colspan {
table.cols += c.colspan - colsLeft
table.resetColumnWidths()
common.Log.Debug("Table: subtable exceeds destination table. Expanding table to %d columns.", table.cols)
}
// Extend number of rows, if needed.
c.row += row - 1
for c.row > table.rows {
table.rows++
table.rowHeights = append(table.rowHeights, table.defaultRowHeight)
}
table.cells = append(table.cells, c)
}
// Sort cells by row, column.
sort.Slice(table.cells, func(i, j int) bool {
rowA := table.cells[i].row
rowB := table.cells[j].row
if rowA < rowB {
return true
}
if rowA > rowB {
return false
}
return table.cells[i].col < table.cells[j].col
})
}
// GeneratePageBlocks generate the page blocks. Multiple blocks are generated if the contents wrap
// over multiple pages.
// Implements the Drawable interface.

View File

@ -406,3 +406,94 @@ func TestTableHeaderTest(t *testing.T) {
t.Fatalf("Fail: %v\n", err)
}
}
func TestTableSubtables(t *testing.T) {
c := New()
headerColor := ColorRGBFrom8bit(255, 255, 0)
footerColor := ColorRGBFrom8bit(0, 255, 0)
generateSubtable := func(rows, cols, index int, rightBorder bool) *Table {
subtable := c.NewTable(cols)
// Add header row.
sp := c.NewStyledParagraph()
sp.Append(fmt.Sprintf("Header of subtable %d", index))
cell := subtable.MultiColCell(cols)
cell.SetContent(sp)
cell.SetBorder(CellBorderSideAll, CellBorderStyleSingle, 1)
cell.SetHorizontalAlignment(CellHorizontalAlignmentCenter)
cell.SetBackgroundColor(headerColor)
for i := 0; i < rows; i++ {
for j := 0; j < cols; j++ {
sp = c.NewStyledParagraph()
sp.Append(fmt.Sprintf("%d-%d", i+1, j+1))
cell = subtable.NewCell()
cell.SetContent(sp)
if j == 0 {
cell.SetBorder(CellBorderSideLeft, CellBorderStyleSingle, 1)
}
if rightBorder && j == cols-1 {
cell.SetBorder(CellBorderSideRight, CellBorderStyleSingle, 1)
}
}
}
// Add footer row.
sp = c.NewStyledParagraph()
sp.Append(fmt.Sprintf("Footer of subtable %d", index))
cell = subtable.MultiColCell(cols)
cell.SetContent(sp)
cell.SetBorder(CellBorderSideAll, CellBorderStyleSingle, 1)
cell.SetHorizontalAlignment(CellHorizontalAlignmentCenter)
cell.SetBackgroundColor(footerColor)
return subtable
}
table := c.NewTable(6)
// Add subtable 1 on row 1, col 1 (4x4)
table.AddSubtable(1, 1, generateSubtable(4, 4, 1, false))
// Add subtable 2 on row 1, col 5 (4x4)
// Table will be expanded to 8 columns because the subtable does not fit.
table.AddSubtable(1, 5, generateSubtable(4, 4, 2, true))
// Add subtable 3 on row 7, col 1 (4x4)
table.AddSubtable(7, 1, generateSubtable(4, 4, 3, false))
// Add subtable 4 on row 7, col 5 (4x4)
table.AddSubtable(7, 5, generateSubtable(4, 4, 4, true))
// Add subtable 5 on row 13, col 3 (4x4)
table.AddSubtable(13, 3, generateSubtable(4, 4, 5, true))
// Add subtable 6 on row 13, col 1 (3x2)
table.AddSubtable(13, 1, generateSubtable(3, 2, 6, false))
// Add subtable 7 on row 13, col 7 (3x2)
table.AddSubtable(13, 7, generateSubtable(3, 2, 7, true))
// Add subtable 8 on row 18, col 1 (3x2)
table.AddSubtable(18, 1, generateSubtable(3, 2, 8, false))
// Add subtable 9 on row 19, col 3 (2x4)
table.AddSubtable(19, 3, generateSubtable(2, 4, 9, true))
// Add subtable 10 on row 18, col 7 (3x2)
table.AddSubtable(18, 7, generateSubtable(3, 2, 10, true))
err := c.Draw(table)
if err != nil {
t.Fatalf("Error drawing: %v", err)
}
err = c.WriteToFile(tempFile("table_add_subtables.pdf"))
if err != nil {
t.Fatalf("Fail: %v\n", err)
}
}