diff --git a/examples/paragraph_format_demo/main.go b/examples/paragraph_format_demo/main.go new file mode 100644 index 0000000..916ff68 --- /dev/null +++ b/examples/paragraph_format_demo/main.go @@ -0,0 +1,201 @@ +package main + +import ( + "log" + + "github.com/ZeroHawkeye/wordZero/pkg/document" +) + +func main() { + // 创建新文档 + doc := document.New() + + // 示例1:使用单独的方法设置段落属性 + title := doc.AddParagraph("第一章:段落格式自定义功能演示") + title.SetAlignment(document.AlignCenter) + title.SetStyle("Heading1") + title.SetKeepWithNext(true) // 与下一段保持在同一页 + title.SetPageBreakBefore(true) // 从新页开始 + title.SetOutlineLevel(0) // 设置为一级大纲 + + // 示例2:使用SetParagraphFormat一次性设置多个属性 + intro := doc.AddParagraph("本章将介绍WordZero库的段落格式自定义功能,包括分页控制、行控制、大纲级别等高级特性。") + intro.SetParagraphFormat(&document.ParagraphFormatConfig{ + Alignment: document.AlignJustify, + LineSpacing: 1.5, + BeforePara: 12, + AfterPara: 12, + FirstLineCm: 0.5, + WidowControl: true, // 启用孤行控制 + }) + + // 示例3:段落保持在一起的演示 + subtitle1 := doc.AddParagraph("1.1 段落分页控制") + subtitle1.SetParagraphFormat(&document.ParagraphFormatConfig{ + Style: "Heading2", + Alignment: document.AlignLeft, + BeforePara: 18, + AfterPara: 6, + KeepWithNext: true, // 确保标题和下一段不被分页 + OutlineLevel: 1, + }) + + content1 := doc.AddParagraph("段落分页控制功能允许您精确控制段落在文档中的分页行为。" + + "通过SetKeepWithNext方法,可以确保标题和其后的内容段落始终保持在同一页面上," + + "避免标题单独出现在页面底部的情况。") + content1.SetParagraphFormat(&document.ParagraphFormatConfig{ + Alignment: document.AlignJustify, + LineSpacing: 1.5, + FirstLineCm: 0.5, + WidowControl: true, + }) + + // 示例4:保持段落行在一起 + subtitle2 := doc.AddParagraph("1.2 段落行保持功能") + subtitle2.SetParagraphFormat(&document.ParagraphFormatConfig{ + Style: "Heading2", + Alignment: document.AlignLeft, + BeforePara: 18, + AfterPara: 6, + KeepWithNext: true, + OutlineLevel: 1, + }) + + content2 := doc.AddParagraph("使用SetKeepLines方法可以确保段落的所有行都保持在同一页面上," + + "防止段落被分割到不同页面。这对于需要保持完整性的段落(如重要说明、引用等)非常有用。") + content2.SetParagraphFormat(&document.ParagraphFormatConfig{ + Alignment: document.AlignJustify, + LineSpacing: 1.5, + FirstLineCm: 0.5, + KeepLines: true, // 段落所有行保持在一起 + }) + + // 示例5:章节开始新页 + chapter2 := doc.AddParagraph("第二章:高级格式特性") + chapter2.SetParagraphFormat(&document.ParagraphFormatConfig{ + Alignment: document.AlignCenter, + Style: "Heading1", + BeforePara: 24, + AfterPara: 12, + PageBreakBefore: true, // 新章节从新页开始 + KeepWithNext: true, + OutlineLevel: 0, + }) + + intro2 := doc.AddParagraph("本章介绍更多高级段落格式特性的使用方法。") + intro2.SetParagraphFormat(&document.ParagraphFormatConfig{ + Alignment: document.AlignJustify, + LineSpacing: 1.5, + FirstLineCm: 0.5, + BeforePara: 6, + AfterPara: 6, + WidowControl: true, + }) + + // 示例6:带缩进的特殊段落 + subtitle3 := doc.AddParagraph("2.1 复杂缩进示例") + subtitle3.SetParagraphFormat(&document.ParagraphFormatConfig{ + Style: "Heading2", + Alignment: document.AlignLeft, + BeforePara: 18, + AfterPara: 6, + KeepWithNext: true, + OutlineLevel: 1, + }) + + // 悬挂缩进示例 + hangingPara := doc.AddParagraph("● 这是一个使用悬挂缩进的列表项。" + + "悬挂缩进是指首行向左突出,而后续行向右缩进的格式。" + + "这种格式常用于编号列表、项目符号列表等场景。") + hangingPara.SetParagraphFormat(&document.ParagraphFormatConfig{ + Alignment: document.AlignLeft, + FirstLineCm: -0.5, // 悬挂缩进 + LeftCm: 1.0, // 左缩进 + LineSpacing: 1.2, + }) + + // 示例7:引用块样式 + quote := doc.AddParagraph("\"优秀的排版不仅仅是让文字看起来漂亮," + + "更重要的是让读者能够轻松理解内容的结构和层次。\"") + quote.SetParagraphFormat(&document.ParagraphFormatConfig{ + Alignment: document.AlignCenter, + LeftCm: 1.5, + RightCm: 1.5, + BeforePara: 12, + AfterPara: 12, + LineSpacing: 1.5, + KeepLines: true, // 引用保持完整 + }) + + // 示例8:大纲级别演示 + section := doc.AddParagraph("2.2 大纲级别的使用") + section.SetParagraphFormat(&document.ParagraphFormatConfig{ + Style: "Heading2", + Alignment: document.AlignLeft, + BeforePara: 18, + AfterPara: 6, + KeepWithNext: true, + OutlineLevel: 1, // 二级标题的大纲级别 + }) + + subsection := doc.AddParagraph("2.2.1 三级标题示例") + subsection.SetParagraphFormat(&document.ParagraphFormatConfig{ + Style: "Heading3", + Alignment: document.AlignLeft, + BeforePara: 12, + AfterPara: 6, + KeepWithNext: true, + OutlineLevel: 2, // 三级标题的大纲级别 + }) + + finalContent := doc.AddParagraph("大纲级别设置可以让Word在导航窗格中显示文档结构," + + "方便读者快速浏览和定位内容。级别范围从0到8,分别对应标题1到标题9," + + "而正文段落通常不设置大纲级别。") + finalContent.SetParagraphFormat(&document.ParagraphFormatConfig{ + Alignment: document.AlignJustify, + LineSpacing: 1.5, + FirstLineCm: 0.5, + BeforePara: 6, + AfterPara: 6, + WidowControl: true, + }) + + // 示例9:总结段落 + summary := doc.AddParagraph("总结") + summary.SetParagraphFormat(&document.ParagraphFormatConfig{ + Alignment: document.AlignCenter, + Style: "Heading1", + BeforePara: 24, + PageBreakBefore: true, + OutlineLevel: 0, + }) + + summaryContent := doc.AddParagraph("通过本文档的演示,您已经了解了WordZero库提供的全面段落格式自定义功能," + + "包括对齐方式、间距设置、缩进控制、分页控制、行控制、孤行控制以及大纲级别设置等。" + + "这些功能可以帮助您创建专业、美观且结构清晰的Word文档。") + summaryContent.SetParagraphFormat(&document.ParagraphFormatConfig{ + Alignment: document.AlignJustify, + LineSpacing: 1.5, + FirstLineCm: 0.5, + BeforePara: 12, + AfterPara: 12, + WidowControl: true, + KeepLines: true, + }) + + // 保存文档 + filename := "examples/output/paragraph_format_demo.docx" + err := doc.Save(filename) + if err != nil { + log.Fatalf("保存文档失败: %v", err) + } + + log.Printf("段落格式演示文档创建成功!文件保存在: %s", filename) + log.Println("该文档演示了以下功能:") + log.Println(" - SetKeepWithNext: 标题与下一段保持在同一页") + log.Println(" - SetKeepLines: 段落行保持在一起不被分页") + log.Println(" - SetPageBreakBefore: 章节从新页开始") + log.Println(" - SetWidowControl: 孤行控制提升排版质量") + log.Println(" - SetOutlineLevel: 大纲级别设置便于文档导航") + log.Println(" - SetParagraphFormat: 一次性设置多个格式属性") +} diff --git a/pkg/document/README.md b/pkg/document/README.md index 6d95d08..46a9b80 100644 --- a/pkg/document/README.md +++ b/pkg/document/README.md @@ -344,6 +344,68 @@ func demonstrateTemplateInheritance() { - [`SetAlignment(alignment AlignmentType)`](document.go) - 设置段落对齐方式 - [`SetSpacing(config *SpacingConfig)`](document.go) - 设置段落间距 - [`SetStyle(styleID string)`](document.go) - 设置段落样式 +- [`SetIndentation(firstLineCm, leftCm, rightCm float64)`](document.go) - 设置段落缩进 ✨ **已完善** +- [`SetKeepWithNext(keep bool)`](document.go) - 设置与下一段落保持在同一页 ✨ **新增** +- [`SetKeepLines(keep bool)`](document.go) - 设置段落所有行保持在同一页 ✨ **新增** +- [`SetPageBreakBefore(pageBreak bool)`](document.go) - 设置段前分页 ✨ **新增** +- [`SetWidowControl(control bool)`](document.go) - 设置孤行控制 ✨ **新增** +- [`SetOutlineLevel(level int)`](document.go) - 设置大纲级别 ✨ **新增** +- [`SetParagraphFormat(config *ParagraphFormatConfig)`](document.go) - 一次性设置所有段落格式属性 ✨ **新增** + +#### 段落格式高级功能 ✨ **新增功能** + +WordZero现在支持完整的段落格式自定义功能,提供与Microsoft Word相同的高级段落控制选项。 + +**分页控制功能**: +- **SetKeepWithNext** - 确保段落与下一段落保持在同一页,避免标题单独出现在页面底部 +- **SetKeepLines** - 防止段落被分页拆分,保持段落完整性 +- **SetPageBreakBefore** - 在段落前强制插入分页符,常用于章节开始 + +**孤行控制**: +- **SetWidowControl** - 防止段落第一行或最后一行单独出现在页面顶部或底部,提升排版质量 + +**大纲级别**: +- **SetOutlineLevel** - 设置段落的大纲级别(0-8),用于文档导航窗格显示和目录生成 + +**综合格式设置**: +- **SetParagraphFormat** - 使用`ParagraphFormatConfig`结构一次性设置所有段落属性 + - 基础格式:对齐方式、样式 + - 间距设置:行间距、段前段后间距、首行缩进 + - 缩进设置:首行缩进、左右缩进(支持悬挂缩进) + - 分页控制:与下段保持、行保持、段前分页、孤行控制 + - 大纲级别:0-8级别设置 + +**使用示例**: + +```go +// 方法1:使用单独的方法设置 +title := doc.AddParagraph("第一章 概述") +title.SetAlignment(document.AlignCenter) +title.SetKeepWithNext(true) +title.SetPageBreakBefore(true) +title.SetOutlineLevel(0) + +// 方法2:使用SetParagraphFormat一次性设置 +para := doc.AddParagraph("重要内容") +para.SetParagraphFormat(&document.ParagraphFormatConfig{ + Alignment: document.AlignJustify, + Style: "Normal", + LineSpacing: 1.5, + BeforePara: 12, + AfterPara: 6, + FirstLineCm: 0.5, + KeepWithNext: true, + KeepLines: true, + WidowControl: true, + OutlineLevel: 0, +}) +``` + +**应用场景**: +- **文档结构化** - 使用大纲级别创建清晰的文档层次结构 +- **专业排版** - 使用分页控制确保标题和内容的关联性 +- **内容保护** - 使用行保持防止重要段落被分页 +- **章节管理** - 使用段前分页实现章节的页面独立性 ### 段落内容操作 - [`AddFormattedText(text string, format *TextFormat)`](document.go) - 添加格式化文本 diff --git a/pkg/document/document.go b/pkg/document/document.go index 0b69a42..dbe6be6 100644 --- a/pkg/document/document.go +++ b/pkg/document/document.go @@ -108,6 +108,11 @@ type ParagraphProperties struct { Spacing *Spacing `xml:"w:spacing,omitempty"` Indentation *Indentation `xml:"w:ind,omitempty"` Justification *Justification `xml:"w:jc,omitempty"` + KeepNext *KeepNext `xml:"w:keepNext,omitempty"` // 与下一段落保持在一起 + KeepLines *KeepLines `xml:"w:keepLines,omitempty"` // 段落中的行保持在一起 + PageBreakBefore *PageBreakBefore `xml:"w:pageBreakBefore,omitempty"` // 段前分页 + WidowControl *WidowControl `xml:"w:widowControl,omitempty"` // 孤行控制 + OutlineLevel *OutlineLevel `xml:"w:outlineLvl,omitempty"` // 大纲级别 } // ParagraphBorder 段落边框 @@ -142,6 +147,36 @@ type Justification struct { Val string `xml:"w:val,attr"` } +// KeepNext 与下一段落保持在一起 +type KeepNext struct { + XMLName xml.Name `xml:"w:keepNext"` + Val string `xml:"w:val,attr,omitempty"` +} + +// KeepLines 段落中的行保持在一起 +type KeepLines struct { + XMLName xml.Name `xml:"w:keepLines"` + Val string `xml:"w:val,attr,omitempty"` +} + +// PageBreakBefore 段前分页 +type PageBreakBefore struct { + XMLName xml.Name `xml:"w:pageBreakBefore"` + Val string `xml:"w:val,attr,omitempty"` +} + +// WidowControl 孤行控制 +type WidowControl struct { + XMLName xml.Name `xml:"w:widowControl"` + Val string `xml:"w:val,attr,omitempty"` +} + +// OutlineLevel 大纲级别 +type OutlineLevel struct { + XMLName xml.Name `xml:"w:outlineLvl"` + Val string `xml:"w:val,attr"` +} + // Run 表示一段文本 type Run struct { XMLName xml.Name `xml:"w:r"` @@ -1219,6 +1254,257 @@ func (p *Paragraph) SetIndentation(firstLineCm, leftCm, rightCm float64) { Debugf("设置段落缩进: 首行=%.2fcm, 左=%.2fcm, 右=%.2fcm", firstLineCm, leftCm, rightCm) } +// SetKeepWithNext 设置段落与下一段落保持在同一页。 +// +// 此方法用于确保当前段落和下一段落不会被分页符分隔, +// 常用于标题和正文的组合,或需要保持连续性的内容。 +// +// 参数: +// - keep: true表示启用该属性,false表示禁用 +// +// 示例: +// +// // 标题与下一段保持在一起 +// title := doc.AddParagraph("第一章 概述") +// title.SetKeepWithNext(true) +// doc.AddParagraph("本章介绍...") // 这段内容会与标题保持在同一页 +func (p *Paragraph) SetKeepWithNext(keep bool) { + if p.Properties == nil { + p.Properties = &ParagraphProperties{} + } + + if keep { + p.Properties.KeepNext = &KeepNext{Val: "1"} + Debugf("设置段落与下一段保持在一起") + } else { + p.Properties.KeepNext = nil + Debugf("取消段落与下一段保持在一起") + } +} + +// SetKeepLines 设置段落中的所有行保持在同一页。 +// +// 此方法用于防止段落在分页时被拆分到多个页面, +// 确保段落的所有行都显示在同一页上。 +// +// 参数: +// - keep: true表示启用该属性,false表示禁用 +// +// 示例: +// +// // 确保整个段落不被分页 +// para := doc.AddParagraph("这是一个重要的段落,需要保持完整显示。") +// para.SetKeepLines(true) +func (p *Paragraph) SetKeepLines(keep bool) { + if p.Properties == nil { + p.Properties = &ParagraphProperties{} + } + + if keep { + p.Properties.KeepLines = &KeepLines{Val: "1"} + Debugf("设置段落行保持在一起") + } else { + p.Properties.KeepLines = nil + Debugf("取消段落行保持在一起") + } +} + +// SetPageBreakBefore 设置段落前插入分页符。 +// +// 此方法用于在段落之前强制插入分页符,使段落从新页开始显示。 +// 常用于章节标题或需要单独成页的内容。 +// +// 参数: +// - pageBreak: true表示启用段前分页,false表示禁用 +// +// 示例: +// +// // 章节标题从新页开始 +// chapter := doc.AddParagraph("第二章 详细说明") +// chapter.SetPageBreakBefore(true) +func (p *Paragraph) SetPageBreakBefore(pageBreak bool) { + if p.Properties == nil { + p.Properties = &ParagraphProperties{} + } + + if pageBreak { + p.Properties.PageBreakBefore = &PageBreakBefore{Val: "1"} + Debugf("设置段前分页") + } else { + p.Properties.PageBreakBefore = nil + Debugf("取消段前分页") + } +} + +// SetWidowControl 设置段落的孤行控制。 +// +// 孤行控制用于防止段落的第一行或最后一行单独出现在页面底部或顶部, +// 提高文档的排版质量。 +// +// 参数: +// - control: true表示启用孤行控制(默认),false表示禁用 +// +// 示例: +// +// para := doc.AddParagraph("这是一个长段落...") +// para.SetWidowControl(true) // 启用孤行控制 +func (p *Paragraph) SetWidowControl(control bool) { + if p.Properties == nil { + p.Properties = &ParagraphProperties{} + } + + if control { + p.Properties.WidowControl = &WidowControl{Val: "1"} + Debugf("启用段落孤行控制") + } else { + p.Properties.WidowControl = &WidowControl{Val: "0"} + Debugf("禁用段落孤行控制") + } +} + +// SetOutlineLevel 设置段落的大纲级别。 +// +// 大纲级别用于在文档导航窗格中显示文档结构,级别范围为0-8。 +// 通常用于标题段落,配合目录功能使用。 +// +// 参数: +// - level: 大纲级别,0-8之间的整数(0表示正文,1-8对应标题1-8) +// +// 示例: +// +// // 设置为一级标题的大纲级别 +// title := doc.AddParagraph("第一章") +// title.SetOutlineLevel(0) // 对应Heading1 +// +// // 设置为二级标题的大纲级别 +// subtitle := doc.AddParagraph("1.1 概述") +// subtitle.SetOutlineLevel(1) // 对应Heading2 +func (p *Paragraph) SetOutlineLevel(level int) { + if p.Properties == nil { + p.Properties = &ParagraphProperties{} + } + + if level < 0 || level > 8 { + Warnf("大纲级别应在0-8之间,已调整为有效范围") + if level < 0 { + level = 0 + } else { + level = 8 + } + } + + p.Properties.OutlineLevel = &OutlineLevel{Val: strconv.Itoa(level)} + Debugf("设置段落大纲级别: %d", level) +} + +// ParagraphFormatConfig 段落格式配置 +// +// 此结构体提供了段落所有格式属性的统一配置接口, +// 允许一次性设置多个段落属性,提高代码的可读性和易用性。 +type ParagraphFormatConfig struct { + // 基础格式 + Alignment AlignmentType // 对齐方式(AlignLeft, AlignCenter, AlignRight, AlignJustify) + Style string // 段落样式ID(如"Heading1", "Normal"等) + + // 间距设置 + LineSpacing float64 // 行间距(倍数,如1.5表示1.5倍行距) + BeforePara int // 段前间距(磅) + AfterPara int // 段后间距(磅) + FirstLineIndent int // 首行缩进(磅) + + // 缩进设置 + FirstLineCm float64 // 首行缩进(厘米,可以为负数表示悬挂缩进) + LeftCm float64 // 左缩进(厘米) + RightCm float64 // 右缩进(厘米) + + // 分页与控制 + KeepWithNext bool // 与下一段落保持在同一页 + KeepLines bool // 段落中的所有行保持在同一页 + PageBreakBefore bool // 段前分页 + WidowControl bool // 孤行控制 + + // 大纲级别 + OutlineLevel int // 大纲级别(0-8,0表示正文,1-8对应标题1-8) +} + +// SetParagraphFormat 使用配置一次性设置段落的所有格式属性。 +// +// 此方法提供了一种便捷的方式来设置段落的所有格式属性, +// 而不需要调用多个单独的设置方法。只有非零值的属性会被应用。 +// +// 参数: +// - config: 段落格式配置,包含所有格式属性 +// +// 示例: +// +// // 创建一个带完整格式的段落 +// para := doc.AddParagraph("重要章节标题") +// para.SetParagraphFormat(&document.ParagraphFormatConfig{ +// Alignment: document.AlignCenter, +// Style: "Heading1", +// LineSpacing: 1.5, +// BeforePara: 24, +// AfterPara: 12, +// KeepWithNext: true, +// PageBreakBefore: true, +// OutlineLevel: 0, +// }) +// +// // 设置带缩进的正文段落 +// para2 := doc.AddParagraph("正文内容...") +// para2.SetParagraphFormat(&document.ParagraphFormatConfig{ +// Alignment: document.AlignJustify, +// FirstLineCm: 0.5, +// LineSpacing: 1.5, +// BeforePara: 6, +// AfterPara: 6, +// WidowControl: true, +// }) +func (p *Paragraph) SetParagraphFormat(config *ParagraphFormatConfig) { + if config == nil { + return + } + + // 设置对齐方式 + if config.Alignment != "" { + p.SetAlignment(config.Alignment) + } + + // 设置样式 + if config.Style != "" { + p.SetStyle(config.Style) + } + + // 设置间距(如果有任何间距设置) + if config.LineSpacing > 0 || config.BeforePara > 0 || config.AfterPara > 0 || config.FirstLineIndent > 0 { + p.SetSpacing(&SpacingConfig{ + LineSpacing: config.LineSpacing, + BeforePara: config.BeforePara, + AfterPara: config.AfterPara, + FirstLineIndent: config.FirstLineIndent, + }) + } + + // 设置缩进(如果有任何缩进设置) + if config.FirstLineCm != 0 || config.LeftCm != 0 || config.RightCm != 0 { + p.SetIndentation(config.FirstLineCm, config.LeftCm, config.RightCm) + } + + // 设置分页和控制属性 + p.SetKeepWithNext(config.KeepWithNext) + p.SetKeepLines(config.KeepLines) + p.SetPageBreakBefore(config.PageBreakBefore) + p.SetWidowControl(config.WidowControl) + + // 设置大纲级别 + if config.OutlineLevel >= 0 && config.OutlineLevel <= 8 { + p.SetOutlineLevel(config.OutlineLevel) + } + + Debugf("应用段落格式配置: 对齐=%s, 样式=%s, 行距=%.1f, 段前=%d, 段后=%d", + config.Alignment, config.Style, config.LineSpacing, config.BeforePara, config.AfterPara) +} + // ParagraphBorderConfig 段落边框配置(区别于表格边框配置) type ParagraphBorderConfig struct { Style BorderStyle // 边框样式 diff --git a/pkg/document/document_test.go b/pkg/document/document_test.go index 11d5517..a313b16 100644 --- a/pkg/document/document_test.go +++ b/pkg/document/document_test.go @@ -361,6 +361,289 @@ func TestParagraphSetIndentation(t *testing.T) { } } +// TestParagraphSetKeepWithNext 测试段落与下一段保持在一起 +func TestParagraphSetKeepWithNext(t *testing.T) { + doc := New() + para := doc.AddParagraph("测试保持与下一段") + + // 测试启用 + para.SetKeepWithNext(true) + + if para.Properties == nil { + t.Fatal("Properties should not be nil") + } + + if para.Properties.KeepNext == nil { + t.Fatal("KeepNext should not be nil") + } + + if para.Properties.KeepNext.Val != "1" { + t.Errorf("Expected KeepNext Val to be '1', got '%s'", para.Properties.KeepNext.Val) + } + + // 测试禁用 + para.SetKeepWithNext(false) + + if para.Properties.KeepNext != nil { + t.Error("KeepNext should be nil when disabled") + } +} + +// TestParagraphSetKeepLines 测试段落行保持在一起 +func TestParagraphSetKeepLines(t *testing.T) { + doc := New() + para := doc.AddParagraph("测试行保持") + + // 测试启用 + para.SetKeepLines(true) + + if para.Properties == nil { + t.Fatal("Properties should not be nil") + } + + if para.Properties.KeepLines == nil { + t.Fatal("KeepLines should not be nil") + } + + if para.Properties.KeepLines.Val != "1" { + t.Errorf("Expected KeepLines Val to be '1', got '%s'", para.Properties.KeepLines.Val) + } + + // 测试禁用 + para.SetKeepLines(false) + + if para.Properties.KeepLines != nil { + t.Error("KeepLines should be nil when disabled") + } +} + +// TestParagraphSetPageBreakBefore 测试段前分页 +func TestParagraphSetPageBreakBefore(t *testing.T) { + doc := New() + para := doc.AddParagraph("测试段前分页") + + // 测试启用 + para.SetPageBreakBefore(true) + + if para.Properties == nil { + t.Fatal("Properties should not be nil") + } + + if para.Properties.PageBreakBefore == nil { + t.Fatal("PageBreakBefore should not be nil") + } + + if para.Properties.PageBreakBefore.Val != "1" { + t.Errorf("Expected PageBreakBefore Val to be '1', got '%s'", para.Properties.PageBreakBefore.Val) + } + + // 测试禁用 + para.SetPageBreakBefore(false) + + if para.Properties.PageBreakBefore != nil { + t.Error("PageBreakBefore should be nil when disabled") + } +} + +// TestParagraphSetWidowControl 测试孤行控制 +func TestParagraphSetWidowControl(t *testing.T) { + doc := New() + para := doc.AddParagraph("测试孤行控制") + + // 测试启用 + para.SetWidowControl(true) + + if para.Properties == nil { + t.Fatal("Properties should not be nil") + } + + if para.Properties.WidowControl == nil { + t.Fatal("WidowControl should not be nil") + } + + if para.Properties.WidowControl.Val != "1" { + t.Errorf("Expected WidowControl Val to be '1', got '%s'", para.Properties.WidowControl.Val) + } + + // 测试禁用 + para.SetWidowControl(false) + + if para.Properties.WidowControl == nil { + t.Fatal("WidowControl should not be nil when set to false") + } + + if para.Properties.WidowControl.Val != "0" { + t.Errorf("Expected WidowControl Val to be '0' when disabled, got '%s'", para.Properties.WidowControl.Val) + } +} + +// TestParagraphSetOutlineLevel 测试大纲级别设置 +func TestParagraphSetOutlineLevel(t *testing.T) { + doc := New() + para := doc.AddParagraph("测试大纲级别") + + // 测试有效级别 + para.SetOutlineLevel(0) + + if para.Properties == nil { + t.Fatal("Properties should not be nil") + } + + if para.Properties.OutlineLevel == nil { + t.Fatal("OutlineLevel should not be nil") + } + + if para.Properties.OutlineLevel.Val != "0" { + t.Errorf("Expected OutlineLevel Val to be '0', got '%s'", para.Properties.OutlineLevel.Val) + } + + // 测试其他级别 + para.SetOutlineLevel(3) + if para.Properties.OutlineLevel.Val != "3" { + t.Errorf("Expected OutlineLevel Val to be '3', got '%s'", para.Properties.OutlineLevel.Val) + } + + // 测试边界值 + para.SetOutlineLevel(8) + if para.Properties.OutlineLevel.Val != "8" { + t.Errorf("Expected OutlineLevel Val to be '8', got '%s'", para.Properties.OutlineLevel.Val) + } + + // 测试超出范围的值(应该被限制) + para.SetOutlineLevel(10) + if para.Properties.OutlineLevel.Val != "8" { + t.Errorf("Expected OutlineLevel to be capped at '8', got '%s'", para.Properties.OutlineLevel.Val) + } + + para.SetOutlineLevel(-1) + if para.Properties.OutlineLevel.Val != "0" { + t.Errorf("Expected OutlineLevel to be floored at '0', got '%s'", para.Properties.OutlineLevel.Val) + } +} + +// TestParagraphSetParagraphFormat 测试综合段落格式设置 +func TestParagraphSetParagraphFormat(t *testing.T) { + doc := New() + para := doc.AddParagraph("测试综合格式设置") + + // 测试完整配置 + config := &ParagraphFormatConfig{ + Alignment: AlignCenter, + Style: "Heading1", + LineSpacing: 1.5, + BeforePara: 24, + AfterPara: 12, + FirstLineCm: 0.5, + LeftCm: 1.0, + RightCm: 0.5, + KeepWithNext: true, + KeepLines: true, + PageBreakBefore: true, + WidowControl: true, + OutlineLevel: 0, + } + + para.SetParagraphFormat(config) + + // 验证所有属性 + if para.Properties == nil { + t.Fatal("Properties should not be nil") + } + + // 验证对齐 + if para.Properties.Justification == nil || para.Properties.Justification.Val != string(AlignCenter) { + t.Error("Alignment not set correctly") + } + + // 验证样式 + if para.Properties.ParagraphStyle == nil || para.Properties.ParagraphStyle.Val != "Heading1" { + t.Error("Style not set correctly") + } + + // 验证间距 + if para.Properties.Spacing == nil { + t.Fatal("Spacing should not be nil") + } + + // 验证缩进 + if para.Properties.Indentation == nil { + t.Fatal("Indentation should not be nil") + } + + // 验证分页控制 + if para.Properties.KeepNext == nil || para.Properties.KeepNext.Val != "1" { + t.Error("KeepNext not set correctly") + } + + if para.Properties.KeepLines == nil || para.Properties.KeepLines.Val != "1" { + t.Error("KeepLines not set correctly") + } + + if para.Properties.PageBreakBefore == nil || para.Properties.PageBreakBefore.Val != "1" { + t.Error("PageBreakBefore not set correctly") + } + + if para.Properties.WidowControl == nil || para.Properties.WidowControl.Val != "1" { + t.Error("WidowControl not set correctly") + } + + // 验证大纲级别 + if para.Properties.OutlineLevel == nil || para.Properties.OutlineLevel.Val != "0" { + t.Error("OutlineLevel not set correctly") + } +} + +// TestParagraphSetParagraphFormatNil 测试nil配置 +func TestParagraphSetParagraphFormatNil(t *testing.T) { + doc := New() + para := doc.AddParagraph("测试nil配置") + + // nil配置不应该导致panic + para.SetParagraphFormat(nil) + + // 段落应该保持默认状态 + if para.Properties != nil && para.Properties.Justification != nil { + t.Error("Properties should remain unchanged with nil config") + } +} + +// TestParagraphSetParagraphFormatPartial 测试部分配置 +func TestParagraphSetParagraphFormatPartial(t *testing.T) { + doc := New() + para := doc.AddParagraph("测试部分配置") + + // 只设置部分属性 + config := &ParagraphFormatConfig{ + Alignment: AlignRight, + KeepWithNext: true, + LineSpacing: 2.0, + } + + para.SetParagraphFormat(config) + + // 验证设置的属性 + if para.Properties == nil { + t.Fatal("Properties should not be nil") + } + + if para.Properties.Justification == nil || para.Properties.Justification.Val != string(AlignRight) { + t.Error("Alignment not set correctly") + } + + if para.Properties.KeepNext == nil || para.Properties.KeepNext.Val != "1" { + t.Error("KeepNext not set correctly") + } + + if para.Properties.Spacing == nil { + t.Error("Spacing should be set") + } + + // 验证未设置的属性保持默认 + if para.Properties.PageBreakBefore != nil { + t.Error("PageBreakBefore should remain nil") + } +} + // TestDocumentSave 测试文档保存 func TestDocumentSave(t *testing.T) { doc := New() diff --git a/pkg/document/table.go b/pkg/document/table.go index f405678..b962b56 100644 --- a/pkg/document/table.go +++ b/pkg/document/table.go @@ -1785,18 +1785,6 @@ type TableRowPropertiesExtended struct { KeepLines *KeepLines `xml:"w:keepLines,omitempty"` } -// KeepNext 与下一段落保持在一起 -type KeepNext struct { - XMLName xml.Name `xml:"w:keepNext"` - Val string `xml:"w:val,attr,omitempty"` -} - -// KeepLines 保持行在一起 -type KeepLines struct { - XMLName xml.Name `xml:"w:keepLines"` - Val string `xml:"w:val,attr,omitempty"` -} - // 扩展现有的TableRowProperties结构 func (trp *TableRowProperties) SetCantSplit(cantSplit bool) { if cantSplit {