-
Notifications
You must be signed in to change notification settings - Fork 39
/
Copy pathindex.html
383 lines (298 loc) · 14.4 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
<html>
<head>
<title>Beautiful PDFs from HTML</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script src="toc.js"></script>
<script src="paged.polyfill.js"></script>
<script>
class handlers extends Paged.Handler {
constructor(chunker, polisher, caller) {
super(chunker, polisher, caller);
}
beforeParsed(content){
createToc({
content: content,
tocElement: '#toc',
titleElements: [ 'h2:not(.cover-header)', 'h3:not(.cover-header)' ]
});
}
}
Paged.registerHandlers(handlers);
</script>
</head>
<body>
<!-- 1.0 Cover Page -->
<div class="cover-page">
<h1>Beautiful PDFs from HTML</h1>
<h3 class="cover-header no-counter">An Example Repo by Ashok Khanna</h3>
<h3 class="cover-header no-counter">April 2021</h3>
</div>
<!-- 2.0 Table of Contents -->
<div class="table-of-contents">
<h1 class="toc-header" id="toc-start">Table of Contents</h1>
<ol id="toc">
<!-- toc.js will automatically insert TOC here -->
</ol>
</div>
<!-- 3.0 Start of Main Content -->
<!-- Setting the footer -->
<p class="footer"><a href="#toc-start">Table of Contents</a></p>
<!-- Reseting the header counters (as cover page also uses them) -->
<div class="counter-reset"></div>
<!-- 3.1 Section 1 - Foreword -->
<h2 class="no-break top">Foreword</h2>
<p>HTML and CSS are beautiful languages and one of the landmark achievements of collaboration and the open web. Together, they allow you to create beautifully typeset digital content that can be viewed on any device, without any special software or paid licenses.</p>
<p>The web however was developed for scrolling content, and not paged media (by which we mean discrete pages, like in print or PDF). Unfortunately, whilst there are <a href="https://www.w3.org/TR/css-page-3/">W3C standards</a> on paged media, adoption by browsers has not been adequate to date. High quality paid alternatives exist in <a href="https://www.princexml.com">PrinceXML</a>, but a native, free solution has been missing.</p>
<p>A <a href="https://news.ycombinator.com/item?id=26578826">happenstance discussion</a> on Hacker News brought my attention to <a href="https://www.pagedjs.org">paged.js</a>, a genius idea to polyfill the required capabilities to allow browsers to natively handle paged media, without having to build the whole rendering engine from scratch.</p>
<p>This guide provides a basic tutorial to using paged.js, for my own future reference and yours, and I highly recommend you visiting their site and supporting their project. In particular, their <a href="https://www.pagedjs.org/documentation/05-designing-for-print/">documentation</a> covers many aspects of advanced paged media and is a worthwhile next step after reading this guide.</p>
<div class="page-break"></div>
<!-- 3.2 Section 2 - Getting Started -->
<h2 class="top">Getting Started</h2>
<h3 class="top">Installation</h3>
<p>There are three elements required to producing PDFs from HTML through paged.js. First, you will need to load the <a href="https://www.pagedjs.org/documentation/releases/">paged.polyfill.js</a> library either by hosting it locally or linking to it directly:</p>
<code><pre>
<script src="https://unpkg.com/pagedjs/dist/paged.polyfill.js"></script>
</pre></code>
<p>Second, you will need to have a web server as paged.js works by modifying your underlying HTML and CSS. One way is to host statically on <a href="https://pages.github.com">GitHub Pages</a> (or similar), another is use a local server like <a href="https://chrome.google.com/webstore/detail/web-server-for-chrome/ofhbbkphhbklhfoeikjpcbhemlocgigb">Web Server for Chrome</a>.</p>
<p>Finally, you need to have your own custom stylesheet where you will enter all the formatting rules to apply. In our example repo, these rules are contained within <a href="https://github.com/ashok-khanna/pdf/blob/main/style.css">style.css</a>.</p>
<h3>Alternate (easier) Installation</h3>
<p>As an easier alternative, simply clone my <a href="https://github.com/ashok-khanna/pdf">example repo</a> (and set up GitHub Pages for the cloned repo) and then read through <light-mark>style.css</light-mark> and <light-mark>index.html</light-mark> to understand how it works. The rest of this guide takes you through the code example in my repo.</p>
<h3>Media Queries</h3>
<p>Before we begin with the main guide, I want to quickly introduce you to <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries">media queries</a>. Media queries allow us to modify the stylesheet associated with our website depending on whether it is viewed on screen or for print (e.g. when printing to PDF or paper) and allow us to set different style sheets for print, screen and mobile.</p>
<p>A common use case for media queries is to hide navigational menus from a print or mobile view. Say we have defined a class <light-mark>sidebar</light-mark>, we could hide it from a print view, whilst keeping it visible in the primary screen view, as follows.</p>
<code><pre>
@media print {
.sidebar { display: none; }
}
</pre></code>
<p>Whilst we will not make use of media queries in the rest of this guide and in the example repo, please do remember them and use them liberally to create distinct views for screen, print and mobile based on your requirements. They are a central component of responsive web design, a topic we could dedicate an entire book to, but not one for today.</p>
<!-- 3.3 Section 3 - Basic Features -->
<h2 class="no-break">Basic Features</h2>
<h3 class="top">Page Size</h3>
<p>Most properties will be captured in CSS as an attribute to the <mark>@page</mark> rule. For example, we can set the page size to A4 with the following. Other valid page sizes include letter, legal, ledged, A0 to A7, A10, B4 and B5.</p>
<code><pre>
@page { size: A4; }
</pre></code>
<p>Portrait is the default orientation, but you print in landscape by using the following variation. Finally, you can use custom page sizes as the second example below shows.</p>
<code><pre>
@page { size: A4 landscape; }
</pre></code>
<code><pre>
@page { size: 140mm 200mm; }
</pre></code>
<h3>Page Breaks</h3>
<p>By default, paged.js will determine how much content fills up a page and then insert page breaks at the overflow points. We can manually insert page breaks by creating a <light-mark>page-break</light-mark> class and setting its <mark>break-after</mark> property to <mark>page</mark>.</p>
<code><pre>
.page-break { break-after: page; }
</pre></code>
<p>We then simply have to add an empty div in HTML at the points we want a page break:</p>
<code><pre>
<div class="page-break"></div>
</pre></code>
<p>Similarly, we could automatically force a page break before every <h2> tag with the following (which also gives us the opportunity to avoid this behaviour by attaching the <light-mark>no-break</light-mark> class to a <h2> header we do not want to auto-break):</p>
<code><pre>
h2:not(.no-break) { break-before: page; }
</pre></code>
<h3>Page Numbers</h3>
<p>We can use CSS counters to add page numbers. The below CSS will add a page number to the bottom right of every page.</p>
<code><pre>
@page {
@bottom-right { content: counter(page); }
}
</pre></code>
<h3>Headers & Footers</h3>
<p>The preceding page number example gives us a sneak peek into how we could add headers and footers. We can add these in six locations - <light-mark>top-left</light-mark>, <light-mark>top-center</light-mark>, <light-mark>top-right</light-mark>, <light-mark>bottom-left</light-mark>, <light-mark>bottom-center</light-mark> and of course <light-mark>bottom-right</light-mark> as before. Below are some basic examples of simple headers & footers, with the second example showing how we can increase the width in case we need to.</p>
<code><pre>
@page {
@top-right { content: "Confidential" }
@bottom-left {
content: "Beautiful PDFs from HTML";
width: 40mm
}
}
</pre></code>
<!-- 3.4 Section 4 - Advanced Features -->
<h2>Advanced Features</h2>
<h3 class="top">Automatic Counters</h3>
<p>One useful feature is to automatically number sections and figures. We achieve this with the following, noting that we use the class <light-mark>no-counter</light-mark> as a general class to manually exclude a section or figure from the numbering. Note that the subsection and figure counters below reset at every new instance of a section.</p>
<code><pre>
body {
counter-reset: sectionNumber;
counter-reset: subsectionNumber;
counter-reset: figureNumber;
}
h2:not(.no-counter) {
counter-increment: sectionNumber;
}
h2 {
counter-reset: subsectionNumber;
}
h3:not(.no-counter) {
counter-increment: subsectionNumber;
}
h3 {
counter-reset: figureNumber;
}
figure:not(.no-counter) {
counter-increment: figureNumber;
}
</pre></code>
<p>We can then take this one step further and use CSS to automatically generate text labels to reflect the counters within our sections and figures.</p>
<div class="page-break"></div>
<figure>
<figcaption>Code to Auto-Generate Numbering Labels</figcaption>
<code><pre>
h2:not(.no-counter):before {
content: counter(sectionNumber) ". ";
}
h3:not(.no-counter)::before {
content: counter(sectionNumber) "." counter(subsectionNumber) " ";
}
figcaption:not(.no-counter)::before {
content: "Fig. " counter(sectionNumber) "." counter(subsectionNumber) "." counter(figureNumber) " ";
}
</pre></code>
</figure>
<h3>Headers & Foooters (contd.)</h3>
<p>We can generate formatted headers by first creating the formatted header in our HTML and then changing its position to be <mark>running</mark> to move it to each page's footer:</p>
<code><pre>
<p class="footer"><a href="#toc-start">Return to Table of Contents</a></p>
</pre></code>
<code><pre>
.footer {
position: running(footerRunning);
}
@page {
@bottom-left {
content: element(footerRunning);
width: 40mm;
}
}
</pre></code>
<p>To create headers with dynamic content we use the <mark>string-set</mark> property.</p>
<code><pre>
h2 {
string-set: section content(text);
break-before: page;
}
h3 {
string-set: subsection content(text);
}
@page {
@top-right {
content: string(section) " - " string(subsection);
}
}
</pre></code>
<p>Note the value in the header will refer to the first instance of the <mark>string-set</mark> property within the page (i.e. if there are multiple <h3> tags on a page, only the first will be used to populate the header).</h3>
<h3>Cover Pages</h3>
<p>We can create a cover page by defining custom pages in our CSS. Note that a custom page will automatically create a page break after it, so no need to manually break after one. Below is an example.</p>
<code><pre>
<b>In HTML</b>:
<div class="cover-page">
<h1>Beautiful PDFs from HTML</h1>
<h3>An introductory guide by Ashok Khanna</h3>
<h3>April 2021</h3>
</div>
<b>In CSS</b>:
.cover-page { page: cover-page; }
@page cover-page {
background-color: #347DBD;
color: white;
@top-right { content: none; }
@bottom-left { content: none; }
@bottom-right { content: none; }
}
</pre></code>
<!-- 3.5 Section 5 - Other Notes -->
<h2>Other Notes</h2>
<h3 class="top">Table of Contents</h3>
<p>The Paged.Js team wrote a <a href="https://www.pagedjs.org/posts/2020-02-19-toc/">great tutorial on adding table of contents</a> to our pages. The first step is to load <mark>toc.js</mark> from our example repo into our HTML file. We then need to add the following script in our HTML, after the call for paged.js:</p>
<code><pre>
class handlers extends Paged.Handler {
constructor(chunker, polisher, caller) {
super(chunker, polisher, caller);
}
beforeParsed(content){
createToc({
content: content,
tocElement: '#toc',
titleElements: [ 'h2:not(.cover-header)', 'h3:not(.cover-header)' ]
});
}
}
Paged.registerHandlers(handlers);
</pre></code>
<p>We then need to do add our table of contents to our HTML code in the place we want it to appear:</p>
<code><pre>
<div class="table-of-contents">
<h1 class="toc-header" id="toc-start">Table of Contents<</h1>
<ol id="toc">
</ol>
</div>
</pre></code>
<p>Refer to <mark>style.css</mark> in the example repo for advanced formatting of our table of contents.</p>
<h3>Exporting to PDF</h3>
<p>You can export your file to PDF using the browser's <light-mark>file > print</light-mark> option (selecting PDF as the printer). You want to select the print background option and deselect the print headers & footers option.</p>
<div class="page-break"></div>
<figure>
<figcaption>Screenshot of Print Settings in Safari</figcaption>
<center><img src="safari.png" class="img-75mm" /></center>
</figure>
<br /><br />
<figure>
<figcaption>Screenshot of Print Settings in Chrome</figcaption>
<center><img src="chrome.png" class="img-75mm" /></center>
</figure>
<div class="banner-class">
<h3 class="top">Banners</h3>
<p>We can also create banners at the top or bottom of our pages with some more advanced CSS. Below is a hacky example, there is probably a cleaner way to do this.</p>
<figure>
<figcaption>Creating Custom Pages with Banners</figcaption>
<code><pre>
<b>In CSS</b>:
@page top-banner {
@top-left-corner { background-color: #445a75; content: " "; }
@top-left { background-color: #445a75; content: " "; }
@top-center {
background-color: #445a75;
color: white;
content: "Sample Banner Text";
}
@top-right { background-color: #445a75; content: " "; }
@top-right-corner { background-color: #445a75; content: " "; }
}
.banner-class { page: top-banner; }
<b>In HTML</b>:
<div class="banner-class">Content Goes Here<div>
</pre></code>
</figure>
</div>
<!-- 4.0 MathJax Add-on
<script>
window.MathJax = {
loader: { load: ['[tex]/ams'] },
tex: { packages: {'[+]': ['ams']} },
"fast-preview": { disabled: true },
extensions: ["tex2jax.js", "asciimath2jax.js", "TeX/AMSmath.js", "TeX/AMSsymbols.js", "TeX/noErrors.js"],
jax: ["input/TeX", "input/AsciiMath", "output/HTML-CSS"],
displayAlign: "left",
tex2jax: {
inlineMath: [ ["\\(","\\)"] ],
displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
processEscapes: true
},
asciimath2jax: { delimiters: [['`','`']], preview: [""]},
"HTML-CSS": { fonts: ["STIX"] }
}
</script>
<script type="text/javascript" sync src="MathJax-2.7.7/MathJax.js"></script>
-->
<!-- Additional MathJax Code (if you want to re-render math)
<script>
const math = document.getElementsByClassName("math");
MathJax.Hub.Queue(["Typeset",MathJax.Hub,math]);
</script>
-->
</body>
</html>