diff --git a/docs/Links.md b/docs/Links.md
index cb2b4dcf4..ec1af546c 100644
--- a/docs/Links.md
+++ b/docs/Links.md
@@ -14,11 +14,33 @@ from fpdf import FPDF
pdf = FPDF()
pdf.add_page()
pdf.set_font("helvetica", size=24)
-pdf.cell(w=40, h=10, txt="Cell link", border=1, align="C", link="https://github.com/PyFPDF/fpdf2")
+pdf.cell(txt="Cell link", border=1, center=True,
+ link="https://github.com/PyFPDF/fpdf2")
pdf.output("hyperlink.pdf")
```
+## Hyperlink with FPDF.multi_cell ##
+
+```python
+from fpdf import FPDF
+
+pdf = FPDF()
+pdf.set_font("helvetica", size=24)
+pdf.add_page()
+pdf.multi_cell(
+ pdf.epw,
+ txt="**Website:** [fpdf2](https://pyfpdf.github.io/fpdf2/) __Go visit it!__",
+ markdown=True,
+)
+pdf.output("hyperlink.pdf")
+```
+
+Links defined this way in Markdown can be styled by setting `FPDF` class attributes `MARKDOWN_LINK_COLOR` (default: `None`) & `MARKDOWN_LINK_UNDERLINE` (default: `True`).
+
+`link="https://...your-url"` can also be used to make the whole cell clickable.
+
+
## Hyperlink with FPDF.link ##
The `FPDF.link` is a low-level method that defines a rectangular clickable area.
@@ -59,6 +81,8 @@ The hyperlinks defined this way will be rendered in blue with underline.
## Internal links ##
+Internal links are links redirecting to other pages in the document.
+
Using `FPDF.cell`:
```python
@@ -67,16 +91,44 @@ from fpdf import FPDF
pdf = FPDF()
pdf.set_font("helvetica", size=24)
pdf.add_page()
-# Displaying a full-width cell with centered text:
-pdf.cell(w=pdf.epw, txt="Welcome on first page!", align="C")
+pdf.cell(txt="Welcome on first page!", align="C", center=True)
pdf.add_page()
link = pdf.add_link(page=1)
pdf.cell(txt="Internal link to first page", border=1, link=link)
pdf.output("internal_link.pdf")
```
-Similarly, `FPDF.link` can be used instead of `FPDF.cell`,
-however `write_html` does not allow to define internal links.
+Other methods can also insert internal links:
+
+* [FPDF.multi_cell](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.multi_cell) using `link=` **or** `markdown=True` and this syntax: `[link text](page number)`
+* [FPDF.link](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.link)
+* [FPDF.write_html](HTML.md) using anchor tags: `link text`
+
+The unit tests `test_internal_links()` in [test_links.py](https://github.com/PyFPDF/fpdf2/blob/master/test/test_links.py) provides examples for all of those methods.
+
+
+## Links to other documents on the filesystem ##
+
+Using `FPDF.cell`:
+
+```python
+from fpdf import FPDF
+
+pdf = FPDF()
+pdf.set_font("helvetica", size=24)
+pdf.add_page()
+pdf.cell(txt="Link to other_doc.pdf", border=1, link="other_doc.pdf")
+pdf.output("link_to_other_doc.pdf")
+```
+
+Other methods can also insert internal links:
+
+* [FPDF.multi_cell](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.multi_cell) using `link=` **or** `markdown=True` and this syntax: `[link text](other_doc.pdf)`
+* [FPDF.link](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.link)
+* [FPDF.write_html](HTML.md) using anchor tags: `link text`
+
+The unit test `test_link_to_other_document()` in [test_links.py](https://github.com/PyFPDF/fpdf2/blob/master/test/test_links.py) provides examples for all of those methods.
+
## Alternative description ##
diff --git a/fpdf/fpdf.py b/fpdf/fpdf.py
index 5e00aa311..5a15d0820 100644
--- a/fpdf/fpdf.py
+++ b/fpdf/fpdf.py
@@ -229,6 +229,7 @@ class FPDF(GraphicsStateMixin):
MARKDOWN_UNDERLINE_MARKER = "--"
MARKDOWN_LINK_REGEX = re.compile(r"^\[([^][]+)\]\(([^()]+)\)(.*)$")
MARKDOWN_LINK_COLOR = None
+ MARKDOWN_LINK_UNDERLINE = True
HTML2FPDF_CLASS = HTML2FPDF
@@ -2934,13 +2935,13 @@ def _render_styled_text_line(
underlines.append(
(self.x + dx + s_width, frag_width, frag.font, frag.font_size)
)
- if frag.url:
+ if frag.link:
self.link(
x=self.x + dx + s_width,
y=self.y + (0.5 * h) - (0.5 * frag.font_size),
w=frag_width,
h=frag.font_size,
- link=frag.url,
+ link=frag.link,
)
s_width += frag_width
@@ -3166,14 +3167,19 @@ def frag():
continue
is_link = self.MARKDOWN_LINK_REGEX.match(txt)
if is_link:
- link_text, link_url, txt = is_link.groups()
+ link_text, link_dest, txt = is_link.groups()
if txt_frag:
yield frag()
gstate = self._get_current_graphics_state()
- gstate["underline"] = True
+ gstate["underline"] = self.MARKDOWN_LINK_UNDERLINE
if self.MARKDOWN_LINK_COLOR:
gstate["text_color"] = self.MARKDOWN_LINK_COLOR
- yield Fragment(list(link_text), gstate, self.k, url=link_url)
+ try:
+ page = int(link_dest)
+ link_dest = self.add_link(page=page)
+ except ValueError:
+ pass
+ yield Fragment(list(link_text), gstate, self.k, link=link_dest)
continue
if self.is_ttf_font and txt[0] != "\n" and not ord(txt[0]) in font_glyphs:
style = ("B" if in_bold else "") + ("I" if in_italics else "")
diff --git a/fpdf/line_break.py b/fpdf/line_break.py
index 3d8a335af..56c71a96d 100644
--- a/fpdf/line_break.py
+++ b/fpdf/line_break.py
@@ -6,7 +6,7 @@
They may change at any time without prior warning or any deprecation period.
"""
-from typing import NamedTuple, Any, Union, Sequence
+from typing import NamedTuple, Any, Optional, Union, Sequence
from .enums import CharVPos, WrapMode
from .errors import FPDFException
@@ -27,7 +27,7 @@ def __init__(
characters: Union[list, str],
graphics_state: dict,
k: float,
- url: str = None,
+ link: Optional[Union[int, str]] = None,
):
if isinstance(characters, str):
self.characters = list(characters)
@@ -35,7 +35,7 @@ def __init__(
self.characters = characters
self.graphics_state = graphics_state
self.k = k
- self.url = url
+ self.link = link
def __repr__(self):
gstate = self.graphics_state.copy()
@@ -43,7 +43,7 @@ def __repr__(self):
del gstate["current_font"] # TMI
return (
f"Fragment(characters={self.characters},"
- f" graphics_state={gstate}, k={self.k}, url={self.url})"
+ f" graphics_state={gstate}, k={self.k}, link={self.link})"
)
@property
@@ -459,7 +459,7 @@ def get_line_of_given_width(self, maximum_width: float, wordsplit: bool = True):
current_fragment.k,
self.fragment_index,
self.character_index,
- current_fragment.url,
+ current_fragment.link,
)
self.character_index += 1
diff --git a/test/links.pdf b/test/hyperlinks.pdf
similarity index 63%
rename from test/links.pdf
rename to test/hyperlinks.pdf
index e72b360d3..5f75b91ca 100644
Binary files a/test/links.pdf and b/test/hyperlinks.pdf differ
diff --git a/test/internal_links.pdf b/test/internal_links.pdf
new file mode 100644
index 000000000..87bf52572
Binary files /dev/null and b/test/internal_links.pdf differ
diff --git a/test/link_to_other_document.pdf b/test/link_to_other_document.pdf
new file mode 100644
index 000000000..98a46cbc3
Binary files /dev/null and b/test/link_to_other_document.pdf differ
diff --git a/test/test_links.py b/test/test_links.py
index 5772d6b27..ca37e6311 100644
--- a/test/test_links.py
+++ b/test/test_links.py
@@ -8,16 +8,14 @@
HERE = Path(__file__).resolve().parent
-def test_links(tmp_path):
+def test_hyperlinks(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("helvetica", size=24)
- line_height = 10
pdf.set_xy(80, 50)
pdf.cell(
- w=40,
- h=line_height,
+ h=pdf.h,
txt="Cell link",
border=1,
align="C",
@@ -32,21 +30,13 @@ def test_links(tmp_path):
width = pdf.get_string_width(text)
pdf.link(
x=80,
- y=150 - line_height,
+ y=150 - pdf.h,
w=width,
- h=line_height,
+ h=pdf.h,
link="https://github.com/PyFPDF/fpdf2",
)
- pdf.add_page()
- link = pdf.add_link()
- pdf.set_link(link, page=1)
- pdf.set_xy(50, 50)
- pdf.cell(
- w=100, h=10, txt="Internal link to first page", border=1, align="C", link=link
- )
-
- assert_pdf_equal(pdf, HERE / "links.pdf", tmp_path)
+ assert_pdf_equal(pdf, HERE / "hyperlinks.pdf", tmp_path)
def test_link_alt_text(tmp_path):
@@ -184,3 +174,104 @@ def test_later_call_to_set_link(tmp_path): # v2.6.1 bug spotted in discussion 7
pdf.cell(txt="Section 1: Bla bla bla")
assert_pdf_equal(pdf, HERE / "later_call_to_set_link.pdf", tmp_path)
+
+
+def test_link_to_other_document(tmp_path):
+ pdf = FPDF()
+ pdf.add_page()
+ pdf.set_font("helvetica", size=24)
+
+ pdf.set_xy(80, 50)
+ pdf.cell(
+ txt="Link defined with FPDF.cell",
+ border=1,
+ align="C",
+ link="links.pdf",
+ )
+
+ pdf.set_y(100)
+ pdf.multi_cell(
+ w=pdf.epw,
+ txt="Link defined with FPDF.multi_cell",
+ border=1,
+ align="C",
+ link="links.pdf",
+ )
+
+ pdf.set_y(150)
+ pdf.multi_cell(
+ w=pdf.epw,
+ txt="Link defined with FPDF.multi_cell and markdown=True: [links.pdf](links.pdf)",
+ border=1,
+ align="C",
+ markdown=True,
+ )
+
+ pdf.set_xy(60, 200)
+ pdf.write_html('Link defined with FPDF.write_html')
+
+ text = "Link defined with FPDF.link"
+ pdf.text(x=80, y=250, txt=text)
+ width = pdf.get_string_width(text)
+ pdf.link(
+ x=80,
+ y=250 - pdf.h,
+ w=width,
+ h=pdf.h,
+ link="links.pdf",
+ )
+
+ assert_pdf_equal(pdf, HERE / "link_to_other_document.pdf", tmp_path)
+
+
+def test_internal_links(tmp_path):
+ pdf = FPDF()
+ pdf.set_font("helvetica", size=24)
+
+ pdf.add_page()
+ pdf.y = 100
+ pdf.cell(txt="Page 1", center=True)
+
+ pdf.add_page()
+
+ pdf.set_xy(80, 50)
+ pdf.cell(
+ txt="Link defined with FPDF.cell",
+ border=1,
+ align="C",
+ link=pdf.add_link(page=1),
+ )
+
+ pdf.set_y(100)
+ pdf.multi_cell(
+ w=pdf.epw,
+ txt="Link defined with FPDF.multi_cell",
+ border=1,
+ align="C",
+ link=pdf.add_link(page=1),
+ )
+
+ pdf.set_y(150)
+ pdf.multi_cell(
+ w=pdf.epw,
+ txt="Link defined with FPDF.multi_cell and markdown=True: [page 1](1)",
+ border=1,
+ align="C",
+ markdown=True,
+ )
+
+ pdf.set_xy(60, 200)
+ pdf.write_html('Link defined with FPDF.write_html')
+
+ text = "Link defined with FPDF.link"
+ pdf.text(x=80, y=250, txt=text)
+ width = pdf.get_string_width(text)
+ pdf.link(
+ x=80,
+ y=250 - pdf.h,
+ w=width,
+ h=pdf.h,
+ link=pdf.add_link(page=1),
+ )
+
+ assert_pdf_equal(pdf, HERE / "internal_links.pdf", tmp_path)