Skip to content
Merged
122 changes: 122 additions & 0 deletions src/WriteDocx.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,86 @@ Base.@kwdef struct Footers
even::Maybe{Footer} = nothing
end

"""
Column(; [width, space])

Describes a single column. `width` sets the column width, and `space` sets the whitespace after the column (before the next column).

See also: [`Columns`](@ref)
"""
Base.@kwdef struct Column
width::Maybe{Twip} = nothing
space::Maybe{Twip} = nothing
end

"""
Columns(; kwargs...)

Sets the columns for a [`Section`](@ref).

## Keyword arguments

| Keyword | Description |
| :-- | :-- |
| `equal::Bool = true` | Sets all columns to be equal-width with `space` between every column |
| `num::Int` | The number of columns to layout. Ignored if `equal==false`. |
| `space::`[`Twip`](@ref) | The space between columns. Ignored if `equal==false`. |
| `sep::Bool = false` | Sets whether a vertical separator line is drawn between columns |
| `cols::Vector{`[`Column`](@ref)`}` | A vector of custom columns. May not be specified with `equal==true`. |
"""
struct Columns
num::Int
space::Maybe{Twip}
sep::Bool
cols::Vector{Column}
equal::Bool

function Columns(;kwargs...)
sd = setdiff(keys(kwargs), (:num, :space, :sep, :cols, :equal))
isempty(sd) || throw(ArgumentError("got unsupported keyword argument(s): $(sd)"))
if haskey(kwargs, :cols) && haskey(kwargs, :equal) && kwargs[:equal]
throw(ArgumentError("keyword arguments \"cols\" and \"equal\" (or equal=true) are mutually incompatible. If \"cols\" is given, \"equal\" must be false or not defined"))
elseif !haskey(kwargs, :cols) && haskey(kwargs, :equal) && !kwargs[:equal]
throw(ArgumentError("\"cols\" must be defined/non-empty when \"equal=false\""))
end
haskey(kwargs, :cols) && isempty(kwargs[:cols]) && throw(ArgumentError("\"cols\" must not be empty"))

return new(
get(kwargs, :num, 1),
get(kwargs, :space, nothing),
get(kwargs, :sep, false),
get(kwargs, :cols, Column[]),
get(kwargs, :equal, !haskey(kwargs, :cols)))
end
end

"""
PageMargins(; top, right, bottom, left, kwargs...)

Describes page margins in a [`Section`](@ref).

## Keyword arguments

| Keyword | Description |
| :-- | :-- |
| `top::`[`Twip`](@ref) | The top margin. |
| `right::`[`Twip`](@ref) | The right margin. |
| `bottom::`[`Twip`](@ref) | The bottom margin. |
| `left::`[`Twip`](@ref) | The left margin. |
| `header::`[`Twip`](@ref)`=Twip(0)` | The header margin. |
| `footer::`[`Twip`](@ref)`=Twip(0)` | The footer margin. |
| `gutter::`[`Twip`](@ref)`=Twip(0)` | The gutter margin. |
"""
Base.@kwdef struct PageMargins
top::Twip
right::Twip
bottom::Twip
left::Twip
header::Twip = Twip(0)
footer::Twip = Twip(0)
gutter::Twip = Twip(0)
end

"""
SectionProperties(; kwargs...)

Expand All @@ -1047,15 +1127,19 @@ Holds properties for a [`Section`](@ref).
| Keyword | Description |
| :-- | :-- |
| `pagesize::PageSize` | The size of each page in the section. |
| `margins::PageMargins` | The margins for each page in the section |
| `valign::PageVerticalAlign.T` | The vertical alignment of content on each page of the section. |
| `headers::`[`Headers`](@ref) | Defines the header content shown at the top of each page of the section. |
| `footers::`[`Footers`](@ref) | Defines the footer content shown at the bottom of each page of the section. |
| `columns::`[`Columns`](@ref) | Configures the columns in the section |
"""
Base.@kwdef struct SectionProperties
pagesize::Maybe{PageSize} = nothing
margins::Maybe{PageMargins} = nothing
valign::Maybe{PageVerticalAlign.T} = nothing
headers::Maybe{Headers} = nothing
footers::Maybe{Footers} = nothing
columns::Maybe{Columns} = nothing
end

