diff --git a/.travis.yml b/.travis.yml
index 0322d53b..15aa4c4d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -76,7 +76,7 @@ jobs:
- bundle exec pod install --project-directory="${ROOT_FOLDER}"
- cd "${ROOT_FOLDER}"
script:
- - set -o pipefail
+ - set -o pipefail
- xcodebuild -workspace "$WORKSPACE"
-scheme "$EXAMPLE_SCHEME"
-sdk iphonesimulator
@@ -122,7 +122,7 @@ jobs:
-clonedSourcePackagesDirPath .
-derivedDataPath ${TRAVIS_BUILD_DIR}/derived_data
-configuration Debug | xcpretty
- - set -o pipefail
+ - set -o pipefail
- xcodebuild -project ${PROJECT}
-scheme ${EXAMPLE_SCHEME}
-clonedSourcePackagesDirPath .
@@ -146,7 +146,7 @@ jobs:
-clonedSourcePackagesDirPath .
-derivedDataPath ${TRAVIS_BUILD_DIR}/derived_data
-configuration Debug | xcpretty
- - set -o pipefail
+ - set -o pipefail
- xcodebuild -project ${PROJECT}
-scheme ${EXAMPLE_SCHEME}
-clonedSourcePackagesDirPath .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 37b74dfc..d3be1fcc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,12 +5,21 @@
**Implemented enhancements:**
+- Added optional table cell splicing disabling
+
**Fixed bugs:**
**Closed issues:**
+- Issue #205
+- Issue #222
+- Issue #243
+- Issue #249
+
**Merged pull requests:**
+- PR #223
+
## [2.3.1](https://github.com/techprimate/TPPDF/tree/2.3.1) (2020-09-23)
[Full Changelog](https://github.com/techprimate/TPPDF/compare/2.3.0...2.3.1)
diff --git a/Shared/Examples/ExperimentFactory.swift b/Shared/Examples/ExperimentFactory.swift
index 82799a05..58234932 100644
--- a/Shared/Examples/ExperimentFactory.swift
+++ b/Shared/Examples/ExperimentFactory.swift
@@ -19,6 +19,7 @@ class ExperimentFactory: ExampleFactory {
table.margin = 10
table.padding = 10
table.showHeadersOnEveryPage = false
+ table.shouldSplitCellsOnPageBreak = false
table.style.columnHeaderCount = 3
for row in 0..
PDFTableCalculatedCell {
- var frame: PDFTableCalculatedCell = (
+ var frame = PDFTableCalculatedCell(
cell: cell,
type: type,
style: style,
@@ -335,8 +330,18 @@ internal class PDFTableObject: PDFRenderObject {
minOffset += headerHeight
}
- var onPageCells: [PDFTableCalculatedCell]
- (onPageCells, nextPageCells) = filterCellsOnPage(for: generator, items: nextPageCells, minOffset: minOffset, maxOffset: maxOffset)
+ let filterResult = filterCellsOnPage(for: generator,
+ items: nextPageCells,
+ minOffset: minOffset,
+ maxOffset: maxOffset,
+ shouldSplitCellsOnPageBeak: table.shouldSplitCellsOnPageBreak)
+ let onPageCells = filterResult.cells
+ nextPageCells = filterResult.remainder
+ // If none of the cells fit on the current page, the algorithm will try again on the next page and if it occurs again, an error should be thrown
+ if onPageCells.isEmpty && !firstPage, let firstInvalidCell = nextPageCells.first {
+ throw PDFError.tableCellTooBig(cell: firstInvalidCell.cell)
+ }
+
for (idx, item) in onPageCells.enumerated() {
let cellFrame = item.frames.cell
@@ -392,29 +397,60 @@ internal class PDFTableObject: PDFRenderObject {
return (objects: result, offset: pageEnd.y)
}
- internal typealias FilteredCells = (cells: [PDFTableCalculatedCell], rest: [PDFTableCalculatedCell])
+ /// Holds two lists of cells, used during table calculations
+ internal struct FilteredCells {
+ /// List of calculated cells on the active page
+ var cells: [PDFTableCalculatedCell]
+ /// List of remaining cells on further pages
+ var remainder: [PDFTableCalculatedCell]
+ }
+
- internal func filterCellsOnPage(for generator: PDFGenerator, items: [PDFTableCalculatedCell], minOffset: CGFloat, maxOffset: CGFloat) -> FilteredCells {
+ /// Filters the given list of cells into the ones that fit on the current page, and all remainding cells, repositioned for the next page.
+ ///
+ /// - Parameters:
+ /// - generator: Active instance of `PDFGenerator`
+ /// - items: List of cells to filter
+ /// - minOffset: Minimum `y`-position on the page
+ /// - maxOffset: Maximum `y`-position on the page
+ /// - shouldSplitCellsOnPageBreak: If `true`, cells won't be sliced and shown on both pages, instead moved entirely to the next page
+ /// - Returns: Two lists of cells, see `FilteredCells`
+ internal func filterCellsOnPage(for generator: PDFGenerator, items: [PDFTableCalculatedCell], minOffset: CGFloat, maxOffset: CGFloat, shouldSplitCellsOnPageBeak: Bool) -> FilteredCells {
+ // Maximum height available
let contentHeight = maxOffset - minOffset
+ var result = FilteredCells(cells: [], remainder: [])
- var cells: [PDFTableCalculatedCell] = []
- var rest: [PDFTableCalculatedCell] = []
+ var offsetFix: CGFloat!
- for item in items {
+ // Iterate each cell and decide if it fits on current page or if it needs to be moved to the further pages
+ for item in items {
let cellFrame = item.frames.cell
- if cellFrame.maxY < maxOffset {
- cells.append(item)
+
+ // Cells needs to fit the current available space entirely
+ if cellFrame.maxY < maxOffset { // TODO: is the row padding relevant here?
+ result.cells.append(item)
} else {
- if cellFrame.minY < maxOffset {
- cells.append(item)
+ // If cells should be split and cell is partially on current page, add it to the cells, the cell will be sliced afterwards
+ if shouldSplitCellsOnPageBeak && 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
- nextPageCell.frames.cell.origin.y -= contentHeight
- nextPageCell.frames.content.origin.y -= contentHeight
- rest.append(nextPageCell)
+ if shouldSplitCellsOnPageBeak {
+ nextPageCell.frames.cell.origin.y -= contentHeight
+ nextPageCell.frames.content.origin.y -= contentHeight
+ } else {
+ let cellContentOffset = nextPageCell.frames.content.minY - nextPageCell.frames.cell.minY
+ if offsetFix == nil {
+ offsetFix = nextPageCell.frames.cell.minY - minOffset
+ }
+ nextPageCell.frames.cell.origin.y -= offsetFix
+ nextPageCell.frames.content.origin.y = nextPageCell.frames.cell.minY + cellContentOffset
+ }
+ result.remainder.append(nextPageCell)
}
}
- return (cells: cells, rest: rest)
+ return result
}
internal func createSliceObject(frame: CGRect, elements: [PDFRenderObject], minOffset: CGFloat, maxOffset: CGFloat) -> PDFSlicedObject {
@@ -430,9 +466,12 @@ internal class PDFTableObject: PDFRenderObject {
return sliceObject
}
- /**
- Creates a render object for the cell background
- */
+ /// Creates a render object for the cell background
+ ///
+ /// - Parameters:
+ /// - style: Style of table cell
+ /// - frame: Frame of cell
+ /// - Returns: Calculated `PDFRectangleObject`
internal func createCellBackgroundObject(style: PDFTableCellStyle, frame: CGRect) -> PDFRenderObject {
let object = PDFRectangleObject(lineStyle: .none, size: frame.size, fillColor: style.colors.fill)
object.frame = frame
@@ -500,14 +539,11 @@ internal class PDFTableObject: PDFRenderObject {
return .content
}
- /**
- Returns the style of a given cell, depending on the type.
-
- - parameters tableStyle: Style configuration of table
- - parameters type: Type of cell
-
- - returns: Style of cell
- */
+ /// Returns the style of a given cell, depending on the type.
+ /// - Parameters:
+ /// - tableStyle: Style configuration of table
+ /// - type: Type of cell
+ /// - Returns: Style of cell
internal func getStyle(tableStyle: PDFTableStyle, type: CellType) -> PDFTableCellStyle {
switch type {
case .header:
@@ -525,14 +561,11 @@ internal class PDFTableObject: PDFRenderObject {
}
}
- /**
- Creates four outline line objects around a given frame using the given style.
-
- - parameter borders: Style of each border direction
- - parameter frame: Frame of rectangle
-
- - returns: Array of `PDFLineObject`
- */
+ /// Creates four outline line objects around a given frame using the given style.
+ /// - Parameters:
+ /// - borders: Style of each border edge
+ /// - frame: Frame of rectangle
+ /// - Returns: Array of `PDFLineObject`
internal func createCellOutlineObjects(borders: PDFTableCellBorders, frame: CGRect) -> [PDFLineObject] {
[
PDFLineObject(style: borders.top,
@@ -550,9 +583,7 @@ internal class PDFTableObject: PDFRenderObject {
]
}
- /**
- Creates a new `PDFTableObject` with the same properties
- */
+ /// Creates a new `PDFTableObject` with the same properties
override internal var copy: PDFRenderObject {
PDFTableObject(table: table.copy)
}
diff --git a/TPPDF.xcodeproj/project.pbxproj b/TPPDF.xcodeproj/project.pbxproj
index f3369aa7..ab96d17f 100644
--- a/TPPDF.xcodeproj/project.pbxproj
+++ b/TPPDF.xcodeproj/project.pbxproj
@@ -161,6 +161,7 @@
D4803D3A24703E5300DDA039 /* PDFAttributedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4803C9F24703E5300DDA039 /* PDFAttributedText.swift */; };
D4803D3B24703E5300DDA039 /* PDFText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4803CA024703E5300DDA039 /* PDFText.swift */; };
D4803D3C24703E5300DDA039 /* PDFTextStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4803CA124703E5300DDA039 /* PDFTextStyle.swift */; };
+ D4839C39253706950005BB87 /* PDFTableCalculatedCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4839C38253706950005BB87 /* PDFTableCalculatedCell.swift */; };
D4EE2F9724A34C990004E3B9 /* PDFContextGraphics.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4EE2F9624A34C990004E3B9 /* PDFContextGraphics.swift */; };
D4EE2F9924A34CBF0004E3B9 /* CrossPlattformGraphics.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4EE2F9824A34CBF0004E3B9 /* CrossPlattformGraphics.swift */; };
OBJ_419 /* AdapterProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_331 /* AdapterProtocols.swift */; };
@@ -621,6 +622,7 @@
D4803C9F24703E5300DDA039 /* PDFAttributedText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PDFAttributedText.swift; sourceTree = ""; };
D4803CA024703E5300DDA039 /* PDFText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PDFText.swift; sourceTree = ""; };
D4803CA124703E5300DDA039 /* PDFTextStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PDFTextStyle.swift; sourceTree = ""; };
+ D4839C38253706950005BB87 /* PDFTableCalculatedCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PDFTableCalculatedCell.swift; sourceTree = ""; };
D4EE2F9624A34C990004E3B9 /* PDFContextGraphics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PDFContextGraphics.swift; sourceTree = ""; };
D4EE2F9824A34CBF0004E3B9 /* CrossPlattformGraphics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrossPlattformGraphics.swift; sourceTree = ""; };
"Nimble::Nimble::Product" /* Nimble.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Nimble.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1054,6 +1056,7 @@
D4803C3724703E5300DDA039 /* Table */ = {
isa = PBXGroup;
children = (
+ D4839C38253706950005BB87 /* PDFTableCalculatedCell.swift */,
D4803C3824703E5300DDA039 /* PDFTableMergeUtil.swift */,
D4803C3924703E5300DDA039 /* PDFTableNode.swift */,
D4803C3A24703E5300DDA039 /* PDFTableObject.swift */,
@@ -2265,6 +2268,7 @@
D4803D1124703E5300DDA039 /* PDFBezierPath.swift in Sources */,
D4803D3A24703E5300DDA039 /* PDFAttributedText.swift in Sources */,
D4803D2224703E5300DDA039 /* PDFTable+RowSubscripts.swift in Sources */,
+ D4839C39253706950005BB87 /* PDFTableCalculatedCell.swift in Sources */,
D4803CF924703E5300DDA039 /* PDFSection.swift in Sources */,
D4803CB924703E5300DDA039 /* PDFPaginationStyle+Equatable.swift in Sources */,
D4803CD124703E5300DDA039 /* PDFRectangleObject.swift in Sources */,
diff --git a/Tests/TPPDFTests/Internal/Table/PDFTableObjectSpec.swift b/Tests/TPPDFTests/Internal/Table/PDFTableObjectSpec.swift
new file mode 100644
index 00000000..32fb5a7b
--- /dev/null
+++ b/Tests/TPPDFTests/Internal/Table/PDFTableObjectSpec.swift
@@ -0,0 +1,181 @@
+import CoreGraphics
+import Quick
+import Nimble
+@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.shouldSplitCellsOnPageBeak = false
+ table.style.columnHeaderCount = 3
+
+ for row in 0.. [CGRect] in
+ (0..<4).map { col -> CGRect in
+ CGRect(x: [70, 117.5, 260, 402.5][col],
+ y: 85 + CGFloat(row) * 47,
+ width: [27.5, 122.5, 122.5, 122.5][col],
+ height: row >= 10 ? 48 : 37)
+ }
+ })
+ let frames_11_13: [[CGRect]] = (11..<14)
+ .map ({ row -> [CGRect] in
+ (0..<4).map { col -> CGRect in
+ CGRect(x: [70, 117.5, 260, 402.5][col],
+ y: 613 + CGFloat(row - 11) * 58,
+ width: [27.5, 122.5, 122.5, 122.5][col],
+ height: 48)
+ }
+ })
+ let frames_13_25: [[CGRect]] = (14..<26)
+ .map ({ row -> [CGRect] in
+ (0..<4).map { col -> CGRect in
+ CGRect(x: [70, 117.5, 260, 402.5][col],
+ y: 60 + CGFloat(row - 14) * 58,
+ width: [27.5, 122.5, 122.5, 122.5][col],
+ height: 48)
+ }
+ })
+ let frames_25_37: [[CGRect]] = (26..<39)
+ .map ({ row -> [CGRect] in
+ (0..<4).map { col -> CGRect in
+ CGRect(x: [70, 117.5, 260, 402.5][col],
+ y: 60 + CGFloat(row - 26) * 58,
+ width: [27.5, 122.5, 122.5, 122.5][col],
+ height: 48)
+ }
+ })
+ let frames_37_40: [[CGRect]] = (38..<40)
+ .map ({ row -> [CGRect] in
+ (0..<4).map { col -> CGRect in
+ CGRect(x: [70, 117.5, 260, 402.5][col],
+ y: 60 + CGFloat(row - 38) * 58,
+ width: [27.5, 122.5, 122.5, 122.5][col],
+ height: 48)
+ }
+ })
+
+ // test cells on first page
+ for row in 0..<14 {
+ for column in 0..<4 {
+ context("cell \(row) \(column)") {
+ let locatedCell = result[row * columns + column]
+
+ it("should be in the correct container") {
+ expect(locatedCell.0) == container
+ }
+
+ it("should have correct frame") {
+ let expectedFrames = frames_0_10 + frames_11_13
+ expect(locatedCell.1.frame) == expectedFrames[row][column]
+ }
+ }
+ }
+ }
+
+ // test cells on second page
+ for row in 14..<26 {
+ for column in 0..<4 {
+ context("cell \(row) \(column)") {
+ let locatedCell = result[1 + (row * columns + column)]
+
+ it("should be in the correct container") {
+ expect(locatedCell.0) == container
+ }
+
+ it("should have correct frame") {
+ expect(locatedCell.1.frame) == frames_13_25[row - 14][column]
+ }
+ }
+ }
+ }
+
+ // test cells on third page
+ for row in 26..<38 {
+ for column in 0..<4 {
+ context("cell \(row) \(column)") {
+ let locatedCell = result[2 + (row * columns + column)]
+
+ it("should be in the correct container") {
+ expect(locatedCell.0) == container
+ }
+
+ it("should have correct frame") {
+ expect(locatedCell.1.frame) == frames_25_37[row - 26][column]
+ }
+ }
+ }
+ }
+
+ // test cells on fourth page
+ for row in 38..<40 {
+ for column in 0..<4 {
+ context("cell \(row) \(column)") {
+ let locatedCell = result[3 + row * columns + column]
+
+ it("should be in the correct container") {
+ expect(locatedCell.0) == container
+ }
+
+ it("should have correct frame") {
+ expect(locatedCell.1.frame) == frames_37_40[row - 38][column]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ #endif
+ }
+}