Skip to content

Commit 8d4b2ec

Browse files
Add support for columns and page margins (#31)
* Add support for columns * Allow specifying page/section margins * fixup columns * fixup pagemargins * Add tests for columns and page margins * Column input types only need to be convertible to twip * Prevent incompatible kwargs combo in `Columns` constructor * fix kwargs * Fix `Columns` constructor defaults * Print the actual number of columns * Update columns test xml * make columns visible in tests --------- Co-authored-by: Julius Krumbiegel <[email protected]>
1 parent 11795d2 commit 8d4b2ec

File tree

13 files changed

+605
-1
lines changed

13 files changed

+605
-1
lines changed

src/WriteDocx.jl

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,86 @@ Base.@kwdef struct Footers
10371037
even::Maybe{Footer} = nothing
10381038
end
10391039

1040+
"""
1041+
Column(; [width, space])
1042+
1043+
Describes a single column. `width` sets the column width, and `space` sets the whitespace after the column (before the next column).
1044+
1045+
See also: [`Columns`](@ref)
1046+
"""
1047+
Base.@kwdef struct Column
1048+
width::Maybe{Twip} = nothing
1049+
space::Maybe{Twip} = nothing
1050+
end
1051+
1052+
"""
1053+
Columns(; kwargs...)
1054+
1055+
Sets the columns for a [`Section`](@ref).
1056+
1057+
## Keyword arguments
1058+
1059+
| Keyword | Description |
1060+
| :-- | :-- |
1061+
| `equal::Bool = true` | Sets all columns to be equal-width with `space` between every column |
1062+
| `num::Int` | The number of columns to layout. Ignored if `equal==false`. |
1063+
| `space::`[`Twip`](@ref) | The space between columns. Ignored if `equal==false`. |
1064+
| `sep::Bool = false` | Sets whether a vertical separator line is drawn between columns |
1065+
| `cols::Vector{`[`Column`](@ref)`}` | A vector of custom columns. May not be specified with `equal==true`. |
1066+
"""
1067+
struct Columns
1068+
num::Int
1069+
space::Maybe{Twip}
1070+
sep::Bool
1071+
cols::Vector{Column}
1072+
equal::Bool
1073+
1074+
function Columns(;kwargs...)
1075+
sd = setdiff(keys(kwargs), (:num, :space, :sep, :cols, :equal))
1076+
isempty(sd) || throw(ArgumentError("got unsupported keyword argument(s): $(sd)"))
1077+
if haskey(kwargs, :cols) && haskey(kwargs, :equal) && kwargs[:equal]
1078+
throw(ArgumentError("keyword arguments \"cols\" and \"equal\" (or equal=true) are mutually incompatible. If \"cols\" is given, \"equal\" must be false or not defined"))
1079+
elseif !haskey(kwargs, :cols) && haskey(kwargs, :equal) && !kwargs[:equal]
1080+
throw(ArgumentError("\"cols\" must be defined/non-empty when \"equal=false\""))
1081+
end
1082+
haskey(kwargs, :cols) && isempty(kwargs[:cols]) && throw(ArgumentError("\"cols\" must not be empty"))
1083+
1084+
return new(
1085+
get(kwargs, :num, 1),
1086+
get(kwargs, :space, nothing),
1087+
get(kwargs, :sep, false),
1088+
get(kwargs, :cols, Column[]),
1089+
get(kwargs, :equal, !haskey(kwargs, :cols)))
1090+
end
1091+
end
1092+
1093+
"""
1094+
PageMargins(; top, right, bottom, left, kwargs...)
1095+
1096+
Describes page margins in a [`Section`](@ref).
1097+
1098+
## Keyword arguments
1099+
1100+
| Keyword | Description |
1101+
| :-- | :-- |
1102+
| `top::`[`Twip`](@ref) | The top margin. |
1103+
| `right::`[`Twip`](@ref) | The right margin. |
1104+
| `bottom::`[`Twip`](@ref) | The bottom margin. |
1105+
| `left::`[`Twip`](@ref) | The left margin. |
1106+
| `header::`[`Twip`](@ref)`=Twip(0)` | The header margin. |
1107+
| `footer::`[`Twip`](@ref)`=Twip(0)` | The footer margin. |
1108+
| `gutter::`[`Twip`](@ref)`=Twip(0)` | The gutter margin. |
1109+
"""
1110+
Base.@kwdef struct PageMargins
1111+
top::Twip
1112+
right::Twip
1113+
bottom::Twip
1114+
left::Twip
1115+
header::Twip = Twip(0)
1116+
footer::Twip = Twip(0)
1117+
gutter::Twip = Twip(0)
1118+
end
1119+
10401120
"""
10411121
SectionProperties(; kwargs...)
10421122
@@ -1047,15 +1127,19 @@ Holds properties for a [`Section`](@ref).
10471127
| Keyword | Description |
10481128
| :-- | :-- |
10491129
| `pagesize::PageSize` | The size of each page in the section. |
1130+
| `margins::PageMargins` | The margins for each page in the section |
10501131
| `valign::PageVerticalAlign.T` | The vertical alignment of content on each page of the section. |
10511132
| `headers::`[`Headers`](@ref) | Defines the header content shown at the top of each page of the section. |
10521133
| `footers::`[`Footers`](@ref) | Defines the footer content shown at the bottom of each page of the section. |
1134+
| `columns::`[`Columns`](@ref) | Configures the columns in the section |
10531135
"""
10541136
Base.@kwdef struct SectionProperties
10551137
pagesize::Maybe{PageSize} = nothing
1138+
margins::Maybe{PageMargins} = nothing
10561139
valign::Maybe{PageVerticalAlign.T} = nothing
10571140
headers::Maybe{Headers} = nothing
10581141
footers::Maybe{Footers} = nothing
1142+
columns::Maybe{Columns} = nothing
10591143
end
10601144

10611145
"""
@@ -1590,9 +1674,15 @@ function to_xml(body::Body, rels)
15901674
if props.pagesize !== nothing
15911675
E.link!(section_params_node, to_xml(props.pagesize, rels))
15921676
end
1677+
if props.margins !== nothing
1678+
E.link!(section_params_node, to_xml(props.margins, rels))
1679+
end
15931680
if props.valign !== nothing
15941681
E.link!(section_params_node, to_xml(props.valign, rels))
15951682
end
1683+
if props.columns !== nothing
1684+
E.link!(section_params_node, to_xml(props.columns, rels))
1685+
end
15961686
if props.headers !== nothing
15971687
for type in (:default, :first, :even)
15981688
x = getproperty(props.headers, type)
@@ -1827,6 +1917,14 @@ function children(p::ParagraphProperties)
18271917
return c
18281918
end
18291919

1920+
function children(p::Columns)
1921+
c = []
1922+
if !p.equal
1923+
append!(c, p.cols)
1924+
end
1925+
return c
1926+
end
1927+
18301928
function children(p::TableCellProperties)
18311929
c = []
18321930
p.borders === nothing || push!(c, p.borders)
@@ -1899,6 +1997,7 @@ function attributes(t::Union{TableCellBorder, ParagraphBorder})
18991997
return attrs
19001998
end
19011999
attributes(p::PageSize) = (("w:h", p.height), ("w:w", p.width), ("w:orient", p.orientation))
2000+
attributes(p::PageMargins) = (("w:top", p.top), ("w:right", p.right), ("w:bottom", p.bottom), ("w:left", p.left), ("w:header", p.header), ("w:footer", p.footer), ("w:gutter", p.gutter))
19022001
attributes(p::PageVerticalAlign.T) = (("w:val", p),)
19032002
attributes(p::Justification.T) = (("w:val", p),)
19042003
function attributes(s::Style)
@@ -1915,6 +2014,26 @@ end
19152014
attributes(p::ParagraphStyle) = (("w:val", p.name),)
19162015
attributes(p::RunStyle) = (("w:val", p.name),)
19172016
attributes(p::VerticalAlignment.T) = (("w:val", p),)
2017+
function attributes(p::Column)
2018+
attrs = Tuple{String, Any}[]
2019+
p.width === nothing || push!(attrs, ("w:w", p.width))
2020+
p.space === nothing || push!(attrs, ("w:space", p.space))
2021+
return attrs
2022+
end
2023+
function attributes(p::Columns)
2024+
attrs = Tuple{String, Any}[]
2025+
if p.num > 1 && p.equal
2026+
push!(attrs, ("w:equalWidth", p.equal))
2027+
push!(attrs, ("w:num", p.num))
2028+
p.sep === false || push!(attrs, ("w:sep", p.sep))
2029+
p.space === nothing || push!(attrs, ("w:space", p.space))
2030+
elseif !p.equal
2031+
push!(attrs, ("w:equalWidth", p.equal))
2032+
push!(attrs, ("w:num", length(p.cols)))
2033+
p.sep === false || push!(attrs, ("w:sep", p.sep))
2034+
end
2035+
return attrs
2036+
end
19182037
function attributes(f::Fonts)
19192038
attrs = Tuple{String, Any}[]
19202039
f.ascii === nothing || push!(attrs, ("w:ascii", f.ascii))
@@ -2011,7 +2130,10 @@ function xmltag(t::Tuple{ParagraphBorder, Symbol})
20112130
throw(ArgumentError("Invalid symbol $(repr(sym)), options are :top, :left, :right, :bottom, :between."))
20122131
end
20132132
xmltag(::PageSize) = "w:pgSz"
2133+
xmltag(::PageMargins) = "w:pgMar"
20142134
xmltag(::PageVerticalAlign.T) = "w:vAlign"
2135+
xmltag(::Columns) = "w:cols"
2136+
xmltag(::Column) = "w:col"
20152137
xmltag(::Style) = "w:style"
20162138
xmltag(::Justification.T) = "w:jc"
20172139
xmltag(::Fonts) = "w:rFonts"

test/references/breaks/word/document.xml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,22 @@
1212
<w:t>after page break</w:t>
1313
</w:r>
1414
</w:p>
15-
<w:sectPr/>
15+
<w:p>
16+
<w:pPr>
17+
<w:sectPr/>
18+
</w:pPr>
19+
</w:p>
20+
<w:p>
21+
<w:pPr/>
22+
<w:r>
23+
<w:rPr/>
24+
<w:t>column 1</w:t>
25+
<w:br w:type="column"/>
26+
<w:t>column 2</w:t>
27+
</w:r>
28+
</w:p>
29+
<w:sectPr>
30+
<w:cols w:equalWidth="true" w:num="2"/>
31+
</w:sectPr>
1632
</w:body>
1733
</w:document>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<Types
3+
xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
4+
<Default Extension="png" ContentType="image/png" />
5+
<Default Extension="svg" ContentType="image/svg+xml" />
6+
<Default Extension="xml" ContentType="application/xml" />
7+
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />
8+
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" />
9+
<Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml" />
10+
</Types>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<Relationships
3+
xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
4+
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
5+
</Relationships>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
3+
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
4+
</Relationships>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing">
3+
<w:body>
4+
<w:p>
5+
<w:pPr/>
6+
<w:r>
7+
<w:rPr>
8+
<w:sz w:val="80"/>
9+
</w:rPr>
10+
<w:t xml:space="preserve">No columns No columns No columns No columns No columns No columns No columns No columns No columns No columns </w:t>
11+
</w:r>
12+
</w:p>
13+
<w:p>
14+
<w:pPr>
15+
<w:sectPr/>
16+
</w:pPr>
17+
</w:p>
18+
<w:p>
19+
<w:pPr/>
20+
<w:r>
21+
<w:rPr>
22+
<w:sz w:val="80"/>
23+
</w:rPr>
24+
<w:t xml:space="preserve">2 columns 2 columns 2 columns 2 columns 2 columns 2 columns 2 columns 2 columns 2 columns 2 columns 2 columns 2 columns 2 columns 2 columns 2 columns 2 columns 2 columns 2 columns 2 columns 2 columns </w:t>
25+
</w:r>
26+
</w:p>
27+
<w:p>
28+
<w:pPr>
29+
<w:sectPr>
30+
<w:cols w:equalWidth="true" w:num="2"/>
31+
</w:sectPr>
32+
</w:pPr>
33+
</w:p>
34+
<w:p>
35+
<w:pPr/>
36+
<w:r>
37+
<w:rPr>
38+
<w:sz w:val="80"/>
39+
</w:rPr>
40+
<w:t xml:space="preserve">different width columns different width columns different width columns different width columns different width columns different width columns different width columns different width columns different width columns different width columns different width columns different width columns </w:t>
41+
</w:r>
42+
</w:p>
43+
<w:sectPr>
44+
<w:cols w:equalWidth="false" w:num="2">
45+
<w:col w:w="5760" w:space="720"/>
46+
<w:col w:w="2880"/>
47+
</w:cols>
48+
</w:sectPr>
49+
</w:body>
50+
</w:document>

0 commit comments

Comments
 (0)