1

I have a big table with lots of columns that I need to fit into a PDF page using DOMPDF. Just like here, my headers are much wider than the corresponding content, so I'm trying to rotate them. The HTML opened in Firefox seems ok, but the resulting PDF is not.

The Setup

HTML:

<td class="cell rotated_vertical_td" style="width:3%;">
    <div class="rotated_vertical">Rotated_</div>
</td>

The width attribute above is being calculated in PHP based on the total number of columns.

CSS:

.cell {
    font-size: 8pt;
}
.rotated_vertical_td {
    height: 280px;
    width: 20px;
    text-transform:uppercase;
    margin:0;
    padding:0;
}
.rotated_vertical {
    -webkit-transform:rotate(270deg);
    -moz-transform:rotate(270deg);
    -ms-transform:rotate(270deg);
    -o-transform:rotate(270deg);
    transform:rotate(270deg);
    transform-origin: 50%;
    width: 20px;
}

I had to apply the .cell class to each <td> because DOMPDF was not picking up the table td rule for some reason.

The Problem

It seems like DOMPDF first renders the text, changes the table cell accordingly, and then rotates it. Which means that the column still takes as much space and that breaks the whole point.

I've tried using substr() to cut the text to be only 2, 4 or 8 characters long. Looks like the column widths are adjusting accordingly.

2 characters 4 characters 8 characters

Those are the screenshots of the actual PDF being rendered. As you can see, the last one fits less columns, even though the markup and the CSS is the same. Only thing thats changed is the characters count. Looks like it completely ignores the width I set on those headers.

In case of HTML, it looks like that width: 20px; on the inner div makes a difference - if I remove it, the HTML headers become wide as well.

So again, it looks like the inner div width stretches the table header cells. I can override that width and it works for HTML, but it does not for PDF.

What makes it worse is that DOMPDF does not seem to support having multiple page orientations in a single document so I can't have that page in the landscape mode.

In an answer to this question it is advised to use the absolute positioning, but I am not sure how that'd work with DOMPDF. For example, they treat the position:fixed elements as page headers, might have something reserved for the absolute positioned ones.

Please help

Update 2015-01-26

Thanks to BrianS for his help, I've managed to make the text rotate using the CSS approach he suggested and the latest DOMPDF downloaded from GitHub. Before that, I was generating the dynamic images with rotated text for each one of the headers.

Several things I'd like to point out for those who got here searching for the solution (including the future me I guess)

  1. Positioning the content is a nightmare. There seems to be no way to predictably control the position and behaviour of the rotated headers neither in Firefox, nor in DOMPDF, and that is frustrating. First I had to change the transform-origin property to be:

    transform-origin: left bottom 0;
    

    ... just so it's position is less random because otherwise changing either top/left or width/height properties kept moving the block in both dimensions. The fact that there are about 7 variables to control (top,left,width/height of the :after element, as well as the line-height and width/height of the wrapping cells) makes it impossible to go through all possible combinations of those to get the desirable position.

  2. If a header is too long and contains any breakable characters - such as spaces - it gets split into several lines and those lines get combined into one by overlaying each other. Please see:

I couldn't find a way to prevent that. Seeing that the text that doesn't contain any breakable characters is still being placed on one line,

  • tried playing with the width/height/line-height controls
  • tried replacing the spaces with &nbsp; inside of the css content property but that gets printed directly and does not act as the non-breakable space
  • tried using the ASCII characters as advised here but it outputs some weird characters instead
  • replaced the spaces with underscores - it works but looks ugly
  • so I've tried replacing the spaces with <span style="color:#fff">_</span> so this way the color of the underscore matches the background, but again, that whole thing gets printed directly, and I've also realised that that span would break the line anyway
  • it feels like that line that gets combined just doesn't have enough width, but if I change the width of the .rotate .content:after element, it just moves that overlayed line to the right, although when I open the HTML in Firefox that seems to help

So for now, my solution is using the underscores, but that doesn't look professional. I'd appreciate if you could help me out with a soution.

Here's the updated setup:

