TPPDF
TPPDF copied to clipboard
Adding a header image and a PDFTable in the document header cuts off the top of the table
ℹ Please fill out this template when filing an issue. All lines beginning with an ℹ symbol instruct you with what info we expect. You can delete those lines once you've filled in the info.
What did you do?
I added a banner image to the document's .headerLeft
and a table in the .headerLeft
. The table has an Image to the left and Text to the right of the image.
document.add(.headerLeft, image: bannerPDFImage)
document.add(.headerLeft, table: headerTable)
What did you expect to happen?
I expected to see the full image and text beneath the banner image. Instead, there's a gap between the banner image and table below, with the top of the table cut off.
What happened instead?

TPPDF Environment
TPPDF version: 2.5.3 Xcode version: 14.2 Swift version: 5.7
Demo Code / Project
From the TableExampleFactory.swift file:
//
// TableExampleFactory.swift
// TPPDF_Example
//
// Created by Philip Niedertscheider on 16.12.19.
// Copyright © 2022 techprimate GmbH. All rights reserved.
//
#if os(iOS)
import UIKit
#elseif os(macOS)
import AppKit
#endif
import TPPDF
class TableExampleFactory: ExampleFactory {
func generateDocument() -> [PDFDocument] {
let document = PDFDocument(format: .a4)
let headerStyle = PDFTableStyleDefaults.none
let headerTable = PDFTable(rows: 1, columns: 2)
headerTable.widths = [0.1, 0.9]
headerTable.style = headerStyle
var tableContent : [[PDFTableContentable]] = [[PDFTableContentable]]()
var rowContent : [PDFTableContentable] = [PDFTableContentable]()
if #available(macOS 12.0, *) {
let bannerImage = Image(named: "Business Logo Banner.jpg")
if let bannerImage = bannerImage {
let bannerPDFImage = PDFImage(image: bannerImage)
document.add(.headerLeft, image: bannerPDFImage)
}
var formIcon = Image(systemSymbolName: "doc.plaintext", accessibilityDescription: "")
formIcon?.resizingMode = .tile
var config = NSImage.SymbolConfiguration(textStyle: .largeTitle,
scale: .large)
config = config.applying(NSImage.SymbolConfiguration.preferringMulticolor())
formIcon = formIcon?.withSymbolConfiguration(config)
if let formIcon = formIcon {
rowContent.append(formIcon)
} else {
rowContent.append("")
}
headerStyle.contentStyle = PDFTableCellStyle(
borders: PDFTableCellBorders(left: PDFLineStyle(type: .none),
top: PDFLineStyle(type: .none),
right: PDFLineStyle(type: .none),
bottom: PDFLineStyle(type: .none)),
font: Font.systemFont(ofSize: 28, weight: .light)
)
rowContent.append("Test Header Text Content")
tableContent.append(rowContent)
headerTable.content = tableContent
headerTable.rows.allCellsAlignment = .left
document.add(.headerLeft, table: headerTable)
} else {
// Fallback on earlier versions
}
// Create a table
var table = PDFTable(rows: 34, columns: 4)
// Tables can contain Strings, Numbers, Images or nil, in case you need an empty cell.
// If you add a unknown content type, an assertion will be thrown and the rendering will stop.
table.content = [
[nil, "Name", "Image", "Description"],
[1, "Waterfall", Image(named: "Image-1.jpg")!, "Water flowing down stones."],
[2, "Forrest", Image(named: "Image-2.jpg")!, "Sunlight shining through the leafs."],
[3, "Fireworks", Image(named: "Image-3.jpg")!, "Fireworks exploding into 100.000 stars"],
[4, "Fields", Image(named: "Image-4.jpg")!, "Crops growing big and providing food."],
[1, "Waterfall", Image(named: "Image-1.jpg")!, "Water flowing down stones."],
[2, "Forrest", Image(named: "Image-2.jpg")!, "Sunlight shining through the leafs."],
[3, "Fireworks", Image(named: "Image-3.jpg")!, "Fireworks exploding into 100.000 stars"],
[4, "Fields", Image(named: "Image-4.jpg")!, "Crops growing big and providing food."],
[1, "Waterfall", Image(named: "Image-1.jpg")!, "Water flowing down stones."],
[2, "Forrest", Image(named: "Image-2.jpg")!, "Sunlight shining through the leafs."],
[3, "Fireworks", Image(named: "Image-3.jpg")!, "Fireworks exploding into 100.000 stars"],
[4, "Fields", Image(named: "Image-4.jpg")!, "Crops growing big and providing food."],
[1, "Waterfall", Image(named: "Image-1.jpg")!, "Water flowing down stones."],
[2, "Forrest", Image(named: "Image-2.jpg")!, "Sunlight shining through the leafs."],
[3, "Fireworks", Image(named: "Image-3.jpg")!, "Fireworks exploding into 100.000 stars"],
[4, "Fields", Image(named: "Image-4.jpg")!, "Crops growing big and providing food."],
[1, "Waterfall", Image(named: "Image-1.jpg")!, "Water flowing down stones."],
[2, "Forrest", Image(named: "Image-2.jpg")!, "Sunlight shining through the leafs."],
[3, "Fireworks", Image(named: "Image-3.jpg")!, "Fireworks exploding into 100.000 stars"],
[4, "Fields", Image(named: "Image-4.jpg")!, "Crops growing big and providing food."],
[1, "Waterfall", Image(named: "Image-1.jpg")!, "Water flowing down stones."],
[2, "Forrest", Image(named: "Image-2.jpg")!, "Sunlight shining through the leafs."],
[3, "Fireworks", Image(named: "Image-3.jpg")!, "Fireworks exploding into 100.000 stars"],
[4, "Fields", Image(named: "Image-4.jpg")!, "Crops growing big and providing food."],
[1, "Waterfall", Image(named: "Image-1.jpg")!, "Water flowing down stones."],
[2, "Forrest", Image(named: "Image-2.jpg")!, "Sunlight shining through the leafs."],
[3, "Fireworks", Image(named: "Image-3.jpg")!, "Fireworks exploding into 100.000 stars"],
[4, "Fields", Image(named: "Image-4.jpg")!, "Crops growing big and providing food."],
[1, "Waterfall", Image(named: "Image-1.jpg")!, "Water flowing down stones."],
[2, "Forrest", Image(named: "Image-2.jpg")!, "Sunlight shining through the leafs."],
[3, "Fireworks", Image(named: "Image-3.jpg")!, "Fireworks exploding into 100.000 stars"],
[4, "Fields", Image(named: "Image-4.jpg")!, "Crops growing big and providing food."],
[nil, nil, nil, "Many beautiful places"]
]
table.rows.allRowsAlignment = [.center, .left, .center, .right]
// The widths of each column is proportional to the total width, set by a value between 0.0 and 1.0, representing percentage.
table.widths = [
0.1, 0.25, 0.35, 0.3
]
// To speed up table styling, use a default and change it
let style = PDFTableStyleDefaults.simple
// Change standardized styles
style.footerStyle = PDFTableCellStyle(
colors: (
fill: Color(red: 0.171875,
green: 0.2421875,
blue: 0.3125,
alpha: 1.0),
text: Color.white
),
borders: PDFTableCellBorders(left: PDFLineStyle(type: .full),
top: PDFLineStyle(type: .full),
right: PDFLineStyle(type: .full),
bottom: PDFLineStyle(type: .full)),
font: Font.systemFont(ofSize: 10)
)
// Simply set the amount of footer and header rows
style.columnHeaderCount = 1
style.footerCount = 1
table.style = style
// Style each cell individually
table[1,1].style = PDFTableCellStyle(colors: (fill: Color.yellow, text: Color.black))
// Set table padding and margin
table.padding = 5.0
table.margin = 10.0
// In case of a linebreak during rendering we want to have table headers on each page.
table.showHeadersOnEveryPage = true
document.add(table: table)
// Another table:
table = PDFTable(rows: 50, columns: 4)
table.widths = [0.1, 0.3, 0.3, 0.3]
table.margin = 10
table.padding = 10
table.showHeadersOnEveryPage = false
table.style.columnHeaderCount = 3
for row in 0..<table.size.rows {
table[row, 0].content = "\(row)".asTableContent
for column in 1..<table.size.columns {
table[row, column].content = "\(row),\(column)".asTableContent
}
}
for i in stride(from: 3, to: 48, by: 3) {
table[rows: i...(i + 2), column: 1].merge(with: PDFTableCell(content: Array(repeating: "\(i),1", count: 3).joined(separator: "\n").asTableContent,
alignment: .center))
}
for i in stride(from: 4, to: 47, by: 3) {
table[rows: i...(i + 2), column: 2].merge(with: PDFTableCell(content: Array(repeating: "\(i),2", count: 3).joined(separator: "\n").asTableContent,
alignment: .center))
}
for i in stride(from: 5, to: 48, by: 3) {
table[rows: i...(i + 2), column: 3].merge(with: PDFTableCell(content: Array(repeating: "\(i),3", count: 3).joined(separator: "\n").asTableContent,
alignment: .center))
}
table[rows: 0..<2, column: 2].merge()
table[rows: 1..<3, column: 3].merge()
document.add(table: table)
return [document]
}
}
and here's the banner image I added to the project for testing purposes.
So I discovered if I make a small change to the TPPDF framework code, the table in the header lines up. I still don't know what's causing the gap between the header banner image and the table in the header. I would think there should be no gap and the whole header should be a bit shorter.
On line 62 of PDFTableObject.swift
, I added 10 to that line's y
parameter:
let origin = CGPoint(x: tableOrigin.x + originX, y: verticalOrigins[node.position.row] + 10)

I don't know why it solves the problem at the moment, but it does. I still think the position of the table is too low. Or at least I should be able to control the gap between the table and the header image.
Well, although that fixes the header issue, the content cells all get a gap between rows too, so that's no good.
It would seem that the table top is getting cut off because I have style.columHeaderCount = 0
. So it seems the code expects every PDFTable to have a header. In this case I'm using a PDFTable simply to draw an icon to the left of the text.
Is there a better way to do that inside the header? I tried with an attributed string and an NSTextAttachment, but it got stripped out when the PDF was generated.