├── work └── .gitignore ├── .jshintrc ├── dist └── all.pdf ├── .gitignore ├── doc ├── fig001.png ├── fig002.png ├── toc.md ├── preface.md ├── appendix.md ├── template.html └── main.md ├── README.md ├── .eslintrc.json ├── scripts ├── ejs.js └── mdit.js ├── LICENSE ├── package.json └── css ├── custom.css └── github-markdown.css /work/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esversion": 8 4 | } -------------------------------------------------------------------------------- /dist/all.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2SC1815J/md2pdf/HEAD/dist/all.pdf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules/ 3 | work/* 4 | .vscode/ 5 | .git/ -------------------------------------------------------------------------------- /doc/fig001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2SC1815J/md2pdf/HEAD/doc/fig001.png -------------------------------------------------------------------------------- /doc/fig002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2SC1815J/md2pdf/HEAD/doc/fig002.png -------------------------------------------------------------------------------- /doc/toc.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |

目次

6 | 7 | 8 |
9 |
10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /doc/preface.md: -------------------------------------------------------------------------------- 1 | # はじめに 2 | 3 | この文書は、次のような方に向けた事例紹介です。 4 | - Visual Studio Codeで、ソフトウェア開発に付随する報告書などの作成も行いたい 5 | - 文書はMarkdownで執筆し、PDFで提出したい 6 | - コマンドライン操作のみでPDFビルドが完了するようにしたい 7 | - GitHubにおけるMarkdown表示のようなスタイリングにしたい 8 | - PDFには表紙や目次、ノンブル(ページ番号)を付けたい 9 | - 目次には、項目タイトルとページ番号、ドキュメント内リンクが自動的に生成されてほしい 10 | - ノンブルは、表紙や目次を除いた本文ページのみに振りたい 11 | - PDFには余計なプロパティを含めたくない 12 | 13 | 上記の要件を全て満たすPDFを得るために筆者が用いたフローを紹介します。(上記の要件の一部が不要であれば、また違ったフローがあり得るでしょう。) 14 | 15 | ここで取り上げる内容は、筆者の環境で手間をかけずに実現するために用いたフローであって、ベストプラクティスではありません。汎用性に欠ける点もありますが、より良い実現方法が共有されていく叩き台になれば幸いです。 16 | 17 | 本稿の公開にあたり、鹿野桂一郎さんにレビューのご協力をいただきました。ありがとうございました。 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # md2pdf 2 | 3 | Markdown原稿から、CSS組版によってPDFにビルドするフローを紹介します。 4 | 5 | ## こんなときに 6 | 7 | - Visual Studio Codeで、ソフトウェア開発に付随する報告書などの作成も行いたい 8 | - 文書はMarkdownで執筆し、PDFで提出したい 9 | - コマンドライン操作のみでPDFビルドが完了するようにしたい 10 | - GitHubでのMarkdown表示のようなスタイリングにしたい 11 | - PDFには表紙や目次、ノンブル(ページ番号)を付けたい 12 | - 目次は、項目タイトルとページ番号、ドキュメント内リンクを自動作成してほしい 13 | - ノンブルは、表紙や目次を除いた本文ページのみに振りたい 14 | - PDFには余計なプロパティを含めたくない 15 | 16 | ## 必要条件 17 | 18 | - [Node.js](https://nodejs.org/ja/) 10+ 19 | - [AH CSS Formatter](https://www.antenna.co.jp/AHF/) V6.6 MR5+(評価版でも動作可) 20 | 21 | ## インストール 22 | 23 | ``` 24 | $ npm install 25 | ``` 26 | 27 | ## PDFのビルド 28 | 29 | ``` 30 | $ npm run build 31 | ``` 32 | distフォルダに、CSS組版された[PDF](dist/all.pdf)が出力されます。 33 | 34 | ## ライセンス 35 | 36 | MITライセンス 37 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "es6": true 5 | }, 6 | "parserOptions": { 7 | "ecmaVersion": 2017 8 | }, 9 | "extends": "eslint:recommended", 10 | "rules": { 11 | "indent": [ 12 | "warn", 13 | 4 14 | ], 15 | "linebreak-style": [ 16 | "error", 17 | "unix" 18 | ], 19 | "quotes": [ 20 | "error", 21 | "single" 22 | ], 23 | "semi": [ 24 | "error", 25 | "always" 26 | ], 27 | "no-unused-vars": [ 28 | "error", 29 | {} 30 | ], 31 | "no-console": "off", 32 | "no-trailing-spaces": [ 33 | "warn", 34 | {"ignoreComments": true} 35 | ] 36 | } 37 | } -------------------------------------------------------------------------------- /doc/appendix.md: -------------------------------------------------------------------------------- 1 | # 付録 ソースファイル抜粋 2 | 3 | この文書の作成に用いたソースファイル一式のファイル構成と、主要なファイルの内容を挙げます。 4 | 5 | ## ファイル構成 6 | 7 | ``` 8 | md2pdf 9 | │ package.json 10 | │ 11 | ├─ css 12 | │ custom.css 13 | │ github-markdown.css 14 | │ 15 | ├─ dist 16 | │ all.html 17 | │ all.pdf 18 | │ 19 | ├─ doc 20 | │ appendix.md 21 | │ fig001.png 22 | │ fig002.png 23 | │ main.md 24 | │ preface.md 25 | │ template.html 26 | │ toc.md 27 | │ 28 | ├─ node_modules 29 | │ 30 | ├─ scripts 31 | │ ejs.js 32 | │ mdit.js 33 | │ 34 | └─ work 35 | ``` 36 | 37 | ## package.json 38 | 39 |
<%= include('../package.json') %>
40 | 41 | ## scripts/mdit.js 42 | 43 |
<%= include('../scripts/mdit.js') %>
44 | 45 | ## scripts/ejs.js 46 | 47 |
<%= include('../scripts/ejs.js') %>
48 | -------------------------------------------------------------------------------- /scripts/ejs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * md2html 3 | * Copyright 2019 2SC1815J, MIT license 4 | */ 5 | 'use strict'; 6 | if (process.argv.length < 4) { 7 | console.error('Usage: node ejs.js template.html output.html'); 8 | process.exit(1); 9 | } 10 | 11 | const { promisify } = require('util'); 12 | const ejs = require('ejs'); 13 | const tidy = require('htmltidy2'); 14 | const fs = require('fs'); 15 | 16 | (async () => { 17 | const text = await promisify(ejs.renderFile)(process.argv[2]); 18 | const options = { 19 | doctype: 'html5', 20 | indent: 'auto', 21 | wrap: 0, 22 | tidyMark: false, 23 | quoteAmpersand: false, 24 | hideComments: true, 25 | dropEmptyElements: false, 26 | newline: 'LF' 27 | }; 28 | const tidied = await promisify(tidy.tidy)(text, options); 29 | await promisify(fs.writeFile)(process.argv[3], tidied, 'utf8'); 30 | })() 31 | .then(() => { 32 | console.log('Done.'); 33 | }) 34 | .catch((err) => { 35 | console.error(err); 36 | process.exit(1); 37 | }); -------------------------------------------------------------------------------- /doc/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | AH Formatterを用いたMarkdown-PDF変換事例紹介 10 | 11 | 12 | 13 | 14 |
15 |