HTML:

<tr class="table_summary_thead">
    <td class="rotated_vertical_td" style="width:3%;">
        <a hred="appendix_item_44">
            <div class="rotated_vertical_outer">
                <div class="rotated_vertical_inner rtb1f73b0b57649457abd0ca2e0e8c94e0f7d79c25">
                </div>
            </div>
        </a>
        <style type="text/css">
            .rtb1f73b0b57649457abd0ca2e0e8c94e0f7d79c25:after {
                content:"COMPREHENSIVE";
            }
        </style>
    </td>
</tr>

CSS:

.table_summary {
    width:100% !important;
}
.table_summary td {
    text-align:center;
}
.table_summary tbody td {
    font-size: 10pt;
    padding: 4px;
}
.rotated_vertical_td {
    width: 20px !important;
    height: 680px !important;
    font-size: 8pt;
    margin:0;
    padding:0;
    text-align:left;
}
.table_summary_thead {
    line-height: 220px;
    text-align:left;
}
.rotated_vertical_outer {
    position: relative;
    overflow: visible;
    text-align:left;
}
.rotated_vertical_inner:after {
    height: 150px;
    overflow: visible;
    position: absolute;
    text-align: left;
    transform: rotate(270deg);
    transform-origin: left bottom 0;
    width: 20px;
    top: 100px;
    left: 110px;
}

Including most of the CSS here because who knows, some small part that seems irrelevant might make a difference.

Community
  • 1
  • 1
ᴍᴇʜᴏᴠ
  • 4,804
  • 4
  • 44
  • 57
  • I had a feeling that positioning the elements would be a pain. I wish I had been able to come up with better options. – BrianS Feb 05 '15 at 03:39
  • Also note that dompdf doesn't currently handle non-breaking spaces very well. They are pretty much treated as regular spaces. – BrianS Feb 05 '15 at 03:43
  • 1
    To ensure that your lines don't break you should be able to style the `:after` blocks with `white-space: nowrap;`. (Which will probably cause you to have to rework the positioning.) – BrianS Feb 05 '15 at 03:43

4 Answers4

2

There's a lot to cover in this question. In the future you might want to focus a bit more. Let's get started.


First, your impression of how dompdf deals with tables is correct. Cells are rendered before transforms are applied. Actually, I'm pretty sure right now that transforms have no effect on the flow of the document, just the appearance of the transformed content.

Second, it is true that dompdf does not currently support multiple page orientations. If you want to use dompdf for that you'll have to create each PDF separately and use something like pdftk or fpdf/fpdi to combine the results.

Third, position: fixed isn't treated as page headers but as the CSS spec outlines, i.e. persistent across pages. position: absolute is also treated per the spec (for the most part). Absolutely positioned content is placed at the specified coordinates according to either a) the page where it is encountered or b) the first parent element without static positioning.

If you set a parent element to position: relative and then absolutely position one of it's children the child element will be positioned relative to the parent. This is why the last referenced question recommends that styling. It should work in dompdf, except that dompdf's wonky table handling is resulting in the wrong column width.

So how to work around the issue? If you leave the content out of the HTML then dompdf will render it the width you specify. You can add the content back in using CSS. The following seems to work well enough:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">

  <style type='text/css'>
  td {
    width: 20px;
  }
  .rotate .container {
    position: relative;
    overflow: visible;
  }
  .rotate .content:after {
    width: 150px; height: 150px;
    overflow: visible;
    content: "Overall Satisfaction";
    transform: rotate(-90deg);
    transform-origin: center center;
    position: absolute; left: -150px; top: -200px;
  }
  </style>
</head>
<body>
  <table border="1">
    <thead>
      <tr style="line-height: 200px;">
        <th><div>Facility</div></th>
        <th><div>Date</div></th>
        <th><div>Score</div></th>
        <th class="rotate"><div class="container"><div class="content"></div></div></th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Los Angeles</td>
        <td>11/12/2010</td>
        <td>3.5</td>
        <td>2.5</td>
        </tr>
      <tr>
         <td>San Diego</td>
         <td>11/17/2010</td>
         <td>10.0</td>
         <td>10.0</td>
      </tr>
    </tbody>
