├── cmd ├── run.sh ├── chinese.pdf ├── md2pdf │ ├── test.pdf │ ├── russian.pdf │ ├── helvetica_1251.z │ ├── test.md │ ├── helvetica_1251.json │ ├── russian.md │ ├── cp1251.map │ └── md2pdf.go ├── russian_test.pdf ├── test_syntax_highlighting.pdf ├── run_syntax_highlighting.sh ├── test_syntax_highlighting.md ├── russian_test.md └── chinese.md ├── testdata ├── Nested blockquotes.text ├── Tabs.pdf ├── Image.pdf ├── Tables.pdf ├── syntax.pdf ├── Tidyness.pdf ├── Auto links.pdf ├── Code Blocks.pdf ├── Code Spans.pdf ├── Horizontal rules.pdf ├── Backslash escapes.pdf ├── Links, inline style.pdf ├── Nested blockquotes.pdf ├── syntax_highlighting.pdf ├── Inline HTML (Simple).pdf ├── Inline HTML comments.pdf ├── Tidyness.text ├── Amps and angle encoding.pdf ├── Inline HTML (Advanced).pdf ├── Links, reference style.pdf ├── Literal quotes in titles.pdf ├── Strong and em together.pdf ├── Links, shortcut references.pdf ├── Blockquotes with code blocks.pdf ├── Ordered and unordered lists.pdf ├── Markdown Documentation - Basics.pdf ├── Hard-wrapped paragraphs with list-like lines.pdf ├── Nested blockquotes.html ├── Strong and em together.text ├── Literal quotes in titles.text ├── Tidyness.html ├── Literal quotes in titles.html ├── Code Spans.text ├── Hard-wrapped paragraphs with list-like lines no empty line before block.pdf ├── Inline HTML (Advanced).text ├── Inline HTML comments.text ├── Code Spans.html ├── Inline HTML (Advanced).html ├── Strong and em together.html ├── Hard-wrapped paragraphs with list-like lines.text ├── Inline HTML comments.html ├── Hard-wrapped paragraphs with list-like lines.html ├── Blockquotes with code blocks.html ├── Hard-wrapped paragraphs with list-like lines no empty line before block.text ├── Links, inline style.text ├── Links, shortcut references.html ├── Auto links.text ├── Blockquotes with code blocks.text ├── Links, shortcut references.text ├── Hard-wrapped paragraphs with list-like lines no empty line before block.html ├── Code Blocks.html ├── Code Blocks.text ├── Tabs.md ├── Links, inline style.html ├── Tabs.text ├── Amps and angle encoding.text ├── Tabs.html ├── Amps and angle encoding.html ├── Horizontal rules.text ├── Auto links.html ├── Horizontal rules.html ├── Hard-wrapped paragraphs with list-like lines.log ├── Hard-wrapped paragraphs with list-like lines no empty line before block.log ├── Literal quotes in titles.log ├── Blockquotes with code blocks.log ├── Inline HTML (Advanced).log ├── Inline HTML (Simple).text ├── Tables.text ├── Nested blockquotes.log ├── Inline HTML (Simple).html ├── Inline HTML comments.log ├── Links, reference style.text ├── Code Blocks.log ├── Code Spans.log ├── Links, reference style.html ├── Strong and em together.log ├── Image.text ├── Links, shortcut references.log ├── Backslash escapes.text ├── Ordered and unordered lists.md ├── Links, inline style.log ├── Backslash escapes.html ├── Tidyness.log ├── Ordered and unordered lists.html ├── Tabs.log ├── Amps and angle encoding.log ├── Inline HTML (Simple).log ├── Horizontal rules.log ├── Auto links.log ├── syntax_highlighting.md ├── Tables.log ├── Image.log ├── Links, reference style.log ├── Markdown Documentation - Basics.text ├── Markdown Documentation - Basics.html └── Backslash escapes.log ├── image ├── bay.jpg ├── fpdf.png └── hiking.png ├── .gitmodules ├── .gitignore ├── .pre-commit-config.yaml ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── go.yml │ └── release.yml ├── go.mod ├── LICENSE ├── doc.go ├── containers.go ├── .goreleaser.yaml ├── go.sum ├── md2pdf.1.md ├── custom_themes ├── dark_theme.json └── light_theme.json ├── mdtopdf_test.go ├── README.md └── colors.go /cmd/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | go run convert.go -i test.md -o test.pdf -------------------------------------------------------------------------------- /testdata/Nested blockquotes.text: -------------------------------------------------------------------------------- 1 | > foo 2 | > 3 | > > bar 4 | > 5 | > foo 6 | -------------------------------------------------------------------------------- /image/bay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/image/bay.jpg -------------------------------------------------------------------------------- /image/fpdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/image/fpdf.png -------------------------------------------------------------------------------- /cmd/chinese.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/cmd/chinese.pdf -------------------------------------------------------------------------------- /image/hiking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/image/hiking.png -------------------------------------------------------------------------------- /testdata/Tabs.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Tabs.pdf -------------------------------------------------------------------------------- /cmd/md2pdf/test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/cmd/md2pdf/test.pdf -------------------------------------------------------------------------------- /testdata/Image.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Image.pdf -------------------------------------------------------------------------------- /testdata/Tables.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Tables.pdf -------------------------------------------------------------------------------- /testdata/syntax.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/syntax.pdf -------------------------------------------------------------------------------- /cmd/md2pdf/russian.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/cmd/md2pdf/russian.pdf -------------------------------------------------------------------------------- /cmd/russian_test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/cmd/russian_test.pdf -------------------------------------------------------------------------------- /testdata/Tidyness.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Tidyness.pdf -------------------------------------------------------------------------------- /testdata/Auto links.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Auto links.pdf -------------------------------------------------------------------------------- /testdata/Code Blocks.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Code Blocks.pdf -------------------------------------------------------------------------------- /testdata/Code Spans.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Code Spans.pdf -------------------------------------------------------------------------------- /cmd/md2pdf/helvetica_1251.z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/cmd/md2pdf/helvetica_1251.z -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "highlight"] 2 | path = highlight 3 | url = https://github.com/jessp01/gohighlight 4 | -------------------------------------------------------------------------------- /testdata/Horizontal rules.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Horizontal rules.pdf -------------------------------------------------------------------------------- /cmd/test_syntax_highlighting.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/cmd/test_syntax_highlighting.pdf -------------------------------------------------------------------------------- /testdata/Backslash escapes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Backslash escapes.pdf -------------------------------------------------------------------------------- /testdata/Links, inline style.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Links, inline style.pdf -------------------------------------------------------------------------------- /testdata/Nested blockquotes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Nested blockquotes.pdf -------------------------------------------------------------------------------- /testdata/syntax_highlighting.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/syntax_highlighting.pdf -------------------------------------------------------------------------------- /testdata/Inline HTML (Simple).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Inline HTML (Simple).pdf -------------------------------------------------------------------------------- /testdata/Inline HTML comments.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Inline HTML comments.pdf -------------------------------------------------------------------------------- /testdata/Tidyness.text: -------------------------------------------------------------------------------- 1 | > A list within a blockquote: 2 | > 3 | > * asterisk 1 4 | > * asterisk 2 5 | > * asterisk 3 6 | -------------------------------------------------------------------------------- /testdata/Amps and angle encoding.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Amps and angle encoding.pdf -------------------------------------------------------------------------------- /testdata/Inline HTML (Advanced).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Inline HTML (Advanced).pdf -------------------------------------------------------------------------------- /testdata/Links, reference style.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Links, reference style.pdf -------------------------------------------------------------------------------- /testdata/Literal quotes in titles.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Literal quotes in titles.pdf -------------------------------------------------------------------------------- /testdata/Strong and em together.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Strong and em together.pdf -------------------------------------------------------------------------------- /testdata/Links, shortcut references.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Links, shortcut references.pdf -------------------------------------------------------------------------------- /testdata/Blockquotes with code blocks.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Blockquotes with code blocks.pdf -------------------------------------------------------------------------------- /testdata/Ordered and unordered lists.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Ordered and unordered lists.pdf -------------------------------------------------------------------------------- /testdata/Markdown Documentation - Basics.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Markdown Documentation - Basics.pdf -------------------------------------------------------------------------------- /testdata/Hard-wrapped paragraphs with list-like lines.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Hard-wrapped paragraphs with list-like lines.pdf -------------------------------------------------------------------------------- /testdata/Nested blockquotes.html: -------------------------------------------------------------------------------- 1 |
2 |

foo

3 | 4 |
5 |

bar

6 |
7 | 8 |

foo

9 |
10 | -------------------------------------------------------------------------------- /testdata/Strong and em together.text: -------------------------------------------------------------------------------- 1 | ***This is strong and em.*** 2 | 3 | So is ***this*** word. 4 | 5 | ___This is strong and em.___ 6 | 7 | So is ___this___ word. 8 | -------------------------------------------------------------------------------- /testdata/Literal quotes in titles.text: -------------------------------------------------------------------------------- 1 | Foo [bar][]. 2 | 3 | Foo [bar](/url/ "Title with "quotes" inside"). 4 | 5 | 6 | [bar]: /url/ "Title with "quotes" inside" 7 | 8 | -------------------------------------------------------------------------------- /cmd/run_syntax_highlighting.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd md2pdf || exit 3 | go run md2pdf.go -i test_syntax_highlighting.md -o test_syntax_highlighting.pdf -s ../gohighlight/syntax_files 4 | -------------------------------------------------------------------------------- /testdata/Tidyness.html: -------------------------------------------------------------------------------- 1 |
2 |

A list within a blockquote:

3 | 4 | 9 |
10 | -------------------------------------------------------------------------------- /testdata/Literal quotes in titles.html: -------------------------------------------------------------------------------- 1 |

Foo bar.

2 | 3 |

Foo bar.

4 | -------------------------------------------------------------------------------- /testdata/Code Spans.text: -------------------------------------------------------------------------------- 1 | `` 2 | 3 | Fix for backticks within HTML tag: like this 4 | 5 | Here's how you put `` `backticks` `` in a code span. 6 | 7 | -------------------------------------------------------------------------------- /testdata/Hard-wrapped paragraphs with list-like lines no empty line before block.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solworktech/md2pdf/HEAD/testdata/Hard-wrapped paragraphs with list-like lines no empty line before block.pdf -------------------------------------------------------------------------------- /testdata/Inline HTML (Advanced).text: -------------------------------------------------------------------------------- 1 | Simple block on one line: 2 | 3 |
foo
4 | 5 | And nested without indentation: 6 | 7 |
8 |
9 |
10 | foo 11 |
12 |
13 |
14 |
bar
15 |
16 | -------------------------------------------------------------------------------- /testdata/Inline HTML comments.text: -------------------------------------------------------------------------------- 1 | Paragraph one. 2 | 3 | 4 | 5 | 8 | 9 | Paragraph two. 10 | 11 | 12 | 13 | The end. 14 | -------------------------------------------------------------------------------- /testdata/Code Spans.html: -------------------------------------------------------------------------------- 1 |

<test a=" content of attribute ">

2 | 3 |

Fix for backticks within HTML tag: like this

4 | 5 |

Here's how you put `backticks` in a code span.

6 | -------------------------------------------------------------------------------- /testdata/Inline HTML (Advanced).html: -------------------------------------------------------------------------------- 1 |

Simple block on one line:

2 | 3 |
foo
4 | 5 |

And nested without indentation:

6 | 7 |
8 |
9 |
10 | foo 11 |
12 |
13 |
14 |
bar
15 |
16 | -------------------------------------------------------------------------------- /testdata/Strong and em together.html: -------------------------------------------------------------------------------- 1 |

This is strong and em.

2 | 3 |

So is this word.

4 | 5 |

This is strong and em.

6 | 7 |

So is this word.

8 | -------------------------------------------------------------------------------- /testdata/Hard-wrapped paragraphs with list-like lines.text: -------------------------------------------------------------------------------- 1 | In Markdown 1.0.0 and earlier. Version 2 | 8. This line turns into a list item. 3 | Because a hard-wrapped line in the 4 | middle of a paragraph looked like a 5 | list item. 6 | 7 | Here's one with a bullet. 8 | * criminey. 9 | -------------------------------------------------------------------------------- /testdata/Inline HTML comments.html: -------------------------------------------------------------------------------- 1 |

Paragraph one.

2 | 3 | 4 | 5 | 8 | 9 |

Paragraph two.

10 | 11 | 12 | 13 |

The end.

14 | -------------------------------------------------------------------------------- /testdata/Hard-wrapped paragraphs with list-like lines.html: -------------------------------------------------------------------------------- 1 |

In Markdown 1.0.0 and earlier. Version 2 | 8. This line turns into a list item. 3 | Because a hard-wrapped line in the 4 | middle of a paragraph looked like a 5 | list item.

6 | 7 |

Here's one with a bullet. 8 | * criminey.

9 | -------------------------------------------------------------------------------- /testdata/Blockquotes with code blocks.html: -------------------------------------------------------------------------------- 1 |
2 |

Example:

3 | 4 |
sub status {
 5 |     print "working";
 6 | }
 7 | 
8 | 9 |

Or:

10 | 11 |
sub status {
12 |     return "working";
13 | }
14 | 
15 |
16 | -------------------------------------------------------------------------------- /testdata/Hard-wrapped paragraphs with list-like lines no empty line before block.text: -------------------------------------------------------------------------------- 1 | In Markdown 1.0.0 and earlier. Version 2 | 8. This line turns into a list item. 3 | Because a hard-wrapped line in the 4 | middle of a paragraph looked like a 5 | list item. 6 | 7 | Here's one with a bullet. 8 | * criminey. 9 | -------------------------------------------------------------------------------- /testdata/Links, inline style.text: -------------------------------------------------------------------------------- 1 | Just a [URL](/url/). 2 | 3 | [URL and title](/url/ "title"). 4 | 5 | [URL and title](/url/ "title preceded by two spaces"). 6 | 7 | [URL and title](/url/ "title preceded by a tab"). 8 | 9 | [URL and title](/url/ "title has spaces afterward" ). 10 | 11 | 12 | [Empty](). 13 | -------------------------------------------------------------------------------- /testdata/Links, shortcut references.html: -------------------------------------------------------------------------------- 1 |

This is the simple case.

2 | 3 |

This one has a line 4 | break.

5 | 6 |

This one has a line 7 | break with a line-ending space.

8 | 9 |

this and the other

10 | -------------------------------------------------------------------------------- /testdata/Auto links.text: -------------------------------------------------------------------------------- 1 | Link: . 2 | 3 | With an ampersand: 4 | 5 | * In a list? 6 | * 7 | * It should. 8 | 9 | > Blockquoted: 10 | 11 | Auto-links should not occur here: `` 12 | 13 | or here: -------------------------------------------------------------------------------- /cmd/test_syntax_highlighting.md: -------------------------------------------------------------------------------- 1 | # Test Syntax Highlighting for Code Blocks 2 | 3 | This is a Go code block 4 | 5 | ```go 6 | package main 7 | 8 | func main() { 9 | println("Hello World") 10 | } 11 | ``` 12 | 13 | This is a Javascript code block 14 | 15 | ```javascript 16 | function myfunc() { 17 | console.log("hello world"); 18 | } 19 | ``` 20 | -------------------------------------------------------------------------------- /testdata/Blockquotes with code blocks.text: -------------------------------------------------------------------------------- 1 | > Example: 2 | > 3 | > sub status { 4 | > print "working"; 5 | > } 6 | > 7 | > Or: 8 | > 9 | > sub status { 10 | > return "working"; 11 | > } 12 | > 13 | > // This has a really, really, really, really, really, really, really, really, really, really, really, really long comment 14 | -------------------------------------------------------------------------------- /testdata/Links, shortcut references.text: -------------------------------------------------------------------------------- 1 | This is the [simple case]. 2 | 3 | [simple case]: /simple 4 | 5 | 6 | 7 | This one has a [line 8 | break]. 9 | 10 | This one has a [line 11 | break] with a line-ending space. 12 | 13 | [line break]: /foo 14 | 15 | 16 | [this] [that] and the [other] 17 | 18 | [this]: /this 19 | [that]: /that 20 | [other]: /other 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | *.log 13 | 14 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 15 | .glide/ 16 | vendor/ 17 | -------------------------------------------------------------------------------- /testdata/Hard-wrapped paragraphs with list-like lines no empty line before block.html: -------------------------------------------------------------------------------- 1 |

In Markdown 1.0.0 and earlier. Version

2 | 3 |
    4 |
  1. This line turns into a list item. 5 | Because a hard-wrapped line in the 6 | middle of a paragraph looked like a 7 | list item.
  2. 8 |
9 | 10 |

Here's one with a bullet.

11 | 12 |
    13 |
  • criminey.
  • 14 |
15 | -------------------------------------------------------------------------------- /testdata/Code Blocks.html: -------------------------------------------------------------------------------- 1 |
code block on the first line
 2 | 
3 | 4 |

Regular text.

5 | 6 |
code block indented by spaces
 7 | 
8 | 9 |

Regular text.

10 | 11 |
the lines in this block  
12 | all contain trailing spaces  
13 | 
14 | 15 |

Regular Text.

16 | 17 |
code block on the last line
18 | 
19 | -------------------------------------------------------------------------------- /testdata/Code Blocks.text: -------------------------------------------------------------------------------- 1 | code block on the first line 2 | 3 | Regular text. 4 | 5 | code block indented by spaces 6 | 7 | Regular text. 8 | 9 | the lines in this block 10 | all contain trailing spaces 11 | This one is really, really, really, really, really, really, really, really, really, really, really, really, really, really long 12 | 13 | Regular Text. 14 | 15 | code block on the last line -------------------------------------------------------------------------------- /testdata/Tabs.md: -------------------------------------------------------------------------------- 1 | + this is a list item 2 | indented with tabs 3 | 4 | + this is a list item 5 | indented with spaces 6 | 7 | Code: 8 | 9 | this code block is indented by one tab 10 | 11 | And: 12 | 13 | this code block is indented by two tabs 14 | 15 | And: 16 | 17 | + this is an example list item 18 | indented with tabs 19 | 20 | + this is an example list item 21 | indented with spaces 22 | -------------------------------------------------------------------------------- /testdata/Links, inline style.html: -------------------------------------------------------------------------------- 1 |

Just a URL.

2 | 3 |

URL and title.

4 | 5 |

URL and title.

6 | 7 |

URL and title.

8 | 9 |

URL and title.

10 | 11 |

[Empty]().

12 | -------------------------------------------------------------------------------- /testdata/Tabs.text: -------------------------------------------------------------------------------- 1 | + this is a list item 2 | indented with tabs 3 | 4 | + this is a list item 5 | indented with spaces 6 | 7 | Code: 8 | 9 | this code block is indented by one tab 10 | 11 | And: 12 | 13 | this code block is indented by two tabs 14 | 15 | And: 16 | 17 | + this is an example list item 18 | indented with tabs 19 | 20 | + this is an example list item 21 | indented with spaces 22 | -------------------------------------------------------------------------------- /cmd/russian_test.md: -------------------------------------------------------------------------------- 1 | # Russian Phrases 2 | 3 | - First phrase is: 4 | "Съешь же ещё этих мягких французских булок, да выпей чаю", which means "Eat these soft French rolls and drink some tea" 5 | - The second is: 6 | "Влюбиться можно в красоту, но полюбить – лишь только душу!", which means "You can fall in love with beauty, but love is only a soul!" 7 | - Finally, "Молоко и творог", which means "Milk and cottage cheese" 8 | 9 | That's all folks! 10 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | - repo: https://github.com/jessp01/pre-commit-golang.git 4 | rev: v0.5.4 5 | hooks: 6 | - id: go-fmt 7 | # - id: go-imports 8 | - id: go-vet 9 | - id: go-lint 10 | - id: go-critic 11 | - id: go-ineffassign 12 | # - repo: https://github.com/crate-ci/typos 13 | # rev: v1.32.0 14 | # hooks: 15 | # - id: typos 16 | # exclude_types: [yaml] 17 | # exclude: go.* 18 | -------------------------------------------------------------------------------- /testdata/Amps and angle encoding.text: -------------------------------------------------------------------------------- 1 | AT&T has an ampersand in their name. 2 | 3 | AT&T is another way to write it. 4 | 5 | This & that. 6 | 7 | 4 < 5. 8 | 9 | 6 > 5. 10 | 11 | Here's a [link] [1] with an ampersand in the URL. 12 | 13 | Here's a link with an amersand in the link text: [AT&T] [2]. 14 | 15 | Here's an inline [link](/script?foo=1&bar=2). 16 | 17 | Here's an inline [link](). 18 | 19 | 20 | [1]: http://example.com/?foo=1&bar=2 21 | [2]: http://att.com/ "AT&T" -------------------------------------------------------------------------------- /testdata/Tabs.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • this is a list item 3 | indented with tabs

  • 4 | 5 |
  • this is a list item 6 | indented with spaces

  • 7 |
8 | 9 |

Code:

10 | 11 |
this code block is indented by one tab
12 | 
13 | 14 |

And:

15 | 16 |
	this code block is indented by two tabs
17 | 
18 | 19 |

And:

20 | 21 |
+	this is an example list item
22 | 	indented with tabs
23 | 
24 | +   this is an example list item
25 |     indented with spaces
26 | 
27 | -------------------------------------------------------------------------------- /testdata/Amps and angle encoding.html: -------------------------------------------------------------------------------- 1 |

AT&T has an ampersand in their name.

2 | 3 |

AT&T is another way to write it.

4 | 5 |

This & that.

6 | 7 |

4 < 5.

8 | 9 |

6 > 5.

10 | 11 |

Here's a link with an ampersand in the URL.

12 | 13 |

Here's a link with an amersand in the link text: AT&T.

14 | 15 |

Here's an inline link.

16 | 17 |

Here's an inline link.

18 | -------------------------------------------------------------------------------- /testdata/Horizontal rules.text: -------------------------------------------------------------------------------- 1 | Dashes: 2 | 3 | --- 4 | 5 | --- 6 | 7 | --- 8 | 9 | --- 10 | 11 | --- 12 | 13 | - - - 14 | 15 | - - - 16 | 17 | - - - 18 | 19 | - - - 20 | 21 | - - - 22 | 23 | 24 | Asterisks: 25 | 26 | *** 27 | 28 | *** 29 | 30 | *** 31 | 32 | *** 33 | 34 | *** 35 | 36 | * * * 37 | 38 | * * * 39 | 40 | * * * 41 | 42 | * * * 43 | 44 | * * * 45 | 46 | 47 | Underscores: 48 | 49 | ___ 50 | 51 | ___ 52 | 53 | ___ 54 | 55 | ___ 56 | 57 | ___ 58 | 59 | _ _ _ 60 | 61 | _ _ _ 62 | 63 | _ _ _ 64 | 65 | _ _ _ 66 | 67 | _ _ _ 68 | -------------------------------------------------------------------------------- /testdata/Auto links.html: -------------------------------------------------------------------------------- 1 |

Link: http://example.com/.

2 | 3 |

With an ampersand: http://example.com/?foo=1&bar=2

4 | 5 | 10 | 11 |
12 |

Blockquoted: http://example.com/

13 |
14 | 15 |

Auto-links should not occur here: <http://example.com/>

16 | 17 |
or here: <http://example.com/>
18 | 
19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Bug description 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | md2pdf version: 13 | 14 | 15 | **To Reproduce** 16 | Steps to reproduce the behaviour: 17 | 18 | Full `md2pdf` command (including all args): 19 | 20 | Output: 21 | 22 | [ ] I've attached an input sample (MD file) 23 | 24 | **Expected behavior** 25 | A clear and concise description of what you expected to happen 26 | (unless the above makes it obvious, in which case, you can remove this section). 27 | -------------------------------------------------------------------------------- /testdata/Horizontal rules.html: -------------------------------------------------------------------------------- 1 |

Dashes:

2 | 3 |
4 | 5 |
6 | 7 |
8 | 9 |
10 | 11 |
---
12 | 
13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 |
21 | 22 |
- - -
23 | 
24 | 25 |

Asterisks:

26 | 27 |
28 | 29 |
30 | 31 |
32 | 33 |
34 | 35 |
***
36 | 
37 | 38 |
39 | 40 |
41 | 42 |
43 | 44 |
45 | 46 |
* * *
47 | 
48 | 49 |

Underscores:

