Skip to content

Commit

Permalink
Page Size (#34)
Browse files Browse the repository at this point in the history
* started page size

* page size and margins are now stored as a flexible Measurement<UnitLenght>

* some changes to PageDefinition to make things more cross-platform compatible

* updated readme

* Update README.md

---------

Co-authored-by: Morten Bertz <[email protected]>
  • Loading branch information
shinjukunian and Morten Bertz authored Mar 5, 2023
1 parent 863f2b7 commit 05d3db9
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 6 deletions.
12 changes: 12 additions & 0 deletions DocX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
5A8406C72609DF39002B8B34 /* NSTextAttachement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A0AD5CE26099AA200C146D3 /* NSTextAttachement+Extensions.swift */; };
5A8406CD2609DF65002B8B34 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A8406CC2609DF65002B8B34 /* Bundle+Extensions.swift */; };
5A8406CE2609DF65002B8B34 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A8406CC2609DF65002B8B34 /* Bundle+Extensions.swift */; };
5A907B882982A35800E1A581 /* DocXPageDefinition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A907B862982A35800E1A581 /* DocXPageDefinition.swift */; };
5A907B892982A35800E1A581 /* DocXPageDefinition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A907B862982A35800E1A581 /* DocXPageDefinition.swift */; };
5A907B8A2982A35800E1A581 /* DocXStyleConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A907B872982A35800E1A581 /* DocXStyleConfiguration.swift */; };
5A907B8B2982A35800E1A581 /* DocXStyleConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A907B872982A35800E1A581 /* DocXStyleConfiguration.swift */; };
5A9F70AA223502D6008E967C /* DocX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A9F70A0223502D6008E967C /* DocX.framework */; };
5A9F70AF223502D6008E967C /* DocXTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9F70AE223502D6008E967C /* DocXTests.swift */; };
5A9F70B1223502D6008E967C /* DocX.h in Headers */ = {isa = PBXBuildFile; fileRef = 5A9F70A3223502D6008E967C /* DocX.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -106,6 +110,8 @@
5A8406B42609DF1B002B8B34 /* NSAttributedString+Writing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Writing.swift"; sourceTree = "<group>"; };
5A8406BB2609DF2B002B8B34 /* ImageRelationship.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRelationship.swift; sourceTree = "<group>"; };
5A8406CC2609DF65002B8B34 /* Bundle+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = "<group>"; };
5A907B862982A35800E1A581 /* DocXPageDefinition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocXPageDefinition.swift; sourceTree = "<group>"; };
5A907B872982A35800E1A581 /* DocXStyleConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocXStyleConfiguration.swift; sourceTree = "<group>"; };
5A9F70A0223502D6008E967C /* DocX.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DocX.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5A9F70A3223502D6008E967C /* DocX.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DocX.h; sourceTree = "<group>"; };
5A9F70A4223502D6008E967C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand Down Expand Up @@ -207,6 +213,8 @@
5A9F70A2223502D6008E967C /* DocX */ = {
isa = PBXGroup;
children = (
5A907B862982A35800E1A581 /* DocXPageDefinition.swift */,
5A907B872982A35800E1A581 /* DocXStyleConfiguration.swift */,
5AE833F2223688A400E68343 /* blank */,
5A9F70BF22350300008E967C /* DocumentRoot.swift */,
5A9F70BC22350300008E967C /* DocX.swift */,
Expand Down Expand Up @@ -466,12 +474,14 @@
5AE833F12236861F00E68343 /* PlatformSpecific-macOS.swift in Sources */,
5A9F70F922350DDE008E967C /* AttributeElements.swift in Sources */,
5AE8341B22375D9E00E68343 /* NSUnderlineStyle+Elements.swift in Sources */,
5A907B8A2982A35800E1A581 /* DocXStyleConfiguration.swift in Sources */,
5AE5BE9B25EE283F009A152F /* NSAttributedString+DocX-macOS.swift in Sources */,
5A9F70FB22354F1C008E967C /* RubyAnnotationElement.swift in Sources */,
5AC6C9B5260ACCEB0059F7B0 /* DocXWriting.swift in Sources */,
5AE8340C22373A5200E68343 /* NSParagraphStyle+Elements.swift in Sources */,
5A4FC7E927FEF5630063F796 /* AttributedString.swift in Sources */,
5A6B3DBF26D86D33009F6859 /* DocXOptions.swift in Sources */,
5A907B882982A35800E1A581 /* DocXPageDefinition.swift in Sources */,
5A9F70C222350300008E967C /* DocX.swift in Sources */,
5A9F70F722350D12008E967C /* ParagraphElement.swift in Sources */,
5A8406C22609DF38002B8B34 /* NSTextAttachement+Extensions.swift in Sources */,
Expand Down Expand Up @@ -503,12 +513,14 @@
5AE833D12236831500E68343 /* NSAttributedString+Extensions.swift in Sources */,
5AE833D52236831500E68343 /* AttributeElements.swift in Sources */,
5AE833DA2236831500E68343 /* RubyAnnotationElement.swift in Sources */,
5A907B8B2982A35800E1A581 /* DocXStyleConfiguration.swift in Sources */,
5AE5BEA025EE2847009A152F /* NSAttributedString+DocX-macOS.swift in Sources */,
5AE833EC223683F000E68343 /* NSAttributedString+DocX.swift in Sources */,
5AC6C9B6260ACCEB0059F7B0 /* DocXWriting.swift in Sources */,
5AE8340D22373A5200E68343 /* NSParagraphStyle+Elements.swift in Sources */,
5A4FC7EA27FEF5630063F796 /* AttributedString.swift in Sources */,
5A6B3DC026D86D33009F6859 /* DocXOptions.swift in Sources */,
5A907B892982A35800E1A581 /* DocXPageDefinition.swift in Sources */,
5AE8342722377A5B00E68343 /* NSUnderlineStyle+Elements.swift in Sources */,
5AE833DB2236831500E68343 /* DocX.swift in Sources */,
5A8406C72609DF39002B8B34 /* NSTextAttachement+Extensions.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions DocX/DocXOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public struct DocXOptions{
/// An optional configuration object for style output
public var styleConfiguration: DocXStyleConfiguration?

/// An optional parameter that specifies the page (paper) size and margins. If not specified (`nil`), Word will use default values based on the current locale and printer settings. The default value is `nil`.
public var pageDefinition: PageDefinition?

public init(){}

Expand Down
228 changes: 228 additions & 0 deletions DocX/DocXPageDefinition.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
//
// DocXPageDefinition.swift
//
//
// Created by Morten Bertz on 2023/01/13.
//

import CoreGraphics
import Foundation
import AEXML

#if canImport(UIKit)
import UIKit
fileprivate typealias NSEdgeInsets = UIEdgeInsets
#endif

public struct PageDefinition: Equatable, CustomStringConvertible, Hashable{

/// The size of a page
public struct PageSize:Equatable, CustomStringConvertible, Hashable{

///Page width in twips
let width:Measurement<UnitLength>

/// Page height in twips
let height:Measurement<UnitLength>

/// Convenience initializer using a `CGSize`. All values are in points.
public init(size:CGSize){
self.width = Measurement(value: size.width, unit: UnitLength.points)
self.height = Measurement(value: size.height, unit: UnitLength.points)
}

/// Convenience initializer to define the page size in length units (mm, cm, inches)
/// - Parameters:
/// - width: the width if the page as a length measurement
/// - height: the width if the page as a length measurement
/// Discussion:
///
/// For an A4 page (21 cm x 29.7 cm), use
/// ```
/// let width=Measurement(value: 21, unit: UnitLength.centimeters)
/// let height=Measurement(value: 29.7, unit: UnitLength.centimeters)
/// let page=PageSize(width: width, height: height)
/// ```
public init(width:Measurement<UnitLength>, height: Measurement<UnitLength>){
self.width = width
self.height = height
}

public var description: String{
let formatter=MeasurementFormatter()
formatter.unitOptions = [.providedUnit]
return "Width: \(formatter.string(from: width.converted(to: .points))), height: \(formatter.string(from: height.converted(to: .points)))\rWidth: \(formatter.string(from: width.converted(to: UnitLength.centimeters))), height: \(formatter.string(from: height.converted(to: UnitLength.centimeters)))"
}

/// An A4 page
public static let A4:PageSize = PageSize(size: .init(width: 595, height: 842))

/// A Letter page
public static let letter:PageSize = PageSize(size: .init(width: 612, height: 792))

var pageSizeElement:AEXMLElement{
let width=Int(width.converted(to: .twips).value)
let height=Int(height.converted(to: .twips).value)
return AEXMLElement(name: "w:pgSz", value: nil, attributes: ["w:w":String(width), "w:h":String(height), "w:orient":"landscape"])
}

/// The size of the page in points
public var cgSize:CGSize{
return size(unit: .points)
}

/// The size of the page in in a desired unit of length (cm, mm, inches, etc.)
public func size(unit:UnitLength)->CGSize{
return CGSize(width: width.converted(to: unit).value, height: height.converted(to: unit).value)
}
}

/// The margins of a page (insets of the printable area).
public struct PageMargins:Equatable, CustomStringConvertible, Hashable{

/// Top margin in twips
let top:Measurement<UnitLength>

/// bottom margin in twips.
let bottom:Measurement<UnitLength>

/// left margin in twips
let left:Measurement<UnitLength>

/// right margin in twips
let right:Measurement<UnitLength>

/// footer margin in twips. The larger value of `bottom` or `footer` will be used
let footer:Measurement<UnitLength>

/// header margin in twips. The larger value of `header` and `top` will be used.
let header:Measurement<UnitLength>


/// Convenience initializer. All values are in points. One inch (2.54 cm) is 72 points.
/// - Parameters:
/// - top: top margin
/// - bottom: bottom margin
/// - left: left margin
/// - right: right margin
/// - footer: footer margin
/// - header: header margin
public init(top:CGFloat, bottom: CGFloat, left: CGFloat, right: CGFloat, footer: CGFloat = 0, header: CGFloat = 0) {
self.top = Measurement(value: top, unit: UnitLength.points)
self.bottom = Measurement(value: bottom, unit: UnitLength.points)
self.left = Measurement(value: left, unit: UnitLength.points)
self.right = Measurement(value: right, unit: UnitLength.points)
self.footer = Measurement(value: footer, unit: UnitLength.points)
self.header = Measurement(value: header, unit: UnitLength.points)
}

/// Memberwise initializer to define the margins size in length units (mm, cm, inches)
/// - Parameters:
/// - top: top margin
/// - bottom: bottom margin
/// - left: left margin
/// - right: right margin
/// - footer: footer margin
/// - header: header margin
public init(top:Measurement<UnitLength>, bottom: Measurement<UnitLength>, left: Measurement<UnitLength>, right: Measurement<UnitLength>, footer: Measurement<UnitLength> = Measurement(value: 0, unit: .centimeters), header: Measurement<UnitLength> = Measurement(value: 0, unit: .centimeters)){
self.top = top
self.bottom = bottom
self.left = left
self.right = right
self.footer = footer
self.header = header
}

#if os(macOS)
/// Convenience initializer. Edge insets are in points.
public init(edgeInsets:NSEdgeInsets){
self.init(top: edgeInsets.top, bottom: edgeInsets.bottom, left: edgeInsets.left, right: edgeInsets.right)
}

/// Effective margins of the page in a unit of length.
public func effectiveMargins(unit:UnitLength = .points)->NSEdgeInsets{
return _effectiveMargins(unit: unit)
}

#elseif os(iOS)
/// Convenience initializer. Edge insets are in points.
public init(edgeInsets:UIEdgeInsets){
self.init(top: edgeInsets.top, bottom: edgeInsets.bottom, left: edgeInsets.left, right: edgeInsets.right)
}

/// Effective margins of the page in a unit of length.
public func effectiveMargins(unit:UnitLength = .points)->UIEdgeInsets{
return _effectiveMargins(unit: unit)
}

#endif

fileprivate func _effectiveMargins(unit:UnitLength)->NSEdgeInsets{
let top = top < Measurement(value: 0, unit: .points) ? top : max(top, header)
let bottom = bottom < Measurement(value: 0, unit: .points) ? bottom : max(bottom, footer)
return NSEdgeInsets(top: top.converted(to: unit).value, left: left.converted(to: unit).value, bottom: bottom.converted(to: unit).value, right: right.converted(to: unit).value)
}

public var description: String{
let formatter=MeasurementFormatter()
formatter.unitOptions = [.providedUnit]
return "Top \(formatter.string(from: top.converted(to: .points))), bottom: \(formatter.string(from: bottom.converted(to: .points))), left: \(formatter.string(from: left.converted(to: .points))), right: \(formatter.string(from: right.converted(to: .points))), footer: \(formatter.string(from: footer.converted(to: .points))), header: \(formatter.string(from: header.converted(to: .points)))\rTop \(formatter.string(from: top.converted(to: .centimeters))), bottom: \(formatter.string(from: bottom.converted(to: .centimeters))), left: \(formatter.string(from: left.converted(to: .centimeters))), right: \(formatter.string(from: right.converted(to: .centimeters))), footer: \(formatter.string(from: footer.converted(to: .centimeters))), header: \(formatter.string(from: header.converted(to: .centimeters)))"
}

/// The default margins of a standard Word document (one inch).
public static let `default` = PageMargins(top: CGFloat(72), bottom: 72, left: 72, right: 72, footer: 35.4, header: 35.4)

var marginElement:AEXMLElement{
return AEXMLElement(name: "w:pgMar", value: nil, attributes: ["w:top":String(Int(top.converted(to: .twips).value)), "w:right":String(Int(right.converted(to: .twips).value)), "w:bottom":String(Int(bottom.converted(to: .twips).value)), "w:left":String(Int(left.converted(to: .twips).value)), "w:header":String(Int(header.converted(to: .twips).value)), "w:footer":String(Int(footer.converted(to: .twips).value)), "w:gutter":"0"])
}
}

/// The page size of the document
let pageSize:PageSize

/// The page margins of the document
let pageMargins:PageMargins

public var description: String{
return "Paper Size: \(pageSize),\r\rMargins: \(pageMargins)"
}


/// Initializes a page Definition with a page (paper) size and margins
/// - Parameters:
/// - pageSize: a page (paper) size, e.g. A4.
/// - pageMargins: the margins from the edge of the page to the borders of the printed text.
public init(pageSize: PageSize, pageMargins: PageMargins = .default) {
self.pageSize = pageSize
self.pageMargins = pageMargins
}


/// The effective printable area of the page in a unit of length.
public func printableSize(unit: UnitLength = .points) ->CGSize{
let size=self.pageSize.size(unit: unit)
let margins=self.pageMargins.effectiveMargins(unit: unit)
return CGSize(width: size.width - margins.right - margins.left, height: size.height - margins.bottom - margins.top)
}


var pageElements:[AEXMLElement]{
return [pageSize.pageSizeElement, pageMargins.marginElement]
}

}


public extension UnitLength{

/// A length units measured in points (1/72 of an inch).
class var points:UnitLength{
return UnitLength(symbol: "points", converter: UnitConverterLinear(coefficient: 1/100 / 72 * 2.54))
}

/// A length units measured in twips. Twips are a twentieth (1/20) of a point (1/72 of an inch)
class var twips:UnitLength{
return UnitLength(symbol: "twips", converter: UnitConverterLinear(coefficient: 1/100 / 72 * 2.54 / 20))
}
}

5 changes: 2 additions & 3 deletions DocX/DocXWriter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ public class DocXWriter{
/// Convenience function to write an array of NSAttributedString to separate pages in a .docx file
/// - Parameters:
/// - pages: an array of NSAttributedStrings. A page break fill be inserted after each page.
/// - url: The destination of the resulting .docx, e.g. ```myfile.docx```
/// - options: an optional instance of `DocXOptions`. This allows you to specify metadata for the document.
/// - configuration: an optional instance of `DocXConfiguration` that allows you to control the docx output.
/// - url: The destination of the resulting .docx, e.g. `myfile.docx`
/// - options: an optional instance of `DocXOptions`. This allows you to specify metadata for the document and customize docx output.
/// - Throws: Throws errors for I/O.
public class func write(pages:[NSAttributedString],
to url:URL,
Expand Down
8 changes: 6 additions & 2 deletions DocX/DocXWriting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,18 @@ enum DocXWriteImageError: Error {
@available(OSX 10.11, *)
extension DocX where Self : NSAttributedString{

var pageDef:AEXMLElement{
func pageDef(options: DocXOptions?) -> AEXMLElement{
let pageDef=AEXMLElement(name: "w:sectPr", value: nil, attributes: ["w:rsidR":"00045791", "w:rsidSect":"004F37A0"])

if self.usesVerticalForms{
let vertical=AEXMLElement(name: "w:textDirection", value: nil, attributes: ["w:val":"tbRl"])
pageDef.addChild(vertical)
}

if let page=options?.pageDefinition{
pageDef.addChildren(page.pageElements)
}

//these elements are added for by word, but not by the cocoa docx exporter. word then falls back to the page setup defined by the print settings of the machine. this seems useful

// let size=AEXMLElement(name: "w:pgSz", value: nil, attributes: ["w:w":"11901", "w:h":"16817", "w:code":"9"])
Expand Down Expand Up @@ -79,7 +83,7 @@ extension DocX where Self : NSAttributedString{
body.addChildren(self.buildParagraphs(paragraphRanges: self.paragraphRanges,
linkRelations: linkRelations,
options: options))
body.addChild(pageDef)
body.addChild(pageDef(options: options))
return document.xmlCompact
}

Expand Down
Loading

0 comments on commit 05d3db9

Please sign in to comment.