Skip to content

Commit e198357

Browse files
authored
Clickable hyperlinks. (#51)
1 parent dfbb26c commit e198357

9 files changed

Lines changed: 136 additions & 15 deletions

File tree

README.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ When creating a PDF file you can:
1717
- Tune the necessary elements using your CSS code
1818
- Use different page sizes within single pdf
1919
- Create tables in `markdown`
20+
- Use clickable hyperlinks. Thanks a lot [@thongtmtrust](https://github.com/thongtmtrust) for ideas and collaboration.
2021

2122
The module utilizes the functions of two great libraries.
2223

@@ -47,24 +48,38 @@ from markdown_pdf import Section
4748
pdf.add_section(Section("# Title\n", toc=False))
4849
```
4950

50-
Add a second section. In the pdf file it starts on a new page.
51+
Add a second section with external and internal hyperlinks.
52+
In the pdf file it starts on a new page.
53+
54+
```python
55+
text = """# Section with links
56+
57+
- [External link](https://github.com/vb64/markdown-pdf)
58+
- [Internal link to Head1](#head1)
59+
- [Internal link to Head3](#head3)
60+
"""
61+
62+
pdf.add_section(Section(text))
63+
```
64+
65+
Add a third section.
5166
The title is centered using CSS, included in the table of contents of the pdf file, and an image from the file `img/python.png` is embedded on the page.
5267

5368
```python
5469
pdf.add_section(
55-
Section("# Head1\n\n![python](img/python.png)\n\nbody\n"),
70+
Section("# <a name='head1'></a>Head1\n\n![python](img/python.png)\n\nbody\n"),
5671
user_css="h1 {text-align:center;}"
5772
)
5873
```
5974

60-
Add a third section. Two headings of different levels from this section are included in the TOC of the pdf file.
75+
Add a next section. Two headings of different levels from this section are included in the TOC of the pdf file.
6176
The section has landscape orientation of A4 pages.
6277

6378
```python
64-
pdf.add_section(Section("## Head2\n\n### Head3\n\n", paper_size="A4-L"))
79+
pdf.add_section(Section("## Head2\n\n### <a id='head3'></a>Head3\n\n", paper_size="A4-L"))
6580
```
6681

67-
Add a fourth section with a table.
82+
Add a section with a table.
6883

6984
```python
7085

README_ru.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- Оформлять нужные элементы при помощи вашего CSS кода
1212
- Использовать разные размеры страниц внутри одного pdf
1313
- Создавать таблицы в `markdown`
14+
- Использовать кликабельные гиперссылки. Спасибо [@thongtmtrust](https://github.com/thongtmtrust) за идеи и сотрудничество!
1415

1516
Модуль использует функции двух замечательных библиотек.
1617

@@ -41,24 +42,38 @@ from markdown_pdf import Section
4142
pdf.add_section(Section("# Title\n", toc=False))
4243
```
4344

44-
Добавляем вторую секцию. В pdf-файле она начинается с новой страницы.
45+
Добавляем вторую секцию с внешними и внутренними гипер-ссылками.
46+
В pdf-файле она начинается с новой страницы.
47+
48+
```python
49+
text = """# Section with links
50+
51+
- [External link](https://github.com/vb64/markdown-pdf)
52+
- [Internal link to Head1](#head1)
53+
- [Internal link to Head3](#head3)
54+
"""
55+
56+
pdf.add_section(Section(text))
57+
```
58+
59+
Добавляем третью секцию.
4560
Заголовок центрируется при помощи CSS, включается в оглавление pdf-файла и на страницу встраивается изображение из файла `img/python.png`.
4661

4762
```python
4863
pdf.add_section(
49-
Section("# Head1\n\n![python](img/python.png)\n\nbody\n"),
64+
Section("# <a name='head1'>Head1\n\n![python](img/python.png)\n\nbody\n"),
5065
user_css="h1 {text-align:center;}"
5166
)
5267
```
5368

54-
Добавляем третью секцию. Два заголовка разного уровня из этой секции включаются в оглавление pdf-файла.
69+
Добавляем следующую секцию. Два заголовка разного уровня из этой секции включаются в оглавление pdf-файла.
5570
Секция имеет альбомную ориентацию страниц A4.
5671

5772
```python
58-
pdf.add_section(Section("## Head2\n\n### Head3\n\n", paper_size="A4-L"))
73+
pdf.add_section(Section("## Head2\n\n### <a id='head3'></a>Head3\n\n", paper_size="A4-L"))
5974
```
6075

61-
Добавляем четвертую секцию с таблицей.
76+
Добавляем секцию с таблицей.
6277

6378
```python
6479

examples/markdown_pdf.pdf

-119 KB
Binary file not shown.

examples/markdown_pdf_ru.pdf

-120 KB
Binary file not shown.

fixture/hrefs.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Module markdown-pdf
2+
3+
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/vb64/markdown-pdf/pep257.yml?label=Pep257&style=plastic&branch=main)](https://github.com/vb64/markdown-pdf/actions?query=workflow%3Apep257)
4+
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/vb64/markdown-pdf/py3.yml?label=Python%203.8-3.13&style=plastic&branch=main)](https://github.com/vb64/markdown-pdf/actions?query=workflow%3Apy3)
5+
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/27b53043bff34f07bfb79ee1672b7ba0)](https://app.codacy.com/gh/vb64/markdown-pdf/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
6+
[![Codacy Badge](https://app.codacy.com/project/badge/Coverage/27b53043bff34f07bfb79ee1672b7ba0)](https://app.codacy.com/gh/vb64/markdown-pdf/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)
7+
[![PyPI - Downloads](https://img.shields.io/pypi/dm/markdown-pdf?label=pypi%20installs)](https://pypistats.org/packages/markdown-pdf)
8+
9+
The free, open source Python module `markdown-pdf` will create a PDF file from your content in `markdown` format.
10+
11+
When creating a PDF file you can:
12+
13+
- Use `UTF-8` encoded text in `markdown` in any language
14+
- Embed images used in `markdown`
15+
- Break text into pages in the desired order
16+
- Create a TableOfContents (bookmarks) from markdown headings
17+
- Tune the necessary elements using your CSS code
18+
- Use different page sizes within single pdf
19+
- Create tables in `markdown`
20+
21+
The module utilizes the functions of two great libraries.
22+
23+
- [markdown-it-py](https://github.com/executablebooks/markdown-it-py) to convert `markdown` to `html`.
24+
- [PyMuPDF](https://github.com/pymupdf/PyMuPDF) to convert `html` to `pdf`.
25+
26+
## Installation
27+
28+
```bash
29+
pip install markdown-pdf
30+
```

history.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
+ Clickable hyperlinks.
2+
13
07.03.2025 ver.1.4
24
------------------
35

markdown_pdf/__init__.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,15 @@ def __init__(self, toc_level: int = 6, mode: str = 'commonmark'):
5151

5252
self.out_file = io.BytesIO()
5353
self.writer = fitz.DocumentWriter(self.out_file)
54-
self.page = 0
54+
self.page_num = 0
55+
self.hrefs = []
5556

5657
@staticmethod
5758
def _recorder(elpos):
5859
"""Call function invoked during story.place() for making a TOC."""
60+
elpos.page_num = elpos.pdfile.page_num
61+
elpos.pdfile.hrefs.append(elpos)
62+
5963
if not elpos.open_close & 1: # only consider "open" items
6064
return
6165
if not elpos.toc:
@@ -65,7 +69,7 @@ def _recorder(elpos):
6569
elpos.pdfile.toc.append((
6670
elpos.heading,
6771
elpos.text,
68-
elpos.pdfile.page,
72+
elpos.pdfile.page_num,
6973
elpos.rect[1], # top of written rectangle (use for TOC)
7074
))
7175

@@ -77,7 +81,7 @@ def add_section(self, section: Section, user_css: typing.Optional[str] = None) -
7781
story = fitz.Story(html=html, archive=section.root, user_css=user_css)
7882
more = 1
7983
while more: # loop outputting the story
80-
self.page += 1
84+
self.page_num += 1
8185
device = self.writer.begin_page(rect)
8286
more, _ = story.place(where) # layout into allowed rectangle
8387
story.element_positions(self._recorder, {"toc": section.toc, "pdfile": self})
@@ -89,7 +93,8 @@ def add_section(self, section: Section, user_css: typing.Optional[str] = None) -
8993
def save(self, file_name: typing.Union[str, pathlib.Path]) -> None:
9094
"""Save pdf to file."""
9195
self.writer.close()
92-
doc = fitz.open("pdf", self.out_file)
96+
self.out_file.seek(0)
97+
doc = fitz.Story.add_pdf_links(self.out_file, self.hrefs)
9398
doc.set_metadata(self.meta)
9499
if self.toc_level > 0:
95100
doc.set_toc(self.toc)

tests/test/test_converter.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ def test_with_toc(self):
2222

2323
pdf = MarkdownPdf(toc_level=2)
2424
pdf.add_section(Section("# Title\n", toc=False))
25+
pdf.add_section(Section(TABLE_TEXT))
2526
pdf.add_section(
2627
Section("# Head1\n\n![python](img/python.png)\n\nbody\n"),
2728
user_css="h1 {text-align:center;}"
2829
)
2930
pdf.add_section(Section("## Head2\n\n### Head3\n\n"))
30-
pdf.add_section(Section(TABLE_TEXT))
3131
pdf.save(self.build("with_toc.pdf"))
3232

3333
def test_no_toc(self):
@@ -70,3 +70,13 @@ def test_empty_head(self):
7070
pdf = MarkdownPdf(toc_level=2)
7171
pdf.add_section(Section("# "))
7272
pdf.save(self.build("empty-head.pdf"))
73+
74+
def test_hrefs(self):
75+
"""Convert hrefs content to pdf."""
76+
from markdown_pdf import Section, MarkdownPdf
77+
78+
pdf = MarkdownPdf()
79+
pdf.add_section(
80+
Section(open(self.fixture("hrefs.md"), "rt", encoding='utf-8').read())
81+
)
82+
pdf.save(self.build("hrefs.pdf"))

tests/test/test_readme.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Readme example code.
2+
3+
make test T=test_readme.py
4+
"""
5+
from . import TestBase
6+
7+
8+
class TestReadme(TestBase):
9+
"""Latex content."""
10+
11+
def test_en(self):
12+
"""Test README.md example code."""
13+
from markdown_pdf import Section, MarkdownPdf
14+
15+
pdf = MarkdownPdf(toc_level=2)
16+
pdf.add_section(Section("# Title\n", toc=False))
17+
18+
text = """# Section with links
19+
20+
- [External link](https://github.com/vb64/markdown-pdf)
21+
- [Internal link to Head1](#head1)
22+
- [Internal link to Head3](#head3)
23+
"""
24+
pdf.add_section(Section(text))
25+
26+
pdf.add_section(
27+
Section("# <a name='head1'></a>Head1\n\n![python](img/python.png)\n\nbody\n"),
28+
user_css="h1 {text-align:center;}"
29+
)
30+
pdf.add_section(Section("## Head2\n\n### <a id='head3'></a>Head3\n\n", paper_size="A4-L"))
31+
text = """# Section with Table
32+
33+
|TableHeader1|TableHeader2|
34+
|--|--|
35+
|Text1|Text2|
36+
|ListCell|<ul><li>FirstBullet</li><li>SecondBullet</li></ul>|
37+
"""
38+
css = "table, th, td {border: 1px solid black;}"
39+
pdf.add_section(Section(text), user_css=css)
40+
41+
pdf.meta["title"] = "User Guide"
42+
pdf.meta["author"] = "Vitaly Bogomolov"
43+
44+
pdf.save(self.build("readme.pdf"))

0 commit comments

Comments
 (0)