50 | 51 |
52 | 53 |
54 | 55 |
56 | 57 |
58 | 59 |
___
60 | 
61 | 62 |
63 | 64 |
65 | 66 |
67 | 68 |
69 | 70 |
_ _ _
71 | 
72 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/solworktech/md2pdf/v2 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | codeberg.org/go-pdf/fpdf v0.11.1 7 | github.com/canhlinh/svg2png v0.0.0-20201124065332-6ba87c82371f 8 | github.com/gabriel-vasile/mimetype v1.4.8 9 | github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b 10 | github.com/jessp01/gohighlight v0.21.2 11 | github.com/mitchellh/go-wordwrap v1.0.1 12 | golang.org/x/exp v0.0.0-20240707233637-46b078467d37 13 | ) 14 | 15 | require ( 16 | github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 // indirect 17 | github.com/sirupsen/logrus v1.9.3 // indirect 18 | github.com/stretchr/testify v1.8.4 // indirect 19 | golang.org/x/net v0.38.0 // indirect 20 | golang.org/x/sys v0.31.0 // indirect 21 | gopkg.in/yaml.v2 v2.4.0 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /testdata/Hard-wrapped paragraphs with list-like lines.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [Paragraph (entering)] 4 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 5 | [cr()] LH=14 6 | [Text] In Markdown 1.0.0 and earlier. Version 8. This line turns into a list item. Because a hard-wrapped line in the middle of a paragraph looked like a list item. 7 | [Paragraph (leaving)] 8 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 9 | [cr()] LH=14 10 | [Paragraph (entering)] 11 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 12 | [cr()] LH=14 13 | [Text] Here's one with a bullet. * criminey. 14 | [Paragraph (leaving)] 15 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 16 | [cr()] LH=14 17 | [Document] Not Handled 18 | -------------------------------------------------------------------------------- /testdata/Hard-wrapped paragraphs with list-like lines no empty line before block.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [Paragraph (entering)] 4 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 5 | [cr()] LH=14 6 | [Text] In Markdown 1.0.0 and earlier. Version 8. This line turns into a list item. Because a hard-wrapped line in the middle of a paragraph looked like a list item. 7 | [Paragraph (leaving)] 8 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 9 | [cr()] LH=14 10 | [Paragraph (entering)] 11 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 12 | [cr()] LH=14 13 | [Text] Here's one with a bullet. * criminey. 14 | [Paragraph (leaving)] 15 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 16 | [cr()] LH=14 17 | [Document] Not Handled 18 | -------------------------------------------------------------------------------- /testdata/Literal quotes in titles.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [Paragraph (entering)] 4 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 5 | [cr()] LH=14 6 | [Text] Foo 7 | -[Link (entering)] Destination[/url/] Title[Title with "quotes" inside] 8 | -[Text] bar 9 | -[Link (leaving)] 10 | [Text] . 11 | [Paragraph (leaving)] 12 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 13 | [cr()] LH=14 14 | [Paragraph (entering)] 15 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 16 | [cr()] LH=14 17 | [Text] Foo 18 | -[Link (entering)] Destination[/url/] Title[Title with "quotes" inside] 19 | -[Text] bar 20 | -[Link (leaving)] 21 | [Text] . 22 | [Paragraph (leaving)] 23 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 24 | [cr()] LH=14 25 | [Document] Not Handled 26 | -------------------------------------------------------------------------------- /testdata/Blockquotes with code blocks.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [BlockQuote (entering)] 4 | -[Paragraph (entering)] 5 | -[... Margins (left, top, right, bottom:] 58.338 28.35 28.35 56.7 6 | -[cr()] LH=14 7 | -[Text] Example: 8 | -[Paragraph (leaving)] 9 | -[... Margins (left, top, right, bottom:] 58.338 28.35 28.35 56.7 10 | -[cr()] LH=14 11 | -[Codeblock] Leaf 'sub status {\n print "working";\n}\n' 12 | 13 | -[cr()] LH=14 14 | -[Paragraph (entering)] 15 | -[... Margins (left, top, right, bottom:] 58.338 28.35 28.35 56.7 16 | -[cr()] LH=14 17 | -[Text] Or: 18 | -[Paragraph (leaving)] 19 | -[... Margins (left, top, right, bottom:] 58.338 28.35 28.35 56.7 20 | -[cr()] LH=14 21 | -[Codeblock] Leaf 'sub status {\n return "working";\n…' 22 | 23 | -[cr()] LH=14 24 | -[BlockQuote (leaving)] 25 | [cr()] LH=14 26 | [Document] Not Handled 27 | -------------------------------------------------------------------------------- /testdata/Inline HTML (Advanced).log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [Paragraph (entering)] 4 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 5 | [cr()] LH=14 6 | [Text] Simple block on one line: 7 | [Paragraph (leaving)] 8 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 9 | [cr()] LH=14 10 | [HTMLBlock]
foo
11 | [cr()] LH=14 12 | [cr()] LH=14 13 | [Paragraph (entering)] 14 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 15 | [cr()] LH=14 16 | [Text] And nested without indentation: 17 | [Paragraph (leaving)] 18 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 19 | [cr()] LH=14 20 | [HTMLBlock]
21 |
22 |
23 | foo 24 |
25 |
26 |
27 |
bar
28 |
29 | [cr()] LH=14 30 | [cr()] LH=14 31 | [Document] Not Handled 32 | -------------------------------------------------------------------------------- /testdata/Inline HTML (Simple).text: -------------------------------------------------------------------------------- 1 | Here's a simple block: 2 | 3 |
4 | foo 5 |
6 | 7 | This should be a code block, though: 8 | 9 |
10 | foo 11 |
12 | 13 | As should this: 14 | 15 |
foo
16 | 17 | Now, nested: 18 | 19 |
20 |
21 |
22 | foo 23 |
24 |
25 |
26 | 27 | This should just be an HTML comment: 28 | 29 | 30 | 31 | Multiline: 32 | 33 | 37 | 38 | Code block: 39 | 40 | 41 | 42 | Just plain comment, with trailing spaces on the line: 43 | 44 | 45 | 46 | Code: 47 | 48 |
49 | 50 | Hr's: 51 | 52 |
53 | 54 |
55 | 56 |
57 | 58 |
59 | 60 |
61 | 62 |
63 | 64 |
65 | 66 |
67 | 68 |
69 | 70 | -------------------------------------------------------------------------------- /testdata/Tables.text: -------------------------------------------------------------------------------- 1 | # Table Tests 2 | 3 | This is a simple table: 4 | 5 | | Header | Another header | 6 | |---------|----------------| 7 | | field 1 | something | 8 | | field 2 | something else | 9 | 10 | Here is another table, with more content in the cells: 11 | 12 | | id | process_name | window_name | duration | 13 | |----|--------------|------------------------------------------|----------| 14 | | 1 | lxterminal | devilspie2 | 00:00:02 | 15 | | 2 | firefox-esr | jessp01/devilspie2: Devilspie2 is an X w | 00:00:10 | 16 | | | | indow (Lua) hooks mechanism; it supports | | 17 | | | | the following events: window opened, | | 18 | | | | closed, focused and title changed | | 19 | | 4 | firefox-esr | Watch The Big Bang Theory - Season 6 | 00:00:05 | 20 | -------------------------------------------------------------------------------- /testdata/Nested blockquotes.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [BlockQuote (entering)] 4 | -[Paragraph (entering)] 5 | -[... Margins (left, top, right, bottom:] 58.338 28.35 28.35 56.7 6 | -[cr()] LH=14 7 | -[Text] foo 8 | -[Paragraph (leaving)] 9 | -[... Margins (left, top, right, bottom:] 58.338 28.35 28.35 56.7 10 | -[cr()] LH=14 11 | -[BlockQuote (entering)] 12 | --[Paragraph (entering)] 13 | --[... Margins (left, top, right, bottom:] 88.326 28.35 28.35 56.7 14 | --[cr()] LH=14 15 | --[Text] bar 16 | --[Paragraph (leaving)] 17 | --[... Margins (left, top, right, bottom:] 88.326 28.35 28.35 56.7 18 | --[cr()] LH=14 19 | --[BlockQuote (leaving)] 20 | -[cr()] LH=14 21 | -[Paragraph (entering)] 22 | -[... Margins (left, top, right, bottom:] 58.337999999999994 28.35 28.35 56.7 23 | -[cr()] LH=14 24 | -[Text] foo 25 | -[Paragraph (leaving)] 26 | -[... Margins (left, top, right, bottom:] 58.337999999999994 28.35 28.35 56.7 27 | -[cr()] LH=14 28 | -[BlockQuote (leaving)] 29 | [cr()] LH=14 30 | [Document] Not Handled 31 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: 3 | # https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 4 | 5 | --- 6 | 7 | name: Go 8 | 9 | on: [push, pull_request] 10 | 11 | defaults: 12 | run: 13 | shell: 'bash -Eeuo pipefail -x {0}' 14 | 15 | jobs: 16 | 17 | build: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v3 21 | 22 | - name: Set up Go 23 | uses: actions/setup-go@v4 24 | with: 25 | go-version: '1.23.0' 26 | 27 | - name: Build 28 | run: go build -v 29 | - name: Test 30 | run: | 31 | set -e 32 | go install github.com/go-critic/go-critic/cmd/gocritic@latest 33 | # go install golang.org/x/tools/cmd/goimports@latest 34 | go install golang.org/x/lint/golint@latest 35 | go install github.com/gordonklaus/ineffassign@latest 36 | pip install pre-commit 37 | pre-commit install 38 | pre-commit run --all-files 39 | go test -v 40 | -------------------------------------------------------------------------------- /testdata/Inline HTML (Simple).html: -------------------------------------------------------------------------------- 1 |

Here's a simple block:

2 | 3 |
4 | foo 5 |
6 | 7 |

This should be a code block, though:

8 | 9 |
<div>
10 | 	foo
11 | </div>
12 | 
13 | 14 |

As should this:

15 | 16 |
<div>foo</div>
17 | 
18 | 19 |

Now, nested:

20 | 21 |
22 |
23 |
24 | foo 25 |
26 |
27 |
28 | 29 |

This should just be an HTML comment:

30 | 31 | 32 | 33 |

Multiline:

34 | 35 | 39 | 40 |

Code block:

41 | 42 |
<!-- Comment -->
43 | 
44 | 45 |

Just plain comment, with trailing spaces on the line:

46 | 47 | 48 | 49 |

Code:

50 | 51 |
<hr />
52 | 
53 | 54 |

Hr's:

55 | 56 |
57 | 58 |
59 | 60 |
61 | 62 |
63 | 64 |
65 | 66 |
67 | 68 |
69 | 70 |
71 | 72 |
73 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: goreleaser 3 | 4 | on: 5 | push: 6 | # run only against tags 7 | tags: 8 | - '*' 9 | 10 | permissions: 11 | contents: write 12 | # packages: write 13 | # issues: write 14 | 15 | jobs: 16 | goreleaser: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v3 20 | with: 21 | fetch-depth: 0 22 | - run: git fetch --force --tags 23 | - uses: actions/setup-go@v4 24 | with: 25 | go-version: stable 26 | # More assembly might be required: Docker logins, GPG, etc. It all depends 27 | # on your needs. 28 | - uses: goreleaser/goreleaser-action@v4 29 | with: 30 | # either 'goreleaser' (default) or 'goreleaser-pro': 31 | distribution: goreleaser 32 | version: latest 33 | args: release --clean 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' 37 | # distribution: 38 | # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /testdata/Inline HTML comments.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [Paragraph (entering)] 4 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 5 | [cr()] LH=14 6 | [Text] Paragraph one. 7 | [Paragraph (leaving)] 8 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 9 | [cr()] LH=14 10 | [HTMLBlock] 11 | [cr()] LH=14 12 | [cr()] LH=14 13 | [HTMLBlock] 16 | [cr()] LH=14 17 | [cr()] LH=14 18 | [Paragraph (entering)] 19 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 20 | [cr()] LH=14 21 | [Text] Paragraph two. 22 | [Paragraph (leaving)] 23 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 24 | [cr()] LH=14 25 | [HTMLBlock] 26 | [cr()] LH=14 27 | [cr()] LH=14 28 | [Paragraph (entering)] 29 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 30 | [cr()] LH=14 31 | [Text] The end. 32 | [Paragraph (leaving)] 33 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 34 | [cr()] LH=14 35 | [Document] Not Handled 36 | -------------------------------------------------------------------------------- /testdata/Links, reference style.text: -------------------------------------------------------------------------------- 1 | Foo [bar] [1]. 2 | 3 | Foo [bar][1]. 4 | 5 | Foo [bar] 6 | [1]. 7 | 8 | [1]: /url/ "Title" 9 | 10 | 11 | With [embedded [brackets]] [b]. 12 | 13 | 14 | Indented [once][]. 15 | 16 | Indented [twice][]. 17 | 18 | Indented [thrice][]. 19 | 20 | Indented [four][] times. 21 | 22 | [once]: /url 23 | 24 | [twice]: /url 25 | 26 | [thrice]: /url 27 | 28 | [four]: /url 29 | 30 | 31 | [b]: /url/ 32 | 33 | * * * 34 | 35 | [this] [this] should work 36 | 37 | So should [this][this]. 38 | 39 | And [this] []. 40 | 41 | And [this][]. 42 | 43 | And [this]. 44 | 45 | But not [that] []. 46 | 47 | Nor [that][]. 48 | 49 | Nor [that]. 50 | 51 | [Something in brackets like [this][] should work] 52 | 53 | [Same with [this].] 54 | 55 | In this case, [this](/somethingelse/) points to something else. 56 | 57 | Backslashing should suppress \[this] and [this\]. 58 | 59 | [this]: foo 60 | 61 | 62 | * * * 63 | 64 | Here's one where the [link 65 | breaks] across lines. 66 | 67 | Here's another where the [link 68 | breaks] across lines, but with a line-ending space. 69 | 70 | 71 | [link breaks]: /url/ 72 | -------------------------------------------------------------------------------- /testdata/Code Blocks.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [Codeblock] Leaf 'code block on the first line\n' 4 | 5 | [cr()] LH=14 6 | [Paragraph (entering)] 7 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 8 | [cr()] LH=14 9 | [Text] Regular text. 10 | [Paragraph (leaving)] 11 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 12 | [cr()] LH=14 13 | [Codeblock] Leaf 'code block indented by spaces\n' 14 | 15 | [cr()] LH=14 16 | [Paragraph (entering)] 17 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 18 | [cr()] LH=14 19 | [Text] Regular text. 20 | [Paragraph (leaving)] 21 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 22 | [cr()] LH=14 23 | [Codeblock] Leaf 'the lines in this block \nall contai…' 24 | 25 | [cr()] LH=14 26 | [Paragraph (entering)] 27 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 28 | [cr()] LH=14 29 | [Text] Regular Text. 30 | [Paragraph (leaving)] 31 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 32 | [cr()] LH=14 33 | [Codeblock] Leaf 'code block on the last line\n' 34 | 35 | [cr()] LH=14 36 | [Document] Not Handled 37 | -------------------------------------------------------------------------------- /testdata/Code Spans.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [Paragraph (entering)] 4 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 5 | [cr()] LH=14 6 | [Text] 7 | [processCode] 11 | [Backtick (entering)] 12 | [Paragraph (leaving)] 13 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 14 | [cr()] LH=14 15 | [Paragraph (entering)] 16 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 17 | [cr()] LH=14 18 | [Text] Fix for backticks within HTML tag: 19 | [HTMLSpan] Not handled 20 | [Text] like this 21 | [HTMLSpan] Not handled 22 | [Paragraph (leaving)] 23 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 24 | [cr()] LH=14 25 | [Paragraph (entering)] 26 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 27 | [cr()] LH=14 28 | [Text] Here's how you put 29 | [processCode] `backticks` 30 | [Backtick (entering)] 31 | [Text] in a code span. 32 | [Paragraph (leaving)] 33 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 34 | [cr()] LH=14 35 | [Document] Not Handled 36 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package mdtopdf implements a PDF document generator for markdown documents. 3 | 4 | # Introduction 5 | 6 | This package depends on two other packages: 7 | 8 | * The [gomarkdown](https://github.com/gomarkdown/markdown) parser to read the markdown source 9 | 10 | * The fpdf package to generate the PDF 11 | 12 | The tests included here are from the BlackFriday package. 13 | See the "testdata" folder. 14 | The tests create PDF files and thus while the tests may complete 15 | without errors, visual inspection of the created PDF is the 16 | only way to determine if the tests *really* pass! 17 | 18 | The tests create log files that trace the BlackFriday parser 19 | callbacks. This is a valuable debug tool showing each callback 20 | and data provided in each while the AST is presented. 21 | 22 | # Installation 23 | 24 | To install the package: 25 | 26 | go get github.com/solworktech/md2pdf/v2 27 | 28 | # Quick start 29 | 30 | In the cmd folder is an example using the package. It demonstrates 31 | a number of features. The test PDF was created with this command: 32 | 33 | go run convert.go -i test.md -o test.pdf 34 | 35 | See README for limitations and known issues 36 | */ 37 | package mdtopdf 38 | -------------------------------------------------------------------------------- /testdata/Links, reference style.html: -------------------------------------------------------------------------------- 1 |

Foo bar.

2 | 3 |

Foo bar.

4 | 5 |

Foo bar.

6 | 7 |

With embedded [brackets].

8 | 9 |

Indented once.

10 | 11 |

Indented twice.

12 | 13 |

Indented thrice.

14 | 15 |

Indented [four][] times.

16 | 17 |
[four]: /url
18 | 
19 | 20 |
21 | 22 |

this should work

23 | 24 |

So should this.

25 | 26 |

And this.

27 | 28 |

And this.

29 | 30 |

And this.

31 | 32 |

But not [that] [].

33 | 34 |

Nor [that][].

35 | 36 |

Nor [that].

37 | 38 |

[Something in brackets like this should work]

39 | 40 |

[Same with this.]

41 | 42 |

In this case, this points to something else.

43 | 44 |

Backslashing should suppress [this] and [this].

45 | 46 |
47 | 48 |

Here's one where the link 49 | breaks across lines.

50 | 51 |

Here's another where the link 52 | breaks across lines, but with a line-ending space.

53 | -------------------------------------------------------------------------------- /cmd/chinese.md: -------------------------------------------------------------------------------- 1 | # 标题一 2 | 这是一个带有一些*强调文本*的段落 - 好吗? 3 | 4 | 这是一个无序列表: 5 | 6 | - 这是一个无序列表 7 | - 分项1.a 8 | - 分项1.b 9 | - 这是无序列表中的第二项 10 | - 分项目2.a 11 | - 分项目2.a.i 12 | - 分项目2.a.ii 13 | - 分项目2.b 14 | 15 | 这是一个有序/编号列表: 16 | 17 | 1. 这是一个编号列表 18 | 1. 一个 19 | 1. 两个 20 | 1. 这是编号列表中的第二项 21 | 1. 一个 22 | 1. 两个 23 | 1. A 24 | 1.乙 25 | 1. x 26 | 1. 年 27 | 1.z 28 | 1. 最后编号的项目 29 | 30 | ##标题二 31 | 这是一个带有一些**强调文本**的段落 - 好吗? 32 | 33 | ### 标题三 34 | 这是一个带有___强调文本的段落___ - 好吗? 35 | 36 | #### 标题四 37 | *一点文字* 38 | 39 | #### 标题五 40 | 只是一些普通的文字……没什么特别的 41 | 42 | 这是一个简单的表: 43 | 44 | |标题 |另一个标题 | 45 | |---------|----------------| 46 | |字段 1 |东西| 47 | |领域 2 |别的东西| 48 | |领域 3 |东西| 49 | |领域 4 |别的东西| 50 | |领域 5 |东西| 51 | |字段 6 |别的东西| 52 | 53 | ###### 标题六 54 | 这真的是嵌套得很深。 55 | 56 | 这是一些代码: 57 | `` 58 | 10 REM 老了吧? 59 | 20 REM 是的,太老了! 60 | `` 61 | 文件名是`oldcode.bas`。 62 | 63 | 这三行应该 64 | 只有一个段落,但保留BF 65 | 换行符,必须用空格替换换行符。 66 | 67 | 下面是一个带有内部代码块的块引用。 68 | 69 | > 示例: 70 | > 71 | > 子状态 { 72 | > 打印“工作”; 73 | > } 74 | > 75 | > 或: 76 | > 77 | > 子状态 { 78 | > 返回“工作”; 79 | > } 80 | 81 | 这是 fpdf 徽标(内联):![来自 https://codeberg.org/go-pdf/fpdf/src/branch/main/image](../image/fpdf.png) 82 | 83 | 这是一个地鼠: 84 | 85 | ![来自 https://github.com/egonelbre/gophers](../image/hiking.png "可选标题") 86 | 87 | * Go gopher 由 Renee French 设计。 Gopher 角色设计在知识共享 3.0 署名许可下获得许可。阅读 http://blog.golang.org/gopher 了解更多详情。* 88 | 89 | 90 | __这是文档的最后一行。__ 91 | -------------------------------------------------------------------------------- /testdata/Strong and em together.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [Paragraph (entering)] 4 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 5 | [cr()] LH=14 6 | [Text] 7 | [Strong (entering)] 8 | [Emph (entering)] 9 | [Text] This is strong and em. 10 | [Emph (leaving)] 11 | [Strong (leaving)] 12 | [Paragraph (leaving)] 13 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 14 | [cr()] LH=14 15 | [Paragraph (entering)] 16 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 17 | [cr()] LH=14 18 | [Text] So is 19 | [Strong (entering)] 20 | [Emph (entering)] 21 | [Text] this 22 | [Emph (leaving)] 23 | [Strong (leaving)] 24 | [Text] word. 25 | [Paragraph (leaving)] 26 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 27 | [cr()] LH=14 28 | [Paragraph (entering)] 29 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 30 | [cr()] LH=14 31 | [Text] 32 | [Strong (entering)] 33 | [Emph (entering)] 34 | [Text] This is strong and em. 35 | [Emph (leaving)] 36 | [Strong (leaving)] 37 | [Paragraph (leaving)] 38 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 39 | [cr()] LH=14 40 | [Paragraph (entering)] 41 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 42 | [cr()] LH=14 43 | [Text] So is 44 | [Strong (entering)] 45 | [Emph (entering)] 46 | [Text] this 47 | [Emph (leaving)] 48 | [Strong (leaving)] 49 | [Text] word. 50 | [Paragraph (leaving)] 51 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 52 | [cr()] LH=14 53 | [Document] Not Handled 54 | -------------------------------------------------------------------------------- /testdata/Image.text: -------------------------------------------------------------------------------- 1 |

Images

2 | 3 | Admittedly, it's fairly difficult to devise a "natural" syntax for 4 | placing images into a plain text document format. 5 | 6 | Markdown uses an image syntax that is intended to resemble the syntax 7 | for links, allowing for two styles: *inline* and *reference*. 8 | 9 | Inline image syntax looks like this: 10 | 11 | ![Alt text](./image/fpdf.png) 12 | 13 | ![Alt text](./image/hiking.png "Optional title") 14 | 15 | That is: 16 | 17 | * An exclamation mark: `!`; 18 | * followed by a set of square brackets, containing the `alt` 19 | attribute text for the image; 20 | * followed by a set of parentheses, containing the URL or path to 21 | the image, and an optional `title` attribute enclosed in double 22 | or single quotes. 23 | 24 | Here is the first picture: ![from https://codeberg.org/go-pdf/fpdf/src/branch/main/image](./image/fpdf.png) 25 | 26 | Here is the second picture: 27 | ![from https://github.com/egonelbre/gophers](./image/hiking.png "Optional title") 28 | 29 | The Go gopher was designed by Renee French. The Gopher character design is licensed under the Creative Commons 3.0 Attributions license. Read http://blog.golang.org/gopher for more details. 30 | 31 | Note: this snippet was adapted from the testdata folder file named: 32 | `Markdown Documentation - Syntax.text` 33 | 34 | Here is a non-existent image... should generate a message in trace file. 35 | ![Not from https://jpeg.org/images/jpeg-home.jpg](./image/xbay.jpg "Does not exist!") 36 | 37 | Here is a JPEG image... is it auto-detected? 38 | ![from https://jpeg.org/images/jpeg-home.jpg](./image/bay.jpg "Down by the Bay") 39 | -------------------------------------------------------------------------------- /testdata/Links, shortcut references.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [Paragraph (entering)] 4 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 5 | [cr()] LH=14 6 | [Text] This is the 7 | -[Link (entering)] Destination[/simple] Title[] 8 | -[Text] simple case 9 | -[Link (leaving)] 10 | [Text] . 11 | [Paragraph (leaving)] 12 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 13 | [cr()] LH=14 14 | [Paragraph (entering)] 15 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 16 | [cr()] LH=14 17 | [Text] This one has a 18 | -[Link (entering)] Destination[/foo] Title[] 19 | -[Text] line break 20 | -[Link (leaving)] 21 | [Text] . 22 | [Paragraph (leaving)] 23 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 24 | [cr()] LH=14 25 | [Paragraph (entering)] 26 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 27 | [cr()] LH=14 28 | [Text] This one has a 29 | -[Link (entering)] Destination[/foo] Title[] 30 | -[Text] line 31 | -[Text] break 32 | -[Link (leaving)] 33 | [Text] with a line-ending space. 34 | [Paragraph (leaving)] 35 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 36 | [cr()] LH=14 37 | [Paragraph (entering)] 38 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 39 | [cr()] LH=14 40 | [Text] 41 | -[Link (entering)] Destination[/that] Title[] 42 | -[Text] this 43 | -[Link (leaving)] 44 | [Text] and the 45 | -[Link (entering)] Destination[/other] Title[] 46 | -[Text] other 47 | -[Link (leaving)] 48 | [Paragraph (leaving)] 49 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 50 | [cr()] LH=14 51 | [Document] Not Handled 52 | -------------------------------------------------------------------------------- /testdata/Backslash escapes.text: -------------------------------------------------------------------------------- 1 | These should all get escaped: 2 | 3 | Backslash: \\ 4 | 5 | Backtick: \` 6 | 7 | Asterisk: \* 8 | 9 | Underscore: \_ 10 | 11 | Left brace: \{ 12 | 13 | Right brace: \} 14 | 15 | Left bracket: \[ 16 | 17 | Right bracket: \] 18 | 19 | Left paren: \( 20 | 21 | Right paren: \) 22 | 23 | Greater-than: \> 24 | 25 | Hash: \# 26 | 27 | Period: \. 28 | 29 | Bang: \! 30 | 31 | Plus: \+ 32 | 33 | Minus: \- 34 | 35 | Tilde: \~ 36 | 37 | 38 | 39 | These should not, because they occur within a code block: 40 | 41 | Backslash: \\ 42 | 43 | Backtick: \` 44 | 45 | Asterisk: \* 46 | 47 | Underscore: \_ 48 | 49 | Left brace: \{ 50 | 51 | Right brace: \} 52 | 53 | Left bracket: \[ 54 | 55 | Right bracket: \] 56 | 57 | Left paren: \( 58 | 59 | Right paren: \) 60 | 61 | Greater-than: \> 62 | 63 | Hash: \# 64 | 65 | Period: \. 66 | 67 | Bang: \! 68 | 69 | Plus: \+ 70 | 71 | Minus: \- 72 | 73 | Tilde: \~ 74 | 75 | 76 | Nor should these, which occur in code spans: 77 | 78 | Backslash: `\\` 79 | 80 | Backtick: `` \` `` 81 | 82 | Asterisk: `\*` 83 | 84 | Underscore: `\_` 85 | 86 | Left brace: `\{` 87 | 88 | Right brace: `\}` 89 | 90 | Left bracket: `\[` 91 | 92 | Right bracket: `\]` 93 | 94 | Left paren: `\(` 95 | 96 | Right paren: `\)` 97 | 98 | Greater-than: `\>` 99 | 100 | Hash: `\#` 101 | 102 | Period: `\.` 103 | 104 | Bang: `\!` 105 | 106 | Plus: `\+` 107 | 108 | Minus: `\-` 109 | 110 | Tilde: `\~` 111 | 112 | 113 | These should get escaped, even though they're matching pairs for 114 | other Markdown constructs: 115 | 116 | \*asterisks\* 117 | 118 | \_underscores\_ 119 | 120 | \`backticks\` 121 | 122 | This is a code span with a literal backslash-backtick sequence: `` \` `` 123 | 124 | This is a tag with unescaped backticks bar. 125 | 126 | This is a tag with backslashes bar. 127 | -------------------------------------------------------------------------------- /testdata/Ordered and unordered lists.md: -------------------------------------------------------------------------------- 1 | ## Unordered 2 | 3 | Asterisks tight: 4 | 5 | * asterisk 1 6 | * asterisk 2 7 | * asterisk 3 8 | - Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. 9 | 10 | 11 | Asterisks loose: 12 | 13 | * asterisk 1 14 | 15 | * asterisk 2 16 | 17 | * asterisk 3 18 | 19 | * * * 20 | 21 | Pluses tight: 22 | 23 | + Plus 1 24 | + Plus 2 25 | + Plus 3 26 | 27 | 28 | Pluses loose: 29 | 30 | + Plus 1 31 | 32 | + Plus 2 33 | 34 | + Plus 3 35 | 36 | * * * 37 | 38 | 39 | Minuses tight: 40 | 41 | - Minus 1 42 | - Minus 2 43 | - Minus 3 44 | 45 | 46 | Minuses loose: 47 | 48 | - Minus 1 49 | 50 | - Minus 2 51 | 52 | - Minus 3 53 | 54 | 55 | ## Ordered 56 | 57 | Tight: 58 | 59 | 1. First 60 | 2. Second 61 | 3. Third 62 | 63 | and: 64 | 65 | 1. One 66 | 2. Two 67 | 3. Three 68 | 69 | 70 | Loose using tabs: 71 | 72 | 1. First 73 | 74 | 2. Second 75 | 76 | 3. Third 77 | 78 | and using spaces: 79 | 80 | 1. One 81 | 82 | 2. Two 83 | 84 | 3. Three 85 | 86 | Multiple paragraphs: 87 | 88 | 1. Item 1, graf one. 89 | 90 | Item 2. graf two. The quick brown fox jumped over the lazy dog's 91 | back. 92 | 93 | 2. Item 2. 94 | 95 | 3. Item 3. 96 | 97 | 98 | 99 | ## Nested 100 | 101 | * Tab 102 | * Tab 103 | * Tab 104 | 105 | - Hello 106 | * Hi 107 | * Hey 108 | - Yo 109 | 110 | Here's another: 111 | 112 | 1. First 113 | 2. Second: 114 | * Fee 115 | * Fie 116 | * Foe 117 | 3. Third 118 | 119 | Same thing but with paragraphs: 120 | 121 | 1. First 122 | 123 | 2. Second: 124 | * Fee 125 | * Fie 126 | * Foe 127 | 128 | 3. Third 129 | 130 | 131 | This was an error in Markdown 1.0.1: 132 | 133 | * this 134 | 135 | * sub 136 | 137 | that 138 | -------------------------------------------------------------------------------- /testdata/Links, inline style.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [Paragraph (entering)] 4 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 5 | [cr()] LH=14 6 | [Text] Just a 7 | -[Link (entering)] Destination[/url/] Title[] 8 | -[Text] URL 9 | -[Link (leaving)] 10 | [Text] . 11 | [Paragraph (leaving)] 12 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 13 | [cr()] LH=14 14 | [Paragraph (entering)] 15 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 16 | [cr()] LH=14 17 | [Text] 18 | -[Link (entering)] Destination[/url/] Title[title] 19 | -[Text] URL and title 20 | -[Link (leaving)] 21 | [Text] . 22 | [Paragraph (leaving)] 23 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 24 | [cr()] LH=14 25 | [Paragraph (entering)] 26 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 27 | [cr()] LH=14 28 | [Text] 29 | -[Link (entering)] Destination[/url/] Title[title preceded by two spaces] 30 | -[Text] URL and title 31 | -[Link (leaving)] 32 | [Text] . 33 | [Paragraph (leaving)] 34 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 35 | [cr()] LH=14 36 | [Paragraph (entering)] 37 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 38 | [cr()] LH=14 39 | [Text] 40 | -[Link (entering)] Destination[/url/] Title[title preceded by a tab] 41 | -[Text] URL and title 42 | -[Link (leaving)] 43 | [Text] . 44 | [Paragraph (leaving)] 45 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 46 | [cr()] LH=14 47 | [Paragraph (entering)] 48 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 49 | [cr()] LH=14 50 | [Text] 51 | -[Link (entering)] Destination[/url/] Title[title has spaces afterward] 52 | -[Text] URL and title 53 | -[Link (leaving)] 54 | [Text] . 55 | [Paragraph (leaving)] 56 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 57 | [cr()] LH=14 58 | [Paragraph (entering)] 59 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 60 | [cr()] LH=14 61 | [Text] 62 | -[Link (entering)] Destination[] Title[] 63 | -[Text] Empty 64 | -[Link (leaving)] 65 | [Text] . 66 | [Paragraph (leaving)] 67 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 68 | [cr()] LH=14 69 | [Document] Not Handled 70 | -------------------------------------------------------------------------------- /containers.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Markdown to PDF Converter 3 | * Available at http://github.com/solworktech/md2pdf 4 | * 5 | * Copyright © Cecil New , Jesse Portnoy . 6 | * Distributed under the MIT License. 7 | * See README.md for details. 8 | * 9 | * Dependencies 10 | * This package depends on two other packages: 11 | * 12 | * Go Markdown processor 13 | * Available at https://github.com/gomarkdown/markdown 14 | * 15 | * fpdf - a PDF document generator with high level support for 16 | * text, drawing and images. 17 | * Available at https://codeberg.org/go-pdf/fpdf 18 | */ 19 | 20 | package mdtopdf 21 | 22 | type listType int 23 | 24 | const ( 25 | notlist listType = iota 26 | unordered 27 | ordered 28 | definition 29 | ) 30 | 31 | // This slice of float64 contains the width of each cell 32 | // in the header of a table. These will be the widths used 33 | // in the table body as well. 34 | var cellwidths []float64 35 | var curdatacell int 36 | var fill = false 37 | var incell = false 38 | 39 | func (n listType) String() string { 40 | switch n { 41 | case notlist: 42 | return "Not a List" 43 | case unordered: 44 | return "Unordered" 45 | case ordered: 46 | return "Ordered" 47 | case definition: 48 | return "Definition" 49 | } 50 | return "" 51 | } 52 | 53 | type containerState struct { 54 | textStyle Styler 55 | leftMargin float64 56 | firstParagraph bool 57 | 58 | // populated if node type is a list 59 | listkind listType 60 | itemNumber int // only if an ordered list 61 | 62 | // populated if node type is a link 63 | destination string 64 | 65 | // populated if table cell 66 | isHeader bool 67 | 68 | // populated if table cell (apply styles first) 69 | cellInnerString string 70 | cellInnerStringStyle *Styler 71 | } 72 | 73 | type states struct { 74 | stack []*containerState 75 | } 76 | 77 | func (s *states) push(c *containerState) { 78 | s.stack = append(s.stack, c) 79 | } 80 | 81 | func (s *states) pop() *containerState { 82 | var x *containerState 83 | x, s.stack = s.stack[len(s.stack)-1], s.stack[:len(s.stack)-1] 84 | return x 85 | } 86 | 87 | func (s *states) peek() *containerState { 88 | return s.stack[len(s.stack)-1] 89 | } 90 | 91 | func (s *states) parent() *containerState { 92 | return s.stack[len(s.stack)-2] 93 | } 94 | -------------------------------------------------------------------------------- /cmd/md2pdf/test.md: -------------------------------------------------------------------------------- 1 | # Heading One 2 | This is a paragraph with some *emphasized text* - OK? 3 | 4 | Here is an unordered list: 5 | 6 | - this is an unordered list 7 | - subitem 1.a 8 | - subitem 1.b 9 | - this is second item in unordered list 10 | - subitem 2.a 11 | - subitem 2.a.i 12 | - subitem 2.a.ii 13 | - subitem 2.b 14 | 15 | Here is an ordered/numbered list: 16 | 17 | 1. This is a numbered list 18 | 1. one 19 | 1. two 20 | 1. This is second item in numbered list 21 | 1. one 22 | 1. two 23 | 1. A 24 | 1. B 25 | 1. x 26 | 1. y 27 | 1. z 28 | 1. last numbered item 29 | 30 | ## Heading Two 31 | This is a paragraph with some **emphasized text** - OK? 32 | 33 | ### Heading Three 34 | This is a paragraph with some ___emphasized text___ - OK? 35 | 36 | #### Heading Four 37 | *A wee bit of text* 38 | 39 | #### Heading Five 40 | Just some normal text... nothing fancy 41 | 42 | This is a simple table: 43 | 44 | | Header | Another header | 45 | |---------|----------------| 46 | | field 1 | something | 47 | | field 2 | something else | 48 | | field 3 | something | 49 | | field 4 | something else | 50 | | field 5 | something | 51 | | field 6 | something else | 52 | 53 | ###### Heading Six 54 | This is really deeply nested. 55 | 56 | Here is some code: 57 | ``` 58 | 10 REM Old huh? 59 | 20 REM Yup, way old! 60 | ``` 61 | The file name is `oldcode.bas`. 62 | 63 | These three lines should 64 | only be one paragaph, but with BF retaining 65 | the newlines, the newlines must be replaced with spaces. 66 | 67 | The below is a blockquote with inner codeblocks. 68 | 69 | > Example: 70 | > 71 | > sub status { 72 | > print "working"; 73 | > } 74 | > 75 | > Or: 76 | > 77 | > sub status { 78 | > return "working"; 79 | > } 80 | 81 | Here is the fpdf logo (inline): ![from https://codeberg.org/go-pdf/fpdf/src/branch/main/image](../../image/fpdf.png) 82 | 83 | Here is a Gopher: 84 | 85 | ![from https://github.com/egonelbre/gophers](../../image/hiking.png "Optional title") 86 | 87 | *The Go gopher was designed by Renee French. The Gopher character design is licensed under the Creative Commons 3.0 Attributions license. Read http://blog.golang.org/gopher for more details.* 88 | 89 | 90 | __This is the last line of the document.__ 91 | -------------------------------------------------------------------------------- /cmd/md2pdf/helvetica_1251.json: -------------------------------------------------------------------------------- 1 | {"Tp":"TrueType","Name":"ArialMT","Desc":{"Ascent":728,"Descent":-210,"CapHeight":728,"Flags":32,"FontBBox":{"Xmin":-665,"Ymin":-325,"Xmax":2028,"Ymax":1037},"ItalicAngle":0,"StemV":70,"MissingWidth":750},"Up":-106,"Ut":73,"Cw":[750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,750,278,278,355,556,556,889,667,191,333,333,389,584,278,333,278,278,556,556,556,556,556,556,556,556,556,556,278,278,584,584,584,556,1015,667,667,722,722,667,611,778,722,278,500,667,556,833,722,778,667,778,722,667,611,722,667,944,667,667,611,278,278,278,469,556,333,556,556,500,556,556,278,556,556,222,222,500,222,833,556,556,556,556,333,500,278,556,500,722,500,500,500,334,260,334,584,750,865,542,222,365,333,1000,556,556,556,1000,1057,333,1010,583,854,719,556,222,222,333,333,350,556,1000,750,1000,906,333,813,438,556,552,278,635,500,500,556,489,260,556,667,737,719,556,584,333,737,278,400,549,278,222,411,576,537,278,556,1073,510,556,222,667,500,278,667,656,667,542,677,667,923,604,719,719,583,656,833,722,778,719,667,722,611,635,760,667,740,667,917,938,792,885,656,719,1010,722,556,573,531,365,583,556,669,458,559,559,438,583,688,552,556,542,556,500,458,500,823,500,573,521,802,823,625,719,521,510,750,542],"Enc":"cp1251","Diff":"128 /afii10051 /afii10052 131 /afii10100 136 /Euro 138 /afii10058 140 /afii10059 /afii10061 /afii10060 /afii10145 /afii10099 152 /.notdef 154 /afii10106 156 /afii10107 /afii10109 /afii10108 /afii10193 161 /afii10062 /afii10110 /afii10057 165 /afii10050 168 /afii10023 170 /afii10053 175 /afii10056 178 /afii10055 /afii10103 /afii10098 184 /afii10071 /afii61352 /afii10101 188 /afii10105 /afii10054 /afii10102 /afii10104 /afii10017 /afii10018 /afii10019 /afii10020 /afii10021 /afii10022 /afii10024 /afii10025 /afii10026 /afii10027 /afii10028 /afii10029 /afii10030 /afii10031 /afii10032 /afii10033 /afii10034 /afii10035 /afii10036 /afii10037 /afii10038 /afii10039 /afii10040 /afii10041 /afii10042 /afii10043 /afii10044 /afii10045 /afii10046 /afii10047 /afii10048 /afii10049 /afii10065 /afii10066 /afii10067 /afii10068 /afii10069 /afii10070 /afii10072 /afii10073 /afii10074 /afii10075 /afii10076 /afii10077 /afii10078 /afii10079 /afii10080 /afii10081 /afii10082 /afii10083 /afii10084 /afii10085 /afii10086 /afii10087 /afii10088 /afii10089 /afii10090 /afii10091 /afii10092 /afii10093 /afii10094 /afii10095 /afii10096 /afii10097","File":"helvetica_1251.z","Size1":0,"Size2":0,"OriginalSize":275572,"I":0,"N":0,"DiffN":0} -------------------------------------------------------------------------------- /testdata/Backslash escapes.html: -------------------------------------------------------------------------------- 1 |

These should all get escaped:

2 | 3 |

Backslash: \

4 | 5 |

Backtick: `

6 | 7 |

Asterisk: *

8 | 9 |

Underscore: _

10 | 11 |

Left brace: {

12 | 13 |

Right brace: }

14 | 15 |

Left bracket: [

16 | 17 |

Right bracket: ]

18 | 19 |

Left paren: (

20 | 21 |

Right paren: )

22 | 23 |

Greater-than: >

24 | 25 |

Hash: #

26 | 27 |

Period: .

28 | 29 |

Bang: !

30 | 31 |

Plus: +

32 | 33 |

Minus: -

34 | 35 |

Tilde: ~

36 | 37 |

These should not, because they occur within a code block:

38 | 39 |
Backslash: \\
 40 | 
 41 | Backtick: \`
 42 | 
 43 | Asterisk: \*
 44 | 
 45 | Underscore: \_
 46 | 
 47 | Left brace: \{
 48 | 
 49 | Right brace: \}
 50 | 
 51 | Left bracket: \[
 52 | 
 53 | Right bracket: \]
 54 | 
 55 | Left paren: \(
 56 | 
 57 | Right paren: \)
 58 | 
 59 | Greater-than: \>
 60 | 
 61 | Hash: \#
 62 | 
 63 | Period: \.
 64 | 
 65 | Bang: \!
 66 | 
 67 | Plus: \+
 68 | 
 69 | Minus: \-
 70 | 
 71 | Tilde: \~
 72 | 
73 | 74 |

Nor should these, which occur in code spans:

75 | 76 |

Backslash: \\

77 | 78 |

Backtick: \`

79 | 80 |

Asterisk: \*

81 | 82 |

Underscore: \_

83 | 84 |

Left brace: \{

85 | 86 |

Right brace: \}

87 | 88 |

Left bracket: \[

89 | 90 |

Right bracket: \]

91 | 92 |

Left paren: \(

93 | 94 |

Right paren: \)

95 | 96 |

Greater-than: \>

97 | 98 |

Hash: \#

99 | 100 |

Period: \.

101 | 102 |

Bang: \!

103 | 104 |

Plus: \+

105 | 106 |

Minus: \-

107 | 108 |

Tilde: \~

109 | 110 |

These should get escaped, even though they're matching pairs for 111 | other Markdown constructs:

112 | 113 |

*asterisks*

114 | 115 |

_underscores_

116 | 117 |

`backticks`

118 | 119 |

This is a code span with a literal backslash-backtick sequence: \`

120 | 121 |

This is a tag with unescaped backticks bar.

122 | 123 |

This is a tag with backslashes bar.

124 | -------------------------------------------------------------------------------- /cmd/md2pdf/russian.md: -------------------------------------------------------------------------------- 1 | # Heading One 2 | Here are lines of Russian: 3 | 4 | - Молоко и творог 5 | - Съешь же ещё этих мягких французских булок, да выпей чаю 6 | 7 | This is a paragraph with some *emphasized text* - OK? 8 | 9 | Here is an unordered list: 10 | 11 | - this is an unordered list 12 | - subitem 1.a 13 | - subitem 1.b 14 | - this is second item in unordered list 15 | - subitem 2.a 16 | - subitem 2.a.i 17 | - subitem 2.a.ii 18 | - subitem 2.b 19 | 20 | Here is an ordered/numbered list: 21 | 22 | 1. This is a numbered list 23 | 1. one 24 | 1. two 25 | 1. This is second item in numbered list 26 | 1. one 27 | 1. two 28 | 1. A 29 | 1. B 30 | 1. x 31 | 1. y 32 | 1. z 33 | 1. last numbered item 34 | 35 | ## Heading Two 36 | This is a paragraph with some **emphasized text** - OK? 37 | 38 | ### Heading Three 39 | This is a paragraph with some ___emphasized text___ - OK? 40 | 41 | #### Heading Four 42 | *A wee bit of text* 43 | 44 | #### Heading Five 45 | Just some normal text... nothing fancy 46 | 47 | This is a simple table: 48 | 49 | | Header | Another header | 50 | |---------|----------------| 51 | | field 1 | something | 52 | | field 2 | something else | 53 | | field 3 | something | 54 | | field 4 | something else | 55 | | field 5 | something | 56 | | field 6 | something else | 57 | 58 | ###### Heading Six 59 | This is really deeply nested. 60 | 61 | Here is some code: 62 | ``` 63 | 10 REM Old huh? 64 | 20 REM Yup, way old! 65 | ``` 66 | The file name is `oldcode.bas`. 67 | 68 | These three lines should 69 | only be one paragaph, but with BF retaining 70 | the newlines, the newlines must be replaced with spaces. 71 | 72 | The below is a blockquote with inner codeblocks. 73 | 74 | > Example: 75 | > 76 | > sub status { 77 | > print "working"; 78 | > } 79 | > 80 | > Or: 81 | > 82 | > sub status { 83 | > return "working"; 84 | > } 85 | 86 | Here is the fpdf logo (inline): ![from https://codeberg.org/go-pdf/fpdf/src/branch/main/image](../../image/fpdf.png) 87 | 88 | Here is a Gopher: 89 | 90 | ![from https://github.com/egonelbre/gophers](../../image/hiking.png "Optional title") 91 | 92 | *The Go gopher was designed by Renee French. The Gopher character design is licensed under the Creative Commons 3.0 Attributions license. Read http://blog.golang.org/gopher for more details.* 93 | 94 | 95 | __This is the last line of the document.__ 96 | -------------------------------------------------------------------------------- /testdata/Tidyness.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [BlockQuote (entering)] 4 | -[Paragraph (entering)] 5 | -[... Margins (left, top, right, bottom:] 58.338 28.35 28.35 56.7 6 | -[cr()] LH=14 7 | -[Text] A list within a blockquote: 8 | -[Paragraph (leaving)] 9 | -[... Margins (left, top, right, bottom:] 58.338 28.35 28.35 56.7 10 | -[cr()] LH=14 11 | -[Unordered List (entering)] Container 12 | ListItem 'flags=start' 13 | Paragraph 14 | Text 'asterisk 1' 15 | ListItem 16 | Paragraph 17 | Text 'asterisk 2' 18 | ListItem 19 | Paragraph 20 | Text 'asterisk 3' 21 | 22 | -[... List Left Margin] set to 88.326 23 | --[Unordered Item (entering) #1] Container 24 | Paragraph 25 | Text 'asterisk 1' 26 | 27 | --[cr()] LH=14 28 | ---[Paragraph (entering)] 29 | ---[... Margins (left, top, right, bottom:] 128.31 28.35 28.35 56.7 30 | ---[First Para within a list] breaking 31 | ---[Text] asterisk 1 32 | ---[Paragraph (leaving)] 33 | ---[... Margins (left, top, right, bottom:] 128.31 28.35 28.35 56.7 34 | ---[Unordered Item (leaving)] Container 35 | Paragraph 36 | Text 'asterisk 1' 37 | 38 | --[Unordered Item (entering) #2] Container 39 | Paragraph 40 | Text 'asterisk 2' 41 | 42 | --[cr()] LH=14 43 | ---[Paragraph (entering)] 44 | ---[... Margins (left, top, right, bottom:] 128.31 28.35 28.35 56.7 45 | ---[First Para within a list] breaking 46 | ---[Text] asterisk 2 47 | ---[Paragraph (leaving)] 48 | ---[... Margins (left, top, right, bottom:] 128.31 28.35 28.35 56.7 49 | ---[Unordered Item (leaving)] Container 50 | Paragraph 51 | Text 'asterisk 2' 52 | 53 | --[Unordered Item (entering) #3] Container 54 | Paragraph 55 | Text 'asterisk 3' 56 | 57 | --[cr()] LH=14 58 | ---[Paragraph (entering)] 59 | ---[... Margins (left, top, right, bottom:] 128.31 28.35 28.35 56.7 60 | ---[First Para within a list] breaking 61 | ---[Text] asterisk 3 62 | ---[Paragraph (leaving)] 63 | ---[... Margins (left, top, right, bottom:] 128.31 28.35 28.35 56.7 64 | ---[Unordered Item (leaving)] Container 65 | Paragraph 66 | Text 'asterisk 3' 67 | 68 | --[Unordered List (leaving)] Container 69 | ListItem 'flags=start' 70 | Paragraph 71 | Text 'asterisk 1' 72 | ListItem 73 | Paragraph 74 | Text 'asterisk 2' 75 | ListItem 76 | Paragraph 77 | Text 'asterisk 3' 78 | 79 | --[... Reset List Left Margin] re-set to 58.337999999999994 80 | -[BlockQuote (leaving)] 81 | [cr()] LH=14 82 | [Document] Not Handled 83 | -------------------------------------------------------------------------------- /testdata/Ordered and unordered lists.html: -------------------------------------------------------------------------------- 1 |

Unordered

2 | 3 |

Asterisks tight:

4 | 5 |
    6 |
  • asterisk 1
  • 7 |
  • asterisk 2
  • 8 |
  • asterisk 3
  • 9 |
10 | 11 |

Asterisks loose:

12 | 13 |
    14 |
  • asterisk 1

  • 15 | 16 |
  • asterisk 2

  • 17 | 18 |
  • asterisk 3

  • 19 |
20 | 21 |
22 | 23 |

Pluses tight:

24 | 25 |
    26 |
  • Plus 1
  • 27 |
  • Plus 2
  • 28 |
  • Plus 3
  • 29 |
30 | 31 |

Pluses loose:

32 | 33 |
    34 |
  • Plus 1

  • 35 | 36 |
  • Plus 2

  • 37 | 38 |
  • Plus 3

  • 39 |
40 | 41 |
42 | 43 |

Minuses tight:

44 | 45 |
    46 |
  • Minus 1
  • 47 |
  • Minus 2
  • 48 |
  • Minus 3
  • 49 |
50 | 51 |

Minuses loose:

52 | 53 |
    54 |
  • Minus 1

  • 55 | 56 |
  • Minus 2

  • 57 | 58 |
  • Minus 3

  • 59 |
60 | 61 |

Ordered

62 | 63 |

Tight:

64 | 65 |
    66 |
  1. First
  2. 67 |
  3. Second
  4. 68 |
  5. Third
  6. 69 |
70 | 71 |

and:

72 | 73 |
    74 |
  1. One
  2. 75 |
  3. Two
  4. 76 |
  5. Three
  6. 77 |
78 | 79 |

Loose using tabs:

80 | 81 |
    82 |
  1. First

  2. 83 | 84 |
  3. Second

  4. 85 | 86 |
  5. Third

  6. 87 |
88 | 89 |

and using spaces:

90 | 91 |
    92 |
  1. One

  2. 93 | 94 |
  3. Two

  4. 95 | 96 |
  5. Three

  6. 97 |
98 | 99 |

Multiple paragraphs:

100 | 101 |
    102 |
  1. Item 1, graf one.

    103 | 104 |

    Item 2. graf two. The quick brown fox jumped over the lazy dog's 105 | back.

  2. 106 | 107 |
  3. Item 2.

  4. 108 | 109 |
  5. Item 3.

  6. 110 |
111 | 112 |

Nested

113 | 114 |
    115 |
  • Tab 116 | 117 |
      118 |
    • Tab 119 | 120 |
        121 |
      • Tab
      • 122 |
    • 123 |
  • 124 |
125 | 126 |

Here's another:

127 | 128 |
    129 |
  1. First
  2. 130 |
  3. Second: 131 | 132 |
      133 |
    • Fee
    • 134 |
    • Fie
    • 135 |
    • Foe
    • 136 |
  4. 137 |
  5. Third
  6. 138 |
139 | 140 |

Same thing but with paragraphs:

141 | 142 |
    143 |
  1. First

  2. 144 | 145 |
  3. Second:

    146 | 147 |
      148 |
    • Fee
    • 149 |
    • Fie
    • 150 |
    • Foe
    • 151 |
  4. 152 | 153 |
  5. Third

  6. 154 |
155 | 156 |

This was an error in Markdown 1.0.1:

157 | 158 |
    159 |
  • this

    160 | 161 |
      162 |
    • sub
    • 163 |
    164 | 165 |

    that

  • 166 |
167 | -------------------------------------------------------------------------------- /testdata/Tabs.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [Unordered List (entering)] Container 4 | ListItem 'flags=has_block start' 5 | Paragraph 6 | Text 'this is a list item\nindented with tabs' 7 | ListItem 'flags=has_block end' 8 | Paragraph 9 | Text 'this is a list item\nindented with sp…' 10 | 11 | [... List Left Margin] set to 58.338 12 | -[Unordered Item (entering) #1] Container 13 | Paragraph 14 | Text 'this is a list item\nindented with tabs' 15 | 16 | -[cr()] LH=14 17 | --[Paragraph (entering)] 18 | --[... Margins (left, top, right, bottom:] 98.322 28.35 28.35 56.7 19 | --[First Para within a list] breaking 20 | --[Text] this is a list item indented with tabs 21 | --[Paragraph (leaving)] 22 | --[... Margins (left, top, right, bottom:] 98.322 28.35 28.35 56.7 23 | --[Unordered Item (leaving)] Container 24 | Paragraph 25 | Text 'this is a list item\nindented with tabs' 26 | 27 | -[Unordered Item (entering) #2] Container 28 | Paragraph 29 | Text 'this is a list item\nindented with sp…' 30 | 31 | -[cr()] LH=14 32 | --[Paragraph (entering)] 33 | --[... Margins (left, top, right, bottom:] 98.322 28.35 28.35 56.7 34 | --[First Para within a list] breaking 35 | --[Text] this is a list item indented with spaces 36 | --[Paragraph (leaving)] 37 | --[... Margins (left, top, right, bottom:] 98.322 28.35 28.35 56.7 38 | --[Unordered Item (leaving)] Container 39 | Paragraph 40 | Text 'this is a list item\nindented with sp…' 41 | 42 | -[Unordered List (leaving)] Container 43 | ListItem 'flags=has_block start' 44 | Paragraph 45 | Text 'this is a list item\nindented with tabs' 46 | ListItem 'flags=has_block end' 47 | Paragraph 48 | Text 'this is a list item\nindented with sp…' 49 | 50 | -[... Reset List Left Margin] re-set to 28.35 51 | [cr()] LH=14 52 | [Paragraph (entering)] 53 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 54 | [cr()] LH=14 55 | [Text] Code: 56 | [Paragraph (leaving)] 57 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 58 | [cr()] LH=14 59 | [Codeblock] Leaf 'this code block is indented by one ta…' 60 | 61 | [cr()] LH=14 62 | [Paragraph (entering)] 63 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 64 | [cr()] LH=14 65 | [Text] And: 66 | [Paragraph (leaving)] 67 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 68 | [cr()] LH=14 69 | [Codeblock] Leaf '\tthis code block is indented by two …' 70 | 71 | [cr()] LH=14 72 | [Paragraph (entering)] 73 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 74 | [cr()] LH=14 75 | [Text] And: 76 | [Paragraph (leaving)] 77 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 78 | [cr()] LH=14 79 | [Codeblock] Leaf '+\tthis is an example list item\n\tin…' 80 | 81 | [cr()] LH=14 82 | [Document] Not Handled 83 | -------------------------------------------------------------------------------- /testdata/Amps and angle encoding.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [Paragraph (entering)] 4 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 5 | [cr()] LH=14 6 | [Text] AT&T has an ampersand in their name. 7 | [Paragraph (leaving)] 8 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 9 | [cr()] LH=14 10 | [Paragraph (entering)] 11 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 12 | [cr()] LH=14 13 | [Text] AT 14 | [Text] & 15 | [Text] T is another way to write it. 16 | [Paragraph (leaving)] 17 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 18 | [cr()] LH=14 19 | [Paragraph (entering)] 20 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 21 | [cr()] LH=14 22 | [Text] This & that. 23 | [Paragraph (leaving)] 24 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 25 | [cr()] LH=14 26 | [Paragraph (entering)] 27 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 28 | [cr()] LH=14 29 | [Text] 4 < 5. 30 | [Paragraph (leaving)] 31 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 32 | [cr()] LH=14 33 | [Paragraph (entering)] 34 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 35 | [cr()] LH=14 36 | [Text] 6 > 5. 37 | [Paragraph (leaving)] 38 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 39 | [cr()] LH=14 40 | [Paragraph (entering)] 41 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 42 | [cr()] LH=14 43 | [Text] Here's a 44 | -[Link (entering)] Destination[http://example.com/?foo=1&bar=2] Title[] 45 | -[Text] link 46 | -[Link (leaving)] 47 | [Text] with an ampersand in the URL. 48 | [Paragraph (leaving)] 49 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 50 | [cr()] LH=14 51 | [Paragraph (entering)] 52 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 53 | [cr()] LH=14 54 | [Text] Here's a link with an amersand in the link text: 55 | -[Link (entering)] Destination[http://att.com/] Title[AT&T] 56 | -[Text] AT&T 57 | -[Link (leaving)] 58 | [Text] . 59 | [Paragraph (leaving)] 60 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 61 | [cr()] LH=14 62 | [Paragraph (entering)] 63 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 64 | [cr()] LH=14 65 | [Text] Here's an inline 66 | -[Link (entering)] Destination[/script?foo=1&bar=2] Title[] 67 | -[Text] link 68 | -[Link (leaving)] 69 | [Text] . 70 | [Paragraph (leaving)] 71 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 72 | [cr()] LH=14 73 | [Paragraph (entering)] 74 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 75 | [cr()] LH=14 76 | [Text] Here's an inline 77 | -[Link (entering)] Destination[/script?foo=1&bar=2] Title[] 78 | -[Text] link 79 | -[Link (leaving)] 80 | [Text] . 81 | [Paragraph (leaving)] 82 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 83 | [cr()] LH=14 84 | [Document] Not Handled 85 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # This is an example .goreleaser.yml file with some sensible defaults. 3 | # Make sure to check the documentation at https://goreleaser.com 4 | project_name: mdtopdf 5 | before: 6 | hooks: 7 | - git submodule update --remote --init 8 | # You may remove this if you don't use go modules. 9 | - go mod tidy 10 | # you may remove this if you don't need go generate 11 | - go generate ./... 12 | - go install github.com/cpuguy83/go-md2man@latest 13 | - go-md2man -in md2pdf.1.md -out md2pdf.1 14 | - gzip md2pdf.1 15 | 16 | builds: 17 | - env: [CGO_ENABLED=0] 18 | goos: 19 | - linux 20 | - darwin 21 | - freebsd 22 | - netbsd 23 | goarch: 24 | - amd64 25 | - arm64 26 | 27 | main: cmd/md2pdf/md2pdf.go 28 | id: md2pdf 29 | binary: md2pdf 30 | 31 | 32 | archives: 33 | - format: tar.gz 34 | # this name template makes the OS and Arch compatible with `uname`. 35 | name_template: >- 36 | {{ .ProjectName }}_ 37 | {{- title .Os }}_ 38 | {{- if eq .Arch "amd64" }}x86_64 39 | {{- else if eq .Arch "386" }}i386 40 | {{- else }}{{ .Arch }}{{ end }} 41 | {{- if .Arm }}v{{ .Arm }}{{ end }} 42 | checksum: 43 | name_template: 'checksums.txt' 44 | snapshot: 45 | name_template: "{{ incpatch .Version }}-next" 46 | changelog: 47 | sort: asc 48 | filters: 49 | exclude: 50 | - '^docs:' 51 | - '^test:' 52 | 53 | nfpms: 54 | - package_name: mdtopdf 55 | - file_name_template: "{{ .ConventionalFileName }}" 56 | id: packages 57 | homepage: https://github.com/solworktech/md2pdf 58 | description: |- 59 | Markdown to PDF converter 60 | A CLI utility which, as the name implies, generates PDF from Markdown. 61 | Features: syntax highlighting (for code blocks), 62 | dark, light and custom themes, 63 | pagination control (using horizontal lines - useful for presentations), 64 | page Footer (consisting of author, title and page number), 65 | support of non-Latin charsets and multiple fonts 66 | maintainer: Jesse Portnoy 67 | license: MIT 68 | vendor: Solworktech 69 | bindir: /usr/bin 70 | section: utils 71 | contents: 72 | - src: ./custom_themes 73 | dst: /usr/share/{{ .PackageName }} 74 | expand: true 75 | file_info: 76 | mode: 0755 77 | - src: ./highlight/syntax_files 78 | dst: /usr/share/{{ .PackageName }}/syntax_files 79 | expand: true 80 | file_info: 81 | mode: 0755 82 | type: "config|noreplace" 83 | - src: ./LICENSE 84 | dst: /usr/share/doc/{{ .PackageName }}/copyright 85 | expand: true 86 | file_info: 87 | mode: 0644 88 | # mtime: "{{ .CommitDate }}" 89 | - src: ./README.md 90 | dst: /usr/share/doc/{{ .PackageName }}/ 91 | expand: true 92 | file_info: 93 | mode: 0644 94 | - src: ./md2pdf.1.gz 95 | dst: /usr/share/man/man1/ 96 | expand: true 97 | file_info: 98 | mode: 0644 99 | formats: 100 | - deb 101 | - rpm 102 | - archlinux 103 | # The lines beneath this are called `modelines`. See `:help modeline` 104 | # Feel free to remove those if you don't want/use them. 105 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 106 | # vim: set ts=2 sw=2 tw=0 fo=cnqoj 107 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | codeberg.org/go-pdf/fpdf v0.11.1 h1:U8+coOTDVLxHIXZgGvkfQEi/q0hYHYvEHFuGNX2GzGs= 2 | codeberg.org/go-pdf/fpdf v0.11.1/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoPOc4LjU= 3 | github.com/canhlinh/svg2png v0.0.0-20201124065332-6ba87c82371f h1:Km7aXA1/+77OZ6mq8VV/QJ9nP6y4OUwxj+GQ5nW7X5Y= 4 | github.com/canhlinh/svg2png v0.0.0-20201124065332-6ba87c82371f/go.mod h1:u13M4umOwLc1fTX2itKxGff/6S+YWc7l15kJGtm2IJY= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= 9 | github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= 10 | github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk= 11 | github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= 12 | github.com/jessp01/gohighlight v0.21.2 h1:radLDWQMJeDwzn6b8cduio7kVXhy3zkYFsWxkr/3CRI= 13 | github.com/jessp01/gohighlight v0.21.2/go.mod h1:52r0Yxd1+T9f7uLenaO2/34K3gPOejxCxXwdNc/2Z8Y= 14 | github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 h1:YocNLcTBdEdvY3iDK6jfWXvEaM5OCKkjxPKoJRdB3Gg= 15 | github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo= 16 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 17 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 18 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 19 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 20 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 21 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 22 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 23 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 24 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 25 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 26 | golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= 27 | golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= 28 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 29 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 30 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 31 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 32 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 33 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 34 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 35 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 36 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 37 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 38 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 39 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 40 | -------------------------------------------------------------------------------- /testdata/Inline HTML (Simple).log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [Paragraph (entering)] 4 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 5 | [cr()] LH=14 6 | [Text] Here's a simple block: 7 | [Paragraph (leaving)] 8 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 9 | [cr()] LH=14 10 | [HTMLBlock]
11 | foo 12 |
13 | [cr()] LH=14 14 | [cr()] LH=14 15 | [Paragraph (entering)] 16 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 17 | [cr()] LH=14 18 | [Text] This should be a code block, though: 19 | [Paragraph (leaving)] 20 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 21 | [cr()] LH=14 22 | [Codeblock] Leaf '
\n\tfoo\n
\n' 23 | 24 | [cr()] LH=14 25 | [Paragraph (entering)] 26 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 27 | [cr()] LH=14 28 | [Text] As should this: 29 | [Paragraph (leaving)] 30 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 31 | [cr()] LH=14 32 | [Codeblock] Leaf '
foo
\n' 33 | 34 | [cr()] LH=14 35 | [Paragraph (entering)] 36 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 37 | [cr()] LH=14 38 | [Text] Now, nested: 39 | [Paragraph (leaving)] 40 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 41 | [cr()] LH=14 42 | [HTMLBlock]
43 |
44 |
45 | foo 46 |
47 |
48 |
49 | [cr()] LH=14 50 | [cr()] LH=14 51 | [Paragraph (entering)] 52 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 53 | [cr()] LH=14 54 | [Text] This should just be an HTML comment: 55 | [Paragraph (leaving)] 56 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 57 | [cr()] LH=14 58 | [HTMLBlock] 59 | [cr()] LH=14 60 | [cr()] LH=14 61 | [Paragraph (entering)] 62 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 63 | [cr()] LH=14 64 | [Text] Multiline: 65 | [Paragraph (leaving)] 66 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 67 | [cr()] LH=14 68 | [HTMLBlock] 72 | [cr()] LH=14 73 | [cr()] LH=14 74 | [Paragraph (entering)] 75 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 76 | [cr()] LH=14 77 | [Text] Code block: 78 | [Paragraph (leaving)] 79 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 80 | [cr()] LH=14 81 | [Codeblock] Leaf '\n' 82 | 83 | [cr()] LH=14 84 | [Paragraph (entering)] 85 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 86 | [cr()] LH=14 87 | [Text] Just plain comment, with trailing spaces on the line: 88 | [Paragraph (leaving)] 89 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 90 | [cr()] LH=14 91 | [HTMLBlock] 92 | [cr()] LH=14 93 | [cr()] LH=14 94 | [Paragraph (entering)] 95 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 96 | [cr()] LH=14 97 | [Text] Code: 98 | [Paragraph (leaving)] 99 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 100 | [cr()] LH=14 101 | [Codeblock] Leaf '
\n' 102 | 103 | [cr()] LH=14 104 | [Paragraph (entering)] 105 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 106 | [cr()] LH=14 107 | [Text] Hr's: 108 | [Paragraph (leaving)] 109 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 110 | [cr()] LH=14 111 | [HTMLBlock]
112 | [cr()] LH=14 113 | [cr()] LH=14 114 | [HTMLBlock]
115 | [cr()] LH=14 116 | [cr()] LH=14 117 | [HTMLBlock]
118 | [cr()] LH=14 119 | [cr()] LH=14 120 | [HTMLBlock]
121 | [cr()] LH=14 122 | [cr()] LH=14 123 | [HTMLBlock]
124 | [cr()] LH=14 125 | [cr()] LH=14 126 | [HTMLBlock]
127 | [cr()] LH=14 128 | [cr()] LH=14 129 | [HTMLBlock]
130 | [cr()] LH=14 131 | [cr()] LH=14 132 | [HTMLBlock]
133 | [cr()] LH=14 134 | [cr()] LH=14 135 | [HTMLBlock]
136 | [cr()] LH=14 137 | [cr()] LH=14 138 | [Document] Not Handled 139 | -------------------------------------------------------------------------------- /testdata/Horizontal rules.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [Paragraph (entering)] 4 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 5 | [cr()] LH=14 6 | [Text] Dashes: 7 | [Paragraph (leaving)] 8 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 9 | [cr()] LH=14 10 | [HorizontalRule] 11 | [cr()] LH=14 12 | [... From X,Y] 28.35,70.35 13 | [... To X,Y] 583.65,70.35 14 | [cr()] LH=14 15 | [HorizontalRule] 16 | [cr()] LH=14 17 | [... From X,Y] 28.35,98.35 18 | [... To X,Y] 583.65,98.35 19 | [cr()] LH=14 20 | [HorizontalRule] 21 | [cr()] LH=14 22 | [... From X,Y] 28.35,126.35 23 | [... To X,Y] 583.65,126.35 24 | [cr()] LH=14 25 | [HorizontalRule] 26 | [cr()] LH=14 27 | [... From X,Y] 28.35,154.35 28 | [... To X,Y] 583.65,154.35 29 | [cr()] LH=14 30 | [Codeblock] Leaf '---\n' 31 | 32 | [cr()] LH=14 33 | [HorizontalRule] 34 | [cr()] LH=14 35 | [... From X,Y] 28.35,210.35 36 | [... To X,Y] 583.65,210.35 37 | [cr()] LH=14 38 | [HorizontalRule] 39 | [cr()] LH=14 40 | [... From X,Y] 28.35,238.35 41 | [... To X,Y] 583.65,238.35 42 | [cr()] LH=14 43 | [HorizontalRule] 44 | [cr()] LH=14 45 | [... From X,Y] 28.35,266.35 46 | [... To X,Y] 583.65,266.35 47 | [cr()] LH=14 48 | [HorizontalRule] 49 | [cr()] LH=14 50 | [... From X,Y] 28.35,294.35 51 | [... To X,Y] 583.65,294.35 52 | [cr()] LH=14 53 | [Codeblock] Leaf '- - -\n' 54 | 55 | [cr()] LH=14 56 | [Paragraph (entering)] 57 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 58 | [cr()] LH=14 59 | [Text] Asterisks: 60 | [Paragraph (leaving)] 61 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 62 | [cr()] LH=14 63 | [HorizontalRule] 64 | [cr()] LH=14 65 | [... From X,Y] 28.35,378.35 66 | [... To X,Y] 583.65,378.35 67 | [cr()] LH=14 68 | [HorizontalRule] 69 | [cr()] LH=14 70 | [... From X,Y] 28.35,406.35 71 | [... To X,Y] 583.65,406.35 72 | [cr()] LH=14 73 | [HorizontalRule] 74 | [cr()] LH=14 75 | [... From X,Y] 28.35,434.35 76 | [... To X,Y] 583.65,434.35 77 | [cr()] LH=14 78 | [HorizontalRule] 79 | [cr()] LH=14 80 | [... From X,Y] 28.35,462.35 81 | [... To X,Y] 583.65,462.35 82 | [cr()] LH=14 83 | [Codeblock] Leaf '***\n' 84 | 85 | [cr()] LH=14 86 | [HorizontalRule] 87 | [cr()] LH=14 88 | [... From X,Y] 28.35,518.35 89 | [... To X,Y] 583.65,518.35 90 | [cr()] LH=14 91 | [HorizontalRule] 92 | [cr()] LH=14 93 | [... From X,Y] 28.35,546.35 94 | [... To X,Y] 583.65,546.35 95 | [cr()] LH=14 96 | [HorizontalRule] 97 | [cr()] LH=14 98 | [... From X,Y] 28.35,574.35 99 | [... To X,Y] 583.65,574.35 100 | [cr()] LH=14 101 | [HorizontalRule] 102 | [cr()] LH=14 103 | [... From X,Y] 28.35,602.35 104 | [... To X,Y] 583.65,602.35 105 | [cr()] LH=14 106 | [Codeblock] Leaf '* * *\n' 107 | 108 | [cr()] LH=14 109 | [Paragraph (entering)] 110 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 111 | [cr()] LH=14 112 | [Text] Underscores: 113 | [Paragraph (leaving)] 114 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 115 | [cr()] LH=14 116 | [HorizontalRule] 117 | [cr()] LH=14 118 | [... From X,Y] 28.35,686.35 119 | [... To X,Y] 583.65,686.35 120 | [cr()] LH=14 121 | [HorizontalRule] 122 | [cr()] LH=14 123 | [... From X,Y] 28.35,714.35 124 | [... To X,Y] 583.65,714.35 125 | [cr()] LH=14 126 | [HorizontalRule] 127 | [cr()] LH=14 128 | [... From X,Y] 28.35,42.35 129 | [... To X,Y] 583.65,42.35 130 | [cr()] LH=14 131 | [HorizontalRule] 132 | [cr()] LH=14 133 | [... From X,Y] 28.35,70.35 134 | [... To X,Y] 583.65,70.35 135 | [cr()] LH=14 136 | [Codeblock] Leaf '___\n' 137 | 138 | [cr()] LH=14 139 | [HorizontalRule] 140 | [cr()] LH=14 141 | [... From X,Y] 28.35,126.35 142 | [... To X,Y] 583.65,126.35 143 | [cr()] LH=14 144 | [HorizontalRule] 145 | [cr()] LH=14 146 | [... From X,Y] 28.35,154.35 147 | [... To X,Y] 583.65,154.35 148 | [cr()] LH=14 149 | [HorizontalRule] 150 | [cr()] LH=14 151 | [... From X,Y] 28.35,182.35 152 | [... To X,Y] 583.65,182.35 153 | [cr()] LH=14 154 | [HorizontalRule] 155 | [cr()] LH=14 156 | [... From X,Y] 28.35,210.35 157 | [... To X,Y] 583.65,210.35 158 | [cr()] LH=14 159 | [Codeblock] Leaf '_ _ _\n' 160 | 161 | [cr()] LH=14 162 | [Document] Not Handled 163 | -------------------------------------------------------------------------------- /md2pdf.1.md: -------------------------------------------------------------------------------- 1 | md2pdf 1 "March 2025" md2pdf "User Manual" 2 | ================================================== 3 | 4 | ## DESCRIPTION 5 | 6 | A CLI utility which, as the name implies, generates PDF from Markdown. 7 | 8 | ## Features 9 | 10 | - [Syntax highlighting (for code blocks)](#syntax-highlighting) 11 | - [Dark and light themes](#custom-themes) 12 | - [Customised themes (by passing a JSON file to `md2pdf`)](#custom-themes) 13 | - [Auto Generation of Table of Contents](#auto-generation-of-table-of-contents) 14 | - [Support of non-Latin charsets and multiple fonts](#using-non-ascii-glyphsfonts) 15 | - [Pagination control (using horizontal lines - especially useful for presentations)](#additional-options) 16 | - [Page Footer (consisting of author, title and page number)](#additional-options) 17 | 18 | ## Supported Markdown elements 19 | 20 | - Emphasized and strong text 21 | - Headings 1-6 22 | - Ordered and unordered lists 23 | - Nested lists 24 | - Images 25 | - Tables (but see limitations below) 26 | - Links 27 | - Code blocks and backticked text 28 | 29 | ## Syntax highlighting 30 | 31 | `mdtopdf` supports colourised output via the [gohighlight module](https://github.com/jessp01/gohighlight). 32 | 33 | For examples, see `testdata/Markdown Documentation - Syntax.text` and `testdata/Markdown Documentation - Syntax.pdf` 34 | 35 | ## Custom themes 36 | 37 | `md2pdf` supports both light and dark themes out of the box (use `--theme light` or `--theme dark` - no config required). 38 | 39 | However, if you wish to customise the font faces, sizes and colours, you can use the JSONs in 40 | [custom_themes](./custom_themes) as a starting point. Edit to your liking and pass `--theme /path/to/json` to `md2pdf` 41 | 42 | ## Auto Generation of Table of Contents 43 | 44 | `md2pdf` can automatically generate a TOC (where each item corresponds to a header in the doc) and include it in the first page. 45 | These can then be clicked to navigate to the relevant section. 46 | 47 | To make use of this feature, simple pass `--generate-toc` as argument. 48 | 49 | ## Synopsis 50 | 51 | ```sh 52 | -author string 53 | Author's name; used if -footer is passed 54 | -font-file string 55 | path to font file to use 56 | -font-name string 57 | Font name ID; e.g 'Helvetica-1251' 58 | -generate-toc 59 | Auto Generate Table of Contents (TOC) 60 | -help 61 | Show usage message 62 | -i string 63 | Input filename, dir consisting of .md|.markdown files or HTTP(s) URL; default is os.Stdin 64 | -log-file string 65 | Path to log file 66 | -new-page-on-hr 67 | Interpret HR as a new page; useful for presentations 68 | -o string 69 | Output PDF filename; required 70 | -orientation string 71 | [portrait | landscape] (default "portrait") 72 | -page-size string 73 | [A3 | A4 | A5] (default "A4") 74 | -s string 75 | Path to github.com/jessp01/gohighlight/syntax_files 76 | -theme string 77 | [light | dark | /path/to/custom/theme.json] (default "light") 78 | -title string 79 | Presentation title 80 | -unicode-encoding string 81 | e.g 'cp1251' 82 | -version 83 | Print version and build info 84 | -with-footer 85 | Print doc footer ( <page number>) 86 | ``` 87 | 88 | ## Examples 89 | 90 | $ md2pdf -i doc.md -o doc.pdf 91 | 92 | To benefit from Syntax highlighting, invoke thusly: 93 | $ md2pdf -i syn_doc.md -s /usr/share/mdtopdf/syntax_files -o doc.pdf 94 | 95 | To convert multiple MD files into a single PDF, use: 96 | $ md2pdf -i /path/to/md/directory -o doc.pdf 97 | 98 | For example, the below will: 99 | 100 | - Set the title to `My Grand Title` 101 | - Set `Random Bloke` as the author (used in the footer) 102 | - Set the dark theme 103 | - Start a new page when encountering a HR (`---`); useful for creating presentations 104 | - Print a footer (`author name, title, page number`) 105 | 106 | $ go run md2pdf.go -i /path/to/md \ 107 | -o /path/to/pdf --title "My Grand Title" --author "Random Bloke" \ 108 | --theme dark --new-page-on-hr --with-footer 109 | 110 | ## Using non-ASCII Glyphs/Fonts 111 | 112 | For a full example, run: 113 | 114 | $ md2pdf -i russian.md -o russian.pdf \ 115 | --unicode-encoding cp1251 --font-file helvetica_1251.json --font-name Helvetica_1251 116 | 117 | -------------------------------------------------------------------------------- /testdata/Auto links.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [Paragraph (entering)] 4 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 5 | [cr()] LH=14 6 | [Text] Link: 7 | -[Link (entering)] Destination[http://example.com/] Title[] 8 | -[Text] http://example.com/ 9 | -[Link (leaving)] 10 | [Text] . 11 | [Paragraph (leaving)] 12 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 13 | [cr()] LH=14 14 | [Paragraph (entering)] 15 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 16 | [cr()] LH=14 17 | [Text] With an ampersand: 18 | -[Link (entering)] Destination[http://example.com/?foo=1&bar=2] Title[] 19 | -[Text] http://example.com/?foo=1&bar=2 20 | -[Link (leaving)] 21 | [Paragraph (leaving)] 22 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 23 | [cr()] LH=14 24 | [Unordered List (entering)] Container 25 | ListItem 'flags=start' 26 | Paragraph 27 | Text 'In a list?' 28 | ListItem 29 | Paragraph 30 | Text 31 | Link 'url=http://example.com/' 32 | Text 'http://example.com/' 33 | Text 34 | ListItem 'flags=end' 35 | Paragraph 36 | Text 'It should.' 37 | 38 | [... List Left Margin] set to 58.338 39 | -[Unordered Item (entering) #1] Container 40 | Paragraph 41 | Text 'In a list?' 42 | 43 | -[cr()] LH=14 44 | --[Paragraph (entering)] 45 | --[... Margins (left, top, right, bottom:] 98.322 28.35 28.35 56.7 46 | --[First Para within a list] breaking 47 | --[Text] In a list? 48 | --[Paragraph (leaving)] 49 | --[... Margins (left, top, right, bottom:] 98.322 28.35 28.35 56.7 50 | --[Unordered Item (leaving)] Container 51 | Paragraph 52 | Text 'In a list?' 53 | 54 | -[Unordered Item (entering) #2] Container 55 | Paragraph 56 | Text 57 | Link 'url=http://example.com/' 58 | Text 'http://example.com/' 59 | Text 60 | 61 | -[cr()] LH=14 62 | --[Paragraph (entering)] 63 | --[... Margins (left, top, right, bottom:] 98.322 28.35 28.35 56.7 64 | --[First Para within a list] breaking 65 | --[Text] 66 | ---[Link (entering)] Destination[http://example.com/] Title[] 67 | ---[Text] http://example.com/ 68 | ---[Link (leaving)] 69 | --[Text] 70 | --[Paragraph (leaving)] 71 | --[... Margins (left, top, right, bottom:] 98.322 28.35 28.35 56.7 72 | --[Unordered Item (leaving)] Container 73 | Paragraph 74 | Text 75 | Link 'url=http://example.com/' 76 | Text 'http://example.com/' 77 | Text 78 | 79 | -[Unordered Item (entering) #3] Container 80 | Paragraph 81 | Text 'It should.' 82 | 83 | -[cr()] LH=14 84 | --[Paragraph (entering)] 85 | --[... Margins (left, top, right, bottom:] 98.322 28.35 28.35 56.7 86 | --[First Para within a list] breaking 87 | --[Text] It should. 88 | --[Paragraph (leaving)] 89 | --[... Margins (left, top, right, bottom:] 98.322 28.35 28.35 56.7 90 | --[Unordered Item (leaving)] Container 91 | Paragraph 92 | Text 'It should.' 93 | 94 | -[Unordered List (leaving)] Container 95 | ListItem 'flags=start' 96 | Paragraph 97 | Text 'In a list?' 98 | ListItem 99 | Paragraph 100 | Text 101 | Link 'url=http://example.com/' 102 | Text 'http://example.com/' 103 | Text 104 | ListItem 'flags=end' 105 | Paragraph 106 | Text 'It should.' 107 | 108 | -[... Reset List Left Margin] re-set to 28.35 109 | [cr()] LH=14 110 | [BlockQuote (entering)] 111 | -[Paragraph (entering)] 112 | -[... Margins (left, top, right, bottom:] 58.338 28.35 28.35 56.7 113 | -[cr()] LH=14 114 | -[Text] Blockquoted: 115 | --[Link (entering)] Destination[http://example.com/] Title[] 116 | --[Text] http://example.com/ 117 | --[Link (leaving)] 118 | -[Paragraph (leaving)] 119 | -[... Margins (left, top, right, bottom:] 58.338 28.35 28.35 56.7 120 | -[cr()] LH=14 121 | -[BlockQuote (leaving)] 122 | [cr()] LH=14 123 | [Paragraph (entering)] 124 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 125 | [cr()] LH=14 126 | [Text] Auto-links should not occur here: 127 | [processCode] <http://example.com/> 128 | [Backtick (entering)] 129 | [Paragraph (leaving)] 130 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 131 | [cr()] LH=14 132 | [Codeblock] Leaf 'or here: <http://example.com/>\n' 133 | 134 | [cr()] LH=14 135 | [Document] Not Handled 136 | -------------------------------------------------------------------------------- /custom_themes/dark_theme.json: -------------------------------------------------------------------------------- 1 | { 2 | "Normal": { 3 | "Font": "Arial", 4 | "Style": "", 5 | "Size": 12, 6 | "Spacing": 2, 7 | "TextColor": { 8 | "Red": 255, 9 | "Green": 255, 10 | "Blue": 255 11 | }, 12 | "FillColor": { 13 | "Red": 0, 14 | "Green": 0, 15 | "Blue": 0 16 | } 17 | }, 18 | "Link": { 19 | "Font": "Arial", 20 | "Style": "b", 21 | "Size": 12, 22 | "Spacing": 2, 23 | "TextColor": { 24 | "Red": 100, 25 | "Green": 149, 26 | "Blue": 237 27 | }, 28 | "FillColor": { 29 | "Red": 0, 30 | "Green": 0, 31 | "Blue": 0 32 | } 33 | }, 34 | "Backtick": { 35 | "Font": "Times", 36 | "Style": "", 37 | "Size": 12, 38 | "Spacing": 2, 39 | "TextColor": { 40 | "Red": 211, 41 | "Green": 211, 42 | "Blue": 211 43 | }, 44 | "FillColor": { 45 | "Red": 32, 46 | "Green": 35, 47 | "Blue": 37 48 | } 49 | }, 50 | "Blockquote": { 51 | "Font": "Arial", 52 | "Style": "i", 53 | "Size": 12, 54 | "Spacing": 2, 55 | "TextColor": { 56 | "Red": 169, 57 | "Green": 169, 58 | "Blue": 169 59 | }, 60 | "FillColor": { 61 | "Red": 0, 62 | "Green": 0, 63 | "Blue": 0 64 | } 65 | }, 66 | "IndentValue": 0, 67 | "H1": { 68 | "Font": "Arial", 69 | "Style": "b", 70 | "Size": 24, 71 | "Spacing": 5, 72 | "TextColor": { 73 | "Red": 169, 74 | "Green": 169, 75 | "Blue": 169 76 | }, 77 | "FillColor": { 78 | "Red": 0, 79 | "Green": 0, 80 | "Blue": 0 81 | } 82 | }, 83 | "H2": { 84 | "Font": "Arial", 85 | "Style": "b", 86 | "Size": 21, 87 | "Spacing": 5, 88 | "TextColor": { 89 | "Red": 169, 90 | "Green": 169, 91 | "Blue": 169 92 | }, 93 | "FillColor": { 94 | "Red": 0, 95 | "Green": 0, 96 | "Blue": 0 97 | } 98 | }, 99 | "H3": { 100 | "Font": "Arial", 101 | "Style": "b", 102 | "Size": 20, 103 | "Spacing": 5, 104 | "TextColor": { 105 | "Red": 169, 106 | "Green": 169, 107 | "Blue": 169 108 | }, 109 | "FillColor": { 110 | "Red": 0, 111 | "Green": 0, 112 | "Blue": 0 113 | } 114 | }, 115 | "H4": { 116 | "Font": "Arial", 117 | "Style": "b", 118 | "Size": 18, 119 | "Spacing": 5, 120 | "TextColor": { 121 | "Red": 169, 122 | "Green": 169, 123 | "Blue": 169 124 | }, 125 | "FillColor": { 126 | "Red": 0, 127 | "Green": 0, 128 | "Blue": 0 129 | } 130 | }, 131 | "H5": { 132 | "Font": "Arial", 133 | "Style": "b", 134 | "Size": 16, 135 | "Spacing": 5, 136 | "TextColor": { 137 | "Red": 169, 138 | "Green": 169, 139 | "Blue": 169 140 | }, 141 | "FillColor": { 142 | "Red": 0, 143 | "Green": 0, 144 | "Blue": 0 145 | } 146 | }, 147 | "H6": { 148 | "Font": "Arial", 149 | "Style": "b", 150 | "Size": 14, 151 | "Spacing": 5, 152 | "TextColor": { 153 | "Red": 169, 154 | "Green": 169, 155 | "Blue": 169 156 | }, 157 | "FillColor": { 158 | "Red": 0, 159 | "Green": 0, 160 | "Blue": 0 161 | } 162 | }, 163 | "THeader": { 164 | "Font": "Arial", 165 | "Style": "b", 166 | "Size": 12, 167 | "Spacing": 2, 168 | "TextColor": { 169 | "Red": 169, 170 | "Green": 169, 171 | "Blue": 169 172 | }, 173 | "FillColor": { 174 | "Red": 27, 175 | "Green": 27, 176 | "Blue": 27 177 | } 178 | }, 179 | "TBody": { 180 | "Font": "Arial", 181 | "Style": "", 182 | "Size": 12, 183 | "Spacing": 2, 184 | "TextColor": { 185 | "Red": 128, 186 | "Green": 128, 187 | "Blue": 128 188 | }, 189 | "FillColor": { 190 | "Red": 200, 191 | "Green": 200, 192 | "Blue": 200 193 | } 194 | }, 195 | "Code": { 196 | "Font": "Times", 197 | "Style": "", 198 | "Size": 12, 199 | "Spacing": 2, 200 | "TextColor": { 201 | "Red": 211, 202 | "Green": 211, 203 | "Blue": 211 204 | }, 205 | "FillColor": { 206 | "Red": 32, 207 | "Green": 35, 208 | "Blue": 37 209 | } 210 | }, 211 | "Theme": 3, 212 | "BackgroundColor": { 213 | "Red": 0, 214 | "Green": 0, 215 | "Blue": 0 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /custom_themes/light_theme.json: -------------------------------------------------------------------------------- 1 | { 2 | "Normal": { 3 | "Font": "Arial", 4 | "Style": "", 5 | "Size": 12, 6 | "Spacing": 2, 7 | "TextColor": { 8 | "Red": 0, 9 | "Green": 0, 10 | "Blue": 0 11 | }, 12 | "FillColor": { 13 | "Red": 255, 14 | "Green": 255, 15 | "Blue": 255 16 | } 17 | }, 18 | "Link": { 19 | "Font": "Arial", 20 | "Style": "b", 21 | "Size": 12, 22 | "Spacing": 2, 23 | "TextColor": { 24 | "Red": 100, 25 | "Green": 149, 26 | "Blue": 237 27 | }, 28 | "FillColor": { 29 | "Red": 0, 30 | "Green": 0, 31 | "Blue": 0 32 | } 33 | }, 34 | "Backtick": { 35 | "Font": "Times", 36 | "Style": "", 37 | "Size": 12, 38 | "Spacing": 2, 39 | "TextColor": { 40 | "Red": 37, 41 | "Green": 27, 42 | "Blue": 14 43 | }, 44 | "FillColor": { 45 | "Red": 200, 46 | "Green": 200, 47 | "Blue": 200 48 | } 49 | }, 50 | "Blockquote": { 51 | "Font": "Arial", 52 | "Style": "i", 53 | "Size": 12, 54 | "Spacing": 2, 55 | "TextColor": { 56 | "Red": 0, 57 | "Green": 0, 58 | "Blue": 0 59 | }, 60 | "FillColor": { 61 | "Red": 255, 62 | "Green": 255, 63 | "Blue": 255 64 | } 65 | }, 66 | "IndentValue": 0, 67 | "H1": { 68 | "Font": "Arial", 69 | "Style": "b", 70 | "Size": 24, 71 | "Spacing": 5, 72 | "TextColor": { 73 | "Red": 0, 74 | "Green": 0, 75 | "Blue": 0 76 | }, 77 | "FillColor": { 78 | "Red": 255, 79 | "Green": 255, 80 | "Blue": 255 81 | } 82 | }, 83 | "H2": { 84 | "Font": "Arial", 85 | "Style": "b", 86 | "Size": 21, 87 | "Spacing": 5, 88 | "TextColor": { 89 | "Red": 0, 90 | "Green": 0, 91 | "Blue": 0 92 | }, 93 | "FillColor": { 94 | "Red": 255, 95 | "Green": 255, 96 | "Blue": 255 97 | } 98 | }, 99 | "H3": { 100 | "Font": "Arial", 101 | "Style": "b", 102 | "Size": 20, 103 | "Spacing": 5, 104 | "TextColor": { 105 | "Red": 0, 106 | "Green": 0, 107 | "Blue": 0 108 | }, 109 | "FillColor": { 110 | "Red": 255, 111 | "Green": 255, 112 | "Blue": 255 113 | } 114 | }, 115 | "H4": { 116 | "Font": "Arial", 117 | "Style": "b", 118 | "Size": 18, 119 | "Spacing": 5, 120 | "TextColor": { 121 | "Red": 0, 122 | "Green": 0, 123 | "Blue": 0 124 | }, 125 | "FillColor": { 126 | "Red": 255, 127 | "Green": 255, 128 | "Blue": 255 129 | } 130 | }, 131 | "H5": { 132 | "Font": "Arial", 133 | "Style": "b", 134 | "Size": 16, 135 | "Spacing": 5, 136 | "TextColor": { 137 | "Red": 0, 138 | "Green": 0, 139 | "Blue": 0 140 | }, 141 | "FillColor": { 142 | "Red": 255, 143 | "Green": 255, 144 | "Blue": 255 145 | } 146 | }, 147 | "H6": { 148 | "Font": "Arial", 149 | "Style": "b", 150 | "Size": 14, 151 | "Spacing": 5, 152 | "TextColor": { 153 | "Red": 0, 154 | "Green": 0, 155 | "Blue": 0 156 | }, 157 | "FillColor": { 158 | "Red": 255, 159 | "Green": 255, 160 | "Blue": 255 161 | } 162 | }, 163 | "THeader": { 164 | "Font": "Arial", 165 | "Style": "b", 166 | "Size": 12, 167 | "Spacing": 2, 168 | "TextColor": { 169 | "Red": 0, 170 | "Green": 0, 171 | "Blue": 0 172 | }, 173 | "FillColor": { 174 | "Red": 180, 175 | "Green": 180, 176 | "Blue": 180 177 | } 178 | }, 179 | "TBody": { 180 | "Font": "Arial", 181 | "Style": "", 182 | "Size": 12, 183 | "Spacing": 2, 184 | "TextColor": { 185 | "Red": 0, 186 | "Green": 0, 187 | "Blue": 0 188 | }, 189 | "FillColor": { 190 | "Red": 240, 191 | "Green": 240, 192 | "Blue": 240 193 | } 194 | }, 195 | "Code": { 196 | "Font": "Times", 197 | "Style": "", 198 | "Size": 12, 199 | "Spacing": 2, 200 | "TextColor": { 201 | "Red": 37, 202 | "Green": 27, 203 | "Blue": 14 204 | }, 205 | "FillColor": { 206 | "Red": 200, 207 | "Green": 200, 208 | "Blue": 200 209 | } 210 | }, 211 | "Theme": 3, 212 | "BackgroundColor": { 213 | "Red": 255, 214 | "Green": 255, 215 | "Blue": 255 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /testdata/syntax_highlighting.md: -------------------------------------------------------------------------------- 1 | Below are examples of highlighted blocks of code... 2 | 3 | ### YAML 4 | 5 | ```yaml 6 | filetype: diff 7 | 8 | detect: 9 | filename: "\\.diff$" 10 | header: "^((---.*)|(\\+\\+\\+.*))" 11 | rules: 12 | - statement: "(^\\+\\+\\+.*)" 13 | - statement: "(^---.*)" 14 | - type: "(^@@.*)" 15 | - constant.number: "(^\\+.*)" 16 | - preproc: "(^-.*)" 17 | ``` 18 | 19 | ### INI 20 | 21 | ```ini 22 | [ctype] 23 | ; priority=20 24 | extension=ctype.so 25 | ``` 26 | 27 | ### ping 28 | 29 | ```ping 30 | PING jessex (127.0.1.1) 56(84) bytes of data. 31 | 64 bytes from jessex (127.0.1.1): icmp_seq=1 ttl=64 time=0.031 ms 32 | 64 bytes from jessex (127.0.1.1): icmp_seq=2 ttl=64 time=0.033 ms 33 | 34 | --- jessex ping statistics --- 35 | 4 packets transmitted, 4 received, 0% packet loss, time 3033ms 36 | rtt min/avg/max/mdev = 0.031/0.036/0.041/0.004 ms 37 | 38 | ``` 39 | 40 | ### df 41 | 42 | ```df 43 | Filesystem Size Used Avail Use% Mounted on 44 | udev 7.7G 0 7.7G 0% /dev 45 | tmpfs 1.6G 2.9M 1.6G 1% /run 46 | /dev/nvme0n1p2 23G 20G 1.8G 92% / 47 | tmpfs 7.7G 56M 7.7G 1% /dev/shm 48 | tmpfs 5.0M 4.0K 5.0M 1% /run/lock 49 | /dev/nvme0n1p6 434G 227G 185G 56% /home 50 | ``` 51 | 52 | ---------------------------------------------------------------------------- 53 | 54 | ### Shell 55 | 56 | ```sh 57 | #!/bin/sh -e 58 | PATH="/sbin:/bin" 59 | RUN_DIR="/run/network" 60 | IFSTATE="$RUN_DIR/ifstate" 61 | STATEDIR="$RUN_DIR/state" 62 | 63 | [ -x /sbin/ifup ] || exit 0 64 | [ -x /sbin/ifdown ] || exit 0 65 | 66 | . /lib/lsb/init-functions 67 | 68 | CONFIGURE_INTERFACES=yes 69 | EXCLUDE_INTERFACES= 70 | VERBOSE=no 71 | 72 | [ -f /etc/default/networking ] && . /etc/default/networking 73 | 74 | verbose="" 75 | [ "$VERBOSE" = yes ] && verbose=-v 76 | 77 | check_ifstate() { 78 | if [ ! -d "$RUN_DIR" ] ; then 79 | if ! mkdir -p "$RUN_DIR" ; then 80 | log_failure_msg "can't create $RUN_DIR" 81 | exit 1 82 | fi 83 | if ! chown root:netdev "$RUN_DIR" ; then 84 | log_warning_msg "can't chown $RUN_DIR" 85 | fi 86 | fi 87 | if [ ! -r "$IFSTATE" ] ; then 88 | if ! :> "$IFSTATE" ; then 89 | log_failure_msg "can't initialise $IFSTATE" 90 | exit 1 91 | fi 92 | fi 93 | } 94 | 95 | ``` 96 | 97 | ---------------------------------------------------------------------------- 98 | 99 | ## PHP 100 | 101 | ```php 102 | <?php 103 | if( !function_exists('mb_str_split')){ 104 | function mb_str_split( $string = '', $length = 1 , $encoding = null ){ 105 | if(!empty($string)){ 106 | $split = array(); 107 | $mb_strlen = mb_strlen($string,$encoding); 108 | for($pi = 0; $pi < $mb_strlen; $pi += $length){ 109 | $substr = mb_substr($string, $pi,$length,$encoding); 110 | if( !empty($substr)){ 111 | $split[] = $substr; 112 | } 113 | } 114 | } 115 | return $split; 116 | } 117 | } 118 | ``` 119 | 120 | ## Golang 121 | 122 | ```go 123 | package main 124 | 125 | import ( 126 | "fmt" 127 | "os" 128 | "path/filepath" 129 | ) 130 | 131 | func main() { 132 | ex, err := os.Executable() 133 | if err != nil { 134 | panic(err) 135 | } 136 | exPath := filepath.Dir(ex) 137 | fmt.Println(exPath) 138 | } 139 | ``` 140 | 141 | ## C 142 | 143 | ```c 144 | #include <stdio.h> 145 | int main(int argc, char *argv[]) { 146 | printf("%s\n", "Hello world!"); 147 | } 148 | ``` 149 | 150 | ## XML 151 | 152 | ```xml 153 | <?xml version="1.0" encoding="UTF-8"?> 154 | <deck> 155 | <title>Sample Deck 156 | 157 | 158 | 159 | Deck uses these elements 160 | 161 | 162 |
  • canvas
  • 163 |
  • slide
  • 164 |
  • text
  • 165 |
  • list
  • 166 |
  • image
  • 167 |
  • line
  • 168 |
  • rect
  • 169 |
  • ellipse
  • 170 |
  • curve
  • 171 |
  • arc
  • 172 |
    173 | 174 | 175 | 176 | 177 | 178 | 179 |
    180 | 181 | ``` 182 | -------------------------------------------------------------------------------- /mdtopdf_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Markdown to PDF Converter 3 | * Available at http://github.com/solworktech/md2pdf 4 | * 5 | * Copyright © 2018 Cecil New . 6 | * Distributed under the MIT License. 7 | * See README.md for details. 8 | * 9 | * Dependencies 10 | * This package depends on two other packages: 11 | * 12 | * Go Markdown processor 13 | * Available at https://github.com/gomarkdown/markdown 14 | * 15 | * fpdf - a PDF document generator with high level support for 16 | * text, drawing and images. 17 | * Available at https://codeberg.org/go-pdf/fpdf 18 | */ 19 | 20 | package mdtopdf 21 | 22 | import ( 23 | "github.com/gomarkdown/markdown/parser" 24 | "os" 25 | "path" 26 | "strings" 27 | "testing" 28 | ) 29 | 30 | func testit(inputf string, gohighlight bool, t *testing.T) { 31 | inputd := "./testdata/" 32 | input := path.Join(inputd, inputf) 33 | 34 | tracerfile := path.Join(inputd, strings.TrimSuffix(path.Base(input), path.Ext(input))) 35 | tracerfile += ".log" 36 | 37 | pdffile := path.Join(inputd, strings.TrimSuffix(path.Base(input), path.Ext(input))) 38 | pdffile += ".pdf" 39 | 40 | content, err := os.ReadFile(input) 41 | if err != nil { 42 | t.Errorf("%v:%v", input, err) 43 | } 44 | 45 | var opts []RenderOption 46 | if gohighlight { 47 | opts = []RenderOption{IsHorizontalRuleNewPage(true), SetSyntaxHighlightBaseDir("./highlight/syntax_files")} 48 | } 49 | params := PdfRendererParams{ 50 | Orientation: "", 51 | Papersz: "", 52 | PdfFile: pdffile, 53 | TracerFile: tracerfile, 54 | Opts: opts, 55 | Theme: LIGHT, 56 | CustomThemeFile: "", 57 | FontFile: "", 58 | FontName: "", 59 | } 60 | r := NewPdfRenderer(params) 61 | r.Extensions = parser.NoIntraEmphasis | parser.Tables | parser.FencedCode | parser.Autolink | parser.Strikethrough | parser.SpaceHeadings | parser.HeadingIDs | parser.BackslashLineBreak | parser.DefinitionLists 62 | err = r.Process(content) 63 | if err != nil { 64 | t.Error(err) 65 | } 66 | } 67 | 68 | func TestTables(t *testing.T) { 69 | testit("Tables.text", false, t) 70 | } 71 | 72 | func TestMarkdownDocumenationBasic(t *testing.T) { 73 | testit("Markdown Documentation - Basics.text", false, t) 74 | } 75 | 76 | func TestMarkdownDocumenationSyntax(t *testing.T) { 77 | testit("syntax.md", false, t) 78 | } 79 | 80 | func TestMarkdownDocumenationColourSyntax(t *testing.T) { 81 | testit("syntax_highlighting.md", true, t) 82 | } 83 | 84 | func TestImage(t *testing.T) { 85 | testit("Image.text", false, t) 86 | } 87 | 88 | func TestAutoLinks(t *testing.T) { 89 | testit("Auto links.text", false, t) 90 | } 91 | 92 | func TestAmpersandEncoding(t *testing.T) { 93 | testit("Amps and angle encoding.text", false, t) 94 | } 95 | 96 | func TestInlineLinks(t *testing.T) { 97 | testit("Links, inline style.text", false, t) 98 | } 99 | 100 | func TestLists(t *testing.T) { 101 | testit("Ordered and unordered lists.md", false, t) 102 | } 103 | 104 | func TestStringEmph(t *testing.T) { 105 | testit("Strong and em together.text", false, t) 106 | } 107 | 108 | func TestTabs(t *testing.T) { 109 | testit("Tabs.text", false, t) 110 | } 111 | 112 | func TestBackslashEscapes(t *testing.T) { 113 | testit("Backslash escapes.text", false, t) 114 | } 115 | 116 | func TestBackquotes(t *testing.T) { 117 | testit("Blockquotes with code blocks.text", false, t) 118 | } 119 | 120 | func TestCodeBlocks(t *testing.T) { 121 | testit("Code Blocks.text", false, t) 122 | } 123 | 124 | func TestCodeSpans(t *testing.T) { 125 | testit("Code Spans.text", false, t) 126 | } 127 | 128 | func TestHardWrappedPara(t *testing.T) { 129 | testit("Hard-wrapped paragraphs with list-like lines no empty line before block.text", false, t) 130 | } 131 | 132 | func TestHardWrappedPara2(t *testing.T) { 133 | testit("Hard-wrapped paragraphs with list-like lines.text", false, t) 134 | } 135 | 136 | func TestHorizontalRules(t *testing.T) { 137 | testit("Horizontal rules.text", false, t) 138 | } 139 | 140 | func TestInlineHtmlSimple(t *testing.T) { 141 | testit("Inline HTML (Simple).text", false, t) 142 | } 143 | 144 | func TestInlineHtmlAdvanced(t *testing.T) { 145 | testit("Inline HTML (Advanced).text", false, t) 146 | } 147 | 148 | func TestInlineHtmlComments(t *testing.T) { 149 | testit("Inline HTML comments.text", false, t) 150 | } 151 | 152 | func TestTitleWithQuotes(t *testing.T) { 153 | testit("Literal quotes in titles.text", false, t) 154 | } 155 | 156 | func TestNestedBlockquotes(t *testing.T) { 157 | testit("Nested blockquotes.text", false, t) 158 | } 159 | 160 | func TestLinksReference(t *testing.T) { 161 | testit("Links, reference style.text", false, t) 162 | } 163 | 164 | func TestLinksShortcut(t *testing.T) { 165 | testit("Links, shortcut references.text", false, t) 166 | } 167 | 168 | func TestTidyness(t *testing.T) { 169 | testit("Tidyness.text", false, t) 170 | } 171 | -------------------------------------------------------------------------------- /testdata/Tables.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [cr()] LH=14 4 | [Heading (1, entering)] Container 5 | Text 'Table Tests' 6 | 7 | -[Text] Table Tests 8 | -[Heading (leaving)] 9 | -[cr()] LH=29 10 | [Paragraph (entering)] 11 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 12 | [cr()] LH=14 13 | [Text] This is a simple table: 14 | [Paragraph (leaving)] 15 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 16 | [cr()] LH=14 17 | [Table (entering)] 18 | [cr()] LH=14 19 | -[TableHead (entering)] 20 | --[TableRow (entering)] 21 | ---[TableCell (entering)] 22 | ----[Text] Header 23 | ---[... table header cell] Width=47.2176, height=14 24 | ---[TableCell (leaving)] 25 | ---[TableCell (entering)] 26 | ----[Text] Another header 27 | ---[... table header cell] Width=99.25919999999999, height=14 28 | ---[TableCell (leaving)] 29 | --[TableRow (leaving)] 30 | -[TableHead (leaving)] 31 | -[TableBody (entering)] 32 | --[TableRow (entering)] 33 | ---[TableCell (entering)] 34 | ----[Text] field 1 35 | ---[TableCell (leaving)] 36 | ---[TableCell (entering)] 37 | ----[Text] something 38 | ---[TableCell (leaving)] 39 | --[TableRow (leaving)] 40 | --[TableRow (entering)] 41 | ---[TableCell (entering)] 42 | ----[Text] field 2 43 | ---[TableCell (leaving)] 44 | ---[TableCell (entering)] 45 | ----[Text] something else 46 | ---[TableCell (leaving)] 47 | --[TableRow (leaving)] 48 | -[TableBody (leaving)] 49 | [Table (leaving)] 50 | [cr()] LH=14 51 | [Paragraph (entering)] 52 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 53 | [cr()] LH=14 54 | [Text] Here is another table, with more content in the cells: 55 | [Paragraph (leaving)] 56 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 57 | [cr()] LH=14 58 | [Table (entering)] 59 | [cr()] LH=14 60 | -[TableHead (entering)] 61 | --[TableRow (entering)] 62 | ---[TableCell (entering)] 63 | ----[Text] id 64 | ---[... table header cell] Width=11.2032, height=14 65 | ---[TableCell (leaving)] 66 | ---[TableCell (entering)] 67 | ----[Text] process_name 68 | ---[... table header cell] Width=94.4352, height=14 69 | ---[TableCell (leaving)] 70 | ---[TableCell (entering)] 71 | ----[Text] window_name 72 | ---[... table header cell] Width=269.69759999999997, height=14 73 | ---[TableCell (leaving)] 74 | ---[TableCell (entering)] 75 | ----[Text] duration 76 | ---[... table header cell] Width=56.0448, height=14 77 | ---[TableCell (leaving)] 78 | --[TableRow (leaving)] 79 | -[TableHead (leaving)] 80 | -[TableBody (entering)] 81 | --[TableRow (entering)] 82 | ---[TableCell (entering)] 83 | ----[Text] 1 84 | ---[TableCell (leaving)] 85 | ---[TableCell (entering)] 86 | ----[Text] lxterminal 87 | ---[TableCell (leaving)] 88 | ---[TableCell (entering)] 89 | ----[Text] devilspie2 90 | ---[TableCell (leaving)] 91 | ---[TableCell (entering)] 92 | ----[Text] 00:00:02 93 | ---[TableCell (leaving)] 94 | --[TableRow (leaving)] 95 | --[TableRow (entering)] 96 | ---[TableCell (entering)] 97 | ----[Text] 2 98 | ---[TableCell (leaving)] 99 | ---[TableCell (entering)] 100 | ----[Text] firefox-esr 101 | ---[TableCell (leaving)] 102 | ---[TableCell (entering)] 103 | ----[Text] jessp01/devilspie2: Devilspie2 is an X w 104 | ---[TableCell (leaving)] 105 | ---[TableCell (entering)] 106 | ----[Text] 00:00:10 107 | ---[TableCell (leaving)] 108 | --[TableRow (leaving)] 109 | --[TableRow (entering)] 110 | ---[TableCell (entering)] 111 | ---[TableCell (leaving)] 112 | ---[TableCell (entering)] 113 | ---[TableCell (leaving)] 114 | ---[TableCell (entering)] 115 | ----[Text] indow (Lua) hooks mechanism; it supports 116 | ---[TableCell (leaving)] 117 | ---[TableCell (entering)] 118 | ---[TableCell (leaving)] 119 | --[TableRow (leaving)] 120 | --[TableRow (entering)] 121 | ---[TableCell (entering)] 122 | ---[TableCell (leaving)] 123 | ---[TableCell (entering)] 124 | ---[TableCell (leaving)] 125 | ---[TableCell (entering)] 126 | ----[Text] the following events: window opened, 127 | ---[TableCell (leaving)] 128 | ---[TableCell (entering)] 129 | ---[TableCell (leaving)] 130 | --[TableRow (leaving)] 131 | --[TableRow (entering)] 132 | ---[TableCell (entering)] 133 | ---[TableCell (leaving)] 134 | ---[TableCell (entering)] 135 | ---[TableCell (leaving)] 136 | ---[TableCell (entering)] 137 | ----[Text] closed, focused and title changed 138 | ---[TableCell (leaving)] 139 | ---[TableCell (entering)] 140 | ---[TableCell (leaving)] 141 | --[TableRow (leaving)] 142 | --[TableRow (entering)] 143 | ---[TableCell (entering)] 144 | ----[Text] 4 145 | ---[TableCell (leaving)] 146 | ---[TableCell (entering)] 147 | ----[Text] firefox-esr 148 | ---[TableCell (leaving)] 149 | ---[TableCell (entering)] 150 | ----[Text] Watch The Big Bang Theory - Season 6 151 | ---[TableCell (leaving)] 152 | ---[TableCell (entering)] 153 | ----[Text] 00:00:05 154 | ---[TableCell (leaving)] 155 | --[TableRow (leaving)] 156 | -[TableBody (leaving)] 157 | [Table (leaving)] 158 | [cr()] LH=14 159 | [Document] Not Handled 160 | -------------------------------------------------------------------------------- /cmd/md2pdf/cp1251.map: -------------------------------------------------------------------------------- 1 | !00 U+0000 .notdef 2 | !01 U+0001 .notdef 3 | !02 U+0002 .notdef 4 | !03 U+0003 .notdef 5 | !04 U+0004 .notdef 6 | !05 U+0005 .notdef 7 | !06 U+0006 .notdef 8 | !07 U+0007 .notdef 9 | !08 U+0008 .notdef 10 | !09 U+0009 .notdef 11 | !0A U+000A .notdef 12 | !0B U+000B .notdef 13 | !0C U+000C .notdef 14 | !0D U+000D .notdef 15 | !0E U+000E .notdef 16 | !0F U+000F .notdef 17 | !10 U+0010 .notdef 18 | !11 U+0011 .notdef 19 | !12 U+0012 .notdef 20 | !13 U+0013 .notdef 21 | !14 U+0014 .notdef 22 | !15 U+0015 .notdef 23 | !16 U+0016 .notdef 24 | !17 U+0017 .notdef 25 | !18 U+0018 .notdef 26 | !19 U+0019 .notdef 27 | !1A U+001A .notdef 28 | !1B U+001B .notdef 29 | !1C U+001C .notdef 30 | !1D U+001D .notdef 31 | !1E U+001E .notdef 32 | !1F U+001F .notdef 33 | !20 U+0020 space 34 | !21 U+0021 exclam 35 | !22 U+0022 quotedbl 36 | !23 U+0023 numbersign 37 | !24 U+0024 dollar 38 | !25 U+0025 percent 39 | !26 U+0026 ampersand 40 | !27 U+0027 quotesingle 41 | !28 U+0028 parenleft 42 | !29 U+0029 parenright 43 | !2A U+002A asterisk 44 | !2B U+002B plus 45 | !2C U+002C comma 46 | !2D U+002D hyphen 47 | !2E U+002E period 48 | !2F U+002F slash 49 | !30 U+0030 zero 50 | !31 U+0031 one 51 | !32 U+0032 two 52 | !33 U+0033 three 53 | !34 U+0034 four 54 | !35 U+0035 five 55 | !36 U+0036 six 56 | !37 U+0037 seven 57 | !38 U+0038 eight 58 | !39 U+0039 nine 59 | !3A U+003A colon 60 | !3B U+003B semicolon 61 | !3C U+003C less 62 | !3D U+003D equal 63 | !3E U+003E greater 64 | !3F U+003F question 65 | !40 U+0040 at 66 | !41 U+0041 A 67 | !42 U+0042 B 68 | !43 U+0043 C 69 | !44 U+0044 D 70 | !45 U+0045 E 71 | !46 U+0046 F 72 | !47 U+0047 G 73 | !48 U+0048 H 74 | !49 U+0049 I 75 | !4A U+004A J 76 | !4B U+004B K 77 | !4C U+004C L 78 | !4D U+004D M 79 | !4E U+004E N 80 | !4F U+004F O 81 | !50 U+0050 P 82 | !51 U+0051 Q 83 | !52 U+0052 R 84 | !53 U+0053 S 85 | !54 U+0054 T 86 | !55 U+0055 U 87 | !56 U+0056 V 88 | !57 U+0057 W 89 | !58 U+0058 X 90 | !59 U+0059 Y 91 | !5A U+005A Z 92 | !5B U+005B bracketleft 93 | !5C U+005C backslash 94 | !5D U+005D bracketright 95 | !5E U+005E asciicircum 96 | !5F U+005F underscore 97 | !60 U+0060 grave 98 | !61 U+0061 a 99 | !62 U+0062 b 100 | !63 U+0063 c 101 | !64 U+0064 d 102 | !65 U+0065 e 103 | !66 U+0066 f 104 | !67 U+0067 g 105 | !68 U+0068 h 106 | !69 U+0069 i 107 | !6A U+006A j 108 | !6B U+006B k 109 | !6C U+006C l 110 | !6D U+006D m 111 | !6E U+006E n 112 | !6F U+006F o 113 | !70 U+0070 p 114 | !71 U+0071 q 115 | !72 U+0072 r 116 | !73 U+0073 s 117 | !74 U+0074 t 118 | !75 U+0075 u 119 | !76 U+0076 v 120 | !77 U+0077 w 121 | !78 U+0078 x 122 | !79 U+0079 y 123 | !7A U+007A z 124 | !7B U+007B braceleft 125 | !7C U+007C bar 126 | !7D U+007D braceright 127 | !7E U+007E asciitilde 128 | !7F U+007F .notdef 129 | !80 U+0402 afii10051 130 | !81 U+0403 afii10052 131 | !82 U+201A quotesinglbase 132 | !83 U+0453 afii10100 133 | !84 U+201E quotedblbase 134 | !85 U+2026 ellipsis 135 | !86 U+2020 dagger 136 | !87 U+2021 daggerdbl 137 | !88 U+20AC Euro 138 | !89 U+2030 perthousand 139 | !8A U+0409 afii10058 140 | !8B U+2039 guilsinglleft 141 | !8C U+040A afii10059 142 | !8D U+040C afii10061 143 | !8E U+040B afii10060 144 | !8F U+040F afii10145 145 | !90 U+0452 afii10099 146 | !91 U+2018 quoteleft 147 | !92 U+2019 quoteright 148 | !93 U+201C quotedblleft 149 | !94 U+201D quotedblright 150 | !95 U+2022 bullet 151 | !96 U+2013 endash 152 | !97 U+2014 emdash 153 | !99 U+2122 trademark 154 | !9A U+0459 afii10106 155 | !9B U+203A guilsinglright 156 | !9C U+045A afii10107 157 | !9D U+045C afii10109 158 | !9E U+045B afii10108 159 | !9F U+045F afii10193 160 | !A0 U+00A0 space 161 | !A1 U+040E afii10062 162 | !A2 U+045E afii10110 163 | !A3 U+0408 afii10057 164 | !A4 U+00A4 currency 165 | !A5 U+0490 afii10050 166 | !A6 U+00A6 brokenbar 167 | !A7 U+00A7 section 168 | !A8 U+0401 afii10023 169 | !A9 U+00A9 copyright 170 | !AA U+0404 afii10053 171 | !AB U+00AB guillemotleft 172 | !AC U+00AC logicalnot 173 | !AD U+00AD hyphen 174 | !AE U+00AE registered 175 | !AF U+0407 afii10056 176 | !B0 U+00B0 degree 177 | !B1 U+00B1 plusminus 178 | !B2 U+0406 afii10055 179 | !B3 U+0456 afii10103 180 | !B4 U+0491 afii10098 181 | !B5 U+00B5 mu 182 | !B6 U+00B6 paragraph 183 | !B7 U+00B7 periodcentered 184 | !B8 U+0451 afii10071 185 | !B9 U+2116 afii61352 186 | !BA U+0454 afii10101 187 | !BB U+00BB guillemotright 188 | !BC U+0458 afii10105 189 | !BD U+0405 afii10054 190 | !BE U+0455 afii10102 191 | !BF U+0457 afii10104 192 | !C0 U+0410 afii10017 193 | !C1 U+0411 afii10018 194 | !C2 U+0412 afii10019 195 | !C3 U+0413 afii10020 196 | !C4 U+0414 afii10021 197 | !C5 U+0415 afii10022 198 | !C6 U+0416 afii10024 199 | !C7 U+0417 afii10025 200 | !C8 U+0418 afii10026 201 | !C9 U+0419 afii10027 202 | !CA U+041A afii10028 203 | !CB U+041B afii10029 204 | !CC U+041C afii10030 205 | !CD U+041D afii10031 206 | !CE U+041E afii10032 207 | !CF U+041F afii10033 208 | !D0 U+0420 afii10034 209 | !D1 U+0421 afii10035 210 | !D2 U+0422 afii10036 211 | !D3 U+0423 afii10037 212 | !D4 U+0424 afii10038 213 | !D5 U+0425 afii10039 214 | !D6 U+0426 afii10040 215 | !D7 U+0427 afii10041 216 | !D8 U+0428 afii10042 217 | !D9 U+0429 afii10043 218 | !DA U+042A afii10044 219 | !DB U+042B afii10045 220 | !DC U+042C afii10046 221 | !DD U+042D afii10047 222 | !DE U+042E afii10048 223 | !DF U+042F afii10049 224 | !E0 U+0430 afii10065 225 | !E1 U+0431 afii10066 226 | !E2 U+0432 afii10067 227 | !E3 U+0433 afii10068 228 | !E4 U+0434 afii10069 229 | !E5 U+0435 afii10070 230 | !E6 U+0436 afii10072 231 | !E7 U+0437 afii10073 232 | !E8 U+0438 afii10074 233 | !E9 U+0439 afii10075 234 | !EA U+043A afii10076 235 | !EB U+043B afii10077 236 | !EC U+043C afii10078 237 | !ED U+043D afii10079 238 | !EE U+043E afii10080 239 | !EF U+043F afii10081 240 | !F0 U+0440 afii10082 241 | !F1 U+0441 afii10083 242 | !F2 U+0442 afii10084 243 | !F3 U+0443 afii10085 244 | !F4 U+0444 afii10086 245 | !F5 U+0445 afii10087 246 | !F6 U+0446 afii10088 247 | !F7 U+0447 afii10089 248 | !F8 U+0448 afii10090 249 | !F9 U+0449 afii10091 250 | !FA U+044A afii10092 251 | !FB U+044B afii10093 252 | !FC U+044C afii10094 253 | !FD U+044D afii10095 254 | !FE U+044E afii10096 255 | !FF U+044F afii10097 256 | -------------------------------------------------------------------------------- /testdata/Image.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [HTMLBlock]

    Images

    4 | [cr()] LH=14 5 | [cr()] LH=14 6 | [Paragraph (entering)] 7 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 8 | [cr()] LH=14 9 | [Text] Admittedly, it's fairly difficult to devise a "natural" syntax for placing images into a plain text document format. 10 | [Paragraph (leaving)] 11 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 12 | [cr()] LH=14 13 | [Paragraph (entering)] 14 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 15 | [cr()] LH=14 16 | [Text] Markdown uses an image syntax that is intended to resemble the syntax for links, allowing for two styles: 17 | [Emph (entering)] 18 | [Text] inline 19 | [Emph (leaving)] 20 | [Text] and 21 | [Emph (entering)] 22 | [Text] reference 23 | [Emph (leaving)] 24 | [Text] . 25 | [Paragraph (leaving)] 26 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 27 | [cr()] LH=14 28 | [Paragraph (entering)] 29 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 30 | [cr()] LH=14 31 | [Text] Inline image syntax looks like this: 32 | [Paragraph (leaving)] 33 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 34 | [cr()] LH=14 35 | [Codeblock] Leaf '![Alt text](./image/fpdf.png)\n\n![Al…' 36 | 37 | [cr()] LH=14 38 | [Paragraph (entering)] 39 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 40 | [cr()] LH=14 41 | [Text] That is: 42 | [Paragraph (leaving)] 43 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 44 | [cr()] LH=14 45 | [Unordered List (entering)] Container 46 | ListItem 'flags=start' 47 | Paragraph 48 | Text 'An exclamation mark:' 49 | Code '!' 50 | Text ';' 51 | ListItem 52 | Paragraph 53 | Text 'followed by a set of square brackets,…' 54 | Code 'alt' 55 | Text '\nattribute text for the image;' 56 | ListItem 'flags=end' 57 | Paragraph 58 | Text 'followed by a set of parentheses, con…' 59 | Code 'title' 60 | Text 'attribute enclosed in double\nor sin…' 61 | 62 | [... List Left Margin] set to 58.338 63 | -[Unordered Item (entering) #1] Container 64 | Paragraph 65 | Text 'An exclamation mark:' 66 | Code '!' 67 | Text ';' 68 | 69 | -[cr()] LH=14 70 | --[Paragraph (entering)] 71 | --[... Margins (left, top, right, bottom:] 98.322 28.35 28.35 56.7 72 | --[First Para within a list] breaking 73 | --[Text] An exclamation mark: 74 | --[processCode] ! 75 | --[Backtick (entering)] 76 | --[Text] ; 77 | --[Paragraph (leaving)] 78 | --[... Margins (left, top, right, bottom:] 98.322 28.35 28.35 56.7 79 | --[Unordered Item (leaving)] Container 80 | Paragraph 81 | Text 'An exclamation mark:' 82 | Code '!' 83 | Text ';' 84 | 85 | -[Unordered Item (entering) #2] Container 86 | Paragraph 87 | Text 'followed by a set of square brackets,…' 88 | Code 'alt' 89 | Text '\nattribute text for the image;' 90 | 91 | -[cr()] LH=14 92 | --[Paragraph (entering)] 93 | --[... Margins (left, top, right, bottom:] 98.322 28.35 28.35 56.7 94 | --[First Para within a list] breaking 95 | --[Text] followed by a set of square brackets, containing the 96 | --[processCode] alt 97 | --[Backtick (entering)] 98 | --[Text] attribute text for the image; 99 | --[Paragraph (leaving)] 100 | --[... Margins (left, top, right, bottom:] 98.322 28.35 28.35 56.7 101 | --[Unordered Item (leaving)] Container 102 | Paragraph 103 | Text 'followed by a set of square brackets,…' 104 | Code 'alt' 105 | Text '\nattribute text for the image;' 106 | 107 | -[Unordered Item (entering) #3] Container 108 | Paragraph 109 | Text 'followed by a set of parentheses, con…' 110 | Code 'title' 111 | Text 'attribute enclosed in double\nor sin…' 112 | 113 | -[cr()] LH=14 114 | --[Paragraph (entering)] 115 | --[... Margins (left, top, right, bottom:] 98.322 28.35 28.35 56.7 116 | --[First Para within a list] breaking 117 | --[Text] followed by a set of parentheses, containing the URL or path to the image, and an optional 118 | --[processCode] title 119 | --[Backtick (entering)] 120 | --[Text] attribute enclosed in double or single quotes. 121 | --[Paragraph (leaving)] 122 | --[... Margins (left, top, right, bottom:] 98.322 28.35 28.35 56.7 123 | --[Unordered Item (leaving)] Container 124 | Paragraph 125 | Text 'followed by a set of parentheses, con…' 126 | Code 'title' 127 | Text 'attribute enclosed in double\nor sin…' 128 | 129 | -[Unordered List (leaving)] Container 130 | ListItem 'flags=start' 131 | Paragraph 132 | Text 'An exclamation mark:' 133 | Code '!' 134 | Text ';' 135 | ListItem 136 | Paragraph 137 | Text 'followed by a set of square brackets,…' 138 | Code 'alt' 139 | Text '\nattribute text for the image;' 140 | ListItem 'flags=end' 141 | Paragraph 142 | Text 'followed by a set of parentheses, con…' 143 | Code 'title' 144 | Text 'attribute enclosed in double\nor sin…' 145 | 146 | -[... Reset List Left Margin] re-set to 28.35 147 | [cr()] LH=14 148 | [Paragraph (entering)] 149 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 150 | [cr()] LH=14 151 | [Text] Here is the first picture: 152 | [cr()] LH=14 153 | [Image (entering)] Destination[./image/fpdf.png] Title[] 154 | [Text] from https://codeberg.org/go-pdf/fpdf/src/branch/main/image 155 | [Image (leaving)] 156 | [Paragraph (leaving)] 157 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 158 | [cr()] LH=14 159 | [Paragraph (entering)] 160 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 161 | [cr()] LH=14 162 | [Text] Here is the second picture: 163 | [cr()] LH=14 164 | [Image (entering)] Destination[./image/hiking.png] Title[Optional title] 165 | [Text] from https://github.com/egonelbre/gophers 166 | [Image (leaving)] 167 | [Paragraph (leaving)] 168 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 169 | [cr()] LH=14 170 | [Paragraph (entering)] 171 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 172 | [cr()] LH=14 173 | [Text] The Go gopher was designed by Renee French. The Gopher character design is licensed under the Creative Commons 3.0 Attributions license. Read 174 | -[Link (entering)] Destination[http://blog.golang.org/gopher] Title[] 175 | -[Text] http://blog.golang.org/gopher 176 | -[Link (leaving)] 177 | [Text] for more details. 178 | [Paragraph (leaving)] 179 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 180 | [cr()] LH=14 181 | [Paragraph (entering)] 182 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 183 | [cr()] LH=14 184 | [Text] Note: this snippet was adapted from the testdata folder file named: 185 | [Text] 186 | [processCode] Markdown Documentation - Syntax.text 187 | [Backtick (entering)] 188 | [Paragraph (leaving)] 189 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 190 | [cr()] LH=14 191 | [Paragraph (entering)] 192 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 193 | [cr()] LH=14 194 | [Text] Here is a non-existent image... should generate a message in trace file. 195 | [cr()] LH=14 196 | [Image (entering)] Destination[./image/xbay.jpg] Title[Does not exist!] 197 | [Image (file error)] stat ./image/xbay.jpg: no such file or directory 198 | [Text] Not from https://jpeg.org/images/jpeg-home.jpg 199 | [Image (leaving)] 200 | [Paragraph (leaving)] 201 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 202 | [cr()] LH=14 203 | [Paragraph (entering)] 204 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 205 | [cr()] LH=14 206 | [Text] Here is a JPEG image... is it auto-detected? 207 | [cr()] LH=14 208 | [Image (entering)] Destination[./image/bay.jpg] Title[Down by the Bay] 209 | [Text] from https://jpeg.org/images/jpeg-home.jpg 210 | [Image (leaving)] 211 | [Paragraph (leaving)] 212 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 213 | [cr()] LH=14 214 | [Document] Not Handled 215 | -------------------------------------------------------------------------------- /testdata/Links, reference style.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [Paragraph (entering)] 4 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 5 | [cr()] LH=14 6 | [Text] Foo 7 | -[Link (entering)] Destination[/url/] Title[Title] 8 | -[Text] bar 9 | -[Link (leaving)] 10 | [Text] . 11 | [Paragraph (leaving)] 12 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 13 | [cr()] LH=14 14 | [Paragraph (entering)] 15 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 16 | [cr()] LH=14 17 | [Text] Foo 18 | -[Link (entering)] Destination[/url/] Title[Title] 19 | -[Text] bar 20 | -[Link (leaving)] 21 | [Text] . 22 | [Paragraph (leaving)] 23 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 24 | [cr()] LH=14 25 | [Paragraph (entering)] 26 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 27 | [cr()] LH=14 28 | [Text] Foo 29 | -[Link (entering)] Destination[/url/] Title[Title] 30 | -[Text] bar 31 | -[Link (leaving)] 32 | [Text] . 33 | [Paragraph (leaving)] 34 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 35 | [cr()] LH=14 36 | [Paragraph (entering)] 37 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 38 | [cr()] LH=14 39 | [Text] With 40 | -[Link (entering)] Destination[/url/] Title[] 41 | -[Text] embedded [brackets] 42 | -[Link (leaving)] 43 | [Text] . 44 | [Paragraph (leaving)] 45 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 46 | [cr()] LH=14 47 | [Paragraph (entering)] 48 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 49 | [cr()] LH=14 50 | [Text] Indented 51 | -[Link (entering)] Destination[/url] Title[] 52 | -[Text] once 53 | -[Link (leaving)] 54 | [Text] . 55 | [Paragraph (leaving)] 56 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 57 | [cr()] LH=14 58 | [Paragraph (entering)] 59 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 60 | [cr()] LH=14 61 | [Text] Indented 62 | -[Link (entering)] Destination[/url] Title[] 63 | -[Text] twice 64 | -[Link (leaving)] 65 | [Text] . 66 | [Paragraph (leaving)] 67 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 68 | [cr()] LH=14 69 | [Paragraph (entering)] 70 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 71 | [cr()] LH=14 72 | [Text] Indented 73 | -[Link (entering)] Destination[/url] Title[] 74 | -[Text] thrice 75 | -[Link (leaving)] 76 | [Text] . 77 | [Paragraph (leaving)] 78 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 79 | [cr()] LH=14 80 | [Paragraph (entering)] 81 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 82 | [cr()] LH=14 83 | [Text] Indented [four][] times. 84 | [Paragraph (leaving)] 85 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 86 | [cr()] LH=14 87 | [Codeblock] Leaf '[four]: /url\n' 88 | 89 | [cr()] LH=14 90 | [HorizontalRule] 91 | [cr()] LH=14 92 | [... From X,Y] 28.35,294.35 93 | [... To X,Y] 583.65,294.35 94 | [cr()] LH=14 95 | [Paragraph (entering)] 96 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 97 | [cr()] LH=14 98 | [Text] 99 | -[Link (entering)] Destination[foo] Title[] 100 | -[Text] this 101 | -[Link (leaving)] 102 | [Text] should work 103 | [Paragraph (leaving)] 104 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 105 | [cr()] LH=14 106 | [Paragraph (entering)] 107 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 108 | [cr()] LH=14 109 | [Text] So should 110 | -[Link (entering)] Destination[foo] Title[] 111 | -[Text] this 112 | -[Link (leaving)] 113 | [Text] . 114 | [Paragraph (leaving)] 115 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 116 | [cr()] LH=14 117 | [Paragraph (entering)] 118 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 119 | [cr()] LH=14 120 | [Text] And 121 | -[Link (entering)] Destination[foo] Title[] 122 | -[Text] this 123 | -[Link (leaving)] 124 | [Text] . 125 | [Paragraph (leaving)] 126 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 127 | [cr()] LH=14 128 | [Paragraph (entering)] 129 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 130 | [cr()] LH=14 131 | [Text] And 132 | -[Link (entering)] Destination[foo] Title[] 133 | -[Text] this 134 | -[Link (leaving)] 135 | [Text] . 136 | [Paragraph (leaving)] 137 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 138 | [cr()] LH=14 139 | [Paragraph (entering)] 140 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 141 | [cr()] LH=14 142 | [Text] And 143 | -[Link (entering)] Destination[foo] Title[] 144 | -[Text] this 145 | -[Link (leaving)] 146 | [Text] . 147 | [Paragraph (leaving)] 148 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 149 | [cr()] LH=14 150 | [Paragraph (entering)] 151 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 152 | [cr()] LH=14 153 | [Text] But not [that] []. 154 | [Paragraph (leaving)] 155 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 156 | [cr()] LH=14 157 | [Paragraph (entering)] 158 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 159 | [cr()] LH=14 160 | [Text] Nor [that][]. 161 | [Paragraph (leaving)] 162 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 163 | [cr()] LH=14 164 | [Paragraph (entering)] 165 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 166 | [cr()] LH=14 167 | [Text] Nor [that]. 168 | [Paragraph (leaving)] 169 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 170 | [cr()] LH=14 171 | [Paragraph (entering)] 172 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 173 | [cr()] LH=14 174 | [Text] [Something in brackets like 175 | -[Link (entering)] Destination[foo] Title[] 176 | -[Text] this 177 | -[Link (leaving)] 178 | [Text] should work] 179 | [Paragraph (leaving)] 180 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 181 | [cr()] LH=14 182 | [Paragraph (entering)] 183 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 184 | [cr()] LH=14 185 | [Text] [Same with 186 | -[Link (entering)] Destination[foo] Title[] 187 | -[Text] this 188 | -[Link (leaving)] 189 | [Text] .] 190 | [Paragraph (leaving)] 191 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 192 | [cr()] LH=14 193 | [Paragraph (entering)] 194 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 195 | [cr()] LH=14 196 | [Text] In this case, 197 | -[Link (entering)] Destination[/somethingelse/] Title[] 198 | -[Text] this 199 | -[Link (leaving)] 200 | [Text] points to something else. 201 | [Paragraph (leaving)] 202 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 203 | [cr()] LH=14 204 | [Paragraph (entering)] 205 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 206 | [cr()] LH=14 207 | [Text] Backslashing should suppress 208 | [Text] [ 209 | [Text] this] and [this 210 | [Text] ] 211 | [Text] . 212 | [Paragraph (leaving)] 213 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 214 | [cr()] LH=14 215 | [HorizontalRule] 216 | [cr()] LH=14 217 | [... From X,Y] 28.35,658.35 218 | [... To X,Y] 583.65,658.35 219 | [cr()] LH=14 220 | [Paragraph (entering)] 221 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 222 | [cr()] LH=14 223 | [Text] Here's one where the 224 | -[Link (entering)] Destination[/url/] Title[] 225 | -[Text] link breaks 226 | -[Link (leaving)] 227 | [Text] across lines. 228 | [Paragraph (leaving)] 229 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 230 | [cr()] LH=14 231 | [Paragraph (entering)] 232 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 233 | [cr()] LH=14 234 | [Text] Here's another where the 235 | -[Link (entering)] Destination[/url/] Title[] 236 | -[Text] link 237 | -[Text] breaks 238 | -[Link (leaving)] 239 | [Text] across lines, but with a line-ending space. 240 | [Paragraph (leaving)] 241 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 242 | [cr()] LH=14 243 | [Document] Not Handled 244 | -------------------------------------------------------------------------------- /cmd/md2pdf/md2pdf.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net/http" 10 | "os" 11 | "path/filepath" 12 | "regexp" 13 | "runtime" 14 | "strings" 15 | 16 | "github.com/gomarkdown/markdown/parser" 17 | "github.com/solworktech/md2pdf/v2" 18 | "golang.org/x/exp/slices" 19 | ) 20 | 21 | var input = flag.String("i", "", "Input filename, dir consisting of .md|.markdown files or HTTP(s) URL; default is os.Stdin") 22 | var output = flag.String("o", "", "Output PDF filename; required") 23 | var pathToSyntaxFiles = flag.String("s", "", "Path to github.com/jessp01/gohighlight/syntax_files") 24 | var title = flag.String("title", "", "Presentation title") 25 | var author = flag.String("author", "", "Author's name; used if -footer is passed") 26 | var unicodeSupport = flag.String("unicode-encoding", "", "e.g 'cp1251'") 27 | var fontFile = flag.String("font-file", "", "path to font file to use") 28 | var fontName = flag.String("font-name", "", "Font name ID; e.g 'Helvetica-1251'") 29 | var themeArg = flag.String("theme", "light", "[light | dark | /path/to/custom/theme.json]") 30 | var hrAsNewPage = flag.Bool("new-page-on-hr", false, "Interpret HR as a new page; useful for presentations") 31 | var printFooter = flag.Bool("with-footer", false, "Print doc footer ( <page number>)") 32 | var generateTOC = flag.Bool("generate-toc", false, "Auto Generate Table of Contents (TOC)") 33 | var pageSize = flag.String("page-size", "A4", "[A3 | A4 | A5]") 34 | var orientation = flag.String("orientation", "portrait", "[portrait | landscape]") 35 | var logFile = flag.String("log-file", "", "Path to log file") 36 | var help = flag.Bool("help", false, "Show usage message") 37 | var ver = flag.Bool("version", false, "Print version and build info") 38 | var version = "dev" 39 | var commit = "none" 40 | var date = "unknown" 41 | var _, fileName, fileLine, ok = runtime.Caller(0) 42 | 43 | var opts []mdtopdf.RenderOption 44 | 45 | func processRemoteInputFile(url string) ([]byte, error) { 46 | resp, err := http.Get(url) 47 | if err != nil { 48 | return nil, err 49 | } 50 | defer resp.Body.Close() 51 | if resp.StatusCode != 200 { 52 | return nil, errors.New("Received non 200 response code: " + fmt.Sprintf("HTTP %d", resp.StatusCode)) 53 | } 54 | content, rerr := io.ReadAll(resp.Body) 55 | return content, rerr 56 | } 57 | 58 | func glob(dir string, validExts []string) ([]string, error) { 59 | files := []string{} 60 | err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { 61 | if slices.Contains(validExts, filepath.Ext(path)) { 62 | files = append(files, path) 63 | } 64 | return nil 65 | }) 66 | 67 | return files, err 68 | } 69 | 70 | func main() { 71 | log.SetFlags(log.LstdFlags | log.Lshortfile) 72 | flag.Parse() 73 | 74 | if *help { 75 | usage("") 76 | return 77 | } 78 | 79 | if *ver { 80 | fmt.Printf("md2pdf version: %s, commit: %s, built on: %s\n", version, commit, date) 81 | return 82 | } 83 | 84 | if *output == "" { 85 | usage("Output PDF filename is required") 86 | } 87 | 88 | if *hrAsNewPage == true { 89 | opts = append(opts, mdtopdf.IsHorizontalRuleNewPage(true)) 90 | } 91 | 92 | if *unicodeSupport != "" { 93 | opts = append(opts, mdtopdf.WithUnicodeTranslator(*unicodeSupport)) 94 | } 95 | 96 | if *pathToSyntaxFiles != "" { 97 | opts = append(opts, mdtopdf.SetSyntaxHighlightBaseDir(*pathToSyntaxFiles)) 98 | } else { 99 | if _, err := os.Stat("../../highlight/syntax_files"); err == nil { 100 | opts = append(opts, mdtopdf.SetSyntaxHighlightBaseDir("../../highlight/syntax_files")) 101 | } else if _, err := os.Stat("/usr/share/mdtopdf/syntax_files"); err == nil { 102 | opts = append(opts, mdtopdf.SetSyntaxHighlightBaseDir("/usr/share/mdtopdf/syntax_files")) 103 | } 104 | } 105 | 106 | // get text for PDF 107 | var content []byte 108 | var err error 109 | var inputBaseURL string 110 | if *input == "" { 111 | content, err = io.ReadAll(os.Stdin) 112 | if err != nil { 113 | log.Fatal(err) 114 | } 115 | } else { 116 | httpRegex := regexp.MustCompile("^http(s)?://") 117 | if httpRegex.Match([]byte(*input)) { 118 | content, err = processRemoteInputFile(*input) 119 | if err != nil { 120 | log.Fatal(err) 121 | } 122 | // get the base URL so we can adjust relative links and images 123 | inputBaseURL = strings.Replace(filepath.Dir(*input), ":/", "://", 1) 124 | } else { 125 | fileInfo, err := os.Stat(*input) 126 | if err != nil { 127 | log.Fatal(err) 128 | } 129 | 130 | if fileInfo.IsDir() { 131 | opts = append(opts, mdtopdf.IsHorizontalRuleNewPage(true)) 132 | validExts := []string{".md", ".markdown"} 133 | files, err := glob(*input, validExts) 134 | if err != nil { 135 | log.Fatal(err) 136 | } 137 | for i, filePath := range files { 138 | fileContents, err := os.ReadFile(filePath) 139 | if err != nil { 140 | log.Fatal(err) 141 | } 142 | content = append(content, fileContents...) 143 | if i < len(files)-1 { 144 | content = append(content, []byte("---\n")...) 145 | } 146 | } 147 | } else { 148 | content, err = os.ReadFile(*input) 149 | if err != nil { 150 | log.Fatal(err) 151 | } 152 | } 153 | } 154 | } 155 | 156 | theme := mdtopdf.LIGHT 157 | themeFile := "" 158 | if *themeArg == "dark" { 159 | theme = mdtopdf.DARK 160 | } else if _, err := os.Stat(*themeArg); err == nil { 161 | theme = mdtopdf.CUSTOM 162 | themeFile = *themeArg 163 | } 164 | 165 | params := mdtopdf.PdfRendererParams{ 166 | Orientation: *orientation, 167 | Papersz: *pageSize, 168 | PdfFile: *output, 169 | TracerFile: *logFile, 170 | Opts: opts, 171 | Theme: theme, 172 | CustomThemeFile: themeFile, 173 | FontFile: *fontFile, 174 | FontName: *fontName, 175 | } 176 | 177 | pf := mdtopdf.NewPdfRenderer(params) 178 | 179 | if *generateTOC == true { 180 | headers, err := mdtopdf.GetTOCEntries(content) 181 | if err != nil { 182 | log.Fatal(err) 183 | } 184 | headerLinks := make(map[string]*int) 185 | for _, header := range headers { 186 | linkID := pf.Pdf.AddLink() 187 | headerLinks[header.Title] = &linkID 188 | 189 | // debug 190 | // log.Printf("Header: '%s' (Level %d) -> Link ID: %d\n", 191 | // header.Title, header.Level, linkID) 192 | } 193 | 194 | pf.SetTOCLinks(headerLinks) 195 | pf.Pdf.SetFont("Arial", "B", 24) 196 | 197 | // Add a table of contents with clickable links 198 | pf.Pdf.Cell(40, 10, "Table of Contents") 199 | pf.Pdf.Ln(30) 200 | 201 | for _, header := range headers { 202 | if linkPtr, exists := headerLinks[header.Title]; exists { 203 | link := *linkPtr 204 | pf.Pdf.SetFont("Arial", "", 12) 205 | pf.Pdf.SetTextColor(100, 149, 237) 206 | tr := pf.Pdf.UnicodeTranslatorFromDescriptor("") 207 | bulletChar := tr("•") 208 | indent := strings.Repeat(" ", header.Level-1) 209 | pf.Pdf.WriteLinkID(8, fmt.Sprintf("%s %s %s", indent, bulletChar, header.Title), link) 210 | pf.Pdf.Ln(15) 211 | } 212 | } 213 | pf.Pdf.AddPage() 214 | } 215 | 216 | if inputBaseURL != "" { 217 | pf.InputBaseURL = inputBaseURL 218 | } 219 | pf.Pdf.SetSubject(*title, true) 220 | pf.Pdf.SetTitle(*title, true) 221 | pf.Extensions = parser.NoIntraEmphasis | parser.Tables | parser.FencedCode | parser.Autolink | parser.Strikethrough | parser.SpaceHeadings | parser.HeadingIDs | parser.BackslashLineBreak | parser.DefinitionLists 222 | 223 | if *fontFile != "" && *fontName != "" { 224 | fmt.Println(*fontFile) 225 | // pf.Pdf.AddUTF8Font(*fontName, "", *fontFile) 226 | pf.Pdf.AddFont(*fontName, "", *fontFile) 227 | pf.Pdf.SetFont(*fontName, "", 12) 228 | pf.Normal = mdtopdf.Styler{ 229 | Font: *fontName, 230 | Style: "", 231 | Size: 12, Spacing: 2, 232 | TextColor: pf.Normal.TextColor, 233 | } 234 | 235 | } 236 | 237 | if *printFooter { 238 | pf.Pdf.SetFooterFunc(func() { 239 | pf.Pdf.SetFillColor(pf.BackgroundColor.Red, pf.BackgroundColor.Green, pf.BackgroundColor.Blue) 240 | // Position at 1.5 cm from bottom 241 | pf.Pdf.SetY(-15) 242 | // Arial italic 8 243 | pf.Pdf.SetFont("Arial", "I", 8) 244 | // Text color in gray 245 | pf.Pdf.SetTextColor(128, 128, 128) 246 | w, h, _ := pf.Pdf.PageSize(pf.Pdf.PageNo()) 247 | // fmt.Printf("Width: %f, height: %f, unit: %s\n", w, h, u) 248 | pf.Pdf.SetX(4) 249 | pf.Pdf.CellFormat(0, 10, fmt.Sprintf("%s", *author), "", 0, "", true, 0, "") 250 | middle := w / 2 251 | if *orientation == "landscape" { 252 | middle = h / 2 253 | } 254 | pf.Pdf.SetX(middle - float64(len(*title))) 255 | pf.Pdf.CellFormat(0, 10, fmt.Sprintf("%s", *title), "", 0, "", true, 0, "") 256 | pf.Pdf.SetX(-40) 257 | pf.Pdf.CellFormat(0, 10, fmt.Sprintf("Page %d", pf.Pdf.PageNo()), "", 0, "", true, 0, "") 258 | }) 259 | } 260 | 261 | err = pf.Process(content) 262 | if err != nil { 263 | fmt.Printf("error: %v\n", err) 264 | } 265 | } 266 | 267 | func usage(msg string) { 268 | fmt.Println(msg + "\n") 269 | fmt.Printf("Usage: %s (%s) [options]\n", filepath.Base(fileName), version) 270 | flag.PrintDefaults() 271 | os.Exit(0) 272 | } 273 | -------------------------------------------------------------------------------- /testdata/Markdown Documentation - Basics.text: -------------------------------------------------------------------------------- 1 | Markdown: Basics 2 | ================ 3 | 4 | <ul id="ProjectSubmenu"> 5 | <li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li> 6 | <li><a class="selected" title="Markdown Basics">Basics</a></li> 7 | <li><a href="/projects/markdown/syntax" title="Markdown Syntax Documentation">Syntax</a></li> 8 | <li><a href="/projects/markdown/license" title="Pricing and License Information">License</a></li> 9 | <li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li> 10 | </ul> 11 | 12 | 13 | Getting the Gist of Markdown's Formatting Syntax 14 | ------------------------------------------------ 15 | 16 | This page offers a brief overview of what it's like to use Markdown. 17 | The [syntax page] [s] provides complete, detailed documentation for 18 | every feature, but Markdown should be very easy to pick up simply by 19 | looking at a few examples of it in action. The examples on this page 20 | are written in a before/after style, showing example syntax and the 21 | HTML output produced by Markdown. 22 | 23 | It's also helpful to simply try Markdown out; the [Dingus] [d] is a 24 | web application that allows you type your own Markdown-formatted text 25 | and translate it to XHTML. 26 | 27 | **Note:** This document is itself written using Markdown; you 28 | can [see the source for it by adding '.text' to the URL] [src]. 29 | 30 | [s]: /projects/markdown/syntax "Markdown Syntax" 31 | [d]: /projects/markdown/dingus "Markdown Dingus" 32 | [src]: /projects/markdown/basics.text 33 | 34 | 35 | ## Paragraphs, Headers, Blockquotes ## 36 | 37 | A paragraph is simply one or more consecutive lines of text, separated 38 | by one or more blank lines. (A blank line is any line that looks like a 39 | blank line -- a line containing nothing spaces or tabs is considered 40 | blank.) Normal paragraphs should not be intended with spaces or tabs. 41 | 42 | Markdown offers two styles of headers: *Setext* and *atx*. 43 | Setext-style headers for `<h1>` and `<h2>` are created by 44 | "underlining" with equal signs (`=`) and hyphens (`-`), respectively. 45 | To create an atx-style header, you put 1-6 hash marks (`#`) at the 46 | beginning of the line -- the number of hashes equals the resulting 47 | HTML header level. 48 | 49 | Blockquotes are indicated using email-style '`>`' angle brackets. 50 | 51 | Markdown: 52 | 53 | A First Level Header 54 | ==================== 55 | 56 | A Second Level Header 57 | --------------------- 58 | 59 | Now is the time for all good men to come to 60 | the aid of their country. This is just a 61 | regular paragraph. 62 | 63 | The quick brown fox jumped over the lazy 64 | dog's back. 65 | 66 | ### Header 3 67 | 68 | > This is a blockquote. 69 | > 70 | > This is the second paragraph in the blockquote. 71 | > 72 | > ## This is an H2 in a blockquote 73 | 74 | 75 | Output: 76 | 77 | <h1>A First Level Header</h1> 78 | 79 | <h2>A Second Level Header</h2> 80 | 81 | <p>Now is the time for all good men to come to 82 | the aid of their country. This is just a 83 | regular paragraph.</p> 84 | 85 | <p>The quick brown fox jumped over the lazy 86 | dog's back.</p> 87 | 88 | <h3>Header 3</h3> 89 | 90 | <blockquote> 91 | <p>This is a blockquote.</p> 92 | 93 | <p>This is the second paragraph in the blockquote.</p> 94 | 95 | <h2>This is an H2 in a blockquote</h2> 96 | </blockquote> 97 | 98 | 99 | 100 | ### Phrase Emphasis ### 101 | 102 | Markdown uses asterisks and underscores to indicate spans of emphasis. 103 | 104 | Markdown: 105 | 106 | Some of these words *are emphasized*. 107 | Some of these words _are emphasized also_. 108 | 109 | Use two asterisks for **strong emphasis**. 110 | Or, if you prefer, __use two underscores instead__. 111 | 112 | Output: 113 | 114 | <p>Some of these words <em>are emphasized</em>. 115 | Some of these words <em>are emphasized also</em>.</p> 116 | 117 | <p>Use two asterisks for <strong>strong emphasis</strong>. 118 | Or, if you prefer, <strong>use two underscores instead</strong>.</p> 119 | 120 | 121 | 122 | ## Lists ## 123 | 124 | Unordered (bulleted) lists use asterisks, pluses, and hyphens (`*`, 125 | `+`, and `-`) as list markers. These three markers are 126 | interchangable; this: 127 | 128 | * Candy. 129 | * Gum. 130 | * Booze. 131 | 132 | this: 133 | 134 | + Candy. 135 | + Gum. 136 | + Booze. 137 | 138 | and this: 139 | 140 | - Candy. 141 | - Gum. 142 | - Booze. 143 | 144 | all produce the same output: 145 | 146 | <ul> 147 | <li>Candy.</li> 148 | <li>Gum.</li> 149 | <li>Booze.</li> 150 | </ul> 151 | 152 | Ordered (numbered) lists use regular numbers, followed by periods, as 153 | list markers: 154 | 155 | 1. Red 156 | 2. Green 157 | 3. Blue 158 | 159 | Output: 160 | 161 | <ol> 162 | <li>Red</li> 163 | <li>Green</li> 164 | <li>Blue</li> 165 | </ol> 166 | 167 | If you put blank lines between items, you'll get `<p>` tags for the 168 | list item text. You can create multi-paragraph list items by indenting 169 | the paragraphs by 4 spaces or 1 tab: 170 | 171 | * A list item. 172 | 173 | With multiple paragraphs. 174 | 175 | * Another item in the list. 176 | 177 | Output: 178 | 179 | <ul> 180 | <li><p>A list item.</p> 181 | <p>With multiple paragraphs.</p></li> 182 | <li><p>Another item in the list.</p></li> 183 | </ul> 184 | 185 | 186 | 187 | ### Links ### 188 | 189 | Markdown supports two styles for creating links: *inline* and 190 | *reference*. With both styles, you use square brackets to delimit the 191 | text you want to turn into a link. 192 | 193 | Inline-style links use parentheses immediately after the link text. 194 | For example: 195 | 196 | This is an [example link](http://example.com/). 197 | 198 | Output: 199 | 200 | <p>This is an <a href="http://example.com/"> 201 | example link</a>.</p> 202 | 203 | Optionally, you may include a title attribute in the parentheses: 204 | 205 | This is an [example link](http://example.com/ "With a Title"). 206 | 207 | Output: 208 | 209 | <p>This is an <a href="http://example.com/" title="With a Title"> 210 | example link</a>.</p> 211 | 212 | Reference-style links allow you to refer to your links by names, which 213 | you define elsewhere in your document: 214 | 215 | I get 10 times more traffic from [Google][1] than from 216 | [Yahoo][2] or [MSN][3]. 217 | 218 | [1]: http://google.com/ "Google" 219 | [2]: http://search.yahoo.com/ "Yahoo Search" 220 | [3]: http://search.msn.com/ "MSN Search" 221 | 222 | Output: 223 | 224 | <p>I get 10 times more traffic from <a href="http://google.com/" 225 | title="Google">Google</a> than from <a href="http://search.yahoo.com/" 226 | title="Yahoo Search">Yahoo</a> or <a href="http://search.msn.com/" 227 | title="MSN Search">MSN</a>.</p> 228 | 229 | The title attribute is optional. Link names may contain letters, 230 | numbers and spaces, but are *not* case sensitive: 231 | 232 | I start my morning with a cup of coffee and 233 | [The New York Times][NY Times]. 234 | 235 | [ny times]: http://www.nytimes.com/ 236 | 237 | Output: 238 | 239 | <p>I start my morning with a cup of coffee and 240 | <a href="http://www.nytimes.com/">The New York Times</a>.</p> 241 | 242 | 243 | ### Images ### 244 | 245 | Image syntax is very much like link syntax. 246 | 247 | Inline (titles are optional): 248 | 249 | ![alt text](/path/to/img.jpg "Title") 250 | 251 | Reference-style: 252 | 253 | ![alt text][id] 254 | 255 | [id]: /path/to/img.jpg "Title" 256 | 257 | Both of the above examples produce the same output: 258 | 259 | <img src="/path/to/img.jpg" alt="alt text" title="Title" /> 260 | 261 | 262 | 263 | ### Code ### 264 | 265 | In a regular paragraph, you can create code span by wrapping text in 266 | backtick quotes. Any ampersands (`&`) and angle brackets (`<` or 267 | `>`) will automatically be translated into HTML entities. This makes 268 | it easy to use Markdown to write about HTML example code: 269 | 270 | I strongly recommend against using any `<blink>` tags. 271 | 272 | I wish SmartyPants used named entities like `—` 273 | instead of decimal-encoded entites like `—`. 274 | 275 | Output: 276 | 277 | <p>I strongly recommend against using any 278 | <code><blink></code> tags.</p> 279 | 280 | <p>I wish SmartyPants used named entities like 281 | <code>&mdash;</code> instead of decimal-encoded 282 | entites like <code>&#8212;</code>.</p> 283 | 284 | 285 | To specify an entire block of pre-formatted code, indent every line of 286 | the block by 4 spaces or 1 tab. Just like with code spans, `&`, `<`, 287 | and `>` characters will be escaped automatically. 288 | 289 | Markdown: 290 | 291 | If you want your page to validate under XHTML 1.0 Strict, 292 | you've got to put paragraph tags in your blockquotes: 293 | 294 | <blockquote> 295 | <p>For example.</p> 296 | </blockquote> 297 | 298 | Output: 299 | 300 | <p>If you want your page to validate under XHTML 1.0 Strict, 301 | you've got to put paragraph tags in your blockquotes:</p> 302 | 303 | <pre><code><blockquote> 304 | <p>For example.</p> 305 | </blockquote> 306 | </code></pre> 307 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CI][badge-build]][build] 2 | [![GoDoc][go-docs-badge]][go-docs] 3 | [![GoReportCard][go-report-card-badge]][go-report-card] 4 | [![License][badge-license]][license] 5 | 6 | ## Markdown to PDF 7 | 8 | A CLI utility which, as the name implies, generates a PDF from Markdown. 9 | 10 | This package depends on two other packages: 11 | - [gomarkdown](https://github.com/gomarkdown/markdown) parser to read the markdown source 12 | - [fpdf](https://codeberg.org/go-pdf/fpdf) to generate the PDF 13 | 14 | ## Features 15 | 16 | - [Syntax highlighting (for code blocks)](#syntax-highlighting) 17 | - [Dark and light themes](#custom-themes) 18 | - [Customised themes (by passing a JSON file to `md2pdf`)](#custom-themes) 19 | - [Auto Generation of Table of Contents](#auto-generation-of-table-of-contents) 20 | - [Support of non-Latin charsets and multiple fonts](#using-non-ascii-glyphsfonts) 21 | - [Pagination control (using horizontal lines - especially useful for presentations)](#additional-options) 22 | - [Page Footer (consisting of author, title and page number)](#additional-options) 23 | 24 | ## Supported Markdown elements 25 | 26 | - Emphasised and strong text 27 | - Headings 1-6 28 | - Ordered and unordered lists 29 | - Nested lists 30 | - Images 31 | - Tables 32 | - Links 33 | - Code blocks and backticked text 34 | 35 | ## Installation 36 | 37 | You can obtain the pre-built `md2pdf` binary for your OS and arch 38 | [here](https://github.com/solworktech/md2pdf/releases); 39 | you can also install the `md2pdf` binary directly onto your `$GOBIN` dir with: 40 | 41 | ```sh 42 | $ go install github.com/solworktech/md2pdf/v2/cmd/md2pdf@latest 43 | ``` 44 | 45 | `md2pdf` is also available via [Homebrew](https://formulae.brew.sh/formula/md2pdf): 46 | 47 | ```sh 48 | $ brew install md2pdf 49 | ``` 50 | 51 | ## Syntax highlighting 52 | 53 | `md2pdf` supports colourised output via the [gohighlight module](https://github.com/jessp01/gohighlight). 54 | 55 | For examples, see [testdata/syntax_highlighting.md](./testdata/syntax_highlighting.md) and 56 | [testdata/syntax_highlighting.pdf](./testdata/syntax_highlighting.pdf) 57 | 58 | ## Custom themes 59 | 60 | `md2pdf` supports both light and dark themes out of the box (use `--theme light` or `--theme dark` - no config required). 61 | 62 | However, if you wish to customise the font faces, sizes and colours, you can use the JSONs in 63 | [custom_themes](./custom_themes) as a starting point. Edit to your liking and pass `--theme /path/to/json` to `md2pdf` 64 | 65 | ## Auto Generation of Table of Contents 66 | 67 | `md2pdf` can automatically generate a TOC where each item corresponds to a header in the doc and include it in the first page. 68 | TOC items can then be clicked to navigate to the relevant section (similar to HTML `<a>` anchors). 69 | 70 | To make use of this feature, simply pass `--generate-toc` as an argument. 71 | 72 | ## Quick start 73 | 74 | ``` 75 | $ cd cmd/md2pdf 76 | $ go run md2pdf.go -i test.md -o test.pdf 77 | ``` 78 | 79 | To benefit from Syntax highlighting, invoke thusly: 80 | 81 | ``` 82 | $ go run md2pdf.go -i syn_test.md -s /path/to/syntax_files -o test.pdf 83 | ``` 84 | 85 | To convert multiple MD files into a single PDF, use: 86 | ``` 87 | $ go run md2pdf.go -i /path/to/md/directory -o test.pdf 88 | ``` 89 | 90 | This repo has the [gohighlight module](https://github.com/jessp01/gohighlight) configured as a submodule, so if you clone 91 | with `--recursive`, you will have the `highlight` dir in its root. Alternatively, you may issue the following command to update an 92 | existing clone: 93 | 94 | ```sh 95 | git submodule update --remote --init 96 | ``` 97 | 98 | *Note 1: the `cmd` folder has an example for the syntax highlighting. 99 | See the script `run_syntax_highlighting.sh`. This example assumes that 100 | the folder with the syntax files is located at a relative location: 101 | `../../../jessp01/gohighlight/syntax_files`.* 102 | 103 | *Note 2: when annotating the code block to specify the language, the 104 | annotation name must match the syntax base filename.* 105 | 106 | ### Additional options 107 | 108 | ```sh 109 | -author string 110 | Author name; used if -footer is passed 111 | -font-file string 112 | path to font file to use 113 | -font-name string 114 | Font name ID; e.g 'Helvetica-1251' 115 | -generate-toc 116 | Auto Generate Table of Contents (TOC) 117 | -help 118 | Show usage message 119 | -i string 120 | Input filename, dir consisting of .md|.markdown files or HTTP(s) URL; default is os.Stdin 121 | -log-file string 122 | Path to log file 123 | -new-page-on-hr 124 | Interpret HR as a new page; useful for presentations 125 | -o string 126 | Output PDF filename; required 127 | -orientation string 128 | [portrait | landscape] (default "portrait") 129 | -page-size string 130 | [A3 | A4 | A5] (default "A4") 131 | -s string 132 | Path to github.com/jessp01/gohighlight/syntax_files 133 | -theme string 134 | [light | dark | /path/to/custom/theme.json] (default "light") 135 | -title string 136 | Presentation title 137 | -unicode-encoding string 138 | e.g 'cp1251' 139 | -version 140 | Print version and build info 141 | -with-footer 142 | Print doc footer (<author> <title> <page number>) 143 | ``` 144 | 145 | For example, the below will: 146 | 147 | - Set the title to `My Grand Title` 148 | - Set `Random Bloke` as the author (used in the footer) 149 | - Set the dark theme 150 | - Start a new page when encountering an HR (`---`); useful for creating presentations 151 | - Print a footer (`author name, title, page number`) 152 | 153 | ```sh 154 | $ go run md2pdf.go -i /path/to/md \ 155 | -o /path/to/pdf --title "My Grand Title" --author "Random Bloke" \ 156 | --theme dark --new-page-on-hr --with-footer 157 | ``` 158 | 159 | ## Using non-ASCII Glyphs/Fonts 160 | 161 | To use a non-ASCII language, the PDF generator must be configured with `WithUnicodeTranslator`: 162 | 163 | ```go 164 | // https://en.wikipedia.org/wiki/Windows-1251 165 | pf := mdtopdf.NewPdfRenderer("", "", *output, "trace.log", mdtopdf.WithUnicodeTranslator("cp1251")) 166 | ``` 167 | 168 | In addition, this package's `Styler` must be used to set the font to match what is configured with the PDF generator. 169 | 170 | A complete working example can be found for Russian in the `cmd` folder named 171 | `russian.go`. 172 | 173 | For a full example, run: 174 | 175 | ```sh 176 | $ go run md2pdf.go -i russian.md -o russian.pdf \ 177 | --unicode-encoding cp1251 --font-file helvetica_1251.json --font-name Helvetica_1251 178 | ``` 179 | 180 | ## Tests 181 | 182 | The tests included in this repo (see the `testdata` folder) were taken from the BlackFriday package. 183 | While the tests may complete without errors, visual inspection of the created PDF is the 184 | only way to determine if the tests *really* pass! 185 | 186 | The tests create log files that trace the [gomarkdown](https://github.com/gomarkdown/markdown) parser 187 | callbacks. This is a valuable debugging tool, showing each callback 188 | and the data provided while the AST is presented. 189 | 190 | ## Limitations and Known Issues 191 | 192 | - It is common for Markdown to include HTML. HTML is treated as a "code block". *There is no attempt to convert raw HTML to PDF.* 193 | - Github-flavoured Markdown permits strikethrough using tildes. This is not supported by `fpdf` as a font style at present. 194 | - The markdown link title (which would show when converted to HTML as hover-over text) is not supported. The generated PDF will show the URL, but this is a function of the PDF viewer. 195 | - Definition lists are not supported 196 | - The following text features may be tweaked: font, size, spacing, style, fill colour, and text colour. These are exported and available via the `Styler` struct. Note that fill colour only works when using `CellFormat()`. This is the case for tables, code blocks, and backticked text. 197 | 198 | ## Contributions 199 | 200 | - Set up and run pre-commit hooks: 201 | 202 | ```sh 203 | # Install the needed GO packages: 204 | go install github.com/go-critic/go-critic/cmd/gocritic@latest 205 | go install golang.org/x/tools/cmd/goimports@latest 206 | go install golang.org/x/lint/golint@latest 207 | go install github.com/gordonklaus/ineffassign@latest 208 | 209 | # Install the `pre-commit` util: 210 | pip install pre-commit 211 | 212 | # Generate `.git/hooks/pre-commit`: 213 | pre-commit install 214 | ``` 215 | 216 | Following that, these tests will run every time you invoke `git commit`: 217 | ```sh 218 | go fmt...................................................................Passed 219 | go imports...............................................................Passed 220 | go vet...................................................................Passed 221 | go lint..................................................................Passed 222 | go-critic................................................................Passed 223 | ``` 224 | 225 | - Submit a pull request and include a succinct description of the feature or issue it addresses 226 | 227 | [license]: ./LICENSE 228 | [badge-license]: https://img.shields.io/github/license/solworktech/md2pdf.svg 229 | [go-docs-badge]: https://godoc.org/github.com/solworktech/md2pdf?status.svg 230 | [go-docs]: https://godoc.org/github.com/solworktech/md2pdf/v2 231 | [badge-build]: https://github.com/solworktech/md2pdf/actions/workflows/go.yml/badge.svg 232 | [build]: https://github.com/solworktech/md2pdf/actions/workflows/go.yml 233 | [go-report-card-badge]: https://goreportcard.com/badge/github.com/solworktech/md2pdf/v2 234 | [go-report-card]: https://goreportcard.com/report/github.com/solworktech/md2pdf/v2 235 | -------------------------------------------------------------------------------- /colors.go: -------------------------------------------------------------------------------- 1 | package mdtopdf 2 | 3 | import ( 4 | "math" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // The functions below were taken almost as is from https://github.com/ajstarks/deck/blob/master/cmd/pdfdeck/colors.go 10 | 11 | // colornames maps SVG color names to RGB triples. 12 | var colornames = map[string]Color{ 13 | "aliceblue": {240, 248, 255}, 14 | "antiquewhite": {250, 235, 215}, 15 | "aqua": {0, 255, 255}, 16 | "aquamarine": {127, 255, 212}, 17 | "azure": {240, 255, 255}, 18 | "beige": {245, 245, 220}, 19 | "bisque": {255, 228, 196}, 20 | "black": {0, 0, 0}, 21 | "charlestongreen": {35, 43, 43}, 22 | "eerieblack": {27, 27, 27}, 23 | "jetblack": {52, 52, 52}, 24 | "blanchedalmond": {255, 235, 205}, 25 | "blue": {0, 0, 255}, 26 | "blueviolet": {138, 43, 226}, 27 | "brown": {165, 42, 42}, 28 | "burlywood": {222, 184, 135}, 29 | "cadetblue": {95, 158, 160}, 30 | "chartreuse": {127, 255, 0}, 31 | "chocolate": {210, 105, 30}, 32 | "coral": {255, 127, 80}, 33 | "cornflowerblue": {100, 149, 237}, 34 | "cornsilk": {255, 248, 220}, 35 | "crimson": {220, 20, 60}, 36 | "cyan": {0, 255, 255}, 37 | "darkblue": {0, 0, 139}, 38 | "darkcyan": {0, 139, 139}, 39 | "darkgoldenrod": {184, 134, 11}, 40 | "darkgray": {169, 169, 169}, 41 | "darkgreen": {0, 100, 0}, 42 | "darkgrey": {169, 169, 169}, 43 | "darkkhaki": {189, 183, 107}, 44 | "darkmagenta": {139, 0, 139}, 45 | "darkolivegreen": {85, 107, 47}, 46 | "darkorange": {255, 140, 0}, 47 | "darkorchid": {153, 50, 204}, 48 | "darkred": {139, 0, 0}, 49 | "darksalmon": {233, 150, 122}, 50 | "darkseagreen": {143, 188, 143}, 51 | "darkslateblue": {72, 61, 139}, 52 | "darkslategray": {47, 79, 79}, 53 | "darkslategrey": {47, 79, 79}, 54 | "darkturquoise": {0, 206, 209}, 55 | "darkviolet": {148, 0, 211}, 56 | "deeppink": {255, 20, 147}, 57 | "deepskyblue": {0, 191, 255}, 58 | "dimgray": {105, 105, 105}, 59 | "dimgrey": {105, 105, 105}, 60 | "dodgerblue": {30, 144, 255}, 61 | "firebrick": {178, 34, 34}, 62 | "floralwhite": {255, 250, 240}, 63 | "forestgreen": {34, 139, 34}, 64 | "fuchsia": {255, 0, 255}, 65 | "gainsboro": {220, 220, 220}, 66 | "ghostwhite": {248, 248, 255}, 67 | "gold": {255, 215, 0}, 68 | "goldenrod": {218, 165, 32}, 69 | "gray": {128, 128, 128}, 70 | "green": {0, 128, 0}, 71 | "greenyellow": {173, 255, 47}, 72 | "grey": {128, 128, 128}, 73 | "honeydew": {240, 255, 240}, 74 | "hotpink": {255, 105, 180}, 75 | "indianred": {205, 92, 92}, 76 | "indigo": {75, 0, 130}, 77 | "ivory": {255, 255, 240}, 78 | "khaki": {240, 230, 140}, 79 | "lavender": {230, 230, 250}, 80 | "lavenderblush": {255, 240, 245}, 81 | "lawngreen": {124, 252, 0}, 82 | "lemonchiffon": {255, 250, 205}, 83 | "lightblue": {173, 216, 230}, 84 | "lightcoral": {240, 128, 128}, 85 | "lightcyan": {224, 255, 255}, 86 | "lightgoldenrodyellow": {250, 250, 210}, 87 | "lightgray": {211, 211, 211}, 88 | "lightgreen": {144, 238, 144}, 89 | "lightgrey": {211, 211, 211}, 90 | "lightpink": {255, 182, 193}, 91 | "lightsalmon": {255, 160, 122}, 92 | "lightseagreen": {32, 178, 170}, 93 | "lightskyblue": {135, 206, 250}, 94 | "lightslategray": {119, 136, 153}, 95 | "lightslategrey": {119, 136, 153}, 96 | "lightsteelblue": {176, 196, 222}, 97 | "lightyellow": {255, 255, 224}, 98 | "lime": {0, 255, 0}, 99 | "limegreen": {50, 205, 50}, 100 | "linen": {250, 240, 230}, 101 | "magenta": {255, 0, 255}, 102 | "maroon": {128, 0, 0}, 103 | "mediumaquamarine": {102, 205, 170}, 104 | "mediumblue": {0, 0, 205}, 105 | "mediumorchid": {186, 85, 211}, 106 | "mediumpurple": {147, 112, 219}, 107 | "mediumseagreen": {60, 179, 113}, 108 | "mediumslateblue": {123, 104, 238}, 109 | "mediumspringgreen": {0, 250, 154}, 110 | "mediumturquoise": {72, 209, 204}, 111 | "mediumvioletred": {199, 21, 133}, 112 | "midnightblue": {25, 25, 112}, 113 | "mintcream": {245, 255, 250}, 114 | "mistyrose": {255, 228, 225}, 115 | "moccasin": {255, 228, 181}, 116 | "navajowhite": {255, 222, 173}, 117 | "navy": {0, 0, 128}, 118 | "oldlace": {253, 245, 230}, 119 | "olive": {128, 128, 0}, 120 | "olivedrab": {107, 142, 35}, 121 | "orange": {255, 165, 0}, 122 | "orangered": {255, 69, 0}, 123 | "orchid": {218, 112, 214}, 124 | "palegoldenrod": {238, 232, 170}, 125 | "palegreen": {152, 251, 152}, 126 | "paleturquoise": {175, 238, 238}, 127 | "palevioletred": {219, 112, 147}, 128 | "papayawhip": {255, 239, 213}, 129 | "peachpuff": {255, 218, 185}, 130 | "peru": {205, 133, 63}, 131 | "pink": {255, 192, 203}, 132 | "plum": {221, 160, 221}, 133 | "powderblue": {176, 224, 230}, 134 | "purple": {128, 0, 128}, 135 | "red": {255, 0, 0}, 136 | "rosybrown": {188, 143, 143}, 137 | "royalblue": {65, 105, 225}, 138 | "saddlebrown": {139, 69, 19}, 139 | "salmon": {250, 128, 114}, 140 | "sandybrown": {244, 164, 96}, 141 | "seagreen": {46, 139, 87}, 142 | "seashell": {255, 245, 238}, 143 | "sienna": {160, 82, 45}, 144 | "silver": {192, 192, 192}, 145 | "skyblue": {135, 206, 235}, 146 | "slateblue": {106, 90, 205}, 147 | "slategray": {112, 128, 144}, 148 | "slategrey": {112, 128, 144}, 149 | "snow": {255, 250, 250}, 150 | "springgreen": {0, 255, 127}, 151 | "steelblue": {70, 130, 180}, 152 | "tan": {210, 180, 140}, 153 | "teal": {0, 128, 128}, 154 | "thistle": {216, 191, 216}, 155 | "tomato": {255, 99, 71}, 156 | "turquoise": {64, 224, 208}, 157 | "violet": {238, 130, 238}, 158 | "wheat": {245, 222, 179}, 159 | "white": {255, 255, 255}, 160 | "whitesmoke": {245, 245, 245}, 161 | "yellow": {255, 255, 0}, 162 | "yellowgreen": {154, 205, 50}, 163 | } 164 | 165 | // Colorlookup returns a RGB triple corresponding to the named color, "rgb(r,g,b)" or "#rrggbb" string. 166 | // On error, return black. 167 | func Colorlookup(s string) Color { 168 | color, ok := colornames[s] 169 | if ok { 170 | return Color{color.Red, color.Green, color.Blue} 171 | } 172 | var red, green, blue int 173 | ls := len(s) 174 | // rgb(r, g, b) 175 | if strings.HasPrefix(s, "rgb(") && strings.HasSuffix(s, ")") && ls > 5 { 176 | v := colorNumbers(s) 177 | if len(v) == 3 { 178 | red, _ = strconv.Atoi(v[0]) 179 | green, _ = strconv.Atoi(v[1]) 180 | blue, _ = strconv.Atoi(v[2]) 181 | } 182 | return Color{red, green, blue} 183 | } 184 | // #rrggbb 185 | if strings.HasPrefix(s, "#") && ls == 7 { 186 | r, _ := strconv.ParseInt(s[1:3], 16, 32) 187 | g, _ := strconv.ParseInt(s[3:5], 16, 32) 188 | b, _ := strconv.ParseInt(s[5:7], 16, 32) 189 | return Color{int(r), int(g), int(b)} 190 | } 191 | // hsv(hue, saturation, value) 192 | if strings.HasPrefix(s, "hsv(") && strings.HasSuffix(s, ")") && ls > 5 { 193 | v := colorNumbers(s) 194 | if len(v) == 3 { 195 | hue, _ := strconv.ParseFloat(v[0], 64) 196 | sat, _ := strconv.ParseFloat(v[1], 64) 197 | value, _ := strconv.ParseFloat(v[2], 64) 198 | red, green, blue = hsv2rgb(hue, sat, value) 199 | } 200 | return Color{red, green, blue} 201 | } 202 | return Color{0, 0, 0} 203 | } 204 | 205 | // colorNumbers returns a list of numbers from a comma separated list, 206 | // in the form of xxx(n1, n2, n3), after removing tabs and spaces. 207 | func colorNumbers(s string) []string { 208 | return strings.Split(strings.NewReplacer(" ", "", "\t", "").Replace(s[4:len(s)-1]), ",") 209 | } 210 | 211 | // hsv2rgb converts hsv(h (0-360), s (0-100), v (0-100)) to rgb 212 | // reference: https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB 213 | func hsv2rgb(h, s, v float64) (int, int, int) { 214 | s /= 100 215 | v /= 100 216 | if s > 1 || v > 1 { 217 | return 0, 0, 0 218 | } 219 | h = math.Mod(h, 360) 220 | c := v * s 221 | section := h / 60 222 | x := c * (1 - math.Abs(math.Mod(section, 2)-1)) 223 | 224 | var r, g, b float64 225 | switch { 226 | case section >= 0 && section <= 1: 227 | r = c 228 | g = x 229 | b = 0 230 | case section > 1 && section <= 2: 231 | r = x 232 | g = c 233 | b = 0 234 | case section > 2 && section <= 3: 235 | r = 0 236 | g = c 237 | b = x 238 | case section > 3 && section <= 4: 239 | r = 0 240 | g = x 241 | b = c 242 | case section > 4 && section <= 5: 243 | r = x 244 | g = 0 245 | b = c 246 | case section > 5 && section <= 6: 247 | r = c 248 | g = 0 249 | b = x 250 | default: 251 | return 0, 0, 0 252 | } 253 | m := v - c 254 | r += m 255 | g += m 256 | b += m 257 | return int(r * 255), int(g * 255), int(b * 255) 258 | } 259 | -------------------------------------------------------------------------------- /testdata/Markdown Documentation - Basics.html: -------------------------------------------------------------------------------- 1 | <h1>Markdown: Basics</h1> 2 | 3 | <ul id="ProjectSubmenu"> 4 | <li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li> 5 | <li><a class="selected" title="Markdown Basics">Basics</a></li> 6 | <li><a href="/projects/markdown/syntax" title="Markdown Syntax Documentation">Syntax</a></li> 7 | <li><a href="/projects/markdown/license" title="Pricing and License Information">License</a></li> 8 | <li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li> 9 | </ul> 10 | 11 | <h2>Getting the Gist of Markdown's Formatting Syntax</h2> 12 | 13 | <p>This page offers a brief overview of what it's like to use Markdown. 14 | The <a href="/projects/markdown/syntax" title="Markdown Syntax">syntax page</a> provides complete, detailed documentation for 15 | every feature, but Markdown should be very easy to pick up simply by 16 | looking at a few examples of it in action. The examples on this page 17 | are written in a before/after style, showing example syntax and the 18 | HTML output produced by Markdown.</p> 19 | 20 | <p>It's also helpful to simply try Markdown out; the <a href="/projects/markdown/dingus" title="Markdown Dingus">Dingus</a> is a 21 | web application that allows you type your own Markdown-formatted text 22 | and translate it to XHTML.</p> 23 | 24 | <p><strong>Note:</strong> This document is itself written using Markdown; you 25 | can <a href="/projects/markdown/basics.text">see the source for it by adding '.text' to the URL</a>.</p> 26 | 27 | <h2>Paragraphs, Headers, Blockquotes</h2> 28 | 29 | <p>A paragraph is simply one or more consecutive lines of text, separated 30 | by one or more blank lines. (A blank line is any line that looks like a 31 | blank line -- a line containing nothing spaces or tabs is considered 32 | blank.) Normal paragraphs should not be intended with spaces or tabs.</p> 33 | 34 | <p>Markdown offers two styles of headers: <em>Setext</em> and <em>atx</em>. 35 | Setext-style headers for <code><h1></code> and <code><h2></code> are created by 36 | "underlining" with equal signs (<code>=</code>) and hyphens (<code>-</code>), respectively. 37 | To create an atx-style header, you put 1-6 hash marks (<code>#</code>) at the 38 | beginning of the line -- the number of hashes equals the resulting 39 | HTML header level.</p> 40 | 41 | <p>Blockquotes are indicated using email-style '<code>></code>' angle brackets.</p> 42 | 43 | <p>Markdown:</p> 44 | 45 | <pre><code>A First Level Header 46 | ==================== 47 | 48 | A Second Level Header 49 | --------------------- 50 | 51 | Now is the time for all good men to come to 52 | the aid of their country. This is just a 53 | regular paragraph. 54 | 55 | The quick brown fox jumped over the lazy 56 | dog's back. 57 | 58 | ### Header 3 59 | 60 | > This is a blockquote. 61 | > 62 | > This is the second paragraph in the blockquote. 63 | > 64 | > ## This is an H2 in a blockquote 65 | </code></pre> 66 | 67 | <p>Output:</p> 68 | 69 | <pre><code><h1>A First Level Header</h1> 70 | 71 | <h2>A Second Level Header</h2> 72 | 73 | <p>Now is the time for all good men to come to 74 | the aid of their country. This is just a 75 | regular paragraph.</p> 76 | 77 | <p>The quick brown fox jumped over the lazy 78 | dog's back.</p> 79 | 80 | <h3>Header 3</h3> 81 | 82 | <blockquote> 83 | <p>This is a blockquote.</p> 84 | 85 | <p>This is the second paragraph in the blockquote.</p> 86 | 87 | <h2>This is an H2 in a blockquote</h2> 88 | </blockquote> 89 | </code></pre> 90 | 91 | <h3>Phrase Emphasis</h3> 92 | 93 | <p>Markdown uses asterisks and underscores to indicate spans of emphasis.</p> 94 | 95 | <p>Markdown:</p> 96 | 97 | <pre><code>Some of these words *are emphasized*. 98 | Some of these words _are emphasized also_. 99 | 100 | Use two asterisks for **strong emphasis**. 101 | Or, if you prefer, __use two underscores instead__. 102 | </code></pre> 103 | 104 | <p>Output:</p> 105 | 106 | <pre><code><p>Some of these words <em>are emphasized</em>. 107 | Some of these words <em>are emphasized also</em>.</p> 108 | 109 | <p>Use two asterisks for <strong>strong emphasis</strong>. 110 | Or, if you prefer, <strong>use two underscores instead</strong>.</p> 111 | </code></pre> 112 | 113 | <h2>Lists</h2> 114 | 115 | <p>Unordered (bulleted) lists use asterisks, pluses, and hyphens (<code>*</code>, 116 | <code>+</code>, and <code>-</code>) as list markers. These three markers are 117 | interchangable; this:</p> 118 | 119 | <pre><code>* Candy. 120 | * Gum. 121 | * Booze. 122 | </code></pre> 123 | 124 | <p>this:</p> 125 | 126 | <pre><code>+ Candy. 127 | + Gum. 128 | + Booze. 129 | </code></pre> 130 | 131 | <p>and this:</p> 132 | 133 | <pre><code>- Candy. 134 | - Gum. 135 | - Booze. 136 | </code></pre> 137 | 138 | <p>all produce the same output:</p> 139 | 140 | <pre><code><ul> 141 | <li>Candy.</li> 142 | <li>Gum.</li> 143 | <li>Booze.</li> 144 | </ul> 145 | </code></pre> 146 | 147 | <p>Ordered (numbered) lists use regular numbers, followed by periods, as 148 | list markers:</p> 149 | 150 | <pre><code>1. Red 151 | 2. Green 152 | 3. Blue 153 | </code></pre> 154 | 155 | <p>Output:</p> 156 | 157 | <pre><code><ol> 158 | <li>Red</li> 159 | <li>Green</li> 160 | <li>Blue</li> 161 | </ol> 162 | </code></pre> 163 | 164 | <p>If you put blank lines between items, you'll get <code><p></code> tags for the 165 | list item text. You can create multi-paragraph list items by indenting 166 | the paragraphs by 4 spaces or 1 tab:</p> 167 | 168 | <pre><code>* A list item. 169 | 170 | With multiple paragraphs. 171 | 172 | * Another item in the list. 173 | </code></pre> 174 | 175 | <p>Output:</p> 176 | 177 | <pre><code><ul> 178 | <li><p>A list item.</p> 179 | <p>With multiple paragraphs.</p></li> 180 | <li><p>Another item in the list.</p></li> 181 | </ul> 182 | </code></pre> 183 | 184 | <h3>Links</h3> 185 | 186 | <p>Markdown supports two styles for creating links: <em>inline</em> and 187 | <em>reference</em>. With both styles, you use square brackets to delimit the 188 | text you want to turn into a link.</p> 189 | 190 | <p>Inline-style links use parentheses immediately after the link text. 191 | For example:</p> 192 | 193 | <pre><code>This is an [example link](http://example.com/). 194 | </code></pre> 195 | 196 | <p>Output:</p> 197 | 198 | <pre><code><p>This is an <a href="http://example.com/"> 199 | example link</a>.</p> 200 | </code></pre> 201 | 202 | <p>Optionally, you may include a title attribute in the parentheses:</p> 203 | 204 | <pre><code>This is an [example link](http://example.com/ "With a Title"). 205 | </code></pre> 206 | 207 | <p>Output:</p> 208 | 209 | <pre><code><p>This is an <a href="http://example.com/" title="With a Title"> 210 | example link</a>.</p> 211 | </code></pre> 212 | 213 | <p>Reference-style links allow you to refer to your links by names, which 214 | you define elsewhere in your document:</p> 215 | 216 | <pre><code>I get 10 times more traffic from [Google][1] than from 217 | [Yahoo][2] or [MSN][3]. 218 | 219 | [1]: http://google.com/ "Google" 220 | [2]: http://search.yahoo.com/ "Yahoo Search" 221 | [3]: http://search.msn.com/ "MSN Search" 222 | </code></pre> 223 | 224 | <p>Output:</p> 225 | 226 | <pre><code><p>I get 10 times more traffic from <a href="http://google.com/" 227 | title="Google">Google</a> than from <a href="http://search.yahoo.com/" 228 | title="Yahoo Search">Yahoo</a> or <a href="http://search.msn.com/" 229 | title="MSN Search">MSN</a>.</p> 230 | </code></pre> 231 | 232 | <p>The title attribute is optional. Link names may contain letters, 233 | numbers and spaces, but are <em>not</em> case sensitive:</p> 234 | 235 | <pre><code>I start my morning with a cup of coffee and 236 | [The New York Times][NY Times]. 237 | 238 | [ny times]: http://www.nytimes.com/ 239 | </code></pre> 240 | 241 | <p>Output:</p> 242 | 243 | <pre><code><p>I start my morning with a cup of coffee and 244 | <a href="http://www.nytimes.com/">The New York Times</a>.</p> 245 | </code></pre> 246 | 247 | <h3>Images</h3> 248 | 249 | <p>Image syntax is very much like link syntax.</p> 250 | 251 | <p>Inline (titles are optional):</p> 252 | 253 | <pre><code>![alt text](/path/to/img.jpg "Title") 254 | </code></pre> 255 | 256 | <p>Reference-style:</p> 257 | 258 | <pre><code>![alt text][id] 259 | 260 | [id]: /path/to/img.jpg "Title" 261 | </code></pre> 262 | 263 | <p>Both of the above examples produce the same output:</p> 264 | 265 | <pre><code><img src="/path/to/img.jpg" alt="alt text" title="Title" /> 266 | </code></pre> 267 | 268 | <h3>Code</h3> 269 | 270 | <p>In a regular paragraph, you can create code span by wrapping text in 271 | backtick quotes. Any ampersands (<code>&</code>) and angle brackets (<code><</code> or 272 | <code>></code>) will automatically be translated into HTML entities. This makes 273 | it easy to use Markdown to write about HTML example code:</p> 274 | 275 | <pre><code>I strongly recommend against using any `<blink>` tags. 276 | 277 | I wish SmartyPants used named entities like `&mdash;` 278 | instead of decimal-encoded entites like `&#8212;`. 279 | </code></pre> 280 | 281 | <p>Output:</p> 282 | 283 | <pre><code><p>I strongly recommend against using any 284 | <code>&lt;blink&gt;</code> tags.</p> 285 | 286 | <p>I wish SmartyPants used named entities like 287 | <code>&amp;mdash;</code> instead of decimal-encoded 288 | entites like <code>&amp;#8212;</code>.</p> 289 | </code></pre> 290 | 291 | <p>To specify an entire block of pre-formatted code, indent every line of 292 | the block by 4 spaces or 1 tab. Just like with code spans, <code>&</code>, <code><</code>, 293 | and <code>></code> characters will be escaped automatically.</p> 294 | 295 | <p>Markdown:</p> 296 | 297 | <pre><code>If you want your page to validate under XHTML 1.0 Strict, 298 | you've got to put paragraph tags in your blockquotes: 299 | 300 | <blockquote> 301 | <p>For example.</p> 302 | </blockquote> 303 | </code></pre> 304 | 305 | <p>Output:</p> 306 | 307 | <pre><code><p>If you want your page to validate under XHTML 1.0 Strict, 308 | you've got to put paragraph tags in your blockquotes:</p> 309 | 310 | <pre><code>&lt;blockquote&gt; 311 | &lt;p&gt;For example.&lt;/p&gt; 312 | &lt;/blockquote&gt; 313 | </code></pre> 314 | </code></pre> 315 | -------------------------------------------------------------------------------- /testdata/Backslash escapes.log: -------------------------------------------------------------------------------- 1 | [RenderHeader] Not handled 2 | [Document] Not Handled 3 | [Paragraph (entering)] 4 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 5 | [cr()] LH=14 6 | [Text] These should all get escaped: 7 | [Paragraph (leaving)] 8 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 9 | [cr()] LH=14 10 | [Paragraph (entering)] 11 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 12 | [cr()] LH=14 13 | [Text] Backslash: 14 | [Text] \ 15 | [Paragraph (leaving)] 16 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 17 | [cr()] LH=14 18 | [Paragraph (entering)] 19 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 20 | [cr()] LH=14 21 | [Text] Backtick: 22 | [Text] ` 23 | [Paragraph (leaving)] 24 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 25 | [cr()] LH=14 26 | [Paragraph (entering)] 27 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 28 | [cr()] LH=14 29 | [Text] Asterisk: 30 | [Text] * 31 | [Paragraph (leaving)] 32 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 33 | [cr()] LH=14 34 | [Paragraph (entering)] 35 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 36 | [cr()] LH=14 37 | [Text] Underscore: 38 | [Text] _ 39 | [Paragraph (leaving)] 40 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 41 | [cr()] LH=14 42 | [Paragraph (entering)] 43 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 44 | [cr()] LH=14 45 | [Text] Left brace: 46 | [Text] { 47 | [Paragraph (leaving)] 48 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 49 | [cr()] LH=14 50 | [Paragraph (entering)] 51 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 52 | [cr()] LH=14 53 | [Text] Right brace: 54 | [Text] } 55 | [Paragraph (leaving)] 56 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 57 | [cr()] LH=14 58 | [Paragraph (entering)] 59 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 60 | [cr()] LH=14 61 | [Text] Left bracket: 62 | [Text] [ 63 | [Paragraph (leaving)] 64 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 65 | [cr()] LH=14 66 | [Paragraph (entering)] 67 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 68 | [cr()] LH=14 69 | [Text] Right bracket: 70 | [Text] ] 71 | [Paragraph (leaving)] 72 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 73 | [cr()] LH=14 74 | [Paragraph (entering)] 75 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 76 | [cr()] LH=14 77 | [Text] Left paren: 78 | [Text] ( 79 | [Paragraph (leaving)] 80 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 81 | [cr()] LH=14 82 | [Paragraph (entering)] 83 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 84 | [cr()] LH=14 85 | [Text] Right paren: 86 | [Text] ) 87 | [Paragraph (leaving)] 88 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 89 | [cr()] LH=14 90 | [Paragraph (entering)] 91 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 92 | [cr()] LH=14 93 | [Text] Greater-than: 94 | [Text] > 95 | [Paragraph (leaving)] 96 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 97 | [cr()] LH=14 98 | [Paragraph (entering)] 99 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 100 | [cr()] LH=14 101 | [Text] Hash: 102 | [Text] # 103 | [Paragraph (leaving)] 104 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 105 | [cr()] LH=14 106 | [Paragraph (entering)] 107 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 108 | [cr()] LH=14 109 | [Text] Period: 110 | [Text] . 111 | [Paragraph (leaving)] 112 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 113 | [cr()] LH=14 114 | [Paragraph (entering)] 115 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 116 | [cr()] LH=14 117 | [Text] Bang: 118 | [Text] ! 119 | [Paragraph (leaving)] 120 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 121 | [cr()] LH=14 122 | [Paragraph (entering)] 123 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 124 | [cr()] LH=14 125 | [Text] Plus: 126 | [Text] + 127 | [Paragraph (leaving)] 128 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 129 | [cr()] LH=14 130 | [Paragraph (entering)] 131 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 132 | [cr()] LH=14 133 | [Text] Minus: 134 | [Text] - 135 | [Paragraph (leaving)] 136 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 137 | [cr()] LH=14 138 | [Paragraph (entering)] 139 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 140 | [cr()] LH=14 141 | [Text] Tilde: 142 | [Text] ~ 143 | [Paragraph (leaving)] 144 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 145 | [cr()] LH=14 146 | [Paragraph (entering)] 147 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 148 | [cr()] LH=14 149 | [Text] These should not, because they occur within a code block: 150 | [Paragraph (leaving)] 151 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 152 | [cr()] LH=14 153 | [Codeblock] Leaf 'Backslash: \\\n\nBacktick: \`\n\nAste…' 154 | 155 | [cr()] LH=14 156 | [Paragraph (entering)] 157 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 158 | [cr()] LH=14 159 | [Text] Nor should these, which occur in code spans: 160 | [Paragraph (leaving)] 161 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 162 | [cr()] LH=14 163 | [Paragraph (entering)] 164 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 165 | [cr()] LH=14 166 | [Text] Backslash: 167 | [processCode] \\ 168 | [Backtick (entering)] 169 | [Paragraph (leaving)] 170 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 171 | [cr()] LH=14 172 | [Paragraph (entering)] 173 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 174 | [cr()] LH=14 175 | [Text] Backtick: 176 | [processCode] \` 177 | [Backtick (entering)] 178 | [Paragraph (leaving)] 179 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 180 | [cr()] LH=14 181 | [Paragraph (entering)] 182 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 183 | [cr()] LH=14 184 | [Text] Asterisk: 185 | [processCode] \* 186 | [Backtick (entering)] 187 | [Paragraph (leaving)] 188 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 189 | [cr()] LH=14 190 | [Paragraph (entering)] 191 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 192 | [cr()] LH=14 193 | [Text] Underscore: 194 | [processCode] \_ 195 | [Backtick (entering)] 196 | [Paragraph (leaving)] 197 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 198 | [cr()] LH=14 199 | [Paragraph (entering)] 200 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 201 | [cr()] LH=14 202 | [Text] Left brace: 203 | [processCode] \{ 204 | [Backtick (entering)] 205 | [Paragraph (leaving)] 206 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 207 | [cr()] LH=14 208 | [Paragraph (entering)] 209 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 210 | [cr()] LH=14 211 | [Text] Right brace: 212 | [processCode] \} 213 | [Backtick (entering)] 214 | [Paragraph (leaving)] 215 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 216 | [cr()] LH=14 217 | [Paragraph (entering)] 218 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 219 | [cr()] LH=14 220 | [Text] Left bracket: 221 | [processCode] \[ 222 | [Backtick (entering)] 223 | [Paragraph (leaving)] 224 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 225 | [cr()] LH=14 226 | [Paragraph (entering)] 227 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 228 | [cr()] LH=14 229 | [Text] Right bracket: 230 | [processCode] \] 231 | [Backtick (entering)] 232 | [Paragraph (leaving)] 233 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 234 | [cr()] LH=14 235 | [Paragraph (entering)] 236 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 237 | [cr()] LH=14 238 | [Text] Left paren: 239 | [processCode] \( 240 | [Backtick (entering)] 241 | [Paragraph (leaving)] 242 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 243 | [cr()] LH=14 244 | [Paragraph (entering)] 245 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 246 | [cr()] LH=14 247 | [Text] Right paren: 248 | [processCode] \) 249 | [Backtick (entering)] 250 | [Paragraph (leaving)] 251 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 252 | [cr()] LH=14 253 | [Paragraph (entering)] 254 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 255 | [cr()] LH=14 256 | [Text] Greater-than: 257 | [processCode] \> 258 | [Backtick (entering)] 259 | [Paragraph (leaving)] 260 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 261 | [cr()] LH=14 262 | [Paragraph (entering)] 263 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 264 | [cr()] LH=14 265 | [Text] Hash: 266 | [processCode] \# 267 | [Backtick (entering)] 268 | [Paragraph (leaving)] 269 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 270 | [cr()] LH=14 271 | [Paragraph (entering)] 272 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 273 | [cr()] LH=14 274 | [Text] Period: 275 | [processCode] \. 276 | [Backtick (entering)] 277 | [Paragraph (leaving)] 278 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 279 | [cr()] LH=14 280 | [Paragraph (entering)] 281 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 282 | [cr()] LH=14 283 | [Text] Bang: 284 | [processCode] \! 285 | [Backtick (entering)] 286 | [Paragraph (leaving)] 287 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 288 | [cr()] LH=14 289 | [Paragraph (entering)] 290 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 291 | [cr()] LH=14 292 | [Text] Plus: 293 | [processCode] \+ 294 | [Backtick (entering)] 295 | [Paragraph (leaving)] 296 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 297 | [cr()] LH=14 298 | [Paragraph (entering)] 299 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 300 | [cr()] LH=14 301 | [Text] Minus: 302 | [processCode] \- 303 | [Backtick (entering)] 304 | [Paragraph (leaving)] 305 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 306 | [cr()] LH=14 307 | [Paragraph (entering)] 308 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 309 | [cr()] LH=14 310 | [Text] Tilde: 311 | [processCode] \~ 312 | [Backtick (entering)] 313 | [Paragraph (leaving)] 314 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 315 | [cr()] LH=14 316 | [Paragraph (entering)] 317 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 318 | [cr()] LH=14 319 | [Text] These should get escaped, even though they're matching pairs for other Markdown constructs: 320 | [Paragraph (leaving)] 321 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 322 | [cr()] LH=14 323 | [Paragraph (entering)] 324 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 325 | [cr()] LH=14 326 | [Text] 327 | [Text] * 328 | [Text] asterisks 329 | [Text] * 330 | [Paragraph (leaving)] 331 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 332 | [cr()] LH=14 333 | [Paragraph (entering)] 334 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 335 | [cr()] LH=14 336 | [Text] 337 | [Text] _ 338 | [Text] underscores 339 | [Text] _ 340 | [Paragraph (leaving)] 341 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 342 | [cr()] LH=14 343 | [Paragraph (entering)] 344 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 345 | [cr()] LH=14 346 | [Text] 347 | [Text] ` 348 | [Text] backticks 349 | [Text] ` 350 | [Paragraph (leaving)] 351 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 352 | [cr()] LH=14 353 | [Paragraph (entering)] 354 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 355 | [cr()] LH=14 356 | [Text] This is a code span with a literal backslash-backtick sequence: 357 | [processCode] \` 358 | [Backtick (entering)] 359 | [Paragraph (leaving)] 360 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 361 | [cr()] LH=14 362 | [Paragraph (entering)] 363 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 364 | [cr()] LH=14 365 | [Text] This is a tag with unescaped backticks 366 | [HTMLSpan] Not handled 367 | [Text] bar 368 | [HTMLSpan] Not handled 369 | [Text] . 370 | [Paragraph (leaving)] 371 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 372 | [cr()] LH=14 373 | [Paragraph (entering)] 374 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 375 | [cr()] LH=14 376 | [Text] This is a tag with backslashes 377 | [HTMLSpan] Not handled 378 | [Text] bar 379 | [HTMLSpan] Not handled 380 | [Text] . 381 | [Paragraph (leaving)] 382 | [... Margins (left, top, right, bottom:] 28.35 28.35 28.35 56.7 383 | [cr()] LH=14 384 | [Document] Not Handled 385 | --------------------------------------------------------------------------------