"""
Expand Down Expand Up @@ -1590,9 +1674,15 @@ function to_xml(body::Body, rels)
if props.pagesize !== nothing
E.link!(section_params_node, to_xml(props.pagesize, rels))
end
if props.margins !== nothing
E.link!(section_params_node, to_xml(props.margins, rels))
end
if props.valign !== nothing
E.link!(section_params_node, to_xml(props.valign, rels))
end
if props.columns !== nothing
E.link!(section_params_node, to_xml(props.columns, rels))
end
if props.headers !== nothing
for type in (:default, :first, :even)
x = getproperty(props.headers, type)
Expand Down Expand Up @@ -1827,6 +1917,14 @@ function children(p::ParagraphProperties)
return c
end

function children(p::Columns)
c = []
if !p.equal
append!(c, p.cols)
end
return c
end

function children(p::TableCellProperties)
c = []
p.borders === nothing || push!(c, p.borders)
Expand Down Expand Up @@ -1899,6 +1997,7 @@ function attributes(t::Union{TableCellBorder, ParagraphBorder})
return attrs
end
attributes(p::PageSize) = (("w:h", p.height), ("w:w", p.width), ("w:orient", p.orientation))
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))
attributes(p::PageVerticalAlign.T) = (("w:val", p),)
attributes(p::Justification.T) = (("w:val", p),)
function attributes(s::Style)
Expand All @@ -1915,6 +2014,26 @@ end
attributes(p::ParagraphStyle) = (("w:val", p.name),)
attributes(p::RunStyle) = (("w:val", p.name),)
attributes(p::VerticalAlignment.T) = (("w:val", p),)
function attributes(p::Column)
attrs = Tuple{String, Any}[]
p.width === nothing || push!(attrs, ("w:w", p.width))
p.space === nothing || push!(attrs, ("w:space", p.space))
return attrs
end
function attributes(p::Columns)
attrs = Tuple{String, Any}[]
if p.num > 1 && p.equal
push!(attrs, ("w:equalWidth", p.equal))
push!(attrs, ("w:num", p.num))
p.sep === false || push!(attrs, ("w:sep", p.sep))
p.space === nothing || push!(attrs, ("w:space", p.space))
elseif !p.equal
push!(attrs, ("w:equalWidth", p.equal))
push!(attrs, ("w:num", length(p.cols)))
p.sep === false || push!(attrs, ("w:sep", p.sep))
end
return attrs
end
function attributes(f::Fonts)
attrs = Tuple{String, Any}[]
f.ascii === nothing || push!(attrs, ("w:ascii", f.ascii))
Expand Down Expand Up @@ -2011,7 +2130,10 @@ function xmltag(t::Tuple{ParagraphBorder, Symbol})
throw(ArgumentError("Invalid symbol $(repr(sym)), options are :top, :left, :right, :bottom, :between."))
end
xmltag(::PageSize) = "w:pgSz"
xmltag(::PageMargins) = "w:pgMar"
xmltag(::PageVerticalAlign.T) = "w:vAlign"
xmltag(::Columns) = "w:cols"
xmltag(::Column) = "w:col"
xmltag(::Style) = "w:style"
xmltag(::Justification.T) = "w:jc"
xmltag(::Fonts) = "w:rFonts"
Expand Down
18 changes: 17 additions & 1 deletion test/references/breaks/word/document.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@
<w:t>after page break</w:t>
</w:r>
</w:p>
<w:sectPr/>
<w:p>
<w:pPr>
<w:sectPr/>
</w:pPr>
</w:p>
<w:p>
<w:pPr/>
<w:r>
<w:rPr/>
<w:t>column 1</w:t>
<w:br w:type="column"/>
<w:t>column 2</w:t>
</w:r>
</w:p>
<w:sectPr>
<w:cols w:equalWidth="true" w:num="2"/>
</w:sectPr>
</w:body>
</w:document>
10 changes: 10 additions & 0 deletions test/references/columns/[Content_Types].xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types
xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="png" ContentType="image/png" />
<Default Extension="svg" ContentType="image/svg+xml" />
<Default Extension="xml" ContentType="application/xml" />
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" />
<Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml" />
</Types>
5 changes: 5 additions & 0 deletions test/references/columns/_rels/.rels
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships
xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>
4 changes: 4 additions & 0 deletions test/references/columns/word/_rels/document.xml.rels
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
</Relationships>
50 changes: 50 additions & 0 deletions test/references/columns/word/document.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<w:body>
<w:p>
<w:pPr/>
<w:r>
<w:rPr>
<w:sz w:val="80"/>
</w:rPr>
<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>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:sectPr/>
</w:pPr>
</w:p>
<w:p>
<w:pPr/>
<w:r>
<w:rPr>
<w:sz w:val="80"/>
</w:rPr>
<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>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:sectPr>
<w:cols w:equalWidth="true" w:num="2"/>
</w:sectPr>
</w:pPr>
</w:p>
<w:p>
<w:pPr/>
<w:r>
<w:rPr>
<w:sz w:val="80"/>
</w:rPr>
<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>
</w:r>
</w:p>
<w:sectPr>
<w:cols w:equalWidth="false" w:num="2">
<w:col w:w="5760" w:space="720"/>
<w:col w:w="2880"/>
</w:cols>
</w:sectPr>
</w:body>
</w:document>
Loading
Loading