Markdown 파일을 PDF로 변환하기

Markdown 파일을 PDF로 변환하는 방법을 정리한다.


소스 코드 저장소에는 Markdown(.md) 파일로 관리하지만, 이를 외부에 배포할 때에는 당연히 Markdown 파일보다는 PDF 파일로 변환하여 배포하는 것이 더 좋다.
이때 Markdown 파일을 PDF 파일로 변환할 필요가 있는데, 변환 방법에는 여러 가지가 있겠지만, 일단 간단히 아래와 같이 정리해 보았다.

온라인 Markdown 변환 서비스 이용

간단한 방법이기는 하지만 보통 세부적인 설정이 안 되는 문제점이 있다. 예를 들어 아래와 같은 웹사이트가 있다.

Pandoc 툴 이용

통합 문서 변환기인 pandoc 툴을 이용하는 방법이다. 소스는 pandoc 저장소에 있고, 우분투에서는 아래와 같이 설치할 수 있다.

$ sudo apt install pandoc

Markdown 파일을 PDF로 변환하려면 사용할 PDF 엔진 타입에 필요한 패키지를 추가로 설치해야 한다. 예를 들어 wkhtmltopdf 엔진을 사용하려면 아래와 같이 패키지를 설치한다.

$ sudo apt install wkhtmltopdf

이후 아래 예와 같이 변환할 수 있다.

$ pandoc <입력_마크다운_파일> -f markdown --pdf-engine=wkhtmltopdf -o <출력_PDF_파일>

이 방법은 Markdown, PDF 이외에도 다양한 문서 포맷을 지원하지만, 세부적인 설정이 어렵다는 단점이 있다.

Python 패키지 이용

  • md2pdf
    $ pip3 install md2pdf
    $ md2pdf [options] <입력_마크다운_파일> <출력_PDF_파일>
    

    [options]에는 --css가 있다. 이 방법은 세부적인 설정이 어렵고 한글은 아예 변환되지 않았다.

Node.js 패키지 이용

  • md-to-pdf
    $ sudo apt install libnss3-dev libgbm-dev libasound2
    $ sudo npm install -g md-to-pdf
    $ md-to-pdf [options] <입력_마크다운_파일>
    

    [options]에는 --stylesheet, --highlight-style, --pdf-options, --md-file-encoding 등이 있다. 그런데 한글은 아예 변환되지 않았다.

  • mdpdf
    $ sudo npm install -g mdpdf
    $ mdpdf [options] <입력_마크다운_파일>
    

    [options]에는 --stylesheet, --css, --md-file-encoding 등이 있다. 그런데 한글은 아예 변환되지 않았다.

Markdown 전용 에디터 이용

온라인 Markdown 에디터 이용

간단한 방법이기는 하지만 보통 세부적인 설정이 안 되는 문제점이 있다. 예를 들어 Dillinger와 같은 웹사이트가 있다.

Typora 이용

버전 1.0 이전까지 내가 필요시 이용하던 방법으로 PlantUML 등은 변환되지 않았지만, PDF 품질이 좋은 편이었다. 그런데 버전 1.0 이후로 유료(💰)로 변경되면서 더 이상 사용하지 않게 되었다.

VNote 이용

간혹 이용하던 방법 중의 하나로, PlantUML도 제대로 나오고, 세부 설정도 가능한 방법이다.
참고로 디폴트로 코드 블록 안에서 줄 번호가 표시되는데, 줄 번호를 안 나오게 하려면 아래와 같이 하면 된다.

  1. %APPDATA%\vnote\VNote\web\js\prism\prism.min.js 파일을 열어서 Prism 페이지에서 사용한 설정 정보를 찾는다.
  2. 이 URL 전체를 브라우저로 열면 Prism에서 사용한 config가 뜬다.
  3. 여기에서 Plugins 중에 Line Numbers 항목을 끈 후, DOWNLOAD JS 버튼을 누르면 prism.js 파일이 받아진다. (즉, 디폴트 config에서 줄 번호만 disable 시킨 것임)
  4. 이것을 prism.min.js 이름으로 바꿔서 원래 파일에 덮어쓰면 된다.

에디터에서 Markdown 플러그인 이용

VS Code

VS Code에서 Markdown 파일을 작성한 후 (물론 preview를 보면서), PDF로 변환이 필요하면 Markdown PDF와 같은 익스텐션을 이용하여 PDF export 하는 방법으로 내가 가장 애용하는 방법이다.
참고로 아래는 내가 settings.json 파일에서 추가로 설정한 내용들이다.

"markdown-pdf.breaks": true,
"markdown-pdf.displayHeaderFooter": false,
"markdown-pdf.highlightStyle": "atom-one-dark.css",
"markdown-pdf.includeDefaultStyles": false,
"markdown-pdf.margin.left": "1.5cm",
"markdown-pdf.margin.right": "1.5cm",
"markdown-pdf.margin.top": "1.5cm",
"markdown-pdf.margin.bottom": "1.5cm",
"markdown-pdf.plantumlCloseMarker": "```",
"markdown-pdf.plantumlOpenMarker": "```plantuml",
"markdown-pdf.scale": 0.84,
"markdown-pdf.footerTemplate": "<div style=\"font-size: 10px; margin: 0 auto;\">- <span class='pageNumber'></span> -</div>",
"markdown-pdf.headerTemplate": "<div></div>",
"markdown-pdf.styles": [
    "doc/markdown-pdf.css"
],

또 위 설정에서 명시한 바와 같이 프로젝트의 하위 doc 디렉토리에는 markdown-pdf.css 파일을 넣었는데, 이 파일의 내용은 아래와 같이 작성하여 내 취향에 맞는 PDF 파일을 얻었다. (참고로 이 익스텐션은 중간 파일로 html을 사용하고 있어서 이 html 파일을 이용하여 css를 수정하였음)

