Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does bottom table cell that has centered-text get cut off when displayed as PDF in iOS?

Tags:

html

css

ios

pdf

swift

Question

I have an iOS app that takes an html file, turns it into a PDF, and displays it to a WebKit web view. I have a weird problem where the bottom table cell gets cut off when I display to PDF. The weird thing is that the bottom table is only cut off when the text is centered. If it is left-aligned, everything works fine. Why does this happen?

Please note that I am not looking for a work around solution. Rather, I am seeking to understand why this happens. Is this a bug in iOS? Or did I miss something?

Overview

In the image below, there are two tables <table>. One table is large, has a reddish (coral) background, and has a height of 505px. The other table is below the first with a white background (height is not set). Both have some text. The text is centered for both tables.

The navbar title shows the details of the current view. For example, as shown in the image below, a title of PDF Landscape 505 means that the view shows a PDF in landscape dimensions with a main table height of 505px.

enter image description here

The Problem

The problem arises when I increase the height by 10px. In the image below, the main table height is 515px and the lower table is now cut off.

enter image description here

Take the exact same html and css code and change only the text-alignment to be left-aligned. Now the lower table is not cut off anymore. I also changed the background color to green for distinction. Green means that text is left-aligned. Red means that text is centered.

enter image description here

The following image shows a main-table height of 745px and still the lower table is not cut off because it is left-aligned.

enter image description here

Code

Below is the html code used for this test.

<!DOCTYPE html>
<html>
<head>
  <title>#COLOR#</title>
  <meta charset="utf-8">
  <style>
  table, th, td {
    border-collapse: collapse;
    border: 3px solid black;
    text-align: #ALIGN#;
  }
  table.main-table {
    width: 1012px;
    height: #HEIGHT#px;
    background-color: #COLOR#;
  }
  table.bottom-table {
    width: 1012px;
  }
  </style>
</head>

<body>

  <table class="main-table">
    <tr><td>Hello World.</td></tr>
  </table>
  <table class="bottom-table">
    <tr><td>This text gets cut off when centered. It does *not* get cut when left-aligned.</td></tr>
  </table>

</body>
</html>

In MyViewController, the getHTML() function pulls the html source from sample.html. The function then replaces #ALIGN#, #COLOR#, and #HEIGHT# with their respective values.

func getHTML() -> String? {
    let htmlPath: String? = Bundle.main.path(forResource: htmlResource, ofType: "html")
    guard let path = htmlPath else { return nil }
    do {
        // Load the HTML template code into a String variable.
        var html = try String(contentsOfFile: path)
        html = html.replacingOccurrences(of: "#HEIGHT#", with: tableHeight.description)
        html = html.replacingOccurrences(of: "#COLOR#", with: colorState.rawValue)
        html = html.replacingOccurrences(of: "#ALIGN#", with: alignState.rawValue)
        return html
    } catch {
        print("Error: " + error.localizedDescription)
    }
    return nil
}

The PDFBuilder class handles the PDF creation with one function:

static func exportHTMLToPDF(html: String, frame: CGRect) -> Data {
    // Set a printable frame and inset                
    let pageFrame = frame
    let insetRect = pageFrame.insetBy(dx: 10.0, dy: 10.0)

    // Create a UIPrintPageRenderer and set the paperRect and printableRect using above values.
    let pageRenderer = UIPrintPageRenderer()
    pageRenderer.setValue(pageFrame, forKey: "paperRect")
    pageRenderer.setValue(insetRect, forKey: "printableRect")

    // Create a printFormatter and pass the HTML code as a string.
    let printFormatter = UIMarkupTextPrintFormatter(markupText: html)

    // Add the printFormatter to the pageRenderer
    pageRenderer.addPrintFormatter(printFormatter, startingAtPageAt: 0)

    // This data var is where the PDF will be stored once created.
    let data = NSMutableData()

    // This is where the PDF gets drawn.
    UIGraphicsBeginPDFContextToData(data, pageFrame, nil)
    UIGraphicsBeginPDFPage()
    pageRenderer.drawPage(at: 0, in: UIGraphicsGetPDFContextBounds())
    print("bounds: " + UIGraphicsGetPDFContextBounds().debugDescription)        
    UIGraphicsEndPDFContext()

    return data as Data
} 

This project is also on Github: https://github.com/starkindustries/PrintPDFTest

Troubleshooting Steps Taken

  1. Dimensions: I tried playing around with the size of the PDF dimensions. This did not make a difference in the result.
  2. Columns: I tried using multiple columns instead of just one. Same problem; no difference.
  3. Tables: I tried using just one table instead of two. With one table and two cells, the bottom cell still gets cut off when text is centered; bottom cell does not get cut when text is left-aligned.
  4. iPhones: I tried simulating on different iPhone devices (iPhone 6s, iPhone 8, iPad Pro). Same issue.
  5. Div: If you use a div instead of a second table, the problem is magically fixed. But why does a div work and not a table?

App Notes

The top toolbar has two buttons: green and red. Green makes the text left-aligned. Red makes the text centered.

enter image description here

The bottom toolbar has five buttons: rewind, html, portrait, landscape, and fast forward. The rewind button reduces the main table's height by 10px. Fast forward increases the height by 10px. The html button shows the html view. Portrait and landscape show PDF's in their respective orientations.

enter image description here

like image 820
Zion Perez Avatar asked Dec 05 '17 13:12

Zion Perez


1 Answers

I had many troubles with PDF rendering in order to keep the exact same aspect and size then my HTML file once transformed (mostly because of resolution translation problems).

The best and simpliest workaround that I found is to encapsulate the content in a container that exactly fit the height of the document. You can use the media query to apply your css code only on the print result if you want to display your source html file.

table, th, td {
  border-collapse: collapse;
  border: 3px solid black;
  text-align: center;
}

table.main-table {
  width: 1012px;
  height: 515px;
  background-color: #f4f4f4;
}

table.bottom-table {
  width: 1012px;
}

@media print {
  .main-container{
    height:1100px;/* adjust the height to fit your PDF document*/
  }
}
<div class="main-container">
  <table class="main-table">
    <tr><td>Hello World.</td></tr>
  </table>
  <table class="bottom-table">
    <tr><td>This text gets cut off when centered. It does *not* get cut when left-aligned.</td></tr>
  </table>
 </div>
like image 135
Foxlab Avatar answered Nov 16 '22 03:11

Foxlab