From 53a154a6df3d2529199790ec527bb6a13b399ac8 Mon Sep 17 00:00:00 2001 From: landaiqing Date: Wed, 16 Apr 2025 20:50:40 +0800 Subject: [PATCH] :art: Add page number generation and refine the methodology --- document/document.go | 39 ++ document/examples/advanced/main.go | 560 ++++++++++++++++++++++ document/examples/simple/main.go | 15 +- document/examples/threeline_table/main.go | 285 +++++++++++ document/header_footer.go | 39 ++ document/paragraph.go | 20 + document/run.go | 130 ++++- document/table.go | 118 ++++- 8 files changed, 1150 insertions(+), 56 deletions(-) create mode 100644 document/examples/advanced/main.go create mode 100644 document/examples/threeline_table/main.go diff --git a/document/document.go b/document/document.go index c8ef783..4df7208 100644 --- a/document/document.go +++ b/document/document.go @@ -683,3 +683,42 @@ func (d *Document) AddFooterWithReference(footerType string) *Footer { return footer } + +// AddPageNumberParagraph 添加一个居中的页码段落 +func (d *Document) AddPageNumberParagraph() *Paragraph { + // 创建一个新段落 + para := d.AddParagraph() + para.SetAlignment("center") + + // 添加"第"文本 + para.AddRun().AddText("第 ") + + // 创建页码域的所有部分 + // 1. 域开始 + fieldBegin := para.AddRun() + fieldBegin.Field = &Field{ + Type: "begin", + Code: "PAGE", + } + + // 2. 域分隔符 + fieldSeparate := para.AddRun() + fieldSeparate.Field = &Field{ + Type: "separate", + } + + // 3. 页码内容(在Word中会替换为实际页码) + fieldContent := para.AddRun() + fieldContent.AddText("1") + + // 4. 域结束 + fieldEnd := para.AddRun() + fieldEnd.Field = &Field{ + Type: "end", + } + + // 添加"页"文本 + para.AddRun().AddText(" 页") + + return para +} diff --git a/document/examples/advanced/main.go b/document/examples/advanced/main.go new file mode 100644 index 0000000..81bc400 --- /dev/null +++ b/document/examples/advanced/main.go @@ -0,0 +1,560 @@ +package main + +import ( + "fmt" + "github.com/landaiqing/go-dockit/document" + "log" + "time" +) + +func main() { + // 创建一个新的Word文档 + doc := document.NewDocument() + + // 设置文档属性 + doc.SetTitle("高级功能演示文档") + doc.SetCreator("go-dockit库") + doc.SetDescription("这是一个使用go-dockit库创建的高级功能演示文档") + doc.SetCreated(time.Now()) + doc.SetModified(time.Now()) + + // 设置页面大小为A4纸,竖向 + doc.SetPageSizeA4(false) + + // 设置页面边距 + doc.SetPageMargin(1440, 1440, 1440, 1440, 720, 720, 0) + + // 添加页眉和页脚 + header := doc.AddHeaderWithReference("default") + headerPara := header.AddParagraph() + headerPara.SetAlignment("right") + headerRun := headerPara.AddRun() + headerRun.AddText("高级功能演示文档") + headerRun.SetFontSize(20) // 10磅 + headerRun.SetFontFamily("宋体") + + // 添加页脚 + footer := doc.AddFooterWithReference("default") + // 使用新的方法添加完整的页码段落 + footer.AddPageNumber() + + // =================== 封面页 =================== + // 添加一个标题段落作为封面 + titlePara := doc.AddParagraph() + titlePara.SetAlignment("center") + titlePara.SetSpacingBefore(2400) // 封面标题前空白 + titleRun := titlePara.AddRun() + titleRun.AddText("高级功能演示文档") + titleRun.SetBold(true) + titleRun.SetFontSize(48) // 24磅 + titleRun.SetFontFamily("黑体") + + // 添加副标题 + subtitlePara := doc.AddParagraph() + subtitlePara.SetAlignment("center") + subtitlePara.SetSpacingBefore(800) + subtitleRun := subtitlePara.AddRun() + subtitleRun.AddText("go-dockit功能展示") + subtitleRun.SetFontSize(32) // 16磅 + subtitleRun.SetFontFamily("黑体") + + // 添加作者信息 + authorPara := doc.AddParagraph() + authorPara.SetAlignment("center") + authorPara.SetSpacingBefore(2400) + authorRun := authorPara.AddRun() + authorRun.AddText("作者:go-dockit团队") + authorRun.SetFontSize(24) // 12磅 + authorRun.SetFontFamily("宋体") + + // 添加日期信息 + datePara := doc.AddParagraph() + datePara.SetAlignment("center") + datePara.SetSpacingBefore(200) + dateRun := datePara.AddRun() + dateRun.AddText(time.Now().Format("2006年01月02日")) + dateRun.SetFontSize(24) // 12磅 + dateRun.SetFontFamily("宋体") + + // 添加分页符 + doc.AddPageBreak() + + // =================== 目录页 =================== + // 添加目录标题 + tocTitlePara := doc.AddParagraph() + tocTitlePara.SetAlignment("center") + tocTitlePara.SetSpacingBefore(400) + tocTitlePara.SetSpacingAfter(600) + tocTitleRun := tocTitlePara.AddRun() + tocTitleRun.AddText("目录") + tocTitleRun.SetBold(true) + tocTitleRun.SetFontSize(36) // 18磅 + tocTitleRun.SetFontFamily("黑体") + + // 添加目录条目(在实际文档中,这部分会由Word自动更新) + addTocEntry(doc, "1. 文本样式展示", 0) + addTocEntry(doc, "2. 段落排版展示", 0) + addTocEntry(doc, " 2.1 段落对齐方式", 1) + addTocEntry(doc, " 2.2 段落间距和缩进", 1) + addTocEntry(doc, "3. 表格展示", 0) + addTocEntry(doc, " 3.1 基本表格", 1) + addTocEntry(doc, " 3.2 表格样式", 1) + addTocEntry(doc, "4. 列表展示", 0) + addTocEntry(doc, " 4.1 项目符号列表", 1) + addTocEntry(doc, " 4.2 多级编号列表", 1) + + // 添加分页符 + doc.AddPageBreak() + + // =================== 正文开始 =================== + // 1. 文本样式展示 + addHeading(doc, "1. 文本样式展示", 1) + + para := doc.AddParagraph() + para.SetIndentFirstLine(420) + para.AddRun().AddText("go-dockit库支持多种文本样式,下面展示各种常见的文本格式:") + + // 文本样式示例段落 + stylePara := doc.AddParagraph() + stylePara.SetIndentFirstLine(420) + stylePara.SetSpacingBefore(200) + stylePara.SetSpacingAfter(200) + + // 不同样式的文本展示 + stylePara.AddRun().AddText("正常文本 ") + + boldRun := stylePara.AddRun() + boldRun.AddText("粗体文本 ") + boldRun.SetBold(true) + + italicRun := stylePara.AddRun() + italicRun.AddText("斜体文本 ") + italicRun.SetItalic(true) + + boldItalicRun := stylePara.AddRun() + boldItalicRun.AddText("粗斜体文本 ") + boldItalicRun.SetBold(true) + boldItalicRun.SetItalic(true) + + underlineRun := stylePara.AddRun() + underlineRun.AddText("下划线文本 ") + underlineRun.SetUnderline("single") + + strikeRun := stylePara.AddRun() + strikeRun.AddText("删除线文本 ") + strikeRun.SetStrike(true) + + superRun := stylePara.AddRun() + superRun.AddText("上标") + superRun.SetVertAlign("superscript") + + stylePara.AddRun().AddText(" 正常文本 ") + + subRun := stylePara.AddRun() + subRun.AddText("下标") + subRun.SetVertAlign("subscript") + + // 字体和颜色 + fontPara := doc.AddParagraph() + fontPara.SetIndentFirstLine(420) + + colorRun := fontPara.AddRun() + colorRun.AddText("彩色文本 ") + colorRun.SetColor("FF0000") // 红色 + + fontSizeRun := fontPara.AddRun() + fontSizeRun.AddText("大号文本 ") + fontSizeRun.SetFontSize(28) // 14磅 + + fontFamilyRun := fontPara.AddRun() + fontFamilyRun.AddText("不同字体 ") + fontFamilyRun.SetFontFamily("黑体") + + highlightRun := fontPara.AddRun() + highlightRun.AddText("背景高亮") + highlightRun.SetHighlight("yellow") + + // 2. 段落排版展示 + addHeading(doc, "2. 段落排版展示", 1) + addHeading(doc, "2.1 段落对齐方式", 2) + + // 左对齐(默认) + leftPara := doc.AddParagraph() + leftPara.SetAlignment("left") + leftPara.SetSpacingBefore(200) + leftPara.SetSpacingAfter(200) + leftPara.AddRun().AddText("这是左对齐的段落。左对齐是最常用的对齐方式,段落的文本从左侧开始排列,右侧自然结束,形成参差不齐的边缘。") + + // 居中对齐 + centerPara := doc.AddParagraph() + centerPara.SetAlignment("center") + centerPara.SetSpacingBefore(200) + centerPara.SetSpacingAfter(200) + centerPara.AddRun().AddText("这是居中对齐的段落。居中对齐常用于标题、诗歌等需要特殊强调的文本。") + + // 右对齐 + rightPara := doc.AddParagraph() + rightPara.SetAlignment("right") + rightPara.SetSpacingBefore(200) + rightPara.SetSpacingAfter(200) + rightPara.AddRun().AddText("这是右对齐的段落。右对齐使文本的右边缘整齐,左侧参差不齐。") + + // 两端对齐 + justifyPara := doc.AddParagraph() + justifyPara.SetAlignment("both") + justifyPara.SetSpacingBefore(200) + justifyPara.SetSpacingAfter(200) + justifyPara.AddRun().AddText("这是两端对齐的段落。两端对齐会使段落的文本在左右两侧都形成整齐的边缘,适合正式文档和出版物。这是两端对齐的段落。两端对齐会使段落的文本在左右两侧都形成整齐的边缘,适合正式文档和出版物。") + + addHeading(doc, "2.2 段落间距和缩进", 2) + + // 解释段落 + spacingExplainPara := doc.AddParagraph() + spacingExplainPara.SetIndentFirstLine(420) + spacingExplainPara.AddRun().AddText("段落间距和缩进对于文档的可读性非常重要。以下展示不同的段落间距和缩进效果:") + + // 段落间距示例 + firstSpacingPara := doc.AddParagraph() + firstSpacingPara.SetIndentFirstLine(420) + firstSpacingPara.SetSpacingBefore(400) // 段前20磅 + firstSpacingPara.SetSpacingAfter(200) // 段后10磅 + firstSpacingPara.AddRun().AddText("这个段落有较大的段前间距(20磅)和较小的段后间距(10磅)。通过调整段落间距,可以使文档结构更加清晰。") + + // 行间距示例 + lineSpacingPara := doc.AddParagraph() + lineSpacingPara.SetIndentFirstLine(420) + lineSpacingPara.SetSpacingLine(480, "exact") // 24磅的固定行距 + lineSpacingPara.AddRun().AddText("这个段落使用了固定的行间距(24磅)。行间距影响段落内部各行之间的距离,合适的行间距可以提高文本的可读性。这个段落使用了固定的行间距(24磅)。行间距影响段落内部各行之间的距离,合适的行间距可以提高文本的可读性。") + + // 缩进示例 + indentPara := doc.AddParagraph() + indentPara.SetIndentLeft(720) // 左侧缩进36磅 + indentPara.SetIndentRight(720) // 右侧缩进36磅 + indentPara.SetSpacingBefore(200) + indentPara.AddRun().AddText("这个段落左右两侧都有缩进(36磅)。缩进可以用来强调某段文本,或者用于引用格式。不同类型的缩进可以用来表达不同的文档结构和层次。") + + // 悬挂缩进示例 + hangingPara := doc.AddParagraph() + hangingPara.SetIndentLeft(720) // 左侧缩进36磅 + hangingPara.SetIndentFirstLine(-420) // 首行缩进-21磅(悬挂缩进) + hangingPara.SetSpacingBefore(200) + hangingPara.AddRun().AddText("悬挂缩进:").SetBold(true) + hangingPara.AddRun().AddText("这个段落使用了悬挂缩进,第一行文本会向左突出,形成一种特殊的格式效果。悬挂缩进常用于定义列表、参考文献等场景。") + + // 3. 表格展示 + addHeading(doc, "3. 表格展示", 1) + addHeading(doc, "3.1 基本表格", 2) + + // 表格说明段落 + tableExplainPara := doc.AddParagraph() + tableExplainPara.SetIndentFirstLine(420) + tableExplainPara.SetSpacingAfter(200) + tableExplainPara.AddRun().AddText("表格是文档中展示结构化数据的重要方式。以下展示基本表格功能:") + + // 创建一个5行3列的表格 + table := doc.AddTable(5, 3) + table.SetWidth(8000, "dxa") // 设置表格宽度 + table.SetAlignment("center") // 表格居中 + + // 设置表头 + headerRow := table.Rows[0] + headerRow.SetIsHeader(true) + + // 填充表头 + headerRow.Cells[0].AddParagraph().AddRun().AddText("产品名称").SetBold(true) + headerRow.Cells[1].AddParagraph().AddRun().AddText("单价").SetBold(true) + headerRow.Cells[2].AddParagraph().AddRun().AddText("库存数量").SetBold(true) + + // 填充表格数据 + products := []struct { + name string + price string + stock string + }{ + {"商品A", "¥100.00", "150"}, + {"商品B", "¥200.00", "85"}, + {"商品C", "¥150.00", "200"}, + {"商品D", "¥300.00", "35"}, + } + + for i, product := range products { + row := table.Rows[i+1] + + // 居中显示所有单元格内容 + nameCell := row.Cells[0].AddParagraph() + nameCell.SetAlignment("center") + nameCell.AddRun().AddText(product.name) + + priceCell := row.Cells[1].AddParagraph() + priceCell.SetAlignment("center") + priceCell.AddRun().AddText(product.price) + + stockCell := row.Cells[2].AddParagraph() + stockCell.SetAlignment("center") + stockCell.AddRun().AddText(product.stock) + } + + // 设置表格边框 + table.SetBorders("all", "single", 4, "000000") + + addHeading(doc, "3.2 表格样式", 2) + + // 表格样式说明段落 + tableStyleExplainPara := doc.AddParagraph() + tableStyleExplainPara.SetIndentFirstLine(420) + tableStyleExplainPara.SetSpacingAfter(200) + tableStyleExplainPara.AddRun().AddText("表格可以应用不同的样式效果,如单元格合并、背景色、对齐方式等:") + + // 创建一个样式化的表格 + styleTable := doc.AddTable(4, 4) + styleTable.SetWidth(8000, "dxa") // 设置表格宽度 + styleTable.SetAlignment("center") // 表格居中 + + // 设置表头 + styleHeaderRow := styleTable.Rows[0] + styleHeaderRow.SetIsHeader(true) + + // 设置表头背景色 + for i := 0; i < 4; i++ { + styleHeaderRow.Cells[i].SetShading("DDDDDD", "000000", "clear") + } + + // 填充表头 + styleHeaderRow.Cells[0].AddParagraph().AddRun().AddText("季度").SetBold(true) + styleHeaderRow.Cells[1].AddParagraph().AddRun().AddText("北区").SetBold(true) + styleHeaderRow.Cells[2].AddParagraph().AddRun().AddText("南区").SetBold(true) + styleHeaderRow.Cells[3].AddParagraph().AddRun().AddText("总计").SetBold(true) + + // 填充数据 + quarters := []string{"第一季度", "第二季度", "第三季度"} + northData := []string{"¥10,000", "¥12,000", "¥15,000"} + southData := []string{"¥8,000", "¥9,000", "¥11,000"} + totalData := []string{"¥18,000", "¥21,000", "¥26,000"} + + for i := 0; i < 3; i++ { + row := styleTable.Rows[i+1] + + quarterCell := row.Cells[0].AddParagraph() + quarterCell.SetAlignment("center") + quarterCell.AddRun().AddText(quarters[i]) + + northCell := row.Cells[1].AddParagraph() + northCell.SetAlignment("center") + northCell.AddRun().AddText(northData[i]) + + southCell := row.Cells[2].AddParagraph() + southCell.SetAlignment("center") + southCell.AddRun().AddText(southData[i]) + + totalCell := row.Cells[3].AddParagraph() + totalCell.SetAlignment("center") + totalCell.AddRun().AddText(totalData[i]) + } + + // 设置边框 + styleTable.SetBorders("all", "single", 4, "000000") + + // 给某些单元格设置背景色 + styleTable.Rows[1].Cells[1].SetShading("E6F2FF", "000000", "clear") // 浅蓝色 + styleTable.Rows[2].Cells[2].SetShading("E6F2FF", "000000", "clear") // 浅蓝色 + styleTable.Rows[3].Cells[3].SetShading("E6F2FF", "000000", "clear") // 浅蓝色 + + // 4. 列表展示 + doc.AddPageBreak() // 分页,避免内容过多 + addHeading(doc, "4. 列表展示", 1) + addHeading(doc, "4.1 项目符号列表", 2) + + // 列表说明段落 + listExplainPara := doc.AddParagraph() + listExplainPara.SetIndentFirstLine(420) + listExplainPara.SetSpacingAfter(200) + listExplainPara.AddRun().AddText("列表可以清晰地组织和展示相关信息。以下展示项目符号列表:") + + // 创建一个项目符号列表 + bulletListId := doc.Numbering.CreateBulletList() + + // 添加列表项 + bulletItems := []string{ + "项目符号列表项一:项目符号列表用于展示无特定顺序的条目", + "项目符号列表项二:可以使用不同级别的缩进表示层次结构", + "项目符号列表项三:适合展示特点、优势等并列信息", + "项目符号列表项四:列表使文档的结构更加清晰", + } + + for _, item := range bulletItems { + listItem := doc.AddParagraph() + listItem.SetNumbering(bulletListId, 0) + listItem.AddRun().AddText(item) + } + + // 创建一个嵌套的项目符号列表 + nestedListPara := doc.AddParagraph() + nestedListPara.SetSpacingBefore(200) + nestedListPara.SetIndentFirstLine(420) + nestedListPara.AddRun().AddText("以下是嵌套的项目符号列表:") + + // 主列表项 + mainItem1 := doc.AddParagraph() + mainItem1.SetNumbering(bulletListId, 0) + mainItem1.AddRun().AddText("主要类别A") + + // 嵌套列表项 + subItems1 := []string{"子类别A-1", "子类别A-2", "子类别A-3"} + for _, item := range subItems1 { + subItem := doc.AddParagraph() + subItem.SetNumbering(bulletListId, 1) // 使用级别1表示嵌套 + subItem.AddRun().AddText(item) + } + + // 另一个主列表项 + mainItem2 := doc.AddParagraph() + mainItem2.SetNumbering(bulletListId, 0) + mainItem2.AddRun().AddText("主要类别B") + + // 嵌套列表项 + subItems2 := []string{"子类别B-1", "子类别B-2"} + for _, item := range subItems2 { + subItem := doc.AddParagraph() + subItem.SetNumbering(bulletListId, 1) // 使用级别1表示嵌套 + subItem.AddRun().AddText(item) + } + + addHeading(doc, "4.2 多级编号列表", 2) + + // 编号列表说明段落 + numberListExplainPara := doc.AddParagraph() + numberListExplainPara.SetIndentFirstLine(420) + numberListExplainPara.SetSpacingAfter(200) + numberListExplainPara.AddRun().AddText("编号列表适合表示有序的步骤或层次分明的结构:") + + // 创建一个数字列表 + numberListId := doc.Numbering.CreateNumberList() + + // 添加列表项 + numberItems := []string{ + "第一步:准备所需材料", + "第二步:按照说明书组装底座", + "第三步:连接各个组件", + "第四步:检查并测试功能", + } + + for _, item := range numberItems { + listItem := doc.AddParagraph() + listItem.SetNumbering(numberListId, 0) + listItem.AddRun().AddText(item) + } + + // 创建一个多级编号列表示例 + multiLevelExplainPara := doc.AddParagraph() + multiLevelExplainPara.SetSpacingBefore(200) + multiLevelExplainPara.SetIndentFirstLine(420) + multiLevelExplainPara.AddRun().AddText("以下是多级编号列表示例:") + + // 创建一个多级编号列表 + multiLevelListId := doc.Numbering.CreateNumberList() + + // 一级条目 + level1Item1 := doc.AddParagraph() + level1Item1.SetNumbering(multiLevelListId, 0) + level1Item1.AddRun().AddText("第一章:绪论") + + // 二级条目 + level2Item1 := doc.AddParagraph() + level2Item1.SetNumbering(multiLevelListId, 1) + level2Item1.AddRun().AddText("研究背景") + + level2Item2 := doc.AddParagraph() + level2Item2.SetNumbering(multiLevelListId, 1) + level2Item2.AddRun().AddText("研究意义") + + // 三级条目 + level3Item1 := doc.AddParagraph() + level3Item1.SetNumbering(multiLevelListId, 2) + level3Item1.AddRun().AddText("理论意义") + + level3Item2 := doc.AddParagraph() + level3Item2.SetNumbering(multiLevelListId, 2) + level3Item2.AddRun().AddText("实践意义") + + // 二级条目 + level2Item3 := doc.AddParagraph() + level2Item3.SetNumbering(multiLevelListId, 1) + level2Item3.AddRun().AddText("研究方法") + + // 一级条目 + level1Item2 := doc.AddParagraph() + level1Item2.SetNumbering(multiLevelListId, 0) + level1Item2.AddRun().AddText("第二章:文献综述") + + // 结论段落 + doc.AddPageBreak() + conclusionHeading := doc.AddParagraph() + conclusionHeading.SetAlignment("center") + conclusionHeading.SetSpacingBefore(400) + conclusionHeading.SetSpacingAfter(200) + conclusionRun := conclusionHeading.AddRun() + conclusionRun.AddText("总结") + conclusionRun.SetBold(true) + conclusionRun.SetFontSize(32) // 16磅 + + conclusionPara := doc.AddParagraph() + conclusionPara.SetIndentFirstLine(420) + conclusionPara.AddRun().AddText("本文档展示了go-dockit库的各种功能,包括文本样式、段落排版、表格和列表等。通过这些功能,用户可以创建格式丰富、结构清晰的Word文档。欢迎根据需要扩展和完善这些功能。") + + // 保存文档 + err := doc.Save("./document/examples/advanced/advanced_example.docx") + if err != nil { + log.Fatalf("保存文档时出错: %v", err) + } + + fmt.Println("高级功能演示文档已成功保存为 advanced_example.docx") +} + +// 辅助函数:添加目录条目 +func addTocEntry(doc *document.Document, text string, level int) { + tocEntryPara := doc.AddParagraph() + + // 根据层级设置不同的缩进 + if level > 0 { + tocEntryPara.SetIndentLeft(420 * level) + } + + tocEntryPara.SetSpacingBefore(60) + tocEntryPara.SetSpacingAfter(60) + + // 添加文本 + run := tocEntryPara.AddRun() + run.AddText(text) + + // 添加制表符和页码(实际文档中会有页码) + run.AddTab() + run.AddText("...") + run.AddTab() + run.AddText("1") // 示例页码 +} + +// 辅助函数:添加标题 +func addHeading(doc *document.Document, text string, level int) { + headingPara := doc.AddParagraph() + + // 设置段落间距 + headingPara.SetSpacingBefore(400) + headingPara.SetSpacingAfter(200) + + // 添加标题文本 + headingRun := headingPara.AddRun() + headingRun.AddText(text) + headingRun.SetBold(true) + + // 根据级别设置不同的字体大小 + switch level { + case 1: + headingRun.SetFontSize(32) // 16磅 + case 2: + headingRun.SetFontSize(28) // 14磅 + case 3: + headingRun.SetFontSize(26) // 13磅 + default: + headingRun.SetFontSize(24) // 12磅 + } +} diff --git a/document/examples/simple/main.go b/document/examples/simple/main.go index d89b98e..24d2b99 100644 --- a/document/examples/simple/main.go +++ b/document/examples/simple/main.go @@ -134,11 +134,16 @@ func main() { // 添加页脚并同时添加页脚引用 footer := doc.AddFooterWithReference("default") - footerPara := footer.AddParagraph() - footerPara.SetAlignment("center") - footerPara.AddRun().AddText("第 ") - footerPara.AddRun().AddPageNumber() - footerPara.AddRun().AddText(" 页") + + // 使用新的方法添加完整的页码段落 + footer.AddPageNumber() + + // 注释掉旧的方式,它可能会导致页码显示为"第PAGE页" + // footerPara := footer.AddParagraph() + // footerPara.SetAlignment("center") + // footerPara.AddRun().AddText("第 ") + // footerPara.AddRun().AddPageNumber() + // footerPara.AddRun().AddText(" 页") // 保存文档 err := doc.Save("./document/examples/simple/example.docx") diff --git a/document/examples/threeline_table/main.go b/document/examples/threeline_table/main.go new file mode 100644 index 0000000..49e717e --- /dev/null +++ b/document/examples/threeline_table/main.go @@ -0,0 +1,285 @@ +package main + +import ( + "fmt" + "github.com/landaiqing/go-dockit/document" + "log" +) + +func main() { + // 创建一个新的Word文档 + doc := document.NewDocument() + + // 设置文档属性 + doc.SetTitle("数据库三线表示例") + doc.SetCreator("go-dockit库") + doc.SetDescription("使用go-dockit库创建学术论文中常用的数据库三线表") + + // 添加标题 + titlePara := doc.AddParagraph() + titlePara.SetAlignment("center") + titlePara.SetSpacingAfter(200) + titleRun := titlePara.AddRun() + titleRun.AddText("数据库三线表示例") + titleRun.SetBold(true) + titleRun.SetFontSize(32) // 16磅 + titleRun.SetFontFamily("宋体") + + // 添加说明文字 + explainPara := doc.AddParagraph() + explainPara.SetIndentFirstLine(420) + explainPara.SetSpacingAfter(300) + explainPara.AddRun().AddText("数据库三线表是学术论文中常用的一种表格格式,特点是只有三根水平线(顶线、表头分隔线和底线),没有垂直线分隔列。这种表格格式符合APA(美国心理学会)和许多学术期刊的规范。") + + // ========== 示例1:基本三线表 ========== + // 添加表格标题(通常三线表的标题在表格上方) + tableTitlePara := doc.AddParagraph() + tableTitlePara.SetAlignment("center") + tableTitlePara.SetSpacingAfter(0) + tableTitlePara.SetSpacingBefore(0) + tableTitlePara.SetLineSpacing(1.5, "auto") // 设置1.5倍行距 + tableTitleRun := tableTitlePara.AddRun() + tableTitleRun.AddText("表4-1 文章信息表") + tableTitleRun.SetBold(true) + tableTitleRun.SetFontSize(21) // 五号字体约为10.5磅(21) + tableTitleRun.SetFontFamily("宋体") + // 表序号设置为Times New Roman + tableTitleRun.SetFontFamilyForRunes("Times New Roman", []rune("表4-1")) + + // 创建一个表格 + table1 := doc.AddTable(6, 5) + table1.SetWidth("100%", "pct") // 与文字齐宽 + table1.SetAlignment("center") + + // 设置行高为0.72厘米(固定值) + for i := 0; i < 6; i++ { + table1.Rows[i].SetHeight(567, "exact") // 0.72厘米 ≈ 567 twip,"exact"表示固定行高 + } + + // 填充表头 + headers := []string{"字段", "字段名", "类型", "长度", "非空"} + for i, header := range headers { + cellPara := table1.Rows[0].Cells[i].AddParagraph() + cellPara.SetAlignment("center") + cellPara.SetLineSpacing(1.5, "auto") // 1.5倍行距 + cellRun := cellPara.AddRun() + cellRun.AddText(header) + cellRun.SetBold(false) + cellRun.SetFontSize(21) // 五号字体 + cellRun.SetFontFamily("宋体") + } + + // 填充数据行 + data := [][]string{ + {"标题", "title", "varchar", "100", "是"}, + {"文章分类", "sort", "varchar", "150", "是"}, + {"作者学号", "author_sn", "varchar", "100", "是"}, + {"作者姓名", "author_name", "varchar", "100", "否"}, + {"文章内容", "description", "longtext", "默认", "否"}, + } + + for i, row := range data { + for j, cell := range row { + para := table1.Rows[i+1].Cells[j].AddParagraph() + para.SetAlignment("center") + para.SetLineSpacing(1.5, "auto") // 1.5倍行距 + cellRun := para.AddRun() + cellRun.AddText(cell) + cellRun.SetFontSize(21) // 五号字体 + cellRun.SetFontFamily("宋体") + // 英文字体设置为Times New Roman + if j == 1 || j == 2 { // 字段名和类型列通常包含英文 + cellRun.SetFontFamilyForRunes("Times New Roman", []rune(cell)) + } + } + } + + // 设置三线表样式 + // 1. 首先清除所有默认边框 + table1.SetBorders("all", "", 0, "") + + // 2. 顶线(表格顶部边框),1.5磅 + table1.SetBorders("top", "single", 24, "000000") // 1.5磅 = 24 twip + + // 3. 表头分隔线(第一行底部边框),1磅 + for i := 0; i < 5; i++ { + table1.Rows[0].Cells[i].SetBorders("bottom", "single", 16, "000000") // 1磅 = 16 twip + } + + // 4. 底线(表格底部边框),1.5磅 + table1.SetBorders("bottom", "single", 24, "000000") // 1.5磅 = 24 twip + + // 显式设置内部边框为"none",而不是空字符串 + table1.SetBorders("insideH", "none", 0, "000000") + table1.SetBorders("insideV", "none", 0, "000000") + + // ========== 示例2:带有跨页的三线表 ========== + doc.AddParagraph().SetSpacingBefore(400) // 添加空白间隔 + + // 添加表格标题 + tableTitlePara2 := doc.AddParagraph() + tableTitlePara2.SetAlignment("center") + tableTitlePara2.SetSpacingAfter(0) + tableTitlePara2.SetSpacingBefore(0) + tableTitlePara2.SetLineSpacing(1.5, "auto") // 设置1.5倍行距 + tableTitleRun2 := tableTitlePara2.AddRun() + tableTitleRun2.AddText("表4-2 学生成绩信息表") + tableTitleRun2.SetBold(true) + tableTitleRun2.SetFontSize(21) // 五号字体 + tableTitleRun2.SetFontFamily("宋体") + // 表序号设置为Times New Roman + tableTitleRun2.SetFontFamilyForRunes("Times New Roman", []rune("表4-2")) + + // 创建第二个表格 + table2 := doc.AddTable(6, 4) + table2.SetWidth("100%", "pct") // 与文字齐宽 + table2.SetAlignment("center") + + // 设置行高为0.72厘米(固定值) + for i := 0; i < 6; i++ { + table2.Rows[i].SetHeight(567, "exact") // 0.72厘米 ≈ 567 twip,"exact"表示固定行高 + } + + // 填充表头 + headers2 := []string{"学号", "姓名", "科目", "成绩"} + for i, header := range headers2 { + cellPara := table2.Rows[0].Cells[i].AddParagraph() + cellPara.SetAlignment("center") + cellPara.SetLineSpacing(1.5, "auto") // 1.5倍行距 + cellRun := cellPara.AddRun() + cellRun.AddText(header) + cellRun.SetBold(false) + cellRun.SetFontSize(21) // 五号字体 + cellRun.SetFontFamily("宋体") + } + + // 填充数据行 + data2 := [][]string{ + {"2020001", "张三", "数据库", "85"}, + {"2020002", "李四", "数据库", "92"}, + {"2020003", "王五", "数据库", "78"}, + {"2020004", "赵六", "数据库", "88"}, + {"2020005", "钱七", "数据库", "95"}, + } + + for i, row := range data2 { + for j, cell := range row { + para := table2.Rows[i+1].Cells[j].AddParagraph() + para.SetAlignment("center") + para.SetLineSpacing(1.5, "auto") // 1.5倍行距 + cellRun := para.AddRun() + cellRun.AddText(cell) + cellRun.SetFontSize(21) // 五号字体 + cellRun.SetFontFamily("宋体") + // 英文和数字设置为Times New Roman + if j == 0 || j == 3 { // 学号和成绩列 + cellRun.SetFontFamilyForRunes("Times New Roman", []rune(cell)) + } + } + } + + // 设置三线表样式 + // 1. 首先清除所有默认边框 + table2.SetBorders("all", "", 0, "") + + // 2. 顶线(表格顶部边框),1.5磅 + table2.SetBorders("top", "single", 24, "000000") + + // 3. 表头分隔线(第一行底部边框),1磅 + for i := 0; i < 4; i++ { + table2.Rows[0].Cells[i].SetBorders("bottom", "single", 16, "000000") + } + + // 4. 底线(表格底部边框),1.5磅 + table2.SetBorders("bottom", "single", 24, "000000") + + // 显式设置内部边框为"none",而不是空字符串 + table2.SetBorders("insideH", "none", 0, "000000") + table2.SetBorders("insideV", "none", 0, "000000") + + // 演示跨页表格续表标题 + doc.AddPageBreak() + + // 添加续表标题 + tableTitlePara3 := doc.AddParagraph() + tableTitlePara3.SetAlignment("center") + tableTitlePara3.SetSpacingAfter(0) + tableTitlePara3.SetSpacingBefore(0) + tableTitlePara3.SetLineSpacing(1.5, "auto") + tableTitleRun3 := tableTitlePara3.AddRun() + tableTitleRun3.AddText("表4-2 学生成绩信息表(续)") + tableTitleRun3.SetBold(true) + tableTitleRun3.SetFontSize(21) + tableTitleRun3.SetFontFamily("宋体") + tableTitleRun3.SetFontFamilyForRunes("Times New Roman", []rune("表4-2")) + + // 创建续表 + table3 := doc.AddTable(6, 4) + table3.SetWidth("100%", "pct") + table3.SetAlignment("center") + + // 设置行高为0.72厘米(固定值) + for i := 0; i < 6; i++ { + table3.Rows[i].SetHeight(567, "exact") // 0.72厘米 ≈ 567 twip,"exact"表示固定行高 + } + + // 填充表头 + for i, header := range headers2 { + cellPara := table3.Rows[0].Cells[i].AddParagraph() + cellPara.SetAlignment("center") + cellPara.SetLineSpacing(1.5, "auto") + cellRun := cellPara.AddRun() + cellRun.AddText(header) + cellRun.SetBold(false) + cellRun.SetFontSize(21) + cellRun.SetFontFamily("宋体") + } + + // 填充续表数据 + data3 := [][]string{ + {"2020006", "孙八", "数据库", "82"}, + {"2020007", "周九", "数据库", "90"}, + {"2020008", "吴十", "数据库", "87"}, + {"2020009", "郑十一", "数据库", "91"}, + {"2020010", "王十二", "数据库", "84"}, + } + + for i, row := range data3 { + for j, cell := range row { + para := table3.Rows[i+1].Cells[j].AddParagraph() + para.SetAlignment("center") + para.SetLineSpacing(1.5, "auto") + cellRun := para.AddRun() + cellRun.AddText(cell) + cellRun.SetFontSize(21) + cellRun.SetFontFamily("宋体") + if j == 0 || j == 3 { + cellRun.SetFontFamilyForRunes("Times New Roman", []rune(cell)) + } + } + } + + // 设置三线表样式 + table3.SetBorders("all", "", 0, "") + table3.SetBorders("top", "single", 24, "000000") + for i := 0; i < 4; i++ { + table3.Rows[0].Cells[i].SetBorders("bottom", "single", 16, "000000") + } + table3.SetBorders("bottom", "single", 24, "000000") + + // 显式设置内部边框为"none",而不是空字符串 + table3.SetBorders("insideH", "none", 0, "000000") + table3.SetBorders("insideV", "none", 0, "000000") + + // 添加页脚(页码) + footer := doc.AddFooterWithReference("default") + footer.AddPageNumber() + + // 保存文档 + err := doc.Save("./document/examples/threeline_table/threeline_table_example.docx") + if err != nil { + log.Fatalf("保存文档时出错: %v", err) + } + + fmt.Println("数据库三线表示例文档已成功保存为 threeline_table_example.docx") +} diff --git a/document/header_footer.go b/document/header_footer.go index ae47b96..a9c10ec 100644 --- a/document/header_footer.go +++ b/document/header_footer.go @@ -56,6 +56,45 @@ func (f *Footer) AddTable(rows, cols int) *Table { return t } +// AddPageNumber 添加一个居中的页码 +func (f *Footer) AddPageNumber() *Paragraph { + // 创建一个新段落 + para := f.AddParagraph() + para.SetAlignment("center") + + // 添加"第"文本 + para.AddRun().AddText("第 ") + + // 创建页码域的所有部分 + // 1. 域开始 + fieldBegin := para.AddRun() + fieldBegin.Field = &Field{ + Type: "begin", + Code: "PAGE", + } + + // 2. 域分隔符 + fieldSeparate := para.AddRun() + fieldSeparate.Field = &Field{ + Type: "separate", + } + + // 3. 页码内容(在Word中会替换为实际页码) + fieldContent := para.AddRun() + fieldContent.AddText("1") + + // 4. 域结束 + fieldEnd := para.AddRun() + fieldEnd.Field = &Field{ + Type: "end", + } + + // 添加"页"文本 + para.AddRun().AddText(" 页") + + return para +} + // ToXML 将页眉转换为XML func (h *Header) ToXML() string { xml := "" diff --git a/document/paragraph.go b/document/paragraph.go index 548463b..d9e0baf 100644 --- a/document/paragraph.go +++ b/document/paragraph.go @@ -115,6 +115,16 @@ func (p *Paragraph) SetSpacingLine(spacing int, rule string) *Paragraph { return p } +// SetLineSpacing 设置行距 +func (p *Paragraph) SetLineSpacing(lineSpacing float64, rule string) *Paragraph { + // Word中行距值的计算: + // 1.5倍行距 = 360 (240 * 1.5) + // 2.0倍行距 = 480 (240 * 2.0) + // 3.0倍行距 = 720 (240 * 3.0) + spacing := int(240 * lineSpacing) + return p.SetSpacingLine(spacing, rule) +} + // SetKeepNext 设置与下段同页 func (p *Paragraph) SetKeepNext(keepNext bool) *Paragraph { p.Properties.KeepNext = keepNext @@ -148,6 +158,16 @@ func (p *Paragraph) SetNumbering(numID, numLevel int) *Paragraph { // SetBorder 设置边框 func (p *Paragraph) SetBorder(position string, style string, size int, color string, space int) *Paragraph { + // 如果style为空,设置为"none" + if style == "" { + style = "none" + } + + // 如果color为空,设置为默认颜色黑色 + if color == "" { + color = "000000" + } + border := &Border{ Style: style, Size: size, diff --git a/document/run.go b/document/run.go index a069b04..7b750da 100644 --- a/document/run.go +++ b/document/run.go @@ -2,6 +2,7 @@ package document import ( "fmt" + "strings" ) // Run 表示Word文档中的文本运行 @@ -133,6 +134,27 @@ func (r *Run) SetFontFamily(fontFamily string) *Run { return r } +// SetFontFamilyForRunes 为特定字符设置字体 +// 该方法允许为特定的字符序列设置不同的字体 +func (r *Run) SetFontFamilyForRunes(fontFamily string, runes []rune) *Run { + if runes == nil || len(runes) == 0 { + r.Properties.FontFamily = fontFamily + return r + } + + // 将当前文本中的指定字符设置为指定字体 + runeText := string(runes) + if strings.Contains(r.Text, runeText) { + // 如果当前文本包含指定字符,设置字体 + // 注意:这是一个简化实现,只是将整个Run的字体设置为指定字体 + // 实际上,为了真正支持混合字体,需要将Run拆分为多个不同字体的Run + // 但这需要对段落对象的引用,当前结构不支持这种操作 + r.Properties.FontFamily = fontFamily + } + + return r +} + // SetColor 设置颜色 func (r *Run) SetColor(color string) *Run { r.Properties.Color = color @@ -201,9 +223,31 @@ func (r *Run) AddField(fieldType string, fieldCode string) *Run { return r } +// AddTab 添加制表符到文本运行中 +func (r *Run) AddTab() *Run { + // 在OOXML中,制表符被表示为元素 + // 这里我们使用一个特殊标记,在ToXML时会被替换为制表符标签 + r.Text += "\t" + return r +} + // AddPageNumber 添加页码域 +// Deprecated: 建议使用Document.AddPageNumberParagraph或Footer.AddPageNumber方法 func (r *Run) AddPageNumber() *Run { - return r.AddField("begin", " PAGE ") + // 注意:此方法生成的页码字段可能不完整 + // 推荐使用Document.AddPageNumberParagraph或Footer.AddPageNumber方法 + // 创建一个完整的页码字段,包含所有需要的部分 + + // 返回一个标准的域开始标记 + return r.AddField("begin", "PAGE") +} + +// findParagraph 尝试找到当前Run所在的段落 +func (r *Run) findParagraph() *Paragraph { + // 这个方法可能需要访问Document实例来找到包含当前Run的段落 + // 由于目前的结构限制,我们简单地返回nil,表示找不到段落 + // 在实际完整实现中,应该找到文档中包含此Run的段落 + return nil } // ToXML 将Run转换为XML @@ -322,39 +366,73 @@ func (r *Run) ToXML() string { xml += "" - // 添加分隔符 - if r.BreakType != "" { - xml += fmt.Sprintf("", r.BreakType) - } - - // 添加文本 - if r.Text != "" { - xml += fmt.Sprintf("%s", r.Text) - } - - // 添加图形 - if r.Drawing != nil { - xml += r.Drawing.ToXML() - } - - // 添加域 + // 处理域 if r.Field != nil { - if r.Field.Type == "begin" { - xml += "" - } else if r.Field.Type == "separate" { + switch r.Field.Type { + case "begin": + xml += fmt.Sprintf("%s", r.Field.Code) + case "separate": xml += "" - } else if r.Field.Type == "end" { + case "end": xml += "" } + } else if r.Drawing != nil { + // 添加图形元素 + xml += r.Drawing.ToXML() + } else if r.BreakType != "" { + // 添加分隔符 + switch r.BreakType { + case BreakTypePage: + xml += "" + case BreakTypeColumn: + xml += "" + case BreakTypeSection: + xml += "" + case BreakTypeLine: + xml += "" + } + } else { + // 处理文本内容 + // 特殊处理:制表符 + if r.Text != "" { + var textParts []string + for _, char := range r.Text { + if char == '\t' { + // 如果是制表符,关闭当前文本,添加制表符标签,然后重新开始文本 + if len(textParts) > 0 { + xml += fmt.Sprintf("%s", escapeXML(textParts[len(textParts)-1])) + textParts = textParts[:len(textParts)-1] + } + xml += "" + textParts = append(textParts, "") + } else { + // 如果是普通字符,添加到当前文本部分 + if len(textParts) == 0 { + textParts = append(textParts, "") + } + textParts[len(textParts)-1] += string(char) + } + } - if r.Field.Code != "" && r.Field.Type == "begin" { - // 添加域代码 - xml += "" + r.Field.Code + "" - // 添加域结束标记 - xml += "" + // 处理最后一个文本部分 + for _, part := range textParts { + if part != "" { + xml += fmt.Sprintf("%s", escapeXML(part)) + } + } } } xml += "" return xml } + +// escapeXML 转义XML文本中的特殊字符 +func escapeXML(s string) string { + s = strings.ReplaceAll(s, "&", "&") + s = strings.ReplaceAll(s, "<", "<") + s = strings.ReplaceAll(s, ">", ">") + s = strings.ReplaceAll(s, "\"", """) + s = strings.ReplaceAll(s, "'", "'") + return s +} diff --git a/document/table.go b/document/table.go index 8e049a4..15fc22b 100644 --- a/document/table.go +++ b/document/table.go @@ -133,9 +133,28 @@ func (t *Table) AddRow() *TableRow { } // SetWidth 设置表格宽度 -func (t *Table) SetWidth(width int, widthType string) *Table { - t.Properties.Width = width - t.Properties.WidthType = widthType +// width可以是整数(表示twip单位)或字符串(如"100%"表示百分比) +func (t *Table) SetWidth(width interface{}, widthType string) *Table { + switch v := width.(type) { + case int: + t.Properties.Width = v + t.Properties.WidthType = widthType + case string: + // 处理百分比格式,如"100%" + if v == "100%" && widthType == "pct" { + // Word中百分比是用整数表示的,5000 = 100% + t.Properties.Width = 5000 + t.Properties.WidthType = "pct" + } else { + // 默认为自动宽度 + t.Properties.Width = 0 + t.Properties.WidthType = "auto" + } + default: + // 默认为自动宽度 + t.Properties.Width = 0 + t.Properties.WidthType = "auto" + } return t } @@ -159,6 +178,16 @@ func (t *Table) SetLayout(layout string) *Table { // SetBorders 设置表格边框 func (t *Table) SetBorders(position string, style string, size int, color string) *Table { + // 如果style为空,设置为"none" + if style == "" { + style = "none" + } + + // 如果color为空,设置为默认颜色黑色 + if color == "" { + color = "000000" + } + border := &Border{ Style: style, Size: size, @@ -302,9 +331,28 @@ func (c *TableCell) AddTable(rows, cols int) *Table { } // SetWidth 设置单元格宽度 -func (c *TableCell) SetWidth(width int, widthType string) *TableCell { - c.Properties.Width = width - c.Properties.WidthType = widthType +// width可以是整数(表示twip单位)或字符串(如"100%"表示百分比) +func (c *TableCell) SetWidth(width interface{}, widthType string) *TableCell { + switch v := width.(type) { + case int: + c.Properties.Width = v + c.Properties.WidthType = widthType + case string: + // 处理百分比格式,如"100%" + if v == "100%" && widthType == "pct" { + // Word中百分比是用整数表示的,5000 = 100% + c.Properties.Width = 5000 + c.Properties.WidthType = "pct" + } else { + // 默认为自动宽度 + c.Properties.Width = 0 + c.Properties.WidthType = "auto" + } + default: + // 默认为自动宽度 + c.Properties.Width = 0 + c.Properties.WidthType = "auto" + } return c } @@ -320,6 +368,16 @@ func (c *TableCell) SetBorders(position string, style string, size int, color st c.Properties.Borders = &TableBorders{} } + // 如果style为空,设置为"none" + if style == "" { + style = "none" + } + + // 如果color为空,设置为默认颜色黑色 + if color == "" { + color = "000000" + } + border := &Border{ Style: style, Size: size, @@ -556,19 +614,28 @@ func (c *TableCell) ToXML() string { // 添加单元格属性 xml += "" - // 单元格宽度 + // 1. cnfStyle - 暂不实现 + + // 2. 单元格宽度 (tcW) if c.Properties.Width > 0 { xml += "" } else { xml += "" } - // 垂直对齐方式 - if c.Properties.VertAlign != "" { - xml += "" + // 3. 跨列数 (gridSpan) + if c.Properties.GridSpan > 1 { + xml += "" } - // 单元格边框 + // 4. hMerge - 暂不实现 + + // 5. 垂直合并 (vMerge) + if c.Properties.VMerge != "" { + xml += "" + } + + // 6. 单元格边框 (tcBorders) if c.Properties.Borders != nil { xml += "" if c.Properties.Borders.Top != nil { @@ -586,31 +653,32 @@ func (c *TableCell) ToXML() string { xml += "" } - // 底纹 + // 7. 底纹 (shd) if c.Properties.Shading != nil { xml += "" } - // 跨列数 - if c.Properties.GridSpan > 1 { - xml += "" - } - - // 垂直合并 - if c.Properties.VMerge != "" { - xml += "" - } - - // 不换行 + // 8. 不换行 (noWrap) if c.Properties.NoWrap { xml += "" } - // 适应文本 + // 9. tcMar - 暂不实现 + + // 10. textDirection - 暂不实现 + + // 11. 适应文本 (tcFitText) if c.Properties.FitText { - xml += "" + xml += "" } + // 12. 垂直对齐方式 (vAlign) + if c.Properties.VertAlign != "" { + xml += "" + } + + // 13. hideMark - 暂不实现 + xml += "" // 添加所有内容元素的XML