row span summary

This commit is contained in:
jiangyong 2026-04-03 16:30:08 +08:00
parent 4c054b3215
commit 17bf4aaf99
1 changed files with 98 additions and 13 deletions

111
table.go
View File

@ -116,8 +116,17 @@ func NewTableGenerator(config *TableConfig) *TableGenerator {
func (t *TableData) Check() error { func (t *TableData) Check() error {
headerLen := len(t.Headers) 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 { 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") 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 _, row := range data.Rows {
for j, cell := range row { if len(row) == 2 && colCount > 2 {
width, _ := g.dc.MeasureString(cell) // 第一列占一列
width, _ := g.dc.MeasureString(row[0])
totalWidth := width + g.scaled(g.config.Padding)*2 totalWidth := width + g.scaled(g.config.Padding)*2
if totalWidth > colWidths[j] { if totalWidth > colWidths[0] {
colWidths[j] = math.Min(totalWidth, g.scaled(g.config.MaxColWidth)) 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 { for i, row := range data.Rows {
maxHeight := 0.0 maxHeight := 0.0
for j, cell := range row { if len(row) == 2 && len(data.Headers) > 2 {
// 测量文本在限定宽度内的高度 // 第一列占一列
lines := g.wrapText(cell, colWidths[j]-g.scaled(g.config.Padding)*2) lines := g.wrapText(row[0], colWidths[0]-g.scaled(g.config.Padding)*2)
lineHeight := g.getLineHeight() lineHeight := g.getLineHeight()
cellHeight := float64(len(lines))*lineHeight + g.scaled(g.config.Padding)*2 cellHeight := float64(len(lines))*lineHeight + g.scaled(g.config.Padding)*2
if cellHeight > maxHeight { if cellHeight > maxHeight {
maxHeight = cellHeight 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 rowHeights[i+1] = maxHeight
} }
@ -354,8 +406,19 @@ func (g *TableGenerator) Generate(data *TableData, filename string) error {
currentX := margin currentX := margin
rowHeight := rowHeights[i] rowHeight := rowHeights[i]
isTwoColRow := i > 0 && len(data.Rows[i-1]) == 2 && len(data.Headers) > 2
for j := 0; j < len(colWidths); j++ { for j := 0; j < len(colWidths); j++ {
if isTwoColRow && j >= 2 {
continue
}
colWidth := colWidths[j] colWidth := colWidths[j]
if isTwoColRow && j == 1 {
for k := 2; k < len(colWidths); k++ {
colWidth += colWidths[k]
}
}
// 设置单元格背景色 // 设置单元格背景色
if i == 0 { if i == 0 {
@ -374,7 +437,15 @@ func (g *TableGenerator) Generate(data *TableData, filename string) error {
cellText = data.Headers[j] cellText = data.Headers[j]
dc.SetColor(g.config.HeaderTextColor) dc.SetColor(g.config.HeaderTextColor)
} else { } 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) 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) 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.SetColor(g.config.LineColor)
dc.SetLineWidth(g.scaled(g.config.LineWidth)) dc.SetLineWidth(g.scaled(g.config.LineWidth))
@ -425,8 +496,22 @@ func (g *TableGenerator) drawGridLines(dc *gg.Context, colWidths, rowHeights []f
// 绘制垂直线 // 绘制垂直线
currentX := margin currentX := margin
for j := 0; j <= len(colWidths); j++ { for j := 0; j <= len(colWidths); j++ {
dc.DrawLine(currentX, startY, currentX, startY+tableHeight) if j == 0 || j == len(colWidths) || len(data.Headers) <= 2 {
dc.Stroke() // 最左/最右的线直接画到底,或者列数<=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) { if j < len(colWidths) {
currentX += colWidths[j] currentX += colWidths[j]
} }