From 599656518be4c1655eed0273eef5b4b4668bd3d9 Mon Sep 17 00:00:00 2001 From: Morten Bertz Date: Fri, 13 Jan 2023 18:23:31 +0900 Subject: [PATCH 1/5] started page size --- DocX/DocXOptions.swift | 2 + DocX/DocXPageDefinition.swift | 231 ++++++++++++++++++++++++++++++++++ DocX/DocXWriting.swift | 8 +- DocXTests/DocXTests.swift | 34 +++++ 4 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 DocX/DocXPageDefinition.swift diff --git a/DocX/DocXOptions.swift b/DocX/DocXOptions.swift index c080056..f74231f 100644 --- a/DocX/DocXOptions.swift +++ b/DocX/DocXOptions.swift @@ -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(){} diff --git a/DocX/DocXPageDefinition.swift b/DocX/DocXPageDefinition.swift new file mode 100644 index 0000000..72c9ef4 --- /dev/null +++ b/DocX/DocXPageDefinition.swift @@ -0,0 +1,231 @@ +// +// 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:Int + + /// Page height in twips + let height:Int + + /// Memberwise initializer, width and height are in twips (point / 20) + init(width: Int, height: Int) { + self.width = width + self.height = height + } + + /// Convenience initializer using a `CGSize`. All values are in points. + public init(size:CGSize){ + self.width = Int(Measurement(value: size.width, unit: UnitLength.points).converted(to: .twips).value) + self.height = Int(Measurement(value: size.height, unit: UnitLength.points).converted(to: .twips).value) + } + + /// 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, height: Measurement){ + self.width = Int(width.converted(to: .twips).value) + self.height = Int(height.converted(to: .twips).value) + } + + public var description: String{ + let formatter=MeasurementFormatter() + formatter.unitOptions = [.providedUnit] + return "Width: \(formatter.string(from: Measurement(value: Double(width), unit: UnitLength.twips).converted(to: .points))), height: \(formatter.string(from: Measurement(value: Double(height), unit: UnitLength.twips).converted(to: .points)))\rWidth: \(formatter.string(from: Measurement(value: Double(width), unit: UnitLength.twips).converted(to: UnitLength.centimeters))), height: \(formatter.string(from: Measurement(value: Double(height), unit: UnitLength.twips).converted(to: UnitLength.centimeters)))" + } + + /// An A4 page + public static let A4:PageSize = PageSize(size: .init(width: CGFloat(595), height: 842)) + + /// A Letter page + public static let letter:PageSize = PageSize(size: .init(width: CGFloat(612), height: 792)) + + var pageSizeElement:AEXMLElement{ + 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: Measurement(value: Double(width), unit: UnitLength.twips).converted(to: unit).value, height: Measurement(value: Double(width), unit: UnitLength.twips).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:Int + + /// bottom margin in twips. + let bottom:Int + + /// left margin in twips + let left:Int + + /// right margin in twips + let right:Int + + /// footer margin in twips. The larger value of `bottom` or `footer` will be used + let footer:Int + + /// header margin in twips. The larger value of `header` and `top` will be used. + let header:Int + + /// memberwise initializer. All values are in twips (twentieth of an inch) + /// - Parameters: + /// - top: top margin + /// - bottom: bottom margin + /// - left: left margin + /// - right: right margin + /// - footer: footer margin + /// - header: header margin + init(top: Int, bottom: Int, left: Int, right: Int, footer: Int=0, header: Int=0) { + self.top = top + self.bottom = bottom + self.left = left + self.right = right + self.footer = footer + self.header = header + } + + /// memberwise 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 = Int(Measurement(value: top, unit: UnitLength.points).converted(to: .twips).value) + self.bottom = Int(Measurement(value: bottom, unit: UnitLength.points).converted(to: .twips).value) + self.left = Int(Measurement(value: left, unit: UnitLength.points).converted(to: .twips).value) + self.right = Int(Measurement(value: right, unit: UnitLength.points).converted(to: .twips).value) + self.footer = Int(Measurement(value: footer, unit: UnitLength.points).converted(to: .twips).value) + self.header = Int(Measurement(value: header, unit: UnitLength.points).converted(to: .twips).value) + } + + /// Convenience initializer to define the margins size in length units (mm, cm, inches) + public init(top:Measurement, bottom: Measurement, left: Measurement, right: Measurement, footer: Measurement = Measurement(value: 0, unit: .centimeters), header: Measurement = Measurement(value: 0, unit: .centimeters)){ + self.top = Int(top.converted(to: .twips).value) + self.bottom = Int(bottom.converted(to: .twips).value) + self.left = Int(left.converted(to: .twips).value) + self.right = Int(right.converted(to: .twips).value) + self.footer = Int(footer.converted(to: .twips).value) + self.header = Int(header.converted(to: .twips).value) + } + +#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) + } + +#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) + } + +#endif + fileprivate var effectiveMargins:NSEdgeInsets{ + return effectiveMargins(unit: .points) + } + + /// Effective margins of the page in a unit of length. + public func effectiveMargins(unit:UnitLength)->NSEdgeInsets{ + let top = top < 0 ? top : max(top, header) + let bottom = bottom < 0 ? bottom : max(bottom, footer) + return NSEdgeInsets(top: Measurement(value: Double(top), unit: UnitLength.twips).converted(to: unit).value, left: Measurement(value: Double(left), unit: UnitLength.twips).converted(to: unit).value, bottom: Measurement(value: Double(bottom), unit: UnitLength.twips).converted(to: unit).value, right: Measurement(value: Double(right), unit: UnitLength.twips).converted(to: unit).value) + } + + public var description: String{ + let formatter=MeasurementFormatter() + formatter.unitOptions = [.providedUnit] + return "Top \(formatter.string(from: Measurement(value: Double(top), unit: UnitLength.twips).converted(to: .points))), bottom: \(formatter.string(from: Measurement(value: Double(bottom), unit: UnitLength.twips).converted(to: .points))), left: \(formatter.string(from: Measurement(value: Double(left), unit: UnitLength.twips).converted(to: .points))), right: \(formatter.string(from: Measurement(value: Double(right), unit: UnitLength.twips).converted(to: .points))), footer: \(formatter.string(from: Measurement(value: Double(footer), unit: UnitLength.twips).converted(to: .points))), header: \(formatter.string(from: Measurement(value: Double(header), unit: UnitLength.twips).converted(to: .points)))\rTop \(formatter.string(from: Measurement(value: Double(top), unit: UnitLength.twips).converted(to: .centimeters))), bottom: \(formatter.string(from: Measurement(value: Double(bottom), unit: UnitLength.twips).converted(to: .centimeters))), left: \(formatter.string(from: Measurement(value: Double(left), unit: UnitLength.twips).converted(to: .centimeters))), right: \(formatter.string(from: Measurement(value: Double(right), unit: UnitLength.twips).converted(to: .centimeters))), footer: \(formatter.string(from: Measurement(value: Double(footer), unit: UnitLength.twips).converted(to: .centimeters))), header: \(formatter.string(from: Measurement(value: Double(header), unit: UnitLength.twips).converted(to: .centimeters)))" + } + + /// The default margins if a standard Word document. + 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(top), "w:right":String(right), "w:bottom":String(bottom), "w:left":String(left), "w:header":String(header), "w:footer":String(footer), "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{ + class var points:UnitLength{ + return UnitLength(symbol: "points", converter: UnitConverterLinear(coefficient: 1/100 / 28.3465)) + } + + class var twips:UnitLength{ + return UnitLength(symbol: "twips", converter: UnitConverterLinear(coefficient: 1/100 / 28.3465 / 20)) + } +} + diff --git a/DocX/DocXWriting.swift b/DocX/DocXWriting.swift index d79fb21..1417e56 100644 --- a/DocX/DocXWriting.swift +++ b/DocX/DocXWriting.swift @@ -18,7 +18,7 @@ import AppKit @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{ @@ -26,6 +26,10 @@ extension DocX where Self : NSAttributedString{ 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"]) @@ -72,7 +76,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 } diff --git a/DocXTests/DocXTests.swift b/DocXTests/DocXTests.swift index ffe09f7..6fad39f 100644 --- a/DocXTests/DocXTests.swift +++ b/DocXTests/DocXTests.swift @@ -755,6 +755,40 @@ let string = """ } + func testPageSize() throws{ + let loremIpsum = """ + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + """ + + func makeText(howMany:Int)->NSMutableAttributedString{ + let text=NSMutableAttributedString() + + for _ in 0...howMany{ + text.append(NSAttributedString(string: loremIpsum)) + text.append(NSAttributedString(string: "\r\r")) + } + return text + } + + + let defs = [PageDefinition(pageSize: .A4), + PageDefinition(pageSize: .letter), + PageDefinition(pageSize: .A4, pageMargins: .init(edgeInsets: NSEdgeInsets(top: 500, left: 100, bottom: 50, right: 40))), + PageDefinition(pageSize: .init(width: Measurement(value: 10, unit: .centimeters), height: Measurement(value: 10, unit: .centimeters))), + PageDefinition(pageSize: .init(width: Measurement(value: 10, unit: .inches), height: Measurement(value: 10, unit: .centimeters)), pageMargins: PageDefinition.PageMargins(top: Measurement(value: 1, unit: .centimeters), bottom: Measurement(value: 25, unit: .millimeters), left: .init(value: 1, unit: .inches), right: .init(value: 50, unit: .points)))] + + for def in defs{ + var options=DocXOptions() + options.pageDefinition=def + + let text=makeText(howMany: 20) + text.insert(NSAttributedString(string: "\(def.description)\r\r", attributes: [.foregroundColor: NSColor.red]), at: 0) + try writeAndValidateDocX(attributedString: text, options: options) + } + + + } + // MARK: Performance Tests From ffedac8bc6a5b0bc3f064f53b9baddda5f32e6ea Mon Sep 17 00:00:00 2001 From: Morten Bertz Date: Mon, 16 Jan 2023 14:09:05 +0900 Subject: [PATCH 2/5] page size and margins are now stored as a flexible Measurement --- DocX/DocXOptions.swift | 2 +- DocX/DocXPageDefinition.swift | 100 +++++++++++++++------------------- DocX/DocXWriter.swift | 5 +- 3 files changed, 47 insertions(+), 60 deletions(-) diff --git a/DocX/DocXOptions.swift b/DocX/DocXOptions.swift index f74231f..c82d951 100644 --- a/DocX/DocXOptions.swift +++ b/DocX/DocXOptions.swift @@ -42,7 +42,7 @@ 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`. + /// 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(){} diff --git a/DocX/DocXPageDefinition.swift b/DocX/DocXPageDefinition.swift index 72c9ef4..df83320 100644 --- a/DocX/DocXPageDefinition.swift +++ b/DocX/DocXPageDefinition.swift @@ -15,26 +15,20 @@ 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:Int + let width:Measurement /// Page height in twips - let height:Int - - /// Memberwise initializer, width and height are in twips (point / 20) - init(width: Int, height: Int) { - self.width = width - self.height = height - } + let height:Measurement /// Convenience initializer using a `CGSize`. All values are in points. public init(size:CGSize){ - self.width = Int(Measurement(value: size.width, unit: UnitLength.points).converted(to: .twips).value) - self.height = Int(Measurement(value: size.height, unit: UnitLength.points).converted(to: .twips).value) + 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) @@ -50,23 +44,25 @@ public struct PageDefinition: Equatable, CustomStringConvertible, Hashable{ /// let page=PageSize(width: width, height: height) /// ``` public init(width:Measurement, height: Measurement){ - self.width = Int(width.converted(to: .twips).value) - self.height = Int(height.converted(to: .twips).value) + self.width = width + self.height = height } public var description: String{ let formatter=MeasurementFormatter() formatter.unitOptions = [.providedUnit] - return "Width: \(formatter.string(from: Measurement(value: Double(width), unit: UnitLength.twips).converted(to: .points))), height: \(formatter.string(from: Measurement(value: Double(height), unit: UnitLength.twips).converted(to: .points)))\rWidth: \(formatter.string(from: Measurement(value: Double(width), unit: UnitLength.twips).converted(to: UnitLength.centimeters))), height: \(formatter.string(from: Measurement(value: Double(height), unit: UnitLength.twips).converted(to: UnitLength.centimeters)))" + 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: CGFloat(595), height: 842)) + public static let A4:PageSize = PageSize(size: .init(width: 595, height: 842)) /// A Letter page - public static let letter:PageSize = PageSize(size: .init(width: CGFloat(612), height: 792)) + 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"]) } @@ -77,7 +73,7 @@ public struct PageDefinition: Equatable, CustomStringConvertible, Hashable{ /// 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: Measurement(value: Double(width), unit: UnitLength.twips).converted(to: unit).value, height: Measurement(value: Double(width), unit: UnitLength.twips).converted(to: unit).value) + return CGSize(width: width.converted(to: unit).value, height: height.converted(to: unit).value) } } @@ -85,24 +81,25 @@ public struct PageDefinition: Equatable, CustomStringConvertible, Hashable{ public struct PageMargins:Equatable, CustomStringConvertible, Hashable{ /// Top margin in twips - let top:Int + let top:Measurement /// bottom margin in twips. - let bottom:Int + let bottom:Measurement /// left margin in twips - let left:Int + let left:Measurement /// right margin in twips - let right:Int + let right:Measurement /// footer margin in twips. The larger value of `bottom` or `footer` will be used - let footer:Int + let footer:Measurement /// header margin in twips. The larger value of `header` and `top` will be used. - let header:Int + let header:Measurement + - /// memberwise initializer. All values are in twips (twentieth of an inch) + /// Convenience initializer. All values are in points. One inch (2.54 cm) is 72 points. /// - Parameters: /// - top: top margin /// - bottom: bottom margin @@ -110,16 +107,16 @@ public struct PageDefinition: Equatable, CustomStringConvertible, Hashable{ /// - right: right margin /// - footer: footer margin /// - header: header margin - init(top: Int, bottom: Int, left: Int, right: Int, footer: Int=0, header: Int=0) { - self.top = top - self.bottom = bottom - self.left = left - self.right = right - self.footer = footer - self.header = header + 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. All values are in points. One inch (2.54 cm) is 72 points. + /// Memberwise initializer to define the margins size in length units (mm, cm, inches) /// - Parameters: /// - top: top margin /// - bottom: bottom margin @@ -127,23 +124,13 @@ public struct PageDefinition: Equatable, CustomStringConvertible, Hashable{ /// - 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 = Int(Measurement(value: top, unit: UnitLength.points).converted(to: .twips).value) - self.bottom = Int(Measurement(value: bottom, unit: UnitLength.points).converted(to: .twips).value) - self.left = Int(Measurement(value: left, unit: UnitLength.points).converted(to: .twips).value) - self.right = Int(Measurement(value: right, unit: UnitLength.points).converted(to: .twips).value) - self.footer = Int(Measurement(value: footer, unit: UnitLength.points).converted(to: .twips).value) - self.header = Int(Measurement(value: header, unit: UnitLength.points).converted(to: .twips).value) - } - - /// Convenience initializer to define the margins size in length units (mm, cm, inches) public init(top:Measurement, bottom: Measurement, left: Measurement, right: Measurement, footer: Measurement = Measurement(value: 0, unit: .centimeters), header: Measurement = Measurement(value: 0, unit: .centimeters)){ - self.top = Int(top.converted(to: .twips).value) - self.bottom = Int(bottom.converted(to: .twips).value) - self.left = Int(left.converted(to: .twips).value) - self.right = Int(right.converted(to: .twips).value) - self.footer = Int(footer.converted(to: .twips).value) - self.header = Int(header.converted(to: .twips).value) + self.top = top + self.bottom = bottom + self.left = left + self.right = right + self.footer = footer + self.header = header } #if os(macOS) @@ -165,22 +152,22 @@ public struct PageDefinition: Equatable, CustomStringConvertible, Hashable{ /// Effective margins of the page in a unit of length. public func effectiveMargins(unit:UnitLength)->NSEdgeInsets{ - let top = top < 0 ? top : max(top, header) - let bottom = bottom < 0 ? bottom : max(bottom, footer) - return NSEdgeInsets(top: Measurement(value: Double(top), unit: UnitLength.twips).converted(to: unit).value, left: Measurement(value: Double(left), unit: UnitLength.twips).converted(to: unit).value, bottom: Measurement(value: Double(bottom), unit: UnitLength.twips).converted(to: unit).value, right: Measurement(value: Double(right), unit: UnitLength.twips).converted(to: unit).value) + 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: Measurement(value: Double(top), unit: UnitLength.twips).converted(to: .points))), bottom: \(formatter.string(from: Measurement(value: Double(bottom), unit: UnitLength.twips).converted(to: .points))), left: \(formatter.string(from: Measurement(value: Double(left), unit: UnitLength.twips).converted(to: .points))), right: \(formatter.string(from: Measurement(value: Double(right), unit: UnitLength.twips).converted(to: .points))), footer: \(formatter.string(from: Measurement(value: Double(footer), unit: UnitLength.twips).converted(to: .points))), header: \(formatter.string(from: Measurement(value: Double(header), unit: UnitLength.twips).converted(to: .points)))\rTop \(formatter.string(from: Measurement(value: Double(top), unit: UnitLength.twips).converted(to: .centimeters))), bottom: \(formatter.string(from: Measurement(value: Double(bottom), unit: UnitLength.twips).converted(to: .centimeters))), left: \(formatter.string(from: Measurement(value: Double(left), unit: UnitLength.twips).converted(to: .centimeters))), right: \(formatter.string(from: Measurement(value: Double(right), unit: UnitLength.twips).converted(to: .centimeters))), footer: \(formatter.string(from: Measurement(value: Double(footer), unit: UnitLength.twips).converted(to: .centimeters))), header: \(formatter.string(from: Measurement(value: Double(header), unit: UnitLength.twips).converted(to: .centimeters)))" + 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 if a standard Word document. + /// 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(top), "w:right":String(right), "w:bottom":String(bottom), "w:left":String(left), "w:header":String(header), "w:footer":String(footer), "w:gutter":"0"]) + 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"]) } } @@ -194,6 +181,7 @@ public struct PageDefinition: Equatable, CustomStringConvertible, Hashable{ 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. @@ -221,11 +209,11 @@ public struct PageDefinition: Equatable, CustomStringConvertible, Hashable{ public extension UnitLength{ class var points:UnitLength{ - return UnitLength(symbol: "points", converter: UnitConverterLinear(coefficient: 1/100 / 28.3465)) + return UnitLength(symbol: "points", converter: UnitConverterLinear(coefficient: 1/100 / 72 * 2.54)) } class var twips:UnitLength{ - return UnitLength(symbol: "twips", converter: UnitConverterLinear(coefficient: 1/100 / 28.3465 / 20)) + return UnitLength(symbol: "twips", converter: UnitConverterLinear(coefficient: 1/100 / 72 * 2.54 / 20)) } } diff --git a/DocX/DocXWriter.swift b/DocX/DocXWriter.swift index 27a2cdc..c4415f6 100644 --- a/DocX/DocXWriter.swift +++ b/DocX/DocXWriter.swift @@ -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, From 890d78cf9bacae57c0cecc08e5e3a5aa2cbb16b1 Mon Sep 17 00:00:00 2001 From: Morten Bertz Date: Mon, 16 Jan 2023 16:42:45 +0900 Subject: [PATCH 3/5] an attempt at image autosizing --- DocX/DocX.swift | 2 +- DocX/DocXWriting.swift | 8 ++++---- DocX/ImageRelationship.swift | 10 ++++++++-- DocX/NSAttributedString+DocX-macOS.swift | 2 +- DocX/NSAttributedString+Writing.swift | 2 +- DocX/NSTextAttachement+Extensions.swift | 25 +++++++++++++++++++----- DocXTests/DocXTests.swift | 25 ++++++++++++++++++++++++ 7 files changed, 60 insertions(+), 14 deletions(-) diff --git a/DocX/DocX.swift b/DocX/DocX.swift index cb9b277..d1a601b 100644 --- a/DocX/DocX.swift +++ b/DocX/DocX.swift @@ -29,7 +29,7 @@ protocol DocX{ options:DocXOptions) throws ->String func writeDocX(to url:URL)throws func writeDocX(to url:URL, options:DocXOptions) throws - func prepareLinks(linkXML:AEXMLDocument, mediaURL:URL)->[DocumentRelationship] + func prepareLinks(linkXML:AEXMLDocument, mediaURL:URL, options:DocXOptions)->[DocumentRelationship] } public let docXUTIType="org.openxmlformats.wordprocessingml.document" diff --git a/DocX/DocXWriting.swift b/DocX/DocXWriting.swift index 1417e56..2299ebf 100644 --- a/DocX/DocXWriting.swift +++ b/DocX/DocXWriting.swift @@ -97,10 +97,10 @@ extension DocX where Self : NSAttributedString{ return lastIdIDX } - func prepareLinks(linkXML: AEXMLDocument, mediaURL:URL) -> [DocumentRelationship] { + func prepareLinks(linkXML: AEXMLDocument, mediaURL:URL, options:DocXOptions) -> [DocumentRelationship] { var linkURLS=[URL]() - let imageRelationships = prepareImages(linkXML: linkXML, mediaURL:mediaURL) + let imageRelationships = prepareImages(linkXML: linkXML, mediaURL:mediaURL, options: options) self.enumerateAttribute(.link, in: NSRange(location: 0, length: self.length), options: [.longestEffectiveRangeNotRequired], using: {attribute, _, stop in if let link=attribute as? URL{ @@ -124,7 +124,7 @@ extension DocX where Self : NSAttributedString{ return linkRelationShips + imageRelationships } - func prepareImages(linkXML: AEXMLDocument, mediaURL:URL) -> [DocumentRelationship]{ + func prepareImages(linkXML: AEXMLDocument, mediaURL:URL, options:DocXOptions) -> [DocumentRelationship]{ var attachements=[NSTextAttachment]() self.enumerateAttribute(.attachment, in: NSRange(location: 0, length: self.length), options: [.longestEffectiveRangeNotRequired], using: {attribute, _, stop in if let link=attribute as? NSTextAttachment{ @@ -173,7 +173,7 @@ extension DocX where Self : NSAttributedString{ return nil } - let relationShip=ImageRelationship(relationshipID: newID, linkURL: destURL, attachement: attachement) + let relationShip=ImageRelationship(relationshipID: newID, linkURL: destURL, attachement: attachement, pageDefinition: options.pageDefinition) return relationShip }) diff --git a/DocX/ImageRelationship.swift b/DocX/ImageRelationship.swift index 61214ba..750c245 100644 --- a/DocX/ImageRelationship.swift +++ b/DocX/ImageRelationship.swift @@ -19,6 +19,9 @@ struct ImageRelationship: DocumentRelationship{ let relationshipID:String let linkURL:URL let attachement:NSTextAttachment + let pageDefinition:PageDefinition? + + } extension ImageRelationship{ @@ -97,7 +100,9 @@ extension ImageRelationship{ let frameProperties=AEXMLElement(name: "wp:cNvGraphicFramePr") frameProperties.addChild(AEXMLElement(name: "a:graphicFrameLocks", value: nil, attributes: ["xmlns:a":"http://schemas.openxmlformats.org/drawingml/2006/main", "noChangeAspect":"1"])) - inline.addChildren(attachement.extentAttributes + [docPr, frameProperties, graphic]) + let extentAttributes=attachement.extentAttribute(pageSize: pageDefinition) + + inline.addChildren(extentAttributes + [docPr, frameProperties, graphic]) let graphicData=AEXMLElement(name: "a:graphicData", value: nil, attributes: ["uri":"http://schemas.openxmlformats.org/drawingml/2006/picture"]) @@ -126,7 +131,8 @@ extension ImageRelationship{ pic.addChild(shapeProperties) let xFrame=AEXMLElement(name: "a:xfrm") xFrame.addChild(AEXMLElement(name: "a:off", value: nil, attributes: ["x":"0","y":"0"])) - let extent=AEXMLElement(name: "a:ext", value: nil, attributes: self.attachement.extentInEMU.extentAttributes) + let extentInEmu=attachement.extentInEMU(pageSize: pageDefinition) + let extent=AEXMLElement(name: "a:ext", value: nil, attributes: extentInEmu.extentAttributes) xFrame.addChild(extent) shapeProperties.addChild(xFrame) diff --git a/DocX/NSAttributedString+DocX-macOS.swift b/DocX/NSAttributedString+DocX-macOS.swift index 46c4778..26e99ef 100644 --- a/DocX/NSAttributedString+DocX-macOS.swift +++ b/DocX/NSAttributedString+DocX-macOS.swift @@ -56,7 +56,7 @@ extension NSAttributedString{ options.escape = true let linkDocument=try AEXMLDocument(xml: linkData, options: options) - let linkRelations=self.prepareLinks(linkXML: linkDocument, mediaURL: mediaURL) + let linkRelations=self.prepareLinks(linkXML: linkDocument, mediaURL: mediaURL, options: DocXOptions()) let updatedLinks=linkDocument.xmlCompact try updatedLinks.write(to: linkURL, atomically: true, encoding: .utf8) diff --git a/DocX/NSAttributedString+Writing.swift b/DocX/NSAttributedString+Writing.swift index bed6aeb..fe6a679 100644 --- a/DocX/NSAttributedString+Writing.swift +++ b/DocX/NSAttributedString+Writing.swift @@ -67,7 +67,7 @@ extension NSAttributedString{ linkDocument.root.addChild(name: "Relationship", value: nil, attributes: attrs) } - let linkRelations=self.prepareLinks(linkXML: linkDocument, mediaURL: mediaURL) + let linkRelations=self.prepareLinks(linkXML: linkDocument, mediaURL: mediaURL, options: options) let updatedLinks=linkDocument.xmlCompact try updatedLinks.write(to: linkURL, atomically: true, encoding: .utf8) diff --git a/DocX/NSTextAttachement+Extensions.swift b/DocX/NSTextAttachement+Extensions.swift index 53236c5..e450444 100644 --- a/DocX/NSTextAttachement+Extensions.swift +++ b/DocX/NSTextAttachement+Extensions.swift @@ -68,7 +68,7 @@ extension NSTextAttachment{ } } - var extentInEMU:Size{ + func extentInEMU(pageSize:PageDefinition?) -> Size{ let size:CGSize if self.bounds != .zero{ size=self.bounds.size @@ -77,8 +77,23 @@ extension NSTextAttachment{ size=self.dataImageSize } - let width=size.width - let height=size.height + let width:CGFloat + let height:CGFloat + + + // we have a page size defined and the image is larger (in one dimension) than the page. we shrink the image to fit the printable area of the page. + if let bounds=pageSize?.printableSize(unit: .points), + (bounds.height < size.height || bounds.width < size.width) { + let ratio=min(bounds.height / size.height, bounds.width / size.width) + let scaledSize=size.applying(.init(scaleX: ratio, y: ratio)) + width=scaledSize.width + height=scaledSize.height + } + else{ + width=size.width + height=size.height + } + let emuPerInch=CGFloat(914400) let dpi=CGFloat(72) @@ -88,8 +103,8 @@ extension NSTextAttachment{ } - var extentAttributes:[AEXMLElement]{ - let size=self.extentInEMU + func extentAttribute(pageSize:PageDefinition?) -> [AEXMLElement]{ + let size=self.extentInEMU(pageSize: pageSize) let extent=size.extentAttribute let effectiveExtent=AEXMLElement(name: "wp:effectExtent", value: nil, attributes: ["l":"0", "t":"0","r":"0","b":"0"]) return [extent,effectiveExtent] diff --git a/DocXTests/DocXTests.swift b/DocXTests/DocXTests.swift index 6fad39f..aa21d59 100644 --- a/DocXTests/DocXTests.swift +++ b/DocXTests/DocXTests.swift @@ -790,6 +790,31 @@ let string = """ } + func testScaleImageToSize() throws{ + let loremIpsum = """ + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + """ + + let imageURL=try XCTUnwrap(bundle.url(forResource: "lenna", withExtension: "png"), "ImageURL not found") + let imageData=try XCTUnwrap(Data(contentsOf: imageURL), "Image not found") + let attachement=NSTextAttachment(data: imageData, ofType: kUTTypePNG as String) + + let text=NSMutableAttributedString() + text.append(NSAttributedString(string: loremIpsum, attributes: [.foregroundColor: NSColor.red])) + text.append(NSAttributedString(string: "\r")) + text.append(NSAttributedString(attachment: attachement)) + text.append(NSAttributedString(string: loremIpsum, attributes: [.foregroundColor: NSColor.black, .font: NSFont(name: "Helvetica", size: 19)!])) + + let defs = [PageDefinition(pageSize: .A4)] + + for def in defs{ + var options=DocXOptions() + options.pageDefinition=def + try writeAndValidateDocX(attributedString: text, options: options) + } + + } + // MARK: Performance Tests func testPerformanceLongBook() { From 1d20cb9305385c6dff02d118c17b721eb0b281c0 Mon Sep 17 00:00:00 2001 From: Morten Bertz Date: Thu, 19 Jan 2023 18:50:09 +0900 Subject: [PATCH 4/5] some refactoring and documentation for autosizing behaviour. We now respect user-defined sizes even if they are larger than the page. --- DocX/ImageRelationship.swift | 4 +-- DocX/NSTextAttachement+Extensions.swift | 33 +++++++++++++++++++------ DocXTests/DocXTests.swift | 1 + 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/DocX/ImageRelationship.swift b/DocX/ImageRelationship.swift index 750c245..a5b4d17 100644 --- a/DocX/ImageRelationship.swift +++ b/DocX/ImageRelationship.swift @@ -100,7 +100,7 @@ extension ImageRelationship{ let frameProperties=AEXMLElement(name: "wp:cNvGraphicFramePr") frameProperties.addChild(AEXMLElement(name: "a:graphicFrameLocks", value: nil, attributes: ["xmlns:a":"http://schemas.openxmlformats.org/drawingml/2006/main", "noChangeAspect":"1"])) - let extentAttributes=attachement.extentAttribute(pageSize: pageDefinition) + let extentAttributes=attachement.extentAttributes(pageSize: pageDefinition) inline.addChildren(extentAttributes + [docPr, frameProperties, graphic]) @@ -131,7 +131,7 @@ extension ImageRelationship{ pic.addChild(shapeProperties) let xFrame=AEXMLElement(name: "a:xfrm") xFrame.addChild(AEXMLElement(name: "a:off", value: nil, attributes: ["x":"0","y":"0"])) - let extentInEmu=attachement.extentInEMU(pageSize: pageDefinition) + let extentInEmu=attachement.extentInEMU(size: attachement.extent(for: pageDefinition)) let extent=AEXMLElement(name: "a:ext", value: nil, attributes: extentInEmu.extentAttributes) xFrame.addChild(extent) shapeProperties.addChild(xFrame) diff --git a/DocX/NSTextAttachement+Extensions.swift b/DocX/NSTextAttachement+Extensions.swift index e450444..a2edc1b 100644 --- a/DocX/NSTextAttachement+Extensions.swift +++ b/DocX/NSTextAttachement+Extensions.swift @@ -35,6 +35,12 @@ extension NSTextAttachment{ let width:Int let height:Int + init(cgSize:CGSize) { + self.height=Int(cgSize.height) + self.width=Int(cgSize.width) + } + + var extentAttribute:AEXMLElement{ return AEXMLElement(name: "wp:extent", value: nil, attributes: self.extentAttributes) } @@ -68,7 +74,13 @@ extension NSTextAttachment{ } } - func extentInEMU(pageSize:PageDefinition?) -> Size{ + /// Autosizing behavior of images. + /// + /// The default behaviour is: + /// - if an explicit `bounds` has been supplied for the `NSTextAttachement`, use this size (in points) + /// - if no `bounds` has been suplied (`bounds == CGRect.zero`), use the size of the supplied image (in points). + /// - if an optional `PageDefinition` is supplied and no explicit size for the `NSTextAttachement` has been set, scale the image to fit the page if it is larger than the printable area, otherwise do nothing. + func extent(for pageSize:PageDefinition?) -> CGSize{ let size:CGSize if self.bounds != .zero{ size=self.bounds.size @@ -82,7 +94,9 @@ extension NSTextAttachment{ // we have a page size defined and the image is larger (in one dimension) than the page. we shrink the image to fit the printable area of the page. + // If there is a user-defined size, we accept this even if it is too large. if let bounds=pageSize?.printableSize(unit: .points), + size == .zero, (bounds.height < size.height || bounds.width < size.width) { let ratio=min(bounds.height / size.height, bounds.width / size.width) let scaledSize=size.applying(.init(scaleX: ratio, y: ratio)) @@ -94,17 +108,20 @@ extension NSTextAttachment{ height=size.height } - + return CGSize(width: width, height: height) + } + + + func extentInEMU(size:CGSize) -> Size{ let emuPerInch=CGFloat(914400) let dpi=CGFloat(72) - let emuWidth=width/dpi*emuPerInch - let emuHeight=height/dpi*emuPerInch - return Size(width: Int(emuWidth), height: Int(emuHeight)) - + let emuSize=size.applying(.init(scaleX: emuPerInch / dpi, y: emuPerInch / dpi)) + return Size(cgSize: emuSize) } - func extentAttribute(pageSize:PageDefinition?) -> [AEXMLElement]{ - let size=self.extentInEMU(pageSize: pageSize) + + func extentAttributes(pageSize:PageDefinition?) -> [AEXMLElement]{ + let size=extentInEMU(size: extent(for: pageSize)) let extent=size.extentAttribute let effectiveExtent=AEXMLElement(name: "wp:effectExtent", value: nil, attributes: ["l":"0", "t":"0","r":"0","b":"0"]) return [extent,effectiveExtent] diff --git a/DocXTests/DocXTests.swift b/DocXTests/DocXTests.swift index aa21d59..79ca1c1 100644 --- a/DocXTests/DocXTests.swift +++ b/DocXTests/DocXTests.swift @@ -813,6 +813,7 @@ let string = """ try writeAndValidateDocX(attributedString: text, options: options) } + } // MARK: Performance Tests From 76e2ef8adc57a4f5c4ea2d6d39c4b8cc74afd968 Mon Sep 17 00:00:00 2001 From: Morten Bertz Date: Thu, 26 Jan 2023 21:09:04 +0900 Subject: [PATCH 5/5] some changes to PageDefinition to make things more cross-platform compatible --- DocX.xcodeproj/project.pbxproj | 12 ++++++++++++ .../xcshareddata/swiftpm/Package.resolved | 2 +- DocX/DocXPageDefinition.swift | 19 ++++++++++++++----- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/DocX.xcodeproj/project.pbxproj b/DocX.xcodeproj/project.pbxproj index b6c72a4..efd3814 100644 --- a/DocX.xcodeproj/project.pbxproj +++ b/DocX.xcodeproj/project.pbxproj @@ -26,6 +26,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, ); }; }; @@ -94,6 +98,8 @@ 5A8406B42609DF1B002B8B34 /* NSAttributedString+Writing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Writing.swift"; sourceTree = ""; }; 5A8406BB2609DF2B002B8B34 /* ImageRelationship.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRelationship.swift; sourceTree = ""; }; 5A8406CC2609DF65002B8B34 /* Bundle+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = ""; }; + 5A907B862982A35800E1A581 /* DocXPageDefinition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocXPageDefinition.swift; sourceTree = ""; }; + 5A907B872982A35800E1A581 /* DocXStyleConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocXStyleConfiguration.swift; sourceTree = ""; }; 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 = ""; }; 5A9F70A4223502D6008E967C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -193,6 +199,8 @@ 5A9F70A2223502D6008E967C /* DocX */ = { isa = PBXGroup; children = ( + 5A907B862982A35800E1A581 /* DocXPageDefinition.swift */, + 5A907B872982A35800E1A581 /* DocXStyleConfiguration.swift */, 5AE833F2223688A400E68343 /* blank */, 5A9F70BF22350300008E967C /* DocumentRoot.swift */, 5A9F70BC22350300008E967C /* DocX.swift */, @@ -444,12 +452,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 */, @@ -479,12 +489,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 */, diff --git a/DocX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DocX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 284d4f0..6fa15ae 100644 --- a/DocX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DocX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,7 +14,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/weichsel/ZIPFoundation.git", "state" : { - "revision" : "1b662e2e7a091710ad8a963263939984e2ebf527", + "revision" : "7254c74b49cec2cb81520523ba993c671f71b066", "version" : "0.9.14" } } diff --git a/DocX/DocXPageDefinition.swift b/DocX/DocXPageDefinition.swift index df83320..3d78d88 100644 --- a/DocX/DocXPageDefinition.swift +++ b/DocX/DocXPageDefinition.swift @@ -139,19 +139,25 @@ public struct PageDefinition: Equatable, CustomStringConvertible, Hashable{ 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) } -#endif - fileprivate var effectiveMargins:NSEdgeInsets{ - return effectiveMargins(unit: .points) + /// Effective margins of the page in a unit of length. + public func effectiveMargins(unit:UnitLength = .points)->UIEdgeInsets{ + return _effectiveMargins(unit: unit) } - /// Effective margins of the page in a unit of length. - public func effectiveMargins(unit:UnitLength)->NSEdgeInsets{ +#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) @@ -208,10 +214,13 @@ public struct PageDefinition: Equatable, CustomStringConvertible, Hashable{ 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)) }