A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="fr">
  4. <!-- Has to be within the first 1024 bytes, hence before the `title` element
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset="utf-8">
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name="viewport" content="width=device-width,initial-scale=1">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Creating a Nice-Looking PDF with pandoc (archive) — David Larlet</title>
  13. <meta name="description" content="Publication mise en cache pour en conserver une trace.">
  14. <!-- That good ol' feed, subscribe :). -->
  15. <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
  16. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  17. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
  18. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
  19. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
  20. <link rel="manifest" href="/static/david/icons2/site.webmanifest">
  21. <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
  22. <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
  23. <meta name="msapplication-TileColor" content="#f7f7f7">
  24. <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
  25. <meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
  26. <meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
  27. <!-- Documented, feel free to shoot an email. -->
  28. <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
  29. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  30. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  31. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  32. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  33. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  34. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  35. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  36. <script>
  37. function toggleTheme(themeName) {
  38. document.documentElement.classList.toggle(
  39. 'forced-dark',
  40. themeName === 'dark'
  41. )
  42. document.documentElement.classList.toggle(
  43. 'forced-light',
  44. themeName === 'light'
  45. )
  46. }
  47. const selectedTheme = localStorage.getItem('theme')
  48. if (selectedTheme !== 'undefined') {
  49. toggleTheme(selectedTheme)
  50. }
  51. </script>
  52. <meta name="robots" content="noindex, nofollow">
  53. <meta content="origin-when-cross-origin" name="referrer">
  54. <!-- Canonical URL for SEO purposes -->
  55. <link rel="canonical" href="https://plaintextproject.online/articles/2022/04/06/pdf.html">
  56. <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
  57. <article>
  58. <header>
  59. <h1>Creating a Nice-Looking PDF with pandoc</h1>
  60. </header>
  61. <nav>
  62. <p class="center">
  63. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  64. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  65. </svg> Accueil</a> •
  66. <a href="https://plaintextproject.online/articles/2022/04/06/pdf.html" title="Lien vers le contenu original">Source originale</a>
  67. </p>
  68. </nav>
  69. <hr>
  70. <p>If you’re familiar with <a href="https://pandoc.org" target="_blank">pandoc</a>, you know how useful and flexible it is. One of the many formats that you can convert to using pandoc is PDF.</p>
  71. <p>Before version 2 of the tool, you needed the TeX typesetting system (and the LaTeX extensions) installed on your computer to go directly to PDF. TeX is great, but it’s also quite big. And if you’re only producing the occasional PDF, then it’s overkill to have even a basic TeX system installed on your computer.</p>
  72. <p>With version 2 of pandoc came a new option: <em>–pdf-engine</em>. That option lets you specify which tool to use when doing a conversion to PDF. While you can still use LaTeX (and a few other tools) to do the deed, you don’t need to bother with all that bulk.</p>
  73. <p>Let’s take a look at how to create a PDF from a <a href="https://gum.co/learnmarkdown" target="_blank">Markdown</a> file using pandoc and a couple of lighter-weight utilities.</p>
  74. <h2 id="getting-started">Getting Started</h2>
  75. <p>You’ll need pandoc installed on your computer, along with one of these tools:</p>
  76. <p>For our purposes, WeasyPrint and wkhtmltopdf are essentially the same, but there are a couple or three little differences. I’ll touch on one of those differences in a moment.</p>
  77. <h2 id="doing-the-conversion">Doing the Conversion</h2>
  78. <p>As I mentioned a few paragraphs ago, I’m going to look at how to create a PDF from a file formatted with Markdown. To do that, open a terminal window on your computer. Navigate to the folder containing the file that you want to convert to PDF and then type either:</p>
  79. <p><code>pandoc [file-name].md --pdf-engine=weasyprint -o [file-name].pdf</code></p>
  80. <p>or</p>
  81. <p><code>pandoc [file-name].md --pdf-engine=wkhtmltopdf -o [file-name].pdf</code></p>
  82. <p>Where <em>[file-name].md</em> is the name of the Markdown file that you want to convert.</p>
  83. <p><strong>Note:</strong> Some people add the <em>-t html</em> option to those commands to create an HTML file that WeasyPrint and wkhtmltopdf then convert to PDF. The command also works without that option.</p>
  84. <p>Here’s the result of a conversion using wkhtmltopdf:</p>
  85. <figure>
  86. <img src="https://plaintextproject.online/images/pdf/wkhtmltopdf-conversion.png" alt="Markdown file converted to PDF with wkhtmltopdf">
  87. </figure>
  88. <p>What you get looks pretty much the same when you use WeasyPrint to do the conversion. The result is functional, but it’s not the nice PDF file that I promised you in the title of this article. So let’s look at how to add a bit of visual flair to a PDF generated with pandoc.</p>
  89. <h2 id="enter-print-css">Enter Print CSS</h2>
  90. <p><a href="https://en.wikipedia.org/wiki/CSS" target="_blank">Cascading Style Sheets</a> (CSS for short) is a way to change the look and feel of a web page or website. Print CSS extends that to formatting a web page or website to be printed. It adds support for changing page sizes, adding page breaks, hiding elements that shouldn’t be printed, adding a cover page, and more.</p>
  91. <p>I’m not going to into print CSS in any detail here, because it’s a big topic that my little brain has trouble wrapping itself around. However, I’m not going to leave you hanging. Here are some sources of information that can help you get started with print CSS:</p>
  92. <h2 id="using-print-css-in-a-conversion">Using Print CSS in a Conversion</h2>
  93. <p>Let’s say you’ve crafted your own print CSS file or begged/borrowed one from somewhere. To use it, you’ll need to add the <em>–css=</em> option to the string of options that you use with pandoc.</p>
  94. <p>So, once again, open a terminal window on your computer. Navigate to the folder containing the file that you want to convert to PDF and then type either:</p>
  95. <p><code>pandoc [file-name].md --pdf-engine=weasyprint --css=pdf-styles.css -o [file-name].pdf</code></p>
  96. <p>or</p>
  97. <p><code>pandoc [file-name].md --pdf-engine=wkhtmltopdf --css=pdf-styles.css -V papersize:a5 -o [file-name].pdf</code></p>
  98. <p>Change <em>pdf-styles.css</em> to the name of your print CSS file. Here’s an example the output, via WeasyPrint, from a print CSS file that I use:</p>
  99. <figure>
  100. <img src="https://plaintextproject.online/images/pdf/weasyprint-conversion.png" alt="Markdown file converted to PDF with WeasyPrint">
  101. </figure>
  102. <p>My stylesheet, while fairly basic:</p>
  103. <ul>
  104. <li>Sets the size of the page to <a href="https://papersizes.io/a/a5" target="_blank">A5</a>.</li>
  105. <li>Adds page breaks before every Heading 1.</li>
  106. <li>Creates margins of 1 inch (2.5 cm) all around.</li>
  107. <li>Uses the Overpass font for all text.</li>
  108. </ul>
  109. <p>The first point in the list above is one area in which WeasyPrint and wkhtmltopdf differ. wkhtmltopdf ignores page sizes that you specify in the CSS file. Instead, you need to specify the page size in the conversion command using the option <em>-V papersize:a5</em>.</p>
  110. <p>The examples I’ve included in this article are fairly basic. But, I hope, they’ll help spark a few ideas for converting your documents formatted with Markdown (or another markup language) to PDF using pandoc.</p>
  111. </article>
  112. <hr>
  113. <footer>
  114. <p>
  115. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  116. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  117. </svg> Accueil</a> •
  118. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  119. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  120. </svg> Suivre</a> •
  121. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  122. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  123. </svg> Pro</a> •
  124. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  125. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  126. </svg> Email</a> •
  127. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  128. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  129. </svg> Légal</abbr>
  130. </p>
  131. <template id="theme-selector">
  132. <form>
  133. <fieldset>
  134. <legend><svg class="icon icon-brightness-contrast">
  135. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  136. </svg> Thème</legend>
  137. <label>
  138. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  139. </label>
  140. <label>
  141. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  142. </label>
  143. <label>
  144. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  145. </label>
  146. </fieldset>
  147. </form>
  148. </template>
  149. </footer>
  150. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  151. <script>
  152. function loadThemeForm(templateName) {
  153. const themeSelectorTemplate = document.querySelector(templateName)
  154. const form = themeSelectorTemplate.content.firstElementChild
  155. themeSelectorTemplate.replaceWith(form)
  156. form.addEventListener('change', (e) => {
  157. const chosenColorScheme = e.target.value
  158. localStorage.setItem('theme', chosenColorScheme)
  159. toggleTheme(chosenColorScheme)
  160. })
  161. const selectedTheme = localStorage.getItem('theme')
  162. if (selectedTheme && selectedTheme !== 'undefined') {
  163. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  164. }
  165. }
  166. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  167. window.addEventListener('load', () => {
  168. let hasDarkRules = false
  169. for (const styleSheet of Array.from(document.styleSheets)) {
  170. let mediaRules = []
  171. for (const cssRule of styleSheet.cssRules) {
  172. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  173. continue
  174. }
  175. // WARNING: Safari does not have/supports `conditionText`.
  176. if (cssRule.conditionText) {
  177. if (cssRule.conditionText !== prefersColorSchemeDark) {
  178. continue
  179. }
  180. } else {
  181. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  182. continue
  183. }
  184. }
  185. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  186. }
  187. // WARNING: do not try to insert a Rule to a styleSheet you are
  188. // currently iterating on, otherwise the browser will be stuck
  189. // in a infinite loop…
  190. for (const mediaRule of mediaRules) {
  191. styleSheet.insertRule(mediaRule.cssText)
  192. hasDarkRules = true
  193. }
  194. }
  195. if (hasDarkRules) {
  196. loadThemeForm('#theme-selector')
  197. }
  198. })
  199. </script>
  200. </body>
  201. </html>