diff --git a/Documentation/Usage.md b/Documentation/Usage.md index 82034d8c..046d03fa 100644 --- a/Documentation/Usage.md +++ b/Documentation/Usage.md @@ -95,6 +95,9 @@ All values are in dots and are rendered using 72 DPI (dots per inch), as this is You can also used the predefined formats. For details please refer to the source file [PDFPageFormat.swift](https://github.com/techprimate/TPPDF/blob/master/Source/PDFPageFormat.swift) +Keep in mind that the `space.header` is only applied, if there is at least one element in a header container. +The same applies to the `space.footer` for footer containers and elements. + If you need your page in landscape format, use the `landscapeSize` variable.  @@ -820,4 +823,4 @@ If you want to enable a debug overlay, set the flag `debug` of the `PDFGenerator let document: PDFDocument let generator = PDFDocumentGenerator(document: document) generator.debug = true -``` \ No newline at end of file +``` diff --git a/Source/Internal/Graphics/PDFContext.swift b/Source/Internal/Graphics/PDFContext.swift index 2f3fdfc0..e060248a 100644 --- a/Source/Internal/Graphics/PDFContext.swift +++ b/Source/Internal/Graphics/PDFContext.swift @@ -32,21 +32,18 @@ public class PDFContext { internal func beginPDFPage(_ pageInfo: CFDictionary?) { // Do not create page immediately, instead invoke it as soon as necessary - print("beginPDFPage") delayedCommands.append(.beginPDFPage(pageConfig: pageInfo)) currentPageContainsDrawnContent = false hasActivePage = true } internal func endPDFPage() { - print("endPDFPage") applyDelayedCommands() cgContext.endPDFPage() hasActivePage = false } internal func closePDF() { - print("closePDFPage") applyDelayedCommands() cgContext.closePDF() hasActivePage = false @@ -61,33 +58,28 @@ public class PDFContext { // MARK: - Translation internal func translateBy(x: CGFloat, y: CGFloat) { - print("translateBy") delayedCommands.append(.translateBy(x: x, y: y)) } internal func scaleBy(x: CGFloat, y: CGFloat) { - print("scaleBy") delayedCommands.append(.scaleBy(x: x, y: y)) } // MARK: - Drawing internal func drawPath(using mode: CGPathDrawingMode) { - print("drawPath") applyDelayedCommands() cgContext.drawPath(using: mode) currentPageContainsDrawnContent = true } internal func drawPDFPage(_ page: CGPDFPage) { - print("drawPDFPage") applyDelayedCommands() cgContext.drawPDFPage(page) currentPageContainsDrawnContent = true } internal func draw(image: CGImage, in frame: CGRect, flipped: Bool) { - print("draw(image:)") applyDelayedCommands() cgContext.draw(image: image, in: frame, flipped: flipped) currentPageContainsDrawnContent = true @@ -96,7 +88,6 @@ public class PDFContext { // MARK: - Colors internal func setFillColor(_ color: CGColor) { - print("setFillColor") applyDelayedCommands() cgContext.setFillColor(color) currentPageContainsDrawnContent = true @@ -105,42 +96,36 @@ public class PDFContext { // MARK: - Paths internal func beginPath() { - print("beginPath") applyDelayedCommands() cgContext.beginPath() currentPageContainsDrawnContent = true } internal func addPath(_ path: CGPath) { - print("addPath") applyDelayedCommands() cgContext.addPath(path) currentPageContainsDrawnContent = true } internal func setLineDash(phase: CGFloat, lengths: [CGFloat]) { - print("setLineDash") applyDelayedCommands() cgContext.setLineDash(phase: phase, lengths: lengths) currentPageContainsDrawnContent = true } internal func setLineCap(_ cap: CGLineCap) { - print("setLineCap") applyDelayedCommands() cgContext.setLineCap(cap) currentPageContainsDrawnContent = true } internal func setLineWidth(_ width: CGFloat) { - print("setLineWidth") applyDelayedCommands() cgContext.setLineWidth(width) currentPageContainsDrawnContent = true } internal func setStrokeColor(_ color: CGColor) { - print("setStrokeColor") applyDelayedCommands() cgContext.setStrokeColor(color) currentPageContainsDrawnContent = true @@ -149,13 +134,11 @@ public class PDFContext { // MARK: - State internal func saveGState() { - print("saveGState") applyDelayedCommands() cgContext.saveGState() } internal func restoreGState() { - print("restoreGState") applyDelayedCommands() cgContext.restoreGState() } @@ -172,7 +155,6 @@ public class PDFContext { } internal func draw(ctFrame frameRef: CTFrame) { - print("draw(ctFrame:)") applyDelayedCommands() CTFrameDraw(frameRef, cgContext) currentPageContainsDrawnContent = true @@ -181,7 +163,6 @@ public class PDFContext { // MARK: - Masking internal func clip() { - print("clip") applyDelayedCommands() cgContext.clip() currentPageContainsDrawnContent = true @@ -190,7 +171,6 @@ public class PDFContext { // MARK: - Metadata internal func setURL(_ url: CFURL, for rect: CGRect) { - print("setURL") applyDelayedCommands() cgContext.setURL(url, for: rect) currentPageContainsDrawnContent = true diff --git a/Source/Internal/Table/PDFTableObject.swift b/Source/Internal/Table/PDFTableObject.swift index 7a631307..9d4290c7 100644 --- a/Source/Internal/Table/PDFTableObject.swift +++ b/Source/Internal/Table/PDFTableObject.swift @@ -284,11 +284,15 @@ internal class PDFTableObject: PDFRenderObject { let startPosition: CGPoint = cells.first?.frames.cell.origin ?? .zero var nextPageCells: [PDFTableCalculatedCell] = cells var pageEnd = CGPoint.null + var headerShift = table.showHeadersOnEveryPage + repeat { var pageStart = CGPoint.null + // Calculate top page inset var minOffset = PDFCalculations.calculateTopMinimum(for: generator) + // Calculate bottom page maximum limit let maxOffset = PDFCalculations.calculateBottomMaximum(for: generator) if !firstPage, let headerCells = headerCells { @@ -296,7 +300,9 @@ internal class PDFTableObject: PDFRenderObject { var cellFrame = item.frames.cell var contentFrame = item.frames.content cellFrame.origin.y -= startPosition.y - minOffset + cellFrame.origin.y += table.margin contentFrame.origin.y -= startPosition.y - minOffset + contentFrame.origin.y += table.margin pageStart = pageStart == .null ? cellFrame.origin : pageStart pageEnd = CGPoint(x: cellFrame.maxX, y: cellFrame.maxY) + CGPoint(x: table.margin, y: table.margin) @@ -329,11 +335,18 @@ internal class PDFTableObject: PDFRenderObject { } minOffset += headerHeight } + if !firstPage { + // shift the rest of the cells down by headerHeight + if headerShift { + nextPageCells = shiftCellsBy(cells: nextPageCells, shiftValue: headerHeight) + headerShift = false + } + //add table padding around cells + nextPageCells = shiftCellsBy(cells: nextPageCells, shiftValue: table.margin) + } - let filterResult = filterCellsOnPage(for: generator, - items: nextPageCells, - minOffset: minOffset, - maxOffset: maxOffset, + let filterResult = filterCellsOnPage(for: generator, items: nextPageCells, + minOffset: minOffset, maxOffset: maxOffset, shouldSplitCellsOnPageBreak: table.shouldSplitCellsOnPageBreak) let onPageCells = filterResult.cells nextPageCells = filterResult.remainder @@ -432,7 +445,6 @@ internal class PDFTableObject: PDFRenderObject { if shouldSplitCellsOnPageBreak && cellFrame.minY < maxOffset { result.cells.append(item) } - // In any case, if the cell does not fit on the active page entirely, it must be repositioned for further pages var nextPageCell = item if shouldSplitCellsOnPageBreak { nextPageCell.frames.cell.origin.y -= contentHeight @@ -450,7 +462,22 @@ internal class PDFTableObject: PDFRenderObject { } return result } - + + internal typealias ShiftedCells = [PDFTableCalculatedCell] + + internal func shiftCellsBy(cells: [PDFTableCalculatedCell], shiftValue: CGFloat) -> ShiftedCells { + var shiftedCells: [PDFTableCalculatedCell] = [] + + for cell in cells { + var shiftedCell = cell + + shiftedCell.frames.cell.origin.y += shiftValue + shiftedCell.frames.content.origin.y += shiftValue + shiftedCells.append(shiftedCell) + } + return shiftedCells + } + internal func createSliceObject(frame: CGRect, elements: [PDFRenderObject], minOffset: CGFloat, maxOffset: CGFloat) -> PDFSlicedObject { let sliceObject = PDFSlicedObject(children: elements, frame: frame) if frame.maxY > maxOffset { diff --git a/Source/Internal/Utils/PDFCalculations.swift b/Source/Internal/Utils/PDFCalculations.swift index aaff5eed..99dc8e77 100644 --- a/Source/Internal/Utils/PDFCalculations.swift +++ b/Source/Internal/Utils/PDFCalculations.swift @@ -125,36 +125,74 @@ internal enum PDFCalculations { - generator.currentPadding.right } - /** - Calculates the available height in a given `container` on the current page. - If the container is a header or a footer container, it has no limits and therefore returns the full page layout height - - - parameter generator: Generator used for calculations - - parameter container: Container in question - - returns: Available height in points - */ + /// Calculates the available height in a given `container` on the current page. + /// If the container is a header or a footer container, it has no limits and therefore returns the full page layout height + /// + /// ┏━━━━━━━━━━━━━━━━┓ + /// ┃ top margin ┃ + /// ┠┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┨ + /// ┃ header ┃ + /// ┠┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┨ + /// ┃ header spacing ┃ + /// ┠┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┨ + /// --> ┃┌──────────────┐┃ <-- top minimum + /// ↕︎ ┃│ Group │┃ + /// --> ┃└──────────────┘┃ <-- bottom maximum + /// ┠┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┨ + /// ┃ footer spacing ┃ + /// ┠┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┨ + /// ┃ footer ┃ + /// ┠┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┨ + /// ┃ bottom margin ┃ + /// ┗━━━━━━━━━━━━━━━━┛ + /// + /// - parameter generator: Generator used for calculations + /// - parameter container: Container in question + /// - returns: Available height in points internal static func calculateAvailableFrameHeight(for generator: PDFGenerator, in container: PDFContainer) -> CGFloat { - let layout = generator.layout let pageLayout = generator.document.layout if container.isHeader || container.isFooter { return pageLayout.height - } else { - return pageLayout.height - - layout.margin.top - - layout.heights.maxHeaderHeight() - - layout.heights.content - - generator.currentPadding.bottom - - layout.heights.maxFooterHeight() - - layout.margin.bottom } + return calculateBottomMaximum(for: generator) - calculateTopMinimum(for: generator) } + /// Calculates the minimum offset from the top edge where content should start + /// + /// This method calculates the limit by the following formula: + /// + /// top margin + /// + header height + /// + header spacing (if header exists) + /// + padding + /// ----------------------------------- + /// offset from top edge + /// + /// --- ┏━━━━━━━━━━━━━━━━┓ + /// ↑ ┃ top margin ┃ + /// ┠┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┨ + /// ┃ header ┃ + /// ┠┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┨ + /// ┃ header spacing ┃ + /// ↓ ┠┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┨ + /// --> ┃┌──────────────┐┃ + /// ┃│ Group │┃ + /// ┃└──────────────┘┃ + /// ┠┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┨ + /// + /// - Parameter generator: Generator currently in use holding information about the document + /// - Returns: Offset from top edge in points internal static func calculateTopMinimum(for generator: PDFGenerator) -> CGFloat { let layout = generator.layout + let pageLayout = generator.document.layout + let headerHeight = layout.heights.maxHeaderHeight() + return layout.margin.top - + layout.heights.maxHeaderHeight() + + headerHeight + + (headerHeight > 0 ? pageLayout.space.header : 0) + + generator.currentPadding.top } /// Calculates the maximum offset from the top edge when the main content should break to the next page @@ -169,7 +207,7 @@ internal enum PDFCalculations { /// ----------------------------------- /// offset from top edge /// - /// --- ┏━━━━━━━━━━┓ + /// --- ┠┄┄┄┄┄┄┄┄┄┄┨ /// ↑ ┃┌────────┐┃ /// ↓ ┃│ Group │┃ /// --> ┃└────────┘┃ @@ -250,9 +288,33 @@ internal enum PDFCalculations { } } - /** - TODO: Documentation - */ + /// Calculates the offset from the top edge where content should start in the given container + /// + /// This method calculates the limit by the following formula: + /// + /// top margin + /// + header height + /// + header spacing (if footer exists) + /// + padding + /// ----------------------------------- + /// offset from top edge + /// + /// --- ┏━━━━━━━━━━━━━━━━━━━━┓ + /// ↑ ┊ ┊ + /// ┃┌──────────────────┐┃ + /// ┃│ Previous Content │┃ + /// ↓ ┃└──────────────────┘┃ + /// --> ┃┌──────────────────┐┃ + /// ┃│ Next Element │┃ + /// ┃└──────────────────┘┃ + /// + /// - Parameter generator: Generator currently in use holding information about the document + /// - Returns: Offset from top edge in points + /// - Parameters: + /// - generator: Active generator + /// - container: Container where element is placed + /// - size: Size of element + /// - Returns: Offset from the top edge private static func calculatePositionY(for generator: PDFGenerator, in container: PDFContainer, with size: CGSize) -> CGFloat { let layout = generator.layout let pageLayout = generator.document.layout @@ -266,10 +328,7 @@ internal enum PDFCalculations { - layout.heights.value(for: container) - size.height } else { - return layout.margin.top - + layout.heights.maxHeaderHeight() - + pageLayout.space.header - + layout.heights.content + return calculateTopMinimum(for: generator) + layout.heights.content } } @@ -289,12 +348,8 @@ internal enum PDFCalculations { - pageLayout.margin.bottom - layout.heights.value(for: container) - element.frame.height) - } else { - return element.frame.minY - - pageLayout.margin.top - - layout.heights.maxHeaderHeight() - - pageLayout.space.header } + return element.frame.minY - calculateTopMinimum(for: generator) } /** @@ -312,12 +367,8 @@ internal enum PDFCalculations { - (pageLayout.height - pageLayout.margin.bottom - layout.heights.value(for: container)) - } else { - return offset - - pageLayout.margin.top - - layout.heights.maxHeaderHeight() - - pageLayout.space.header } + return offset - calculateTopMinimum(for: generator) } // MARK: - LEGACY diff --git a/Tests/TPPDFTests/Internal/Table/PDFTableObjectSpec.swift b/Tests/TPPDFTests/Internal/Table/PDFTableObjectSpec.swift index 509bc131..f4490d0d 100644 --- a/Tests/TPPDFTests/Internal/Table/PDFTableObjectSpec.swift +++ b/Tests/TPPDFTests/Internal/Table/PDFTableObjectSpec.swift @@ -1,181 +1,241 @@ import CoreGraphics +import Foundation import Quick import Nimble +import XCTest @testable import TPPDF -class PDFTableObjectSpec: QuickSpec { - - override func spec() { - // Let's not test on macOS, as small changes in the font messes up all values - #if os(iOS) - describe("PDFTableObject") { - describe("calculation result frames") { - context("unmerged cells on multiple pages without splicing and no table headers on every page") { - let container = PDFContainer.contentLeft - - let rows = 40 - let columns = 4 - let count = rows * columns - let table = PDFTable(rows: rows, columns: columns) - table.widths = [0.1, 0.3, 0.3, 0.3] - table.margin = 10 - table.padding = 10 - table.showHeadersOnEveryPage = false - table.shouldSplitCellsOnPageBreak = false - table.style.columnHeaderCount = 3 - - for row in 0..
Redirecting…
+ + + +