</table>

</body>
</html>

Make sure you're using dompdf 0.6.1.

BrianS
  • 13,284
  • 15
  • 62
  • 125
1

I had the same problem and tried out the answer of BrianS, which worked perfectly - for the first page.

However, when the table spanned several pages (with the header being repeated), the rotated texts on all following pages were positioned wrongly.

Thinking a bit more about this, I found a surprisingly simple solution IF, LIKE ME, YOU WANT THE ROTATED TEXTS IN EXACTLY THE SAME POSITION ON ALL PAGES (if not, then this will not work): Just don't draw the texts at all during the PDF creation, but instead add them later on like the page numbering.

That is, simply create the cells with the needed height and width but leave them empty in the actual HTML layout.

Then add the texts afterwards like this:

// assuming you loaded the HTML,
// create the PDF and grab the canvas
$dompdf->render();
$canvas = $dompdf->get_canvas();

// add rotated text!
$font = Font_Metrics::get_font("helvetica");
$canvas->page_text(250, 50, "Rotated", $font, 9, array(0,0,0), 0, 0, 270);
$canvas->page_text(300, 50, "Text", $font, 9, array(0,0,0), 0, 0, 270);

The last parameter here is the angle of rotation. Attention: There seem to be different dompdf versions out there, with some of them having two spacing parameters between the color and the rotation (like shown here), and some of them having one spacing parameter between the color and the rotation.

1

First, set height of A_LONG_HEADER height:125px and set left and right margin in inside div margin-left: -50px margin-right: -50px. The following seems to work well:

HTML

<table id="codexpl">
    <tr >
        <th >#</th>
        <th><span >98</span></th>
        <th id="rotate"><div id="vertical">A_LONG_HEADER</div></th>
    </tr>
    <tr>
        <td>1</td>
        <td>This</td>
        <td>c</td>
    </tr>
    <tr>
        <td>2</td>
        <td>6</td>
        <td>two</td>
    </tr>
    <tr>
        <td>3</td>
        <td>is</td>
        <td>not</td>
    </tr>
    <tr>
        <td>4</td>
        <td>the</td>
        <td>Column</td>
    </tr>
    <tr>
        <td>5</td>
        <td>first</td>
        <td>One</td>
    </tr>
</table>
<p>TEST</p>

CSS

#rotate
{
  height:125px;
}

#vertical
{
    -webkit-transform:rotate(-90deg);
    -moz-transform:rotate(-90deg);
    -o-transform: rotate(-90deg);
    margin-left: -50px;
    margin-right: -50px;
}
0

Insert info in different places per page can be implemented by inline PHP:

  $header = array();
  foreach($participants_t as $p_id => $p) {
    $header[] = $p['Title'];
  }

...

$dompdf = new DOMPDF();
$html = htmlspecialchars_decode(htmlentities($html, ENT_NOQUOTES, 'UTF-8'), ENT_NOQUOTES);
$dompdf->load_html($html);
$dompdf->set_paper("A4", "landscape");
$dompdf->render();

$pdf = $dompdf->get_canvas(); 

$GLOBALS["header"] = $header;
if (isset($pdf)) {
  $pdf->page_script(
          '$plus_top = 70; '
          . 'if ($PAGE_NUM > 1) { '
          . '$plus_top = 0; '
          . '} '
          . '$font = Font_Metrics::get_font("DeJavu Sans"); '
          . '$header = array(); '
          . '$header = $GLOBALS["header"]; '
          . 'foreach($header as $key => $item) { '
          . '$pdf->text(122 + 22.01 * $key, 178 + $plus_top, $item, $font, 9, array(0,0,0), 0, 0, -90); '
          . '} '
          . ''); 
}

return $dompdf;

So at first page ($PAGE_NUM = 1) top position is 70px bigger. This method has one problem - font subset isn't created for text inserted by inline PHP.

Arunas
  • 1
  • 1