/* body */
body {
    margin: 20px auto;
    width: 800px;
    background-color: #fff;
    color: #222222;
    font-family: 'malgun-gothic', Arial, sans-serif;
    font-size: 14px;
    line-height: 150%;
}

/* links */
a:link {
    color: #00f;
    text-decoration: none;
}

/* blockquote */
blockquote {
    margin: 0 0 1.5em 0;
    padding: 3px 5px 0px 5px;
    border-left: 5px solid;
    background: rgba(127, 127, 127, 0.1);
    border-color: rgba(0, 122, 204, 0.5);
}

/* html tags */

* html code {
    font-size: 101%;
}

* html pre {
    font-size: 101%;
}

/* code */

pre,
code {
    font-size: 14px;
    font-family: D2Coding, Consolas, Monaco, monospace;
}

pre {
    margin-top: 3px;
    margin-bottom: 5px;
    border: 1px solid #c7cfd5;
    border-radius: 3px;
    background: #f1f5f9;
    margin: 2px 0 5px 0;
    padding: 5px;
    text-align: left;
    overflow-x: auto;
    white-space: pre-wrap;
    overflow-wrap: break-word;
}

code {
    line-height: 150%;
}

hr {
    height: 12px;
    border: 0;
    box-shadow: inset 0 12px 12px -12px rgba(0, 0, 0, 0.5);
}

/* for inline code */
:not(pre):not(.hljs) > code {
    background: #f2f4f4;
    border: 1px solid #e7e9ec;
    border-radius: 3px;
    color: rgb(102, 5, 117);
    font-family: D2Coding, Consolas, Monaco, monospace;
    font-size: inherit;
    padding: 0 0.1em;
    overflow-x: auto;
    white-space: pre-wrap;
}

/* kbd tag */
kbd {
    background-color: #eee;
    border: 1px solid #b4b4b4;
    border-radius: 3px;
    box-shadow: 1px 1px 1px rgba(0, 0, 0, .2), 0px 0px 0px rgba(255, 255, 255, .7) inset;
    color: #333;
    display: inline-block;
    font-family: 'malgun-gothic', Arial, serif;
    font-size: inherit;
    line-height: 1;
    margin: 0 2px 0 0;
    padding: 0 0.1em;
    white-space: nowrap;
}

/* headers */

h1,
h2,
h3,
h4,
h5,
h6 {
    font-family: 'malgun-gothic', Arial, serif;
    font-weight: bold;
    color: #111111;
}

h1 {
    margin-top: 0.5em;
    margin-bottom: 0.5em;
    font-weight: bold;
    font-size: 28px;
    border-bottom: 0;
}

h1 {
    background-image: linear-gradient(
        to right,
        rgba(0, 0, 0, 0),
        rgba(0, 0, 0, 0.75),
        rgba(0, 0, 0, 0)
    );
    background-position: 0 100%;
    background-repeat: no-repeat;
    background-size: 100% 1px;
    padding-bottom: 8px;
}

h2 {
    margin-top: 1.4em;
    margin-bottom: 0.5em;
    font-size: 24px;
    padding: 2px;
    border-bottom: 1px solid #efeaea;
}

h3 {
    margin-top: 1.4em;
    margin-bottom: 0.5em;
    font-size: 20px;
}

h4 {
    margin-top: 1.4em;
    margin-bottom: 0.5em;
    font-size: 16px;
}

h5 {
    margin-top: 1.4em;
    margin-bottom: 0.5em;
    padding: 0;
    font-size: 12px;
}

h6 {
    margin-top: 1.4em;
    margin-bottom: 0.5em;
    padding: 0;
    font-size: 10px;
}

p {
    margin-top: 0px;
    margin-bottom: 3px;
}

/* lists */

ul {
    margin: 0 0 0 30px;
    padding: 0 0 12px 6px;
}

li {
    margin-top: 6px;
}

ol {
    list-style-type: decimal;
    list-style-position: outside;
    margin: 0 0 0 30px;
    padding: 0 0 12px 6px;
}

ol ol {
    list-style-type: lower-alpha;
    list-style-position: outside;
    margin: 7px 0 0 30px;
    padding: 0 0 0 6px;
}

ul ul {
    margin-left: 30px;
    padding: 0 0 0 6px;
}

li > p {
    display: inline;
}

li > p + p {
    display: block;
}

li > a + p {
    display: block;
}

/* table */

table {
    width: 100%;
    border-top: 1px solid #919699;
    border-left: 1px solid #919699;
    border-spacing: 0;
}

table th {
    padding: 4px 8px 4px 8px;
    background: #e2e2e2;
    font-size: 18px;
    border-bottom: 1px solid #919699;
    border-right: 1px solid #919699;
}

table th p {
    font-weight: bold;
    margin-bottom: 0px;
}

table td {
    padding: 8px;
    font-size: 15px;
    vertical-align: top;
    border-bottom: 1px solid #919699;
    border-right: 1px solid #919699;
}

table td p {
    margin-bottom: 0px;
}

table td p + p {
    margin-top: 5px;
}

table td p + p + p {
    margin-top: 5px;
}

/* forms */

form {
    margin: 0;
}

button {
    margin: 3px 0 10px 0;
}

input {
    vertical-align: middle;
    padding: 0;
    margin: 0 0 5px 0;
}

select {
    vertical-align: middle;
    padding: 0;
    margin: 0 0 3px 0;
}

textarea {
    margin: 0 0 10px 0;
    width: 100%;
}

결론

Markdown 파일을 품질 좋은 PDF 파일로 변환하기가 쉽지가 않은데, 이렇게 정리하고 공유하여서 누군가에게는 도움이 될 것으로 생각한다. 😊

카테고리:

업데이트: