back
Generating PDFs with HTML and CSS
This is a quick introduction for web developers on using HTML and CSS to generate PDFs.
I'll briefly focus on a few issues specific to print media and how CSS can solve them.
The CSS described here is nothing specific to typeset.sh, but will also work with other renderers
that conform to Print CSS.
Fragmentation
Unlike websites, which can theoretically have an infinite size, the content of a pdf may be split
between multiple pages. This process, called fragmentation, can sometimes lead to ugly or unwanted
text layouts if not addressed properly.
CSS allows you to control how elements are broken up when placed on pages. Below, we’ll introduce
the most crucial CSS properties.
The problem of the abandoned headline
The break-before property
A common issue occurs when within a text flow when a headline gets placed at the bottom of the
current page, but the corresponding paragraph begins on the next page, leaving the headline all
by itself. To avoid this problem, CSS allows you to control and determine where a document can break,
should not break or must break.
Let’s look at this headline example in detail: the code below will ensure that the red h2 headline
is placed on the following page to avoid an abandoned headline.
Go ahead; feel free to change the code below. You will see that, by changing
break-after:auto; to break-after:avoid;,
headline 2 will get placed on the next page, and be reunited with its adjoining paragraph.
Notice how this is also valid if the headline is the only or last child of a containing element.
(e.g. <div><h1>Test</h1></div><p>…</p>)
By the way, this behavior is not just applicable for pages, but would also work for column layouts.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
<style>
html {
font-size: 24pt;
}
/* Avoid page breaks after headlines */
h1, h2, h3, h4, h5, h6 {
break-after:auto; /* change me */
}
.title {
color: red;
}
</style>
</head>
<body>
<h2>Headline 1</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec augue neque, mattis in dolor sit amet,
pellentesque pulvinar felis. Sed dignissim malesuada ipsum, at dignissim elit. Aliquam quis elit finibus,
eleifend lacus a, laoreet odio. Vivamus vel ligula tincidunt magna tempus porta vitae id ligula. Maecenas viverra,
ante eu congue tristique, justo mi iaculis mi, id fringilla nisi enim eu magna. Cras porttitor, elit sed rutrum mollis,
turpis sapien faucibus justo, ac sollicitudin orci ante a neque. Vestibulum porta est ac leo pretium,
vel sollicitudin mauris sollicitudin. Vestibulum vel volutpat magna. Proin sed orci nec lacus elementum
maximus. Nunc tempor vel turpis ac tempus. Nunc elementum elementum porta. </p>
<div class="title">
<h2>Headline 2</h2>
</div>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec augue neque, mattis in dolor sit amet,
pellentesque pulvinar felis. Sed dignissim malesuada ipsum, at dignissim elit. Aliquam quis elit finibus,
eleifend lacus a, laoreet odio. Vivamus vel ligula tincidunt magna tempus porta vitae id ligula. Maecenas viverra,
ante eu congue tristique, justo mi iaculis mi, id fringilla nisi enim eu magna. Cras porttitor, elit sed rutrum mollis,
turpis sapien faucibus justo, ac sollicitudin orci ante a neque. Vestibulum porta est ac leo pretium,
vel sollicitudin mauris sollicitudin. Vestibulum vel volutpat magna. Proin sed orci nec lacus elementum
maximus. Nunc tempor vel turpis ac tempus. Nunc elementum elementum porta. </p>
</body>
</html>
The break-after property
Above, we used the break-after property to influence the fragmentation behavior
for the layout flow. Additionally, it is also possible to control the fragmentation of the flow before an
element by using the property break-before.
This is easily done: Change the break-before property in the example below from auto to page.
This will force a page break before all h1 elements.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
<style>
html {
font-size: 24pt;
}
h1 {
break-before:auto;
}
h1, h2, h3, h4, h5, h6 {
break-after:avoid;
}
</style>
</head>
<body>
<h1>Headline 1</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec augue neque, mattis in dolor sit amet,
pellentesque pulvinar felis. Sed dignissim malesuada ipsum, at dignissim elit. Aliquam quis elit finibus,
eleifend lacus a, laoreet odio. Vivamus vel ligula tincidunt magna tempus porta vitae id ligula. Maecenas viverra,
ante eu congue tristique, justo mi iaculis mi, id fringilla nisi enim eu magna.</p>
<h2>Headline 1.1</h2>
<p>Cras porttitor, elit sed rutrum mollis,
turpis sapien faucibus justo, ac sollicitudin orci ante a neque. Vestibulum porta est ac leo pretium,
vel sollicitudin mauris sollicitudin. Vestibulum vel volutpat magna. Proin sed orci nec lacus elementum
maximus. Nunc tempor vel turpis ac tempus. Nunc elementum elementum porta. </p>
<h1>Headline 2</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec augue neque, mattis in dolor sit amet,
pellentesque pulvinar felis. Sed dignissim malesuada ipsum, at dignissim elit. Aliquam quis elit finibus,
eleifend lacus a, laoreet odio. Vivamus vel ligula tincidunt magna tempus porta vitae id ligula. Maecenas viverra,
ante eu congue tristique, justo mi iaculis mi, id fringilla nisi enim eu magna.</p>
<h2>Headline 2.1</h2>
<p>Cras porttitor, elit sed rutrum mollis,
turpis sapien faucibus justo, ac sollicitudin orci ante a neque. Vestibulum porta est ac leo pretium,
vel sollicitudin mauris sollicitudin. Vestibulum vel volutpat magna. Proin sed orci nec lacus elementum
maximus. Nunc tempor vel turpis ac tempus. Nunc elementum elementum porta. </p>
</body>
</html>
Alternatively, one can also apply left or right to break-before.
This can be helpful when rendering a book and new chapters should start on the left or right page.
Alternatively, you can also apply left or right
to break-before. This can be helpful when rendering a book when new chapters
should start on the left or right side of the page pair.
Orphans and widows
The orphans that are left behind
Another common text layout issue occurs if the headline has enough space to fit on the current page but only
one line of the adjoining paragraph fits on that same page. Technically, the break-after: avoid;
rule is not violated in this case. However, the resulting text flow layout does not look right, as a
paragraph with only a single line looks odd.
Those lines are called orphans. The matching CSS property orphans: <int>; lets
you define the minimum required number of lines of a new paragraph that must be placed together on a page to
be displayed. The CSS default is 2 lines for paragraphs. In the example below,
let’s change orphans: 2; to orphans: 5; to see
how the paragraph and the corresponding headline get moved to the next page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
<style>
html {
font-size: 24pt;
}
h1 {
break-after:avoid;
}
p {
orphans: 2;
}
</style>
</head>
<body>
<div style="background: salmon; height: 22em;">Spacer</div>
<h1>Orphans</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec augue neque, mattis in dolor sit amet,
pellentesque pulvinar felis. Sed dignissim malesuada ipsum, at dignissim elit. Aliquam quis elit finibus,
eleifend lacus a, laoreet odio. Vivamus vel ligula tincidunt magna tempus porta vitae id ligula. Maecenas viverra,
ante eu congue tristique, justo mi iaculis mi, id fringilla nisi enim eu magna. Cras porttitor, elit sed rutrum mollis,
turpis sapien faucibus justo, ac sollicitudin orci ante a neque. Vestibulum porta est ac leo pretium,
vel sollicitudin mauris sollicitudin. Vestibulum vel volutpat magna. Proin sed orci nec lacus elementum
maximus. Nunc tempor vel turpis ac tempus. Nunc elementum elementum porta. </p>
</body>
</html>
The widows that end alone
The opposite to the orphans issue can also occur frequently. Sometimes, a paragraph gets split,
causing the last line(s) to end up alone on the following page. These lines are called widows and
can be addressed with the CSS property widows.
Change the widows: 2 to widows: 4 to make the
text appear more pleasing to the eye. This change will “steal” the missing lines from the previous page.
However, it will do this without breaking the orphan-rule, that is, if one has been set. If there are not
enough lines to accommodate the widows and orphans rule, all lines will be kept together and placed on
the next page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
<style>
html {
font-size: 20pt;
}
h1 {
break-after:avoid;
}
p {
orphans: 5;
widows: 2;
}
</style>
</head>
<body>
<div style="height: 15em; background: red; color: white;">Spacer - scroll down</div>
<h1>Widows</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec augue neque, mattis in dolor sit amet,
pellentesque pulvinar felis. Sed dignissim malesuada ipsum, at dignissim elit. Aliquam quis elit finibus,
eleifend lacus a, laoreet odio. Vivamus vel ligula tincidunt magna tempus porta vitae id ligula. Maecenas viverra,
ante eu congue tristique, justo mi iaculis mi, id fringilla nisi enim eu magna. Cras porttitor, elit sed rutrum mollis,
turpis sapien faucibus justo, ac sollicitudin orci ante a neque. Vestibulum porta est ac leo pretium,
vel sollicitudin mauris sollicitudin. Vestibulum vel volutpat magna. Proin sed orci nec lacus elementum
maximus. Nunc tempor vel turpis ac tempus. Nunc elementum elementum porta. </p>
</body>
</html>
One more note about fragmentation
Having defined all those rules, there is one rule that will always apply and might overwrite your break
control, and that is: the layout must make progress. That means if you have a larger section
of elements that should not break but will also not fit entirely on a new page, it will break at the
first best option, ignoring your control in order to make progress.
Now that you know all about page breaking, let’s look at how to define a page.
Styling pages
Page size
By default, typeset.sh will render the pdf using the A4 page size. The page size can be adjusted
using the @page at-rule.
Here are few common page size examples.
size: A3;
size: A4 landscape;
size: A2 portrait;
size: JIS-B4;
size: letter;
size: 100mm;
size: 100mm 200mm;
Let’s keep the A4 size. Along with the size we can also define the margin, background, bleed area and print markers.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
<style>
@page {
size: A4;
margin: 50mm 20mm 10mm 20mm;
bleed: 20mm;
marks: crop cross colors;
background: lightblue;
}
html {
font-size: 20pt;
}
</style>
</head>
<body>
Hello A4 page
</body>
</html>
Named pages
You can also define named pages, that you references in your content flow and make sure that certain elements
are always rendered on a specific named page.
For example, assume that our document has a few tables that are too wide to fit on an A4 portrait page,
but would fit on an A4 landscape page. We make the table appear on the landscape page by creating a
class for table, telling it to force a page break to a named page called “wide” or any other arbitrary name.
Then all we have to do is, create a named page with the name of “wide” set its size to
A4 landscape.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
<style>
@page {
size: A4;
margin: 2cm;
}
@page wide {
size: A4 landscape;
background: lightblue;
}
html {
font-size: 20pt;
}
table.wide {
border: 2px solid black;
page: wide;
}
table.wide td, table.wide th{
padding: 2mm 5mm;
text-align: right;
border: 1px solid black;
}
</style>
</head>
<body>
<h1>Headline 1</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec augue neque, mattis in dolor sit amet,
pellentesque pulvinar felis. Sed dignissim malesuada ipsum, at dignissim elit. Aliquam quis elit finibus,
eleifend lacus a, laoreet odio. Vivamus vel ligula tincidunt magna tempus porta vitae id ligula. Maecenas viverra,
ante eu congue tristique, justo mi iaculis mi, id fringilla nisi enim eu magna. Cras porttitor, elit sed rutrum mollis,
turpis sapien faucibus justo, ac sollicitudin orci ante a neque. Vestibulum porta est ac leo pretium,
vel sollicitudin mauris sollicitudin. Vestibulum vel volutpat magna. Proin sed orci nec lacus elementum
maximus. Nunc tempor vel turpis ac tempus. Nunc elementum elementum porta. </p>
<table class="wide">
<tr>
<th></th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
</tr>
<tr>
<th>1</th>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
</tr>
<tr>
<th>2</th>
<td>2</td>
<td>4</td>
<td>6</td>
<td>8</td>
<td>10</td>
</tr>
<tr>
<th>3</th>
<td>3</td>
<td>6</td>
<td>9</td>
<td>12</td>
<td>15</td>
</tr>
</table>
<h1>Headline 2</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec augue neque, mattis in dolor sit amet,
pellentesque pulvinar felis. Sed dignissim malesuada ipsum, at dignissim elit. Aliquam quis elit finibus,
eleifend lacus a, laoreet odio. Vivamus vel ligula tincidunt magna tempus porta vitae id ligula. Maecenas viverra,
ante eu congue tristique, justo mi iaculis mi, id fringilla nisi enim eu magna. Cras porttitor, elit sed rutrum mollis,
turpis sapien faucibus justo, ac sollicitudin orci ante a neque. Vestibulum porta est ac leo pretium,
vel sollicitudin mauris sollicitudin. Vestibulum vel volutpat magna. Proin sed orci nec lacus elementum
maximus. Nunc tempor vel turpis ac tempus. Nunc elementum elementum porta. </p>
</body>
</html>
Empty pages
Remember when I mentioned that by using left or right
for the break-after property above, you could end up generating a blank page? That is what
the :empty page selector is for. It allows you to customize an empty
page where no content is placed.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Blank page selector</title>
<style>
@page {
size: 200px 100px;
margin: 10px;
}
@page:left {
background: red;
}
p {
break-after: left;
}
</style>
</head>
<body>
<p>Hello World 1</p>
<p>Hello World 2</p>
<p>Hello World 3</p>
</body>
</html>
Page margin regions
Within the page-at-rule, you can also define 16 margin-regions and add content to them. Take a look at the
example below which defines all 16 margin-regions.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>page-margins.html</title>
<style>
@page {
size:A4;
margin: 20mm;
@top-left {
content: "left";
background: yellow;
}
@top-right {
content: "right";
background: red;
}
@top-center {
content: "center";
background: green;
}
@bottom-left {
content: "left";
background: yellow;
}
@bottom-right {
content: "right";
background: red;
}
@bottom-center {
content: "center";
background: green;
}
@left-top {
content: "top";
background: yellow;
}
@left-bottom {
content: "bottom";
background: red;
}
@left-middle {
content: "middle";
background: green;
}
@right-top {
content: "top";
background: yellow;
}
@right-bottom {
content: "bottom";
background: red;
}
@right-middle {
content: "middle";
background: green;
}
@bottom-left-corner {
content: "bl";
background: #587b80;
}
@bottom-right-corner {
content: "br";
background: #587b80;
}
@top-left-corner {
content: "tl";
background: #587b80;
}
@top-right-corner {
content: "tr";
background: #587b80;
}
}
html {
font-size: 20pt;
}
</style>
</head>
<body>
<h1>Page margins</h1>
<p>You should see all 16 page margins, 3 at the top, bottom, left and right plus 4 in each corner.</p>
</body>
</html>
Play with the page margin and see how it affects the margin-regions size.
The rule, on how much space a region can take is
defined here.
It’s a bit complicated, but basically, if you only use the top-left and top-right regions, then both
regions will take the content of top-center region.
Content property
Simply setting static content is a bit useless. Luckily, CSS allows a few dynamic content functions for your
page margin regions.
The page and pages counter
The page and pages counters are reserved
counters that will always return the current page number and total number of pages respectively.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
<style>
@page {
size: A5;
margin: 20mm;
@bottom-right {
content: "Page " counter(page) " of " counter(pages);
font-size: 15pt;
color: red;
}
}
html {
font-size: 24pt;
}
/* Begin new chapter with a new page */
h1 {
break-before:page;
}
/* Avoid page breaks after headlines */
h1, h2, h3, h4, h5, h6 {
break-after:avoid;
}
</style>
</head>
<body>
<h1>Headline 1</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec augue neque, mattis in dolor sit amet,
pellentesque pulvinar felis. Sed dignissim malesuada ipsum, at dignissim elit. Aliquam quis elit finibus,
eleifend lacus a, laoreet odio. Vivamus vel ligula tincidunt magna tempus porta vitae id ligula. Maecenas viverra,
ante eu congue tristique, justo mi iaculis mi, id fringilla nisi enim eu magna.</p>
<h2>Headline 1.1</h2>
<p>Cras porttitor, elit sed rutrum mollis,
turpis sapien faucibus justo, ac sollicitudin orci ante a neque. Vestibulum porta est ac leo pretium,
vel sollicitudin mauris sollicitudin. Vestibulum vel volutpat magna. Proin sed orci nec lacus elementum
maximus. Nunc tempor vel turpis ac tempus. Nunc elementum elementum porta. </p>
<h1>Headline 2</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec augue neque, mattis in dolor sit amet,
pellentesque pulvinar felis. Sed dignissim malesuada ipsum, at dignissim elit. Aliquam quis elit finibus,
eleifend lacus a, laoreet odio. Vivamus vel ligula tincidunt magna tempus porta vitae id ligula. Maecenas viverra,
ante eu congue tristique, justo mi iaculis mi, id fringilla nisi enim eu magna.</p>
<h2>Headline 2.1</h2>
<p>Cras porttitor, elit sed rutrum mollis,
turpis sapien faucibus justo, ac sollicitudin orci ante a neque. Vestibulum porta est ac leo pretium,
vel sollicitudin mauris sollicitudin. Vestibulum vel volutpat magna. Proin sed orci nec lacus elementum
maximus. Nunc tempor vel turpis ac tempus. Nunc elementum elementum porta. </p>
</body>
</html>
Running elements
As well as being able to add static CSS content to your margin areas that you want repeated on each page,
you can also add content from your document flow and place it in your margin regions.
This is still a draft in CSS and might be subject to change, but nevertheless, it is really useful and
we have implemented it as it is with the current state.
In the example below, you can see how the top-center margin region always shows the first major
headline of the current page. You can control which element to use with the second parameter,
in case a single page contains more than one possible element.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
<style>
@page {
size: A5;
margin: 20mm;
@top-center {
content: element(top-title);
background: #eee;
}
}
html {
font-size: 24pt;
}
.title {
position: running(top-title);
font-size: 15pt;
color: red;
text-align: center;
}
h1, h2, h3, h4, h5, h6 {
break-after:avoid;
}
</style>
</head>
<body>
<div class="title">Headline 1 - foo bar</div>
<h1>Headline 1</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec augue neque, mattis in dolor sit amet,
pellentesque pulvinar felis. Sed dignissim malesuada ipsum, at dignissim elit. Aliquam quis elit finibus,
eleifend lacus a, laoreet odio. Vivamus vel ligula tincidunt magna tempus porta vitae id ligula. Maecenas viverra,
ante eu congue tristique, justo mi iaculis mi, id fringilla nisi enim eu magna.</p>
<h2>Headline 1.1</h2>
<p>Cras porttitor, elit sed rutrum mollis,
turpis sapien faucibus justo, ac sollicitudin orci ante a neque. Vestibulum porta est ac leo pretium,
vel sollicitudin mauris sollicitudin. Vestibulum vel volutpat magna. Proin sed orci nec lacus elementum
maximus. Nunc tempor vel turpis ac tempus. Nunc elementum elementum porta. </p>
<div class="title">Headline 2 - bar foo</div>
<h1>Headline 2</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec augue neque, mattis in dolor sit amet,
pellentesque pulvinar felis. Sed dignissim malesuada ipsum, at dignissim elit. Aliquam quis elit finibus,
eleifend lacus a, laoreet odio. Vivamus vel ligula tincidunt magna tempus porta vitae id ligula. Maecenas viverra,
ante eu congue tristique, justo mi iaculis mi, id fringilla nisi enim eu magna.</p>
<h2>Headline 2.1</h2>
<p>Cras porttitor, elit sed rutrum mollis,
turpis sapien faucibus justo, ac sollicitudin orci ante a neque. Vestibulum porta est ac leo pretium,
vel sollicitudin mauris sollicitudin. Vestibulum vel volutpat magna. Proin sed orci nec lacus elementum
maximus. Nunc tempor vel turpis ac tempus. Nunc elementum elementum porta. </p>
</body>
</html>
Target page counters
Remember the special page counter from above? Well, you can also retrieve the
page number of an element within the document. This is extremely helpful if you want to make a table of contents.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>page-counter.html</title>
<style>
@page {
size: 100mm 150mm;
margin: 10mm;
@bottom-right {
content: "Page: " counter(page) " of " counter(pages);
font-size: 0.7em;
}
}
h2 {
break-inside: avoid;
break-after: avoid;
}
.pages {
content: counter(pages)
}
#toc > li > a::after {
content: "(page: " target-counter(attr(href url), page) ")";
display: inline;
white-space: nowrap;
}
</style>
</head>
<body>
<h1>Page counter(s)</h1>
<p>The page counter lets you display the current page number or the total number of pages. This can be used even with
a text flow, for example, this document hast <span class="pages"></span> pages.</p>
<p>Another lovely CSS feature is the target-counter() function, which gives you back the page number at which an element
is first displayed. Combined with the attr() function, you can get the page number for different elements based on the
on a attribute. This lets you create simple table of contents with the page number.</p>
<ol id="toc">
<li><a href="#topic1">The rise of pudding </a></li>
<li><a href="#topic2">The fundamentals of pudding </a></li>
<li><a href="#topic3">Major historical pudding events </a></li>
<li><a href="#topic4">Famous people who don't like pudding </a></li>
<li><a href="#topic5">Why pudding? </a></li>
</ol>
<h2 id="topic1">The rise of pudding</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas ac tempor orci, in pharetra leo.
Aliquam rhoncus vulputate turpis. Vestibulum sed eros eu dolor pellentesque suscipit. Sed vitae elit dui.
Sed finibus eleifend rhoncus. Curabitur aliquam interdum elit, ut dignissim lorem facilisis eu. In hac habitasse
platea dictumst. Quisque at dui ante. Suspendisse et tortor aliquam enim tristique viverra vitae et risus. </p>
<h2 id="topic2">The fundamentals of pudding</h2>
<p>Nam at ligula lacinia, pretium ante eget, euismod neque. Curabitur vitae lectus nisl. Nullam finibus quam
justo, ac interdum arcu congue a. Proin finibus imperdiet semper. Aliquam ac lacus quis sem accumsan gravida vel a
diam. Pellentesque sed ex mollis, sagittis massa quis, tempus libero.</p>
<h3>Proin finibus imperdiet semper</h3>
<p> Etiam venenatis elit eget quam consectetur
tincidunt. Aenean sed justo faucibus, vulputate lorem eget, malesuada nulla. Nullam interdum, ante eget elementum
pulvinar, nulla nunc sodales tellus, sit amet porta odio orci a ex. Morbi ullamcorper urna non lectus faucibus
fringilla nec a eros. Duis euismod congue lacus in luctus. Sed elementum vitae diam sed consectetur. Praesent id
varius nisi. Aenean placerat tristique ex. </p>
<h3>Class aptent taciti sociosqu</h3>
<p> Etiam venenatis elit eget quam consectetur
tincidunt. Aenean sed justo faucibus, vulputate lorem eget, malesuada nulla. Nullam interdum, ante eget elementum
pulvinar, nulla nunc sodales tellus, sit amet porta odio orci a ex. Morbi ullamcorper urna non lectus faucibus
fringilla nec a eros. Duis euismod congue lacus in luctus. Sed elementum vitae diam sed consectetur. Praesent id
varius nisi. Aenean placerat tristique ex. </p>
<h2 id="topic3">Major historical pudding events</h2>
<p>Curabitur egestas mauris non fringilla maximus. Integer eleifend consequat nisl et porta. Pellentesque habitant
morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed eget ipsum in lorem consectetur
vehicula. Integer porta felis a malesuada lobortis. Quisque vulputate ligula non enim rutrum tempor. Suspendisse
eget sapien lectus. Aenean non mattis felis. Aliquam eget iaculis augue, et placerat magna. Donec vestibulum lectus
ut laoreet condimentum. </p>
<h2 id="topic4">Famous people who don't like pudding</h2>
<p>Nam auctor malesuada blandit. Nam tincidunt, massa at sodales interdum, elit justo iaculis libero, quis varius
sem nibh id purus. Duis rhoncus volutpat laoreet. Suspendisse imperdiet erat eget purus lacinia, id finibus magna
scelerisque. Morbi malesuada convallis vestibulum. Etiam rutrum eget mi eu venenatis. Donec eget egestas ligula,
id feugiat dui. Praesent a magna vel justo molestie bibendum in quis ligula. Etiam sodales nunc et libero aliquam
faucibus sit amet non eros. Vivamus pellentesque neque id molestie fringilla. Integer gravida ultricies efficitur.
Integer in nibh sit amet turpis fermentum gravida. Class aptent taciti sociosqu ad litora torquent per conubia
nostra, per inceptos himenaeos. Morbi elementum volutpat sem, vel fermentum ligula molestie gravida. </p>
<h2 id="topic5">Why pudding?</h2>
<p>Aliquam erat volutpat. In varius nibh sit amet tempor auctor. Quisque massa ipsum, ornare at augue vel, bibendum
condimentum purus. Sed fringilla consectetur velit, eget dapibus mi gravida id. Ut eget tincidunt lacus.
Quisque leo velit, aliquam in orci a, dignissim molestie ex. Nunc vulputate est sit amet lacus sagittis, sit
amet condimentum nunc blandit. Donec varius lacus laoreet, posuere purus eu, mollis risus. Pellentesque
facilisis nibh nec feugiat blandit. Nunc a lacus at purus porttitor aliquet vel ac nisi. Nullam sit amet nisl at
quam molestie ornare. Pellentesque sit amet tortor fringilla, facilisis nisl vitae, porta est. </p>
</body>
</html>
Let us quickly take the content value apart (content: "(page: " target-counter(attr(href url), page) ")";).
The attr(href url) is a reference to the href attribute of the current element, the url
is the type or unit keyword, in this case we expect a url string. The target-counter() will then
retrieve the counter named page from the element.
Bookmarks
PDFs support bookmarks which are sometimes referred to as outlines. These are generic tables of contents
that can be accessed by the viewer, so that they can easily navigate through a document.
CSS lets you define which elements should be added to the bookmarks. By default, all headlines
are added according to their level.
There are three CSS properties that can be used to fine tune your bookmarks.
- bookmark-label
- bookmark-level
- bookmark-state
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>bookmarks.html</title>
<style>
@page {
size: 100mm 150mm;
margin: 10mm;
}
h1 {
bookmark-label: attr(title) ": " content();
}
</style>
</head>
<body>
<h1 title="Foo">H1 1</h1>
<h2>H2 1.1</h2>
<h2>H2 1.2</h2>
<h3>H3 1.2.1</h3>
<h3>H3 1.2.2</h3>
<h2>H2 1.3</h2>
<h2>H2 1.4</h2>
<h2>H2 1.5</h2>
<h4>H4 1.5.X.1</h4>
<h1 title="Bar">H1 2</h1>
<h4>H4 2.X.X.1</h4>
</body>
</html>
By default, the elements h1, h2, etc. are already configured for the bookmark-levels and labels,
but you can always overwrite this default behavior. In the example above, we prefix the bookmark
label of the h1 element by the elements title.
Named references
Similar to bookmarks, there are also named references. Named references are created automatically
and work in a similar way as in html, when using an id attribute on elements.
These ids can then be used in the url and allow you to jump straight to the part given by the id.
e.g. /catalog.pdf#prices
I hope you enjoyed this quick introduction to print CSS and that it’s given you everything you need to
get started on your next pdf project.