Repository navigation
write_html: make an id or <a name> a target for href="#..." links, li… - #2048
RavSinghChandan wants to merge 5 commits into
Conversation
62e812b to
2a213d4
Compare
|
Thanks, you were right on both points. I checked: a heading that did not fit at the bottom of the page was rendered on page 2 while its destination pointed to page 1. Now the targets are collected while parsing and set when the first line of the paragraph that holds them is rendered (in TextColumns._render_column_lines, after the page break check), so the destination is on the page where the text really is. An element with both an id and a name registers both, as in your example. A target inside a table uses the table position, since cells are not rendered as paragraphs. Added tests for your id + name example and for a heading moved to the next page. I also rebased on master. |
| and anchor.strip() not in self._pending_anchors | ||
| ): | ||
| self._pending_anchors.append(anchor.strip()) | ||
| if tag == "table" or self.table is not None: |
There was a problem hiding this comment.
This block runs after the img branch, so <img id="img"> stays pending and is flushed at the next paragraph or at the end of feed(). The destination then points below the image: with <a href="#img">go</a><img id="img" ...>, named_destinations["img"].top is the image bottom, not the image top. Flushing pending anchors before the img branch would anchor the image correctly.
|
Good catch, thanks. Confirmed: with go<img id="img" ...> the destination was at the bottom of the image. Targets are now collected at the start of the tag, and for an Added a test with your example that compares the destination with where the image is actually drawn, on the same page and after a page break. |
|
Confirmed on the latest commit ( |
|
Thanks a lot for testing it so carefully, @agustin18! Glad the image and page break cases work now. |
| assert destination.page_number == page == (2 if space_left else 1) | ||
| assert destination.top == pytest.approx((pdf.h - top) * pdf.k) | ||
|
|
||
|
|
There was a problem hiding this comment.
def test_html_inline_anchor_after_a_page_break():
"""An inline target points to its own line, not the paragraph's first line."""
pdf = FPDF()
pdf.add_page()
pdf.write_html(
'<a href="#target">Go to target</a>'
"<p>"
+ "words " * 1100
+ '<a id="target">TARGET</a></p>'
)
pdf.output()
assert pdf.pages_count == 2
assert pdf.named_destinations["target"].page_number == 2
def test_html_anchor_on_a_table_moved_to_the_next_page():
"""A table target follows the table when layout causes a page break."""
pdf = FPDF()
pdf.add_page()
pdf.set_y(pdf.h - pdf.b_margin - 3)
pdf.write_html(
'<table id="target"><tr><td>TARGET</td></tr></table>'
'<a href="#target">Back to table</a>'
)
pdf.output()
assert pdf.pages_count == 2
destination = pdf.named_destinations["target"]
assert destination.page_number == 2
assert destination.top == pytest.approx((pdf.h - pdf.t_margin) * pdf.k)daaaa4b to
c448704
Compare
|
Thanks for the two tests, both failed and both pass now (added as you wrote them). Inline targets: a target is now attached to the first character of the text that follows it. When lines are broken, each line records the targets whose character starts on it, and the destination is set when that line is rendered, so it points to the target's own line and page, also after a page break in the middle of a paragraph. Tables: Table.render() now records where its first row is rendered (first_row_position), and write_html() sets the table's targets there after rendering, so they follow the table to the next page. I also checked that an inline target lands on the exact line where its text is drawn, including when its text starts with a space that is skipped at the start of a line. |
2b7bfef to
5ee45d7
Compare
9877769 to
0b175c5
Compare
…where its first row is rendered
0b175c5 to
f9cfda5
Compare
A link to a place in the same document, such as a table of contents with
<a href="#intro">, madewrite_html()fail at output withFPDFException: Named destination 'intro' was referenced but never set, because nothing in the HTML created that destination. Browsers use theidof any element, or thenameof an<a>, as the target.write_html()now registers theidof an element, and thenameof an<a>, as a named destination at its position, using the existingset_link(name=...), so these links work, including to a later page. A link to a target that does not exist still raises as before.test_html_anchor_without_href(from #1984) now also defines the two destinations in its expected document: the anchors still render as plain text, and the PDF now contains their destinations. Added tests foridand<a name>targets, and for a target on a later page.