Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Page Size #34

Merged
merged 9 commits into from
Mar 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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