AH Formatterを用いた
Markdown-PDF変換事例紹介

16 |
2019年5月
17 |
フェリックス・スタイル
18 |
19 |
20 | <%- include('../work/all_md.html') %> 21 |
22 |
23 |
24 | AH Formatterを用いたMarkdown-PDF変換事例紹介
25 | 2019年5月1日
26 | Copyright © 2019 Jun HOMMA (@2SC1815J) 27 |
28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 2SC1815J 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "md2pdf", 3 | "version": "1.0.0", 4 | "description": "Convert Markdown documents to PDF", 5 | "scripts": { 6 | "build:doc-1": "npx minicat doc/preface.md doc/toc.md doc/main.md doc/appendix.md > work/all.md", 7 | "build:doc-2": "npx doctoc --notitle --maxlevel 3 work/all.md", 8 | "build:doc-3": "node scripts/mdit.js work/all.md work/all_md.html", 9 | "build:doc-4": "node scripts/ejs.js doc/template.html work/all.html", 10 | "build:doc-5": "npx html-inline work/all.html -b doc -o dist/all.html", 11 | "build:doc-6": "AHFCmd -d dist/all.html -p @PDF -pdfver 1.5 -base \" \" -x 4 -pgbar -o dist/all.pdf", 12 | "build": "npm run build:doc-1 && npm run build:doc-2 && npm run build:doc-3 && npm run build:doc-4 && npm run build:doc-5 && npm run build:doc-6" 13 | }, 14 | "keywords": [ 15 | "Markdown", 16 | "PDF" 17 | ], 18 | "author": "2SC1815J", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "anchor-markdown-header": "^0.5.7", 22 | "doctoc": "^1.4.0", 23 | "ejs": "^2.6.1", 24 | "eslint": "^5.16.0", 25 | "html-inline": "^1.2.0", 26 | "htmltidy2": "^0.3.0", 27 | "markdown-it": "^8.4.2", 28 | "markdown-it-implicit-figures": "^0.9.0", 29 | "markdown-it-named-headers": "0.0.4", 30 | "minicat": "^1.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /scripts/mdit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * md2html 3 | * Copyright 2019 2SC1815J, MIT license 4 | */ 5 | 'use strict'; 6 | if (process.argv.length < 4) { 7 | console.error('Usage: node mdit.js input.md output.html'); 8 | process.exit(1); 9 | } 10 | 11 | const header_instances = {}; 12 | const anchor = require('anchor-markdown-header'); 13 | const mdit = require('markdown-it')( 14 | { 15 | html: true 16 | }) 17 | .use(require('markdown-it-named-headers'), { 18 | slugify: function(header) { 19 | if (header_instances[header] !== void 0) { 20 | header_instances[header]++; 21 | } else { 22 | header_instances[header] = 0; 23 | } 24 | const match = anchor(header, 'github.com', header_instances[header]).match(/]\(#(.+?)\)$/); 25 | return match ? decodeURI(match[1]) : header; 26 | } 27 | }) 28 | .use(require('markdown-it-implicit-figures'), { 29 | figcaption: true 30 | }); 31 | 32 | const { promisify } = require('util'); 33 | const fs = require('fs'); 34 | 35 | (async () => { 36 | const md = await promisify(fs.readFile)(process.argv[2], 'utf8'); 37 | const html = mdit.render(md); 38 | await promisify(fs.writeFile)(process.argv[3], html, 'utf8'); 39 | })() 40 | .then(() => { 41 | console.log('Done.'); 42 | }) 43 | .catch((err) => { 44 | console.error(err); 45 | process.exit(1); 46 | }); -------------------------------------------------------------------------------- /css/custom.css: -------------------------------------------------------------------------------- 1 | @media screen { 2 | body { 3 | box-sizing: border-box; 4 | max-width: 980px; 5 | margin: 0 auto; 6 | padding: 30px 45px; 7 | } 8 | .markdown-body { 9 | margin-bottom: 1em; 10 | } 11 | .FrontCover { 12 | margin-bottom: 60px; 13 | } 14 | .Footnote::before { 15 | content: "("; 16 | } 17 | .Footnote::after { 18 | content: ")"; 19 | } 20 | } 21 | 22 | @media print { 23 | body { 24 | box-sizing: border-box; 25 | } 26 | 27 | /* github-markdown-cssのカスタマイズ */ 28 | .markdown-body { 29 | font-family: "Source Han Serif JP", "MS PMincho", serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Source Han Sans JP", "Hiragino Kaku Gothic Pro", "MS PGothic"; 30 | } 31 | h1, h2, h3, h4, h5, h6 { 32 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Source Han Sans JP", "Hiragino Kaku Gothic Pro", "MS PGothic"; 33 | } 34 | .bg-colored { 35 | background-color: #f6f8fa !important; 36 | } 37 | .bg-white { 38 | background-color: #ffffff !important; 39 | } 40 | table { 41 | margin-left: auto; 42 | margin-right: auto; 43 | display: table !important; 44 | margin-bottom: 1em !important; 45 | } 46 | table.page-break-inside-avoid { 47 | page-break-inside: avoid; 48 | } 49 | figure { 50 | margin-bottom: 1em; 51 | text-align: center; 52 | page-break-inside: avoid; 53 | } 54 | caption, 55 | figcaption { 56 | font-family: "Source Han Sans JP", source-sans-pro, source-han-sans, sans-serif; 57 | } 58 | pre { 59 | white-space: pre-wrap !important; 60 | } 61 | code { 62 | white-space: pre-wrap !important; 63 | } 64 | 65 | /* 表紙 */ 66 | .FrontCover { 67 | font-family: "Source Han Sans JP", source-sans-pro, source-han-sans, sans-serif; 68 | } 69 | /* 文書タイトル */ 70 | .FrontCover h1 { 71 | margin-top: 55mm; 72 | margin-bottom: 140mm; 73 | font-weight: 900; 74 | font-size: 28pt; 75 | text-align: center; 76 | page-break-after: avoid; 77 | } 78 | /* 宛先 */ 79 | .Attn { 80 | font-weight: 600; 81 | font-size: 18pt; 82 | page-break-after: avoid; 83 | } 84 | /* 著者 */ 85 | .Author { 86 | margin-bottom: 4mm; 87 | text-align: center; 88 | font-size: 16pt; 89 | line-height: 1em; 90 | } 91 | /* 日付 */ 92 | .Published { 93 | margin-bottom: 4mm; 94 | text-align: center; 95 | font-size: 16pt; 96 | line-height: 1em; 97 | } 98 | 99 | /* はじめに */ 100 | article:first-of-type { 101 | page-break-before: always; 102 | page: Preface; 103 | } 104 | 105 | /* 目次 */ 106 | .TOC a::after { 107 | content: leader(dotted) " " target-counter(attr(href), page); 108 | } 109 | .TOC { 110 | page: TOC; 111 | } 112 | 113 | /* 本文 */ 114 | article { 115 | counter-reset: page 1; 116 | } 117 | article h1:nth-child(n+2) { 118 | page-break-before: always; 119 | } 120 | 121 | /* 脚注 */ 122 | .Footnote { 123 | text-align: justify; 124 | float: footnote; 125 | text-indent: 0; 126 | font-size: 10pt; 127 | line-height: 1.2; 128 | font-weight: normal; 129 | } 130 | ::footnote-call { 131 | content: counter(footnote, decimal) ")"; 132 | font-size: 10pt; 133 | font-weight: normal; 134 | vertical-align: super; 135 | } 136 | ::footnote-marker { 137 | content: counter(footnote, decimal) ")"; 138 | font-weight: normal; 139 | font-size: 1em; 140 | vertical-align: baseline; 141 | } 142 | 143 | /* 裏表紙 */ 144 | .BackCover { 145 | page: BackCover; 146 | font-family: "Source Han Sans JP", source-sans-pro, source-han-sans, sans-serif; 147 | } 148 | .Colophon { 149 | text-align: justify; 150 | float: bottom page; 151 | text-indent: 0; 152 | font-size: 10pt; 153 | line-height: 1.2; 154 | font-weight: normal; 155 | border-top: .5pt solid black; 156 | padding-top: 0.5em; 157 | } 158 | 159 | /* ブックマーク */ 160 | .markdown-body h1 { 161 | bookmark-level: 1; 162 | } 163 | .markdown-body h2 { 164 | bookmark-level: 2; 165 | } 166 | .markdown-body h3 { 167 | bookmark-level: 3; 168 | } 169 | 170 | @page { 171 | size: A4; 172 | @bottom-center { 173 | content: counter(page); 174 | } 175 | @footnote { 176 | float: page bottom; 177 | border-top: .5pt solid black; 178 | border-length: 30% 0; 179 | margin-top: 0.4em; 180 | padding-top: 0.3em; 181 | padding-left: 15pt; 182 | } 183 | } 184 | @page :first { 185 | @bottom-center { 186 | content: none; 187 | } 188 | } 189 | @page Preface { 190 | @bottom-center { 191 | content: counter(page, lower-roman); 192 | } 193 | } 194 | @page TOC { 195 | @bottom-center { 196 | content: none; 197 | } 198 | } 199 | @page BackCover { 200 | @bottom-center { 201 | content: none; 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /doc/main.md: -------------------------------------------------------------------------------- 1 | # MarkdownからCSS組版によりPDF文書を生成する 2 | 3 | 本稿では、Markdown形式の原稿から、表紙や目次、ノンブルなどを備えたPDFの文書へと変換するフローを紹介します。 4 | 5 | MarkdownをPDFに変換するフローとしては、ブラウザのレンダリング機能を利用する手法や、Pandocを利用しLaTeXを経由してPDFを得る手法がよく知られています。しかし、前者は組版機能が十分ではないケースや、グラフィカルユーザインターフェイスでの操作を必要とするケースもあり、後者は見た目の調整にTeXの知識が必要です。本稿ではAH CSS Formatterを利用したCSS組版によってPDFを得る手法を紹介します。この手法には以下の特徴があります。 6 | - 表紙や目次を除いてノンブルを振るなどの組版処理が可能 7 | - コマンドライン操作のみで完結し、繰り返しビルドが容易 8 | - TeXの知識がなくてもPDFの見た目を調整可能 9 | 10 | ## 変換の方針 11 | 12 | AH CSS FormatterによるCSS組版のために、まずはMarkdownの原稿をHTMLへと変換します。具体的には下記のような前処理を行います。 13 | 14 | - 複数のMarkdownファイル(前書き、本文、付録など)を結合する 15 | - 結合したMarkdownの見出し要素から目次を生成する 16 | - 目次を含むMarkdownファイルからHTMLファイルへと変換する 17 | - HTMLファイルにメタデータなどを付与する 18 | - 画像などをHTMLファイル中にインライン化して埋め込む 19 | 20 | MarkdownやHTMLに対するこれらの操作では、JavaScript製の便利なツールがNode.jsパッケージとして利用できます。そこで今回は、AH CSS FormatterによるCSS組版を含めた処理全体を、Node.jsのパッケージ管理ツールであるnpmにより実行できるようにしました。 21 | 22 | 以降では、npmのタスク実行機能を用いて上記の前処理およびCSS組版を逐次実行しMarkdown原稿からPDFへとビルドする手順を、この文書そのものを例に紹介します。その後、ビルドプロセスの各ステップについて、簡単に内容を説明していきます。 23 | 24 | ## 実際にMarkdownからPDFのビルドを試す 25 | 26 | この文書の作成に用いたソースファイル一式「[md2pdf](https://github.com/2SC1815J/md2pdf)」https://github.com/2SC1815J/md2pdfをダウンロードして、Markdown原稿からPDFへのビルドを試すことができます。 27 | 28 | ### 動作環境 29 | 30 | ここで紹介するフローは、以下の環境で動作します。 31 | 32 | - Node.js 10+ 33 | - AH CSS Formatter V6.6 MR5+ 34 | 35 | また、必須ではありませんが、執筆作業のための環境としてVisual Studio Code(VS Code)があると便利でしょう。以降では、VS CodeでMarkdown原稿の編集やプレビューを設定する方法や、PDFへのビルドなどを一つのウィンドウでできるようにする方法も説明します。 36 | 37 | ### セットアップ 38 | 39 | ソースファイル一式をダウンロードしたら、適当な場所に解凍してください。 40 | 41 | 次に、VS Codeを用いる場合には、VS Codeの「ファイル」メニューから「フォルダーを開く」を選択し、解凍したフォルダを開いてください。続いて、「ターミナル」メニューから「新しいターミナル」を選択します。VS Codeを用いない場合は、ターミナル(Windowsであればコマンドプロンプトなど)を開き、解凍したフォルダに移動してください。 42 | 43 | 準備ができたら、ターミナルから以下のコマンドを実行してください。MarkdownからPDFへビルドするプロセスに必要なNode.jsパッケージがインストールされます。 44 | 45 | ``` 46 | npm install 47 | ``` 48 | 49 | ![VS Codeのターミナル(右下)](fig001.png) 50 | 51 | ### Markdown Preview Github Stylingの導入 52 | 53 | VS CodeでMarkdown原稿を執筆する場合、VS Codeの拡張機能「[Markdown Preview Github Styling](https://marketplace.visualstudio.com/itemdetails?itemName=bierner.markdown-preview-github-styles)」https://marketplace.visualstudio.com/itemdetails?itemName=bierner.markdown-preview-github-stylesをインストールすると、MarkdownがGitHub風のスタイリングでプレビュー表示されるようになり、変換結果をイメージしやすくなります。 54 | 55 | ![「Markdown Preview Github Styling」によるプレビュー表示](fig002.png) 56 | 57 | ### 複雑な表の記述 58 | 59 | Markdownでは表現力が十分でない部分(セルが結合された表など)は、表1のように、Markdown中にHTMLで直接記述しています(もちろん、生成されたPDF上では、HTMLではなく表として描画されているはずです)。 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 |
表1
見出し1見出し2
見出し2-1見出し2-2
 
 
82 | 83 | なお、AH Formatter V6.6 MR4では、結合されたセルの背景色が正しく設定されないことがありました。この問題はAH Formatter V6.6 MR5で修正されています。AH Formatter V6.6 MR4までの場合、明示的にクラス指定を行うことで対処します。 84 | 85 | ### PDFのビルド 86 | 87 | ターミナルから次のコマンドを実行することで、docフォルダ内のMarkdown原稿からHTMLとPDFが生成され、distフォルダに出力されます。VS Codeを利用している場合は、VS Codeの`npm.enableScriptExplorer`設定を有効にすることで、コマンドを入力しなくてもマウスクリックでビルドを実行できるようになります。 88 | 89 | ``` 90 | npm run build 91 | ``` 92 | 93 | このビルドプロセスは、package.jsonファイルのscriptsプロパティに記載された、`build:doc-1`から`build:doc-6`のステップに従って実行されます。以降では、この各ステップに沿って、Markdown原稿をPDFへと変換するまでの処理内容を見ていきます。 94 | 95 | ## Markdown原稿から目次を生成 96 | 97 | ### `build:doc-1` Markdownファイルの連結 98 | 99 | 目次のひな形(doc/toc.md)を、複数のMarkdown原稿(doc/preface.md, doc/main.md, doc/appendix.md)と合わせて、単一のMarkdownファイルにします。 100 | 101 | ``` 102 | npx minicat doc/preface.md doc/toc.md doc/main.md doc/appendix.md > work/all.md 103 | ``` 104 | 105 | このステップによって、仮Markdownファイル(work/all.md)が生成されます。 106 | 107 | ### `build:doc-2` DocTocによる目次生成 108 | 109 | Node.jsパッケージ「[DocToc](https://github.com/thlorenz/doctoc)」https://github.com/thlorenz/doctocを用いて、目次を生成します。Markdownに記述された見出し要素が抽出され、目次が自動的に作成されます。 110 | 111 | ``` 112 | npx doctoc --notitle --maxlevel 3 work/all.md 113 | ``` 114 | 115 | このステップによって、仮Markdownファイル(work/all.md)の目次が更新されます。 116 | 117 | ## MarkdownからHTMLへ変換 118 | 119 | ### `build:doc-3` markdown-itによる変換 120 | 121 | Node.jsパッケージ「[markdown-it](https://github.com/markdown-it/markdown-it)」https://github.com/markdown-it/markdown-itを用いて、MarkdownからHTMLへ変換します。 122 | 123 | ``` 124 | node scripts/mdit.js work/all.md work/all_md.html 125 | ``` 126 | 127 | Markdownの見出し要素を日本語で記述しており、目次部分のhrefにURIエンコードされた日本語文字列が設定されている場合、AH Formatter V6.6 MR4では、CSS組版によるページ番号表示(target-counter(attr(href))の処理)が正しく動作しない問題がありましたが、AH Formatter V6.6 MR5で修正されました。AH Formatter V6.6 MR4までの場合、Markdownの見出し要素からアンカーを作成するNode.jsパッケージ「[anchor-markdown-header](https://github.com/thlorenz/anchor-markdown-header)」に変更を加えて対処する方法があります。 128 | 129 | このステップによって、部分的なHTMLファイル(work/all_md.html)が生成されます。 130 | 131 | ### `build:doc-4` Markdown原稿に含まれない内容を付与 132 | 133 | markdown-itが生成するHTMLには、<html>や<head>、<body>、CSSファイルへのリンクなどは含まれていません。これらを記述したテンプレートHTMLファイル(doc/template.html)を別途用意し、markdown-itによって生成されたHTMLがその中に含まれるようにします。今回は表紙の内容もここに記述しました。 134 | 135 | ``` 136 | node scripts/ejs.js doc/template.html work/all.html 137 | ``` 138 | 139 | AH Formatter V6.6では、HTMLファイルのmetaタグに次の記載を含めることで、このHTMLから変換されたPDFをビューワで開いたときにページ全体がウィンドウに収まっている状態にズームさせることができます。 140 | ``` 141 | 142 | ``` 143 | 144 | また、AH Formatter V6.6 MR5からは、次の記載を含めることで、生成されるPDFの文書情報に含まれる作成日(/CreationDate)と更新日(/ModDate)を指定の値に設定することができます。AH Formatter V6.6 MR4までの場合、「[Coherent PDF Command Line Tools (cpdf)](https://community.coherentpdf.com/)」を利用して設定する方法があります。 145 | ``` 146 | 147 | 148 | ``` 149 | 150 | GitHubでのMarkdown表示のようなスタイリングを実現するには、CSSファイル「[github-markdown-css](https://github.com/sindresorhus/github-markdown-css)」https://github.com/sindresorhus/github-markdown-cssが利用できます(css/github-markdown.css)。 151 | 152 | CSS組版用のCSSファイル(css/custom.css)を、「[CSSページ組版入門](https://www.antenna.co.jp/AHF/ahf_publication/index.html#CSSPrint)」https://www.antenna.co.jp/AHF/ahf_publication/index.html#CSSPrintなどを参考にして記述し、テンプレートHTMLファイルから読み込まれるようにしておきます。 153 | 154 | このステップによって、仮HTMLファイル(work/all.html)が生成されます。 155 | 156 | CSS組版用のCSSファイルを調整する場合は、このHTMLファイルをAH Formatterのグラフィカルユーザインターフェイスで読み込み、組版結果を確認しながら行うと良いでしょう。 157 | 158 | ### `build:doc-5` html-inlineによるインライン化 159 | 160 | Node.jsパッケージ「[html-inline](https://github.com/substack/html-inline)」https://github.com/substack/html-inlineを用いて、HTMLファイルから参照されているCSSファイルや画像ファイルをHTMLファイル内にインライン化(埋め込み)します。AH Formatter V6.6 MR5までの場合、生成されるPDFのプロパティ「ベースURL」に生成元HTMLファイルの場所が表示され、同表示を空にするには、HTMLファイルから参照されている外部ファイルをインライン化した上で、PDF出力時にコマンドラインパラメータ指定(-base " ")を行う必要がありました。AH Formatter V6.6 MR6からは、これらの処理は不要となっています。 161 | 162 | ``` 163 | npx html-inline work/all.html -b doc -o dist/all.html 164 | ``` 165 | 166 | このステップによって、必要な情報が全て含まれたHTMLファイル(dist/all.html)が生成されます。 167 | 168 | ## HTMLからPDFへ変換 169 | 170 | ### `build:doc-6` AHFCmdによる変換 171 | 172 | AH FormatterのコマンドラインインターフェイスAHFCmdを用いて、必要な情報が全て含まれたHTMLファイルをCSS組版し、PDFファイルとして出力します。 173 | 174 | ``` 175 | AHFCmd -d dist/all.html -p @PDF -pdfver 1.5 -base " " -x 4 -pgbar -o dist/all.pdf 176 | ``` 177 | 178 | このステップによって、最終的なPDFファイル(dist/all.pdf)が生成されます。 179 | -------------------------------------------------------------------------------- /css/github-markdown.css: -------------------------------------------------------------------------------- 1 | /* github-markdown-css v3.0.1 2 | * https://github.com/sindresorhus/github-markdown-css 3 | * 4 | * MIT License 5 | * Copyright (c) Sindre Sorhus (sindresorhus.com) 6 | */ 7 | @font-face { 8 | font-family: octicons-link; 9 | src: url(data:font/woff;charset=utf-8;base64,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) format('woff'); 10 | } 11 | 12 | .markdown-body .octicon { 13 | display: inline-block; 14 | fill: currentColor; 15 | vertical-align: text-bottom; 16 | } 17 | 18 | .markdown-body .anchor { 19 | float: left; 20 | line-height: 1; 21 | margin-left: -20px; 22 | padding-right: 4px; 23 | } 24 | 25 | .markdown-body .anchor:focus { 26 | outline: none; 27 | } 28 | 29 | .markdown-body h1 .octicon-link, 30 | .markdown-body h2 .octicon-link, 31 | .markdown-body h3 .octicon-link, 32 | .markdown-body h4 .octicon-link, 33 | .markdown-body h5 .octicon-link, 34 | .markdown-body h6 .octicon-link { 35 | color: #1b1f23; 36 | vertical-align: middle; 37 | visibility: hidden; 38 | } 39 | 40 | .markdown-body h1:hover .anchor, 41 | .markdown-body h2:hover .anchor, 42 | .markdown-body h3:hover .anchor, 43 | .markdown-body h4:hover .anchor, 44 | .markdown-body h5:hover .anchor, 45 | .markdown-body h6:hover .anchor { 46 | text-decoration: none; 47 | } 48 | 49 | .markdown-body h1:hover .anchor .octicon-link, 50 | .markdown-body h2:hover .anchor .octicon-link, 51 | .markdown-body h3:hover .anchor .octicon-link, 52 | .markdown-body h4:hover .anchor .octicon-link, 53 | .markdown-body h5:hover .anchor .octicon-link, 54 | .markdown-body h6:hover .anchor .octicon-link { 55 | visibility: visible; 56 | } 57 | 58 | .markdown-body { 59 | -ms-text-size-adjust: 100%; 60 | -webkit-text-size-adjust: 100%; 61 | color: #24292e; 62 | line-height: 1.5; 63 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol; 64 | font-size: 16px; 65 | line-height: 1.5; 66 | word-wrap: break-word; 67 | } 68 | 69 | .markdown-body .pl-c { 70 | color: #6a737d; 71 | } 72 | 73 | .markdown-body .pl-c1, 74 | .markdown-body .pl-s .pl-v { 75 | color: #005cc5; 76 | } 77 | 78 | .markdown-body .pl-e, 79 | .markdown-body .pl-en { 80 | color: #6f42c1; 81 | } 82 | 83 | .markdown-body .pl-s .pl-s1, 84 | .markdown-body .pl-smi { 85 | color: #24292e; 86 | } 87 | 88 | .markdown-body .pl-ent { 89 | color: #22863a; 90 | } 91 | 92 | .markdown-body .pl-k { 93 | color: #d73a49; 94 | } 95 | 96 | .markdown-body .pl-pds, 97 | .markdown-body .pl-s, 98 | .markdown-body .pl-s .pl-pse .pl-s1, 99 | .markdown-body .pl-sr, 100 | .markdown-body .pl-sr .pl-cce, 101 | .markdown-body .pl-sr .pl-sra, 102 | .markdown-body .pl-sr .pl-sre { 103 | color: #032f62; 104 | } 105 | 106 | .markdown-body .pl-smw, 107 | .markdown-body .pl-v { 108 | color: #e36209; 109 | } 110 | 111 | .markdown-body .pl-bu { 112 | color: #b31d28; 113 | } 114 | 115 | .markdown-body .pl-ii { 116 | background-color: #b31d28; 117 | color: #fafbfc; 118 | } 119 | 120 | .markdown-body .pl-c2 { 121 | background-color: #d73a49; 122 | color: #fafbfc; 123 | } 124 | 125 | .markdown-body .pl-c2:before { 126 | content: "^M"; 127 | } 128 | 129 | .markdown-body .pl-sr .pl-cce { 130 | color: #22863a; 131 | font-weight: 700; 132 | } 133 | 134 | .markdown-body .pl-ml { 135 | color: #735c0f; 136 | } 137 | 138 | .markdown-body .pl-mh, 139 | .markdown-body .pl-mh .pl-en, 140 | .markdown-body .pl-ms { 141 | color: #005cc5; 142 | font-weight: 700; 143 | } 144 | 145 | .markdown-body .pl-mi { 146 | color: #24292e; 147 | font-style: italic; 148 | } 149 | 150 | .markdown-body .pl-mb { 151 | color: #24292e; 152 | font-weight: 700; 153 | } 154 | 155 | .markdown-body .pl-md { 156 | background-color: #ffeef0; 157 | color: #b31d28; 158 | } 159 | 160 | .markdown-body .pl-mi1 { 161 | background-color: #f0fff4; 162 | color: #22863a; 163 | } 164 | 165 | .markdown-body .pl-mc { 166 | background-color: #ffebda; 167 | color: #e36209; 168 | } 169 | 170 | .markdown-body .pl-mi2 { 171 | background-color: #005cc5; 172 | color: #f6f8fa; 173 | } 174 | 175 | .markdown-body .pl-mdr { 176 | color: #6f42c1; 177 | font-weight: 700; 178 | } 179 | 180 | .markdown-body .pl-ba { 181 | color: #586069; 182 | } 183 | 184 | .markdown-body .pl-sg { 185 | color: #959da5; 186 | } 187 | 188 | .markdown-body .pl-corl { 189 | color: #032f62; 190 | text-decoration: underline; 191 | } 192 | 193 | .markdown-body details { 194 | display: block; 195 | } 196 | 197 | .markdown-body summary { 198 | display: list-item; 199 | } 200 | 201 | .markdown-body a { 202 | background-color: transparent; 203 | } 204 | 205 | .markdown-body a:active, 206 | .markdown-body a:hover { 207 | outline-width: 0; 208 | } 209 | 210 | .markdown-body strong { 211 | font-weight: inherit; 212 | font-weight: bolder; 213 | } 214 | 215 | .markdown-body h1 { 216 | font-size: 2em; 217 | margin: .67em 0; 218 | } 219 | 220 | .markdown-body img { 221 | border-style: none; 222 | } 223 | 224 | .markdown-body code, 225 | .markdown-body kbd, 226 | .markdown-body pre { 227 | font-family: monospace,monospace; 228 | font-size: 1em; 229 | } 230 | 231 | .markdown-body hr { 232 | box-sizing: content-box; 233 | height: 0; 234 | overflow: visible; 235 | } 236 | 237 | .markdown-body input { 238 | font: inherit; 239 | margin: 0; 240 | } 241 | 242 | .markdown-body input { 243 | overflow: visible; 244 | } 245 | 246 | .markdown-body [type=checkbox] { 247 | box-sizing: border-box; 248 | padding: 0; 249 | } 250 | 251 | .markdown-body * { 252 | box-sizing: border-box; 253 | } 254 | 255 | .markdown-body input { 256 | font-family: inherit; 257 | font-size: inherit; 258 | line-height: inherit; 259 | } 260 | 261 | .markdown-body a { 262 | color: #0366d6; 263 | text-decoration: none; 264 | } 265 | 266 | .markdown-body a:hover { 267 | text-decoration: underline; 268 | } 269 | 270 | .markdown-body strong { 271 | font-weight: 600; 272 | } 273 | 274 | .markdown-body hr { 275 | background: transparent; 276 | border: 0; 277 | border-bottom: 1px solid #dfe2e5; 278 | height: 0; 279 | margin: 15px 0; 280 | overflow: hidden; 281 | } 282 | 283 | .markdown-body hr:before { 284 | content: ""; 285 | display: table; 286 | } 287 | 288 | .markdown-body hr:after { 289 | clear: both; 290 | content: ""; 291 | display: table; 292 | } 293 | 294 | .markdown-body table { 295 | border-collapse: collapse; 296 | border-spacing: 0; 297 | } 298 | 299 | .markdown-body td, 300 | .markdown-body th { 301 | padding: 0; 302 | } 303 | 304 | .markdown-body details summary { 305 | cursor: pointer; 306 | } 307 | 308 | .markdown-body h1, 309 | .markdown-body h2, 310 | .markdown-body h3, 311 | .markdown-body h4, 312 | .markdown-body h5, 313 | .markdown-body h6 { 314 | margin-bottom: 0; 315 | margin-top: 0; 316 | } 317 | 318 | .markdown-body h1 { 319 | font-size: 32px; 320 | } 321 | 322 | .markdown-body h1, 323 | .markdown-body h2 { 324 | font-weight: 600; 325 | } 326 | 327 | .markdown-body h2 { 328 | font-size: 24px; 329 | } 330 | 331 | .markdown-body h3 { 332 | font-size: 20px; 333 | } 334 | 335 | .markdown-body h3, 336 | .markdown-body h4 { 337 | font-weight: 600; 338 | } 339 | 340 | .markdown-body h4 { 341 | font-size: 16px; 342 | } 343 | 344 | .markdown-body h5 { 345 | font-size: 14px; 346 | } 347 | 348 | .markdown-body h5, 349 | .markdown-body h6 { 350 | font-weight: 600; 351 | } 352 | 353 | .markdown-body h6 { 354 | font-size: 12px; 355 | } 356 | 357 | .markdown-body p { 358 | margin-bottom: 10px; 359 | margin-top: 0; 360 | } 361 | 362 | .markdown-body blockquote { 363 | margin: 0; 364 | } 365 | 366 | .markdown-body ol, 367 | .markdown-body ul { 368 | margin-bottom: 0; 369 | margin-top: 0; 370 | padding-left: 0; 371 | } 372 | 373 | .markdown-body ol ol, 374 | .markdown-body ul ol { 375 | list-style-type: lower-roman; 376 | } 377 | 378 | .markdown-body ol ol ol, 379 | .markdown-body ol ul ol, 380 | .markdown-body ul ol ol, 381 | .markdown-body ul ul ol { 382 | list-style-type: lower-alpha; 383 | } 384 | 385 | .markdown-body dd { 386 | margin-left: 0; 387 | } 388 | 389 | .markdown-body code, 390 | .markdown-body pre { 391 | font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace; 392 | font-size: 12px; 393 | } 394 | 395 | .markdown-body pre { 396 | margin-bottom: 0; 397 | margin-top: 0; 398 | } 399 | 400 | .markdown-body input::-webkit-inner-spin-button, 401 | .markdown-body input::-webkit-outer-spin-button { 402 | -webkit-appearance: none; 403 | appearance: none; 404 | margin: 0; 405 | } 406 | 407 | .markdown-body .border { 408 | border: 1px solid #e1e4e8!important; 409 | } 410 | 411 | .markdown-body .border-0 { 412 | border: 0!important; 413 | } 414 | 415 | .markdown-body .border-bottom { 416 | border-bottom: 1px solid #e1e4e8!important; 417 | } 418 | 419 | .markdown-body .rounded-1 { 420 | border-radius: 3px!important; 421 | } 422 | 423 | .markdown-body .bg-white { 424 | background-color: #fff!important; 425 | } 426 | 427 | .markdown-body .bg-gray-light { 428 | background-color: #fafbfc!important; 429 | } 430 | 431 | .markdown-body .text-gray-light { 432 | color: #6a737d!important; 433 | } 434 | 435 | .markdown-body .mb-0 { 436 | margin-bottom: 0!important; 437 | } 438 | 439 | .markdown-body .my-2 { 440 | margin-bottom: 8px!important; 441 | margin-top: 8px!important; 442 | } 443 | 444 | .markdown-body .pl-0 { 445 | padding-left: 0!important; 446 | } 447 | 448 | .markdown-body .py-0 { 449 | padding-bottom: 0!important; 450 | padding-top: 0!important; 451 | } 452 | 453 | .markdown-body .pl-1 { 454 | padding-left: 4px!important; 455 | } 456 | 457 | .markdown-body .pl-2 { 458 | padding-left: 8px!important; 459 | } 460 | 461 | .markdown-body .py-2 { 462 | padding-bottom: 8px!important; 463 | padding-top: 8px!important; 464 | } 465 | 466 | .markdown-body .pl-3, 467 | .markdown-body .px-3 { 468 | padding-left: 16px!important; 469 | } 470 | 471 | .markdown-body .px-3 { 472 | padding-right: 16px!important; 473 | } 474 | 475 | .markdown-body .pl-4 { 476 | padding-left: 24px!important; 477 | } 478 | 479 | .markdown-body .pl-5 { 480 | padding-left: 32px!important; 481 | } 482 | 483 | .markdown-body .pl-6 { 484 | padding-left: 40px!important; 485 | } 486 | 487 | .markdown-body .f6 { 488 | font-size: 12px!important; 489 | } 490 | 491 | .markdown-body .lh-condensed { 492 | line-height: 1.25!important; 493 | } 494 | 495 | .markdown-body .text-bold { 496 | font-weight: 600!important; 497 | } 498 | 499 | .markdown-body:before { 500 | content: ""; 501 | display: table; 502 | } 503 | 504 | .markdown-body:after { 505 | clear: both; 506 | content: ""; 507 | display: table; 508 | } 509 | 510 | .markdown-body>:first-child { 511 | margin-top: 0!important; 512 | } 513 | 514 | .markdown-body>:last-child { 515 | margin-bottom: 0!important; 516 | } 517 | 518 | .markdown-body a:not([href]) { 519 | color: inherit; 520 | text-decoration: none; 521 | } 522 | 523 | .markdown-body blockquote, 524 | .markdown-body dl, 525 | .markdown-body ol, 526 | .markdown-body p, 527 | .markdown-body pre, 528 | .markdown-body table, 529 | .markdown-body ul { 530 | margin-bottom: 16px; 531 | margin-top: 0; 532 | } 533 | 534 | .markdown-body hr { 535 | background-color: #e1e4e8; 536 | border: 0; 537 | height: .25em; 538 | margin: 24px 0; 539 | padding: 0; 540 | } 541 | 542 | .markdown-body blockquote { 543 | border-left: .25em solid #dfe2e5; 544 | color: #6a737d; 545 | padding: 0 1em; 546 | } 547 | 548 | .markdown-body blockquote>:first-child { 549 | margin-top: 0; 550 | } 551 | 552 | .markdown-body blockquote>:last-child { 553 | margin-bottom: 0; 554 | } 555 | 556 | .markdown-body kbd { 557 | background-color: #fafbfc; 558 | border: 1px solid #c6cbd1; 559 | border-bottom-color: #959da5; 560 | border-radius: 3px; 561 | box-shadow: inset 0 -1px 0 #959da5; 562 | color: #444d56; 563 | display: inline-block; 564 | font-size: 11px; 565 | line-height: 10px; 566 | padding: 3px 5px; 567 | vertical-align: middle; 568 | } 569 | 570 | .markdown-body h1, 571 | .markdown-body h2, 572 | .markdown-body h3, 573 | .markdown-body h4, 574 | .markdown-body h5, 575 | .markdown-body h6 { 576 | font-weight: 600; 577 | line-height: 1.25; 578 | margin-bottom: 16px; 579 | margin-top: 24px; 580 | } 581 | 582 | .markdown-body h1 { 583 | font-size: 2em; 584 | } 585 | 586 | .markdown-body h1, 587 | .markdown-body h2 { 588 | border-bottom: 1px solid #eaecef; 589 | padding-bottom: .3em; 590 | } 591 | 592 | .markdown-body h2 { 593 | font-size: 1.5em; 594 | } 595 | 596 | .markdown-body h3 { 597 | font-size: 1.25em; 598 | } 599 | 600 | .markdown-body h4 { 601 | font-size: 1em; 602 | } 603 | 604 | .markdown-body h5 { 605 | font-size: .875em; 606 | } 607 | 608 | .markdown-body h6 { 609 | color: #6a737d; 610 | font-size: .85em; 611 | } 612 | 613 | .markdown-body ol, 614 | .markdown-body ul { 615 | padding-left: 2em; 616 | } 617 | 618 | .markdown-body ol ol, 619 | .markdown-body ol ul, 620 | .markdown-body ul ol, 621 | .markdown-body ul ul { 622 | margin-bottom: 0; 623 | margin-top: 0; 624 | } 625 | 626 | .markdown-body li { 627 | word-wrap: break-all; 628 | } 629 | 630 | .markdown-body li>p { 631 | margin-top: 16px; 632 | } 633 | 634 | .markdown-body li+li { 635 | margin-top: .25em; 636 | } 637 | 638 | .markdown-body dl { 639 | padding: 0; 640 | } 641 | 642 | .markdown-body dl dt { 643 | font-size: 1em; 644 | font-style: italic; 645 | font-weight: 600; 646 | margin-top: 16px; 647 | padding: 0; 648 | } 649 | 650 | .markdown-body dl dd { 651 | margin-bottom: 16px; 652 | padding: 0 16px; 653 | } 654 | 655 | .markdown-body table { 656 | display: block; 657 | overflow: auto; 658 | width: 100%; 659 | } 660 | 661 | .markdown-body table th { 662 | font-weight: 600; 663 | } 664 | 665 | .markdown-body table td, 666 | .markdown-body table th { 667 | border: 1px solid #dfe2e5; 668 | padding: 6px 13px; 669 | } 670 | 671 | .markdown-body table tr { 672 | background-color: #fff; 673 | border-top: 1px solid #c6cbd1; 674 | } 675 | 676 | .markdown-body table tr:nth-child(2n) { 677 | background-color: #f6f8fa; 678 | } 679 | 680 | .markdown-body img { 681 | background-color: #fff; 682 | box-sizing: content-box; 683 | max-width: 100%; 684 | } 685 | 686 | .markdown-body img[align=right] { 687 | padding-left: 20px; 688 | } 689 | 690 | .markdown-body img[align=left] { 691 | padding-right: 20px; 692 | } 693 | 694 | .markdown-body code { 695 | background-color: rgba(27,31,35,.05); 696 | border-radius: 3px; 697 | font-size: 85%; 698 | margin: 0; 699 | padding: .2em .4em; 700 | } 701 | 702 | .markdown-body pre { 703 | word-wrap: normal; 704 | } 705 | 706 | .markdown-body pre>code { 707 | background: transparent; 708 | border: 0; 709 | font-size: 100%; 710 | margin: 0; 711 | padding: 0; 712 | white-space: pre; 713 | word-break: normal; 714 | } 715 | 716 | .markdown-body .highlight { 717 | margin-bottom: 16px; 718 | } 719 | 720 | .markdown-body .highlight pre { 721 | margin-bottom: 0; 722 | word-break: normal; 723 | } 724 | 725 | .markdown-body .highlight pre, 726 | .markdown-body pre { 727 | background-color: #f6f8fa; 728 | border-radius: 3px; 729 | font-size: 85%; 730 | line-height: 1.45; 731 | overflow: auto; 732 | padding: 16px; 733 | } 734 | 735 | .markdown-body pre code { 736 | background-color: transparent; 737 | border: 0; 738 | display: inline; 739 | line-height: inherit; 740 | margin: 0; 741 | max-width: auto; 742 | overflow: visible; 743 | padding: 0; 744 | word-wrap: normal; 745 | } 746 | 747 | .markdown-body .commit-tease-sha { 748 | color: #444d56; 749 | display: inline-block; 750 | font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace; 751 | font-size: 90%; 752 | } 753 | 754 | .markdown-body .blob-wrapper { 755 | border-bottom-left-radius: 3px; 756 | border-bottom-right-radius: 3px; 757 | overflow-x: auto; 758 | overflow-y: hidden; 759 | } 760 | 761 | .markdown-body .blob-wrapper-embedded { 762 | max-height: 240px; 763 | overflow-y: auto; 764 | } 765 | 766 | .markdown-body .blob-num { 767 | -moz-user-select: none; 768 | -ms-user-select: none; 769 | -webkit-user-select: none; 770 | color: rgba(27,31,35,.3); 771 | cursor: pointer; 772 | font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace; 773 | font-size: 12px; 774 | line-height: 20px; 775 | min-width: 50px; 776 | padding-left: 10px; 777 | padding-right: 10px; 778 | text-align: right; 779 | user-select: none; 780 | vertical-align: top; 781 | white-space: nowrap; 782 | width: 1%; 783 | } 784 | 785 | .markdown-body .blob-num:hover { 786 | color: rgba(27,31,35,.6); 787 | } 788 | 789 | .markdown-body .blob-num:before { 790 | content: attr(data-line-number); 791 | } 792 | 793 | .markdown-body .blob-code { 794 | line-height: 20px; 795 | padding-left: 10px; 796 | padding-right: 10px; 797 | position: relative; 798 | vertical-align: top; 799 | } 800 | 801 | .markdown-body .blob-code-inner { 802 | color: #24292e; 803 | font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace; 804 | font-size: 12px; 805 | overflow: visible; 806 | white-space: pre; 807 | word-wrap: normal; 808 | } 809 | 810 | .markdown-body .pl-token.active, 811 | .markdown-body .pl-token:hover { 812 | background: #ffea7f; 813 | cursor: pointer; 814 | } 815 | 816 | .markdown-body kbd { 817 | background-color: #fafbfc; 818 | border: 1px solid #d1d5da; 819 | border-bottom-color: #c6cbd1; 820 | border-radius: 3px; 821 | box-shadow: inset 0 -1px 0 #c6cbd1; 822 | color: #444d56; 823 | display: inline-block; 824 | font: 11px SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace; 825 | line-height: 10px; 826 | padding: 3px 5px; 827 | vertical-align: middle; 828 | } 829 | 830 | .markdown-body :checked+.radio-label { 831 | border-color: #0366d6; 832 | position: relative; 833 | z-index: 1; 834 | } 835 | 836 | .markdown-body .tab-size[data-tab-size="1"] { 837 | -moz-tab-size: 1; 838 | tab-size: 1; 839 | } 840 | 841 | .markdown-body .tab-size[data-tab-size="2"] { 842 | -moz-tab-size: 2; 843 | tab-size: 2; 844 | } 845 | 846 | .markdown-body .tab-size[data-tab-size="3"] { 847 | -moz-tab-size: 3; 848 | tab-size: 3; 849 | } 850 | 851 | .markdown-body .tab-size[data-tab-size="4"] { 852 | -moz-tab-size: 4; 853 | tab-size: 4; 854 | } 855 | 856 | .markdown-body .tab-size[data-tab-size="5"] { 857 | -moz-tab-size: 5; 858 | tab-size: 5; 859 | } 860 | 861 | .markdown-body .tab-size[data-tab-size="6"] { 862 | -moz-tab-size: 6; 863 | tab-size: 6; 864 | } 865 | 866 | .markdown-body .tab-size[data-tab-size="7"] { 867 | -moz-tab-size: 7; 868 | tab-size: 7; 869 | } 870 | 871 | .markdown-body .tab-size[data-tab-size="8"] { 872 | -moz-tab-size: 8; 873 | tab-size: 8; 874 | } 875 | 876 | .markdown-body .tab-size[data-tab-size="9"] { 877 | -moz-tab-size: 9; 878 | tab-size: 9; 879 | } 880 | 881 | .markdown-body .tab-size[data-tab-size="10"] { 882 | -moz-tab-size: 10; 883 | tab-size: 10; 884 | } 885 | 886 | .markdown-body .tab-size[data-tab-size="11"] { 887 | -moz-tab-size: 11; 888 | tab-size: 11; 889 | } 890 | 891 | .markdown-body .tab-size[data-tab-size="12"] { 892 | -moz-tab-size: 12; 893 | tab-size: 12; 894 | } 895 | 896 | .markdown-body .task-list-item { 897 | list-style-type: none; 898 | } 899 | 900 | .markdown-body .task-list-item+.task-list-item { 901 | margin-top: 3px; 902 | } 903 | 904 | .markdown-body .task-list-item input { 905 | margin: 0 .2em .25em -1.6em; 906 | vertical-align: middle; 907 | } 908 | 909 | .markdown-body hr { 910 | border-bottom-color: #eee; 911 | } 912 | 913 | .markdown-body .pl-0 { 914 | padding-left: 0!important; 915 | } 916 | 917 | .markdown-body .pl-1 { 918 | padding-left: 4px!important; 919 | } 920 | 921 | .markdown-body .pl-2 { 922 | padding-left: 8px!important; 923 | } 924 | 925 | .markdown-body .pl-3 { 926 | padding-left: 16px!important; 927 | } 928 | 929 | .markdown-body .pl-4 { 930 | padding-left: 24px!important; 931 | } 932 | 933 | .markdown-body .pl-5 { 934 | padding-left: 32px!important; 935 | } 936 | 937 | .markdown-body .pl-6 { 938 | padding-left: 40px!important; 939 | } 940 | 941 | .markdown-body .pl-7 { 942 | padding-left: 48px!important; 943 | } 944 | 945 | .markdown-body .pl-8 { 946 | padding-left: 64px!important; 947 | } 948 | 949 | .markdown-body .pl-9 { 950 | padding-left: 80px!important; 951 | } 952 | 953 | .markdown-body .pl-10 { 954 | padding-left: 96px!important; 955 | } 956 | 957 | .markdown-body .pl-11 { 958 | padding-left: 112px!important; 959 | } 960 | 961 | .markdown-body .pl-12 { 962 | padding-left: 128px!important; 963 | } 964 | --------------------------------------------------------------------------------