From 17bf4aaf99ed91be71568927bf8a80803913dbaa Mon Sep 17 00:00:00 2001 From: jiangyong Date: Fri, 3 Apr 2026 16:30:08 +0800 Subject: [PATCH] row span summary --- table.go | 111 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 98 insertions(+), 13 deletions(-) diff --git a/table.go b/table.go index 320cc3c..ece6514 100644 --- a/table.go +++ b/table.go @@ -116,8 +116,17 @@ func NewTableGenerator(config *TableConfig) *TableGenerator { func (t *TableData) Check() error { headerLen := len(t.Headers) + if headerLen <= 2 { + for _, row := range t.Rows { + if headerLen != len(row) { + return fmt.Errorf("header length does not match row length") + } + } + return nil + } for _, row := range t.Rows { - if headerLen != len(row) { + rowLen := len(row) + if rowLen != headerLen && rowLen != 2 { return fmt.Errorf("header length does not match row length") } } @@ -150,11 +159,34 @@ func (g *TableGenerator) calculateColumnWidths(data *TableData) []float64 { // 检查每行内容的宽度 for _, row := range data.Rows { - for j, cell := range row { - width, _ := g.dc.MeasureString(cell) + if len(row) == 2 && colCount > 2 { + // 第一列占一列 + width, _ := g.dc.MeasureString(row[0]) totalWidth := width + g.scaled(g.config.Padding)*2 - if totalWidth > colWidths[j] { - colWidths[j] = math.Min(totalWidth, g.scaled(g.config.MaxColWidth)) + if totalWidth > colWidths[0] { + colWidths[0] = math.Min(totalWidth, g.scaled(g.config.MaxColWidth)) + } + // 第二列跨剩余所有列 + width, _ = g.dc.MeasureString(row[1]) + totalWidth = width + g.scaled(g.config.Padding)*2 + remainingWidth := 0.0 + for k := 1; k < colCount; k++ { + remainingWidth += colWidths[k] + } + if totalWidth > remainingWidth { + diff := totalWidth - remainingWidth + perCol := diff / float64(colCount-1) + for k := 1; k < colCount; k++ { + colWidths[k] += perCol + } + } + } else { + for j, cell := range row { + width, _ := g.dc.MeasureString(cell) + totalWidth := width + g.scaled(g.config.Padding)*2 + if totalWidth > colWidths[j] { + colWidths[j] = math.Min(totalWidth, g.scaled(g.config.MaxColWidth)) + } } } } @@ -174,14 +206,34 @@ func (g *TableGenerator) calculateRowHeights(data *TableData, colWidths []float6 // 计算每行内容的高度 for i, row := range data.Rows { maxHeight := 0.0 - for j, cell := range row { - // 测量文本在限定宽度内的高度 - lines := g.wrapText(cell, colWidths[j]-g.scaled(g.config.Padding)*2) + if len(row) == 2 && len(data.Headers) > 2 { + // 第一列占一列 + lines := g.wrapText(row[0], colWidths[0]-g.scaled(g.config.Padding)*2) lineHeight := g.getLineHeight() cellHeight := float64(len(lines))*lineHeight + g.scaled(g.config.Padding)*2 if cellHeight > maxHeight { maxHeight = cellHeight } + // 第二列跨剩余所有列 + remainingWidth := 0.0 + for k := 1; k < len(colWidths); k++ { + remainingWidth += colWidths[k] + } + lines = g.wrapText(row[1], remainingWidth-g.scaled(g.config.Padding)*2) + cellHeight = float64(len(lines))*lineHeight + g.scaled(g.config.Padding)*2 + if cellHeight > maxHeight { + maxHeight = cellHeight + } + } else { + for j, cell := range row { + // 测量文本在限定宽度内的高度 + lines := g.wrapText(cell, colWidths[j]-g.scaled(g.config.Padding)*2) + lineHeight := g.getLineHeight() + cellHeight := float64(len(lines))*lineHeight + g.scaled(g.config.Padding)*2 + if cellHeight > maxHeight { + maxHeight = cellHeight + } + } } rowHeights[i+1] = maxHeight } @@ -354,8 +406,19 @@ func (g *TableGenerator) Generate(data *TableData, filename string) error { currentX := margin rowHeight := rowHeights[i] + isTwoColRow := i > 0 && len(data.Rows[i-1]) == 2 && len(data.Headers) > 2 + for j := 0; j < len(colWidths); j++ { + if isTwoColRow && j >= 2 { + continue + } + colWidth := colWidths[j] + if isTwoColRow && j == 1 { + for k := 2; k < len(colWidths); k++ { + colWidth += colWidths[k] + } + } // 设置单元格背景色 if i == 0 { @@ -374,7 +437,15 @@ func (g *TableGenerator) Generate(data *TableData, filename string) error { cellText = data.Headers[j] dc.SetColor(g.config.HeaderTextColor) } else { - cellText = data.Rows[i-1][j] + if isTwoColRow { + if j == 0 { + cellText = data.Rows[i-1][0] + } else { + cellText = data.Rows[i-1][1] + } + } else { + cellText = data.Rows[i-1][j] + } dc.SetColor(g.config.TextColor) } @@ -399,7 +470,7 @@ func (g *TableGenerator) Generate(data *TableData, filename string) error { } // 绘制网格线(使用缩放后的线宽) - g.drawGridLines(dc, colWidths, rowHeights, margin, tableStartY, tableWidth, tableHeight) + g.drawGridLines(dc, colWidths, rowHeights, margin, tableStartY, tableWidth, tableHeight, data) // 绘制外边框(使用缩放后的边框宽度) g.drawOuterBorder(dc, margin, tableStartY, tableWidth, tableHeight) @@ -408,7 +479,7 @@ func (g *TableGenerator) Generate(data *TableData, filename string) error { } // 绘制网格线(高清优化) -func (g *TableGenerator) drawGridLines(dc *gg.Context, colWidths, rowHeights []float64, margin, startY, tableWidth, tableHeight float64) { +func (g *TableGenerator) drawGridLines(dc *gg.Context, colWidths, rowHeights []float64, margin, startY, tableWidth, tableHeight float64, data *TableData) { dc.SetColor(g.config.LineColor) dc.SetLineWidth(g.scaled(g.config.LineWidth)) @@ -425,8 +496,22 @@ func (g *TableGenerator) drawGridLines(dc *gg.Context, colWidths, rowHeights []f // 绘制垂直线 currentX := margin for j := 0; j <= len(colWidths); j++ { - dc.DrawLine(currentX, startY, currentX, startY+tableHeight) - dc.Stroke() + if j == 0 || j == len(colWidths) || len(data.Headers) <= 2 { + // 最左/最右的线直接画到底,或者列数<=2时不需要跳过 + dc.DrawLine(currentX, startY, currentX, startY+tableHeight) + dc.Stroke() + } else { + // 内部垂直线,需要跳过2列合并行的内部 + currentY2 := startY + for i := 0; i < len(rowHeights); i++ { + isTwoColRow := i > 0 && len(data.Rows[i-1]) == 2 && len(data.Headers) > 2 + if !(isTwoColRow && j >= 2) { + dc.DrawLine(currentX, currentY2, currentX, currentY2+rowHeights[i]) + dc.Stroke() + } + currentY2 += rowHeights[i] + } + } if j < len(colWidths) { currentX += colWidths[j] }