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.
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)
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.
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 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.
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.
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.
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;
}
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.
I've got a pretty regular HTML <table> with one cell that spans multiple rows via rowspan. Inside of this cell I've got a <div> that I want to occupy the entire height of the cell but for the life of me I can't seem to figure it out. It seems similar to this post which mentions this Chrome bug but also seems so simple that maybe I'm just not thinking clearly.
Here's a stripped down version of my HTML:
<table>
<tr>
<td class="a" rowspan="2"><div>A</div></td>
<td class="b"><div>B</div></td>
</tr>
<tr>
<td class="c"><div>C</div></td>
</tr>
</table>
And CSS:
td
{
vertical-align: top;
}
td.a div
{
background-color: #f00;
height: 100%;
}
And a JSFiddle. And here's what I'm getting and what I'm trying to get:
What's really weird is if I use Chrome's inspector to change the <div> to display: inline-block and then set it back to display: block it actually looks pretty much exactly how I want it to.
(And no, switching away from a table isn't an option for this project, there's other code not shown that requires that.)
Option 1
Simply add overflow:auto; to your div CSS
Demo Fiddle
td
{
vertical-align: top;
}
td.a div
{
background-color: #f00;
height: 100%;overflow:auto;
}
Option 2
Alternatively you'll need to define the height of your table in order for the child to be able to calculate what its 100% is 100% of.
Option 3
The only other way would be to set position:relative on the td elements then position:absolute for the child div
It's not easy explaining the need here, but here is the playground for the problem.
Playground
Requirements:
First cell has FIXED width
Middle cell width takes the rest of the space
Last cell's width depends on it's children's width
The Question:
How can the middle cell take the rest of the row's space, without being "taken over" by it's child's greater width?
this is a simplified version of my problem, using real tables instead of CSS tables)
Without specific markup, it's hard to propose an exact solution, but here are some things to consider.
The left-most fixed-width cell is easily handled by setting its width. e.g. width: 100px. (This cell isn't really relevant to the problem; in a sense it can be ignored.)
If I'm interpreting correctly, you want to prevent the right-most cell from wrapping. That's easy or hard, depending on the content. For pure text, it can be achieved with white-space: nowrap. If the content isn't strictly text, perhaps you can coerce it into acting like text, e.g. display: inline.
For the middle cell, you don't specify what you want to happen to the excess content. Hide it? Add a horizontal scroll bar? You also don't indicate what this content is. But most likely you'll want to set the overflow-x property to some suitable value.
Solution playground
HTML
<table>
<tr>
<td></td>
<td>
<div>
<div></div>
</div>
</td>
<td>
<b></b>
<b></b>
<b></b>
</td>
</tr>
</table>
CSS
table{ width:100%; }
/*
This is the trick. There is a wrapping DIV with a position:relative which
holds the actual content DIV which is positioned Absolute, so it's width won't
affect it's own cell width's
*/
td > div{ position:relative; width:100%; height:20px; }
div div{
position:absolute;
background:green; height:100%; width:800px;
}
/* First TD has FIXED width */
td:nth-child(1){ width:100px; background:#EEE; }
/* Middle TD width takes the rest of the space */
td:nth-child(2){ overflow:hidden; }
/* Last TD has width depends on it's children's width */
td:nth-child(3){ white-space:nowrap; width:1%; }
Here's my example.
If you have two table columns, one with taller content than the other, then divs inside the other column don't expand to the first column's, even with height:100%. How can I create this behavior with CSS?
<table>
<tr>
<td class="one">
<ul>
<li>fasdf</li>
<li>asgasdf</li>
<li>afdsaggrea</li>
<li>asgasdf</li>
<li>afdsaggrea</li>
</ul>
</td>
<td class="two">
<div class="three">
First div
<div>
Second div
</div>
</div>
</td>
</tr>
</table>
i.e. in my jsfiddle, the behavior I want is the green box filling the entire yellow box.
The reason your div wont grow when you assign it a height of 100% is that the parent doesn't have a height.
ie. You have a list that is causing the table to grow larger.
To remedy this, you can add a height to the parent cell and give the child a height of 100% as shown in this fiddle
.two{
background-color:yellow;
height:100px;
}
.three{
background-color:green;
height:100%;
}
You can add oveflow auto as well.
.three {
height: 100%;
overflow: auto;
}
It will work in some smart browsers like chrome. But it will be an issue in others because the parent doesnt have a known height at the time of rendering.
You can set height to your .two class and then height and overflow properties in .three class should fix your problem.
Or use javascript :)
If you don't want to have to set a concrete height, and you want it to work in all browsers, I added a nicely coded javascript solution to your forked JSFiddle.
var tableCritter = new function TableCritter(){
var critter=this;
var $window = $(window);
var $two = critter.$two = $('.two');
var $three = critter.$three = $('.three');
function adjustHeight(){
$three.css({ height:$two.height() });
};
adjustHeight();
$window.on('resize',adjustHeight);
critter.stop = function(){
$window.off('resize',adjustHeight);
};
};
I'm trying to format the debug log of an app as an HTML table. I have two columns: one for time stamps, and one for the text of log entries. The text can easily contain long character sequences. Example:
<table>
<tr>
<td class="ts">12:50:01.683</td>
<td class="txt">[1]: NDIS-WDM_Driver_for_HighSpeed_USB-Ethernet_Adapter_(Microsoft's_Packet_Scheduler)_:\Device\NPF_{7D9F4819-8B81-49FA-B321-5F1ACBD6740D}</td>
</tr>
<tr>
<td class="ts">12:50:01.683</td>
<td class="txt">[2]: Realtek_10/100/1000_Ethernet_NIC_________________________________(Microsoft's_Packet_Scheduler)_:\Device\NPF_{90EF96D0-AC16-41E5-AFFD-1B25D439A0D9}</td>
</tr>
<tr>
<td class="ts">12:50:01.683</td>
<td class="txt">[3]: Realtek_10/100/1000_Ethernet_NIC_________________________________(Microsoft's_Packet_Scheduler)_:\Device\NPF_{D3FA28DC-C59B-4027-AC43-6480B775ACD9}</td>
</tr>
</table>
I would like the table to always use 100% browser width. The width of the time stamp column should adapt to its content, and the text of the second column should wrap automatically. The CSS I tried:
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
td {
border: solid 1px #000;
}
.ts {
width: 100px;
}
.txt {
word-wrap: break-word;
}
In Chrome 26, without table-layout: fixed;, the table is wider than the browser, and a horizontal scroll bar appears. Since this setting makes the width of my columns equal, I had to set the width of the time stamp column to a predefined value: width: 100px;.
The problem is, if the user changes the zoom factor by Ctrl + Mouse Wheel, timestamps can easily become wider than their column, which looks ugly. Is it possible to make the width of the timestamp column follow the width of its content? (I could make it unbreakable with white-space: pre;.) Or, is there another approach which is not based on table-layout: fixed;?
Since 1em is equal to the current font size, it's better to set the width of the time stamp column in em units instead of px. This way the width of the column will be proportional to its content, which solves the zooming problem.
This solution is sub-optimal because the actual width value will depend on the font-family. For example for Times New Roman, the width of the time stamp column can be set to 6em, but a wider font like Courier New requires 8em.