diff --git a/src/WriteDocx.jl b/src/WriteDocx.jl index abfa7d6..8995541 100644 --- a/src/WriteDocx.jl +++ b/src/WriteDocx.jl @@ -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...) @@ -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 """ @@ -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) @@ -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) @@ -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) @@ -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)) @@ -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" diff --git a/test/references/breaks/word/document.xml b/test/references/breaks/word/document.xml index 4ae1d18..f4270f6 100644 --- a/test/references/breaks/word/document.xml +++ b/test/references/breaks/word/document.xml @@ -12,6 +12,22 @@ after page break - + + + + + + + + + + column 1 + + column 2 + + + + + diff --git a/test/references/columns/[Content_Types].xml b/test/references/columns/[Content_Types].xml new file mode 100644 index 0000000..95c03c6 --- /dev/null +++ b/test/references/columns/[Content_Types].xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/test/references/columns/_rels/.rels b/test/references/columns/_rels/.rels new file mode 100644 index 0000000..27e1e49 --- /dev/null +++ b/test/references/columns/_rels/.rels @@ -0,0 +1,5 @@ + + + + diff --git a/test/references/columns/word/_rels/document.xml.rels b/test/references/columns/word/_rels/document.xml.rels new file mode 100644 index 0000000..66cfbbf --- /dev/null +++ b/test/references/columns/word/_rels/document.xml.rels @@ -0,0 +1,4 @@ + + + + diff --git a/test/references/columns/word/document.xml b/test/references/columns/word/document.xml new file mode 100644 index 0000000..34cb0c6 --- /dev/null +++ b/test/references/columns/word/document.xml @@ -0,0 +1,50 @@ + + + + + + + + + + No columns No columns No columns No columns No columns No columns No columns No columns No columns No 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 2 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 different width columns + + + + + + + + + + diff --git a/test/references/columns/word/styles.xml b/test/references/columns/word/styles.xml new file mode 100644 index 0000000..7b8142b --- /dev/null +++ b/test/references/columns/word/styles.xml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/references/page_margins/[Content_Types].xml b/test/references/page_margins/[Content_Types].xml new file mode 100644 index 0000000..95c03c6 --- /dev/null +++ b/test/references/page_margins/[Content_Types].xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/test/references/page_margins/_rels/.rels b/test/references/page_margins/_rels/.rels new file mode 100644 index 0000000..27e1e49 --- /dev/null +++ b/test/references/page_margins/_rels/.rels @@ -0,0 +1,5 @@ + + + + diff --git a/test/references/page_margins/word/_rels/document.xml.rels b/test/references/page_margins/word/_rels/document.xml.rels new file mode 100644 index 0000000..66cfbbf --- /dev/null +++ b/test/references/page_margins/word/_rels/document.xml.rels @@ -0,0 +1,4 @@ + + + + diff --git a/test/references/page_margins/word/document.xml b/test/references/page_margins/word/document.xml new file mode 100644 index 0000000..06ab4f1 --- /dev/null +++ b/test/references/page_margins/word/document.xml @@ -0,0 +1,27 @@ + + + + + + + + no margins + + + + + + + + + + + + thiccc margins + + + + + + + diff --git a/test/references/page_margins/word/styles.xml b/test/references/page_margins/word/styles.xml new file mode 100644 index 0000000..7b8142b --- /dev/null +++ b/test/references/page_margins/word/styles.xml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/runtests.jl b/test/runtests.jl index aefa5ca..60d6a33 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -708,6 +708,62 @@ Base.show(io::IO, ::MIME"image/png", p::PNG) = write(io, p.bytes) reftest_docx(doc, "multiple_sections") end + @testset "Columns" begin + doc = W.Document( + W.Body([ + W.Section([ + W.Paragraph([ + W.Run( + [W.Text("No columns " ^ 10)], + W.RunProperties(size = 40W.pt) + ) + ]) + ]) + W.Section([ + W.Paragraph([ + W.Run( + [W.Text("2 columns " ^ 20)], + W.RunProperties(size = 40W.pt) + ) + ]) + ]; columns=W.Columns(;num=2)) + W.Section([ + W.Paragraph([ + W.Run( + [W.Text("different width columns " ^ 12)], + W.RunProperties(size = 40W.pt) + ) + ]) + ]; columns=W.Columns(;cols=[W.Column(;width=4W.inch, space=0.5W.inch), W.Column(;width=2W.inch)])) + ]) + ) + + reftest_docx(doc, "columns") + end + + @testset "Page margins" begin + doc = W.Document( + W.Body([ + W.Section([ + W.Paragraph([ + W.Run([ + W.Text("no margins") + ]) + ]) + ]) + W.Section([ + W.Paragraph([ + W.Run([ + W.Text("thiccc margins") + ]) + ]) + ]; margins=W.PageMargins(;top=2.5W.inch, right=2.5W.inch, bottom=2.5W.inch, left=2.5W.inch)) + ]) + ) + + reftest_docx(doc, "page_margins") + end + @testset "Page sizes" begin doc = W.Document( W.Body( @@ -892,6 +948,17 @@ Base.show(io::IO, ::MIME"image/png", p::PNG) = write(io, p.bytes) ) ]) ]) + W.Section([ + W.Paragraph([ + W.Run( + [ + W.Text("column 1"), + W.Break(W.BreakType.column), + W.Text("column 2"), + ] + ) + ]) + ]; columns=W.Columns(;num=2)) ] ) )