diff --git a/pkg/document/document.go b/pkg/document/document.go index a58011b..ec3fcf9 100644 --- a/pkg/document/document.go +++ b/pkg/document/document.go @@ -883,6 +883,11 @@ func (p *Paragraph) SetAlignment(alignment AlignmentType) { Debugf("设置段落对齐方式: %s", alignment) } +func (p *Paragraph) WithAlignment(alignment AlignmentType) *Paragraph { + p.SetAlignment(alignment) + return p +} + // SetSpacing 设置段落的间距配置。 // // 参数 config 包含各种间距设置,如果为 nil 则不进行任何设置。 @@ -949,6 +954,11 @@ func (p *Paragraph) SetSpacing(config *SpacingConfig) { } } +func (p *Paragraph) WithSpacing(config *SpacingConfig) *Paragraph { + p.SetSpacing(config) + return p +} + // AddFormattedText 向段落添加格式化的文本内容。 // // 此方法允许在一个段落中混合使用不同格式的文本。 @@ -1262,6 +1272,11 @@ func (p *Paragraph) SetStyle(styleID string) { Debugf("设置段落样式: %s", styleID) } +func (p *Paragraph) WithStyle(styleID string) *Paragraph { + p.SetStyle(styleID) + return p +} + // SetIndentation 设置段落的缩进属性。 // // 参数: @@ -1299,6 +1314,11 @@ func (p *Paragraph) SetIndentation(firstLineCm, leftCm, rightCm float64) { Debugf("设置段落缩进: 首行=%.2fcm, 左=%.2fcm, 右=%.2fcm", firstLineCm, leftCm, rightCm) } +func (p *Paragraph) WithIndentation(firstLineCm, leftCm, rightCm float64) *Paragraph { + p.SetIndentation(firstLineCm, leftCm, rightCm) + return p +} + // SetKeepWithNext 设置段落与下一段落保持在同一页。 // // 此方法用于确保当前段落和下一段落不会被分页符分隔, @@ -1327,6 +1347,11 @@ func (p *Paragraph) SetKeepWithNext(keep bool) { } } +func (p *Paragraph) WithKeepWithNext(keep bool) *Paragraph { + p.SetKeepWithNext(keep) + return p +} + // SetKeepLines 设置段落中的所有行保持在同一页。 // // 此方法用于防止段落在分页时被拆分到多个页面, @@ -1354,6 +1379,11 @@ func (p *Paragraph) SetKeepLines(keep bool) { } } +func (p *Paragraph) WithKeepLines(keep bool) *Paragraph { + p.SetKeepLines(keep) + return p +} + // SetPageBreakBefore 设置段落前插入分页符。 // // 此方法用于在段落之前强制插入分页符,使段落从新页开始显示。 @@ -1381,6 +1411,11 @@ func (p *Paragraph) SetPageBreakBefore(pageBreak bool) { } } +func (p *Paragraph) WithPageBreakBefore(pageBreak bool) *Paragraph { + p.SetPageBreakBefore(pageBreak) + return p +} + // SetWidowControl 设置段落的孤行控制。 // // 孤行控制用于防止段落的第一行或最后一行单独出现在页面底部或顶部, @@ -1407,6 +1442,11 @@ func (p *Paragraph) SetWidowControl(control bool) { } } +func (p *Paragraph) WithWidowControl(control bool) *Paragraph { + p.SetWidowControl(control) + return p +} + // SetOutlineLevel 设置段落的大纲级别。 // // 大纲级别用于在文档导航窗格中显示文档结构,级别范围为0-8。 @@ -1442,6 +1482,11 @@ func (p *Paragraph) SetOutlineLevel(level int) { Debugf("设置段落大纲级别: %d", level) } +func (p *Paragraph) WithOutlineLevel(level int) *Paragraph { + p.SetOutlineLevel(level) + return p +} + // SetSnapToGrid 设置段落的网格对齐属性。 // // 网格对齐控制段落的行是否对齐到文档的网格。当文档启用了网格设置时 @@ -1475,6 +1520,11 @@ func (p *Paragraph) SetSnapToGrid(snapToGrid bool) { } } +func (p *Paragraph) WithSnapToGrid(snapToGrid bool) *Paragraph { + p.SetSnapToGrid(snapToGrid) + return p +} + // ParagraphFormatConfig 段落格式配置 // // 此结构体提供了段落所有格式属性的统一配置接口, @@ -1589,6 +1639,11 @@ func (p *Paragraph) SetParagraphFormat(config *ParagraphFormatConfig) { config.Alignment, config.Style, config.LineSpacing, config.BeforePara, config.AfterPara) } +func (p *Paragraph) WithParagraphFormat(config *ParagraphFormatConfig) *Paragraph { + p.SetParagraphFormat(config) + return p +} + // ParagraphBorderConfig 段落边框配置(区别于表格边框配置) type ParagraphBorderConfig struct { Style BorderStyle // 边框样式 @@ -1696,6 +1751,11 @@ func (p *Paragraph) SetBorder(top, left, bottom, right *ParagraphBorderConfig) { Debugf("设置段落边框: 上=%v, 左=%v, 下=%v, 右=%v", top != nil, left != nil, bottom != nil, right != nil) } +func (p *Paragraph) WithBorder(top, left, bottom, right *ParagraphBorderConfig) *Paragraph { + p.SetBorder(top, left, bottom, right) + return p +} + // SetHorizontalRule 设置水平分割线。 // // 此方法是SetBorder的简化版本,专门用于快速创建Markdown风格的分割线效果。 @@ -1728,6 +1788,11 @@ func (p *Paragraph) SetHorizontalRule(style BorderStyle, size int, color string) Debugf("设置水平分割线: 样式=%s, 粗细=%d, 颜色=%s", style, size, color) } +func (p *Paragraph) WithHorizontalRule(style BorderStyle, size int, color string) *Paragraph { + p.SetHorizontalRule(style, size, color) + return p +} + // SetUnderline 设置段落中所有文本的下划线效果。 // // 参数 underline 表示是否启用下划线。 @@ -1752,6 +1817,11 @@ func (p *Paragraph) SetUnderline(underline bool) { Debugf("设置段落下划线: %v", underline) } +func (p *Paragraph) WithUnderline(underline bool) *Paragraph { + p.SetUnderline(underline) + return p +} + // SetBold 设置段落中所有文本的粗体效果。 // // 参数 bold 表示是否启用粗体。 @@ -1778,6 +1848,11 @@ func (p *Paragraph) SetBold(bold bool) { Debugf("设置段落粗体: %v", bold) } +func (p *Paragraph) WithBold(bold bool) *Paragraph { + p.SetBold(bold) + return p +} + // SetItalic 设置段落中所有文本的斜体效果。 // // 参数 italic 表示是否启用斜体。 @@ -1804,6 +1879,11 @@ func (p *Paragraph) SetItalic(italic bool) { Debugf("设置段落斜体: %v", italic) } +func (p *Paragraph) WithItalic(italic bool) *Paragraph { + p.SetItalic(italic) + return p +} + // SetStrike 设置段落中所有文本的删除线效果。 // // 参数 strike 表示是否启用删除线。 @@ -1828,6 +1908,11 @@ func (p *Paragraph) SetStrike(strike bool) { Debugf("设置段落删除线: %v", strike) } +func (p *Paragraph) WithStrike(strike bool) *Paragraph { + p.SetStrike(strike) + return p +} + // SetHighlight 设置段落中所有文本的高亮颜色。 // // 参数 color 是高亮颜色名称,支持的颜色包括: @@ -1854,6 +1939,11 @@ func (p *Paragraph) SetHighlight(color string) { Debugf("设置段落高亮: %s", color) } +func (p *Paragraph) WithHighlight(color string) *Paragraph { + p.SetHighlight(color) + return p +} + // SetFontFamily 设置段落中所有文本的字体。 // // 参数 name 是字体名称,如 "Arial"、"Times New Roman"、"微软雅黑" 等。 @@ -1881,6 +1971,11 @@ func (p *Paragraph) SetFontFamily(name string) { Debugf("设置段落字体: %s", name) } +func (p *Paragraph) WithFontFamily(name string) *Paragraph { + p.SetFontFamily(name) + return p +} + // SetFontSize 设置段落中所有文本的字体大小。 // // 参数 size 是字体大小(磅),如 12、14、16 等。 @@ -1908,6 +2003,11 @@ func (p *Paragraph) SetFontSize(size int) { Debugf("设置段落字体大小: %d", size) } +func (p *Paragraph) WithFontSize(size int) *Paragraph { + p.SetFontSize(size) + return p +} + // SetColor 设置段落中所有文本的颜色。 // // 参数 color 是十六进制颜色值,如 "FF0000"(红色)、"0000FF"(蓝色)等。 @@ -1934,6 +2034,11 @@ func (p *Paragraph) SetColor(color string) { Debugf("设置段落颜色: %s", color) } +func (p *Paragraph) WithColor(color string) *Paragraph { + p.SetColor(color) + return p +} + // GetStyleManager 获取文档的样式管理器。 // // 返回文档的样式管理器,可用于访问和管理样式。 diff --git a/pkg/document/document_test.go b/pkg/document/document_test.go index c8e54f3..3d1a7a9 100644 --- a/pkg/document/document_test.go +++ b/pkg/document/document_test.go @@ -1704,3 +1704,323 @@ func TestParagraphFormattingIntegration(t *testing.T) { t.Errorf("保存文档失败: %v", err) } } + +// TestParagraphWithChainingLayout 测试段落布局相关 With 方法链式调用 +func TestParagraphWithChainingLayout(t *testing.T) { + doc := New() + para := doc.AddParagraph("链式布局测试") + + // 使用 With 方法进行链式调用 + result := para. + WithAlignment(AlignCenter). + WithStyle("Heading1"). + WithSpacing(&SpacingConfig{ + LineSpacing: 1.5, + BeforePara: 12, + AfterPara: 6, + }). + WithIndentation(0.5, 1.0, 0.5). + WithKeepWithNext(true). + WithKeepLines(true). + WithPageBreakBefore(true). + WithWidowControl(true). + WithOutlineLevel(2). + WithSnapToGrid(false) + + // 验证链式调用返回同一个段落指针 + if result != para { + t.Error("With 链式调用应该返回同一个 Paragraph 指针") + } + + if para.Properties == nil { + t.Fatal("链式调用后段落属性不应为 nil") + } + + // 对齐 + if para.Properties.Justification == nil || para.Properties.Justification.Val != string(AlignCenter) { + t.Errorf("链式调用未正确设置对齐方式,期望 %s,实际 %v", AlignCenter, para.Properties.Justification) + } + + // 样式 + if para.Properties.ParagraphStyle == nil || para.Properties.ParagraphStyle.Val != "Heading1" { + t.Errorf("链式调用未正确设置样式,期望 Heading1,实际 %v", para.Properties.ParagraphStyle) + } + + // 间距(只检查是否设置以及部分关键值) + if para.Properties.Spacing == nil { + t.Fatal("链式调用后 Spacing 不应为 nil") + } + if para.Properties.Spacing.Before != "240" { // 12 * 20 + t.Errorf("链式调用未正确设置段前间距,期望 240,实际 %s", para.Properties.Spacing.Before) + } + if para.Properties.Spacing.After != "120" { // 6 * 20 + t.Errorf("链式调用未正确设置段后间距,期望 120,实际 %s", para.Properties.Spacing.After) + } + if para.Properties.Spacing.Line != "360" { // 1.5 * 240 + t.Errorf("链式调用未正确设置行间距,期望 360,实际 %s", para.Properties.Spacing.Line) + } + + // 缩进(厘米到 TWIPs 转换) + if para.Properties.Indentation == nil { + t.Fatal("链式调用后 Indentation 不应为 nil") + } + if para.Properties.Indentation.FirstLine == "" || para.Properties.Indentation.Left == "" || para.Properties.Indentation.Right == "" { + t.Error("链式调用未正确设置缩进属性") + } + + // 分页与控制 + if para.Properties.KeepNext == nil || para.Properties.KeepNext.Val != "1" { + t.Error("链式调用未正确设置 KeepNext 属性") + } + if para.Properties.KeepLines == nil || para.Properties.KeepLines.Val != "1" { + t.Error("链式调用未正确设置 KeepLines 属性") + } + if para.Properties.PageBreakBefore == nil || para.Properties.PageBreakBefore.Val != "1" { + t.Error("链式调用未正确设置 PageBreakBefore 属性") + } + if para.Properties.WidowControl == nil || para.Properties.WidowControl.Val != "1" { + t.Error("链式调用未正确设置 WidowControl 属性") + } + + // 大纲级别 + if para.Properties.OutlineLevel == nil || para.Properties.OutlineLevel.Val != "2" { + t.Errorf("链式调用未正确设置大纲级别,期望 '2',实际 '%v'", para.Properties.OutlineLevel) + } + + // SnapToGrid 应该被禁用,值为 "0" + if para.Properties.SnapToGrid == nil || para.Properties.SnapToGrid.Val != "0" { + t.Errorf("链式调用未正确设置 SnapToGrid 属性,期望 '0',实际 '%v'", para.Properties.SnapToGrid) + } +} + +// TestParagraphWithParagraphFormat 测试 WithParagraphFormat 封装 +func TestParagraphWithParagraphFormat(t *testing.T) { + doc := New() + para := doc.AddParagraph("WithParagraphFormat 测试") + + snapToGrid := false + config := &ParagraphFormatConfig{ + Alignment: AlignCenter, + Style: "Heading2", + LineSpacing: 1.5, + BeforePara: 6, + AfterPara: 3, + FirstLineCm: 0.5, + LeftCm: 1.0, + RightCm: 0.5, + KeepWithNext: true, + KeepLines: true, + PageBreakBefore: true, + WidowControl: true, + SnapToGrid: &snapToGrid, + OutlineLevel: 1, + } + + result := para.WithParagraphFormat(config) + if result != para { + t.Error("WithParagraphFormat 应该返回同一个 Paragraph 指针") + } + + if para.Properties == nil { + t.Fatal("WithParagraphFormat 后段落属性不应为 nil") + } + + if para.Properties.Justification == nil || para.Properties.Justification.Val != string(AlignCenter) { + t.Error("WithParagraphFormat 未正确设置对齐方式") + } + if para.Properties.ParagraphStyle == nil || para.Properties.ParagraphStyle.Val != "Heading2" { + t.Error("WithParagraphFormat 未正确设置样式") + } + if para.Properties.Spacing == nil { + t.Fatal("WithParagraphFormat 未正确设置 Spacing") + } + if para.Properties.Indentation == nil { + t.Fatal("WithParagraphFormat 未正确设置 Indentation") + } + if para.Properties.KeepNext == nil || para.Properties.KeepNext.Val != "1" { + t.Error("WithParagraphFormat 未正确设置 KeepNext") + } + if para.Properties.KeepLines == nil || para.Properties.KeepLines.Val != "1" { + t.Error("WithParagraphFormat 未正确设置 KeepLines") + } + if para.Properties.PageBreakBefore == nil || para.Properties.PageBreakBefore.Val != "1" { + t.Error("WithParagraphFormat 未正确设置 PageBreakBefore") + } + if para.Properties.WidowControl == nil || para.Properties.WidowControl.Val != "1" { + t.Error("WithParagraphFormat 未正确设置 WidowControl") + } + if para.Properties.OutlineLevel == nil || para.Properties.OutlineLevel.Val != "1" { + t.Error("WithParagraphFormat 未正确设置 OutlineLevel") + } + if para.Properties.SnapToGrid == nil || para.Properties.SnapToGrid.Val != "0" { + t.Error("WithParagraphFormat 未正确设置 SnapToGrid(禁用时应为 '0')") + } +} + +// TestParagraphWithBorderAndHorizontalRule 测试 WithBorder 和 WithHorizontalRule +func TestParagraphWithBorderAndHorizontalRule(t *testing.T) { + doc := New() + para := doc.AddParagraph("边框测试") + + border := &ParagraphBorderConfig{ + Style: BorderStyleSingle, + Size: 12, + Color: "FF0000", + Space: 1, + } + + // 使用 WithBorder 设置上下边框 + result := para.WithBorder(border, nil, border, nil) + if result != para { + t.Error("WithBorder 应该返回同一个 Paragraph 指针") + } + + if para.Properties == nil || para.Properties.ParagraphBorder == nil { + t.Fatal("WithBorder 后 ParagraphBorder 不应为 nil") + } + + if para.Properties.ParagraphBorder.Top == nil || para.Properties.ParagraphBorder.Bottom == nil { + t.Fatal("WithBorder 应该设置 Top 和 Bottom 边框") + } + if para.Properties.ParagraphBorder.Top.Val != string(BorderStyleSingle) { + t.Errorf("Top 边框样式不正确,期望 %s,实际 %s", BorderStyleSingle, para.Properties.ParagraphBorder.Top.Val) + } + if para.Properties.ParagraphBorder.Top.Color != "FF0000" { + t.Errorf("Top 边框颜色不正确,期望 FF0000,实际 %s", para.Properties.ParagraphBorder.Top.Color) + } + if para.Properties.ParagraphBorder.Top.Sz != "12" { + t.Errorf("Top 边框粗细不正确,期望 12,实际 %s", para.Properties.ParagraphBorder.Top.Sz) + } + if para.Properties.ParagraphBorder.Top.Space != "1" { + t.Errorf("Top 边框间距不正确,期望 1,实际 %s", para.Properties.ParagraphBorder.Top.Space) + } + + // 传入全 nil 应该清除边框 + para.WithBorder(nil, nil, nil, nil) + if para.Properties.ParagraphBorder != nil { + t.Error("WithBorder 传入全部 nil 时应清除 ParagraphBorder") + } + + // 测试 WithHorizontalRule 只设置底边框 + para2 := doc.AddParagraph("水平线测试") + result2 := para2.WithHorizontalRule(BorderStyleDouble, 16, "0000FF") + if result2 != para2 { + t.Error("WithHorizontalRule 应该返回同一个 Paragraph 指针") + } + + if para2.Properties == nil || para2.Properties.ParagraphBorder == nil { + t.Fatal("WithHorizontalRule 后 ParagraphBorder 不应为 nil") + } + if para2.Properties.ParagraphBorder.Bottom == nil { + t.Fatal("WithHorizontalRule 应该设置 Bottom 边框") + } + if para2.Properties.ParagraphBorder.Bottom.Val != string(BorderStyleDouble) { + t.Errorf("Bottom 边框样式不正确,期望 %s,实际 %s", BorderStyleDouble, para2.Properties.ParagraphBorder.Bottom.Val) + } + if para2.Properties.ParagraphBorder.Bottom.Color != "0000FF" { + t.Errorf("Bottom 边框颜色不正确,期望 0000FF,实际 %s", para2.Properties.ParagraphBorder.Bottom.Color) + } + if para2.Properties.ParagraphBorder.Bottom.Sz != "16" { + t.Errorf("Bottom 边框粗细不正确,期望 16,实际 %s", para2.Properties.ParagraphBorder.Bottom.Sz) + } + if para2.Properties.ParagraphBorder.Bottom.Space != "1" { + t.Errorf("Bottom 边框间距不正确,期望 1,实际 %s", para2.Properties.ParagraphBorder.Bottom.Space) + } + + // 上、左、右边框应该保持为 nil + if para2.Properties.ParagraphBorder.Top != nil || para2.Properties.ParagraphBorder.Left != nil || para2.Properties.ParagraphBorder.Right != nil { + t.Error("WithHorizontalRule 只应设置 Bottom 边框") + } +} + +// TestParagraphWithTextFormatting 测试文本格式相关 With 方法 +func TestParagraphWithTextFormatting(t *testing.T) { + doc := New() + para := doc.AddParagraph("文本格式 With 测试") + + // 链式应用所有文本格式 + result := para. + WithBold(true). + WithItalic(true). + WithUnderline(true). + WithStrike(true). + WithHighlight("yellow"). + WithFontFamily("宋体"). + WithFontSize(16). + WithColor("#FF0000") + + if result != para { + t.Error("文本格式 With 链式调用应该返回同一个 Paragraph 指针") + } + + if len(para.Runs) == 0 { + t.Fatal("段落应至少包含一个 Run") + } + + props := para.Runs[0].Properties + if props == nil { + t.Fatal("Run 属性不应为 nil") + } + + if props.Bold == nil || props.BoldCs == nil { + t.Error("WithBold 未正确设置粗体属性") + } + if props.Italic == nil || props.ItalicCs == nil { + t.Error("WithItalic 未正确设置斜体属性") + } + if props.Underline == nil || props.Underline.Val != "single" { + t.Error("WithUnderline 未正确设置下划线属性") + } + if props.Strike == nil { + t.Error("WithStrike 未正确设置删除线属性") + } + if props.Highlight == nil || props.Highlight.Val != "yellow" { + t.Error("WithHighlight 未正确设置高亮属性") + } + if props.FontFamily == nil || props.FontFamily.ASCII != "宋体" { + t.Error("WithFontFamily 未正确设置字体属性") + } + if props.FontSize == nil || props.FontSize.Val != "32" { // 16 * 2 + t.Errorf("WithFontSize 未正确设置字体大小,期望 '32',实际 '%v'", props.FontSize) + } + if props.Color == nil || props.Color.Val != "FF0000" { // '#' 前缀应被移除 + t.Error("WithColor 未正确设置颜色属性(应移除 # 前缀)") + } + + // 再次使用 With 方法关闭所有格式 + para. + WithBold(false). + WithItalic(false). + WithUnderline(false). + WithStrike(false). + WithHighlight(""). + WithFontFamily(""). + WithFontSize(0). + WithColor("") + + props = para.Runs[0].Properties + if props.Bold != nil || props.BoldCs != nil { + t.Error("WithBold(false) 后粗体属性应为 nil") + } + if props.Italic != nil || props.ItalicCs != nil { + t.Error("WithItalic(false) 后斜体属性应为 nil") + } + if props.Underline != nil { + t.Error("WithUnderline(false) 后下划线属性应为 nil") + } + if props.Strike != nil { + t.Error("WithStrike(false) 后删除线属性应为 nil") + } + if props.Highlight != nil { + t.Error("WithHighlight(\"\") 后高亮属性应为 nil") + } + if props.FontFamily != nil { + t.Error("WithFontFamily(\"\") 后字体属性应为 nil") + } + if props.FontSize != nil || props.FontSizeCs != nil { + t.Error("WithFontSize(0) 后字体大小属性应为 nil") + } + if props.Color != nil { + t.Error("WithColor(\"\") 后颜色属性应为 nil") + } +}