Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions TeXmacs/tests/tmu/212_2_beamer_hlink.tmu
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<TMU|<tuple|1.0.2|1.2.8-rc1>>

<style|<tuple|beamer|no-page-numbers>>

<\body>
<screens|<\shown>
Test slide with hyperlinks

<hlink|Mogan Research|https://mogan.app>

<hlink|GNU GPL v3|https://www.gnu.org/licenses/gpl-3.0.html>
</shown>|<\shown>
Second slide

<hlink|GitHub|https://github.com>
</shown>>
</body>

<\initial>
<\collection>
<associate|font-base-size|24>
<associate|page-medium|beamer>
<associate|page-screen-margin|false>
</collection>
</initial>
27 changes: 27 additions & 0 deletions devel/212_2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 212_2: Fix hyperlinks unclickable in beamer PDF export

## Issue
Hyperlinks become invalid and unclickable after exporting a slideshow/beamer presentation to PDF. (Issue #2843)

## How to test
1. Open `TeXmacs/tests/tmu/212_2_beamer_hlink.tmu`
2. Export as PDF via File → Export → Pdf...
3. Open the exported PDF and verify all hyperlinks are clickable

## 2026/02/24

### What
Fixed hyperlinks (`hlink`) becoming unclickable after exporting a beamer presentation to PDF.

### Why
When exporting a beamer presentation to PDF, `wrapped-print-to-file` creates a temporary buffer copy, calls `dynamic-make-slides` to expand slides, then calls `print-to-file` (→ C++ `print_doc`).

In `print_doc`, `typeset_as_document` creates a typesetter, typesets the document (registering link/ID observers in global tables), then immediately destroys the typesetter — which unregisters all link observers.

During the rendering loop that follows, `display_links` (called by `box_rep::redraw` for non-screen renderers) queries these global link tables via `get_ids()`/`get_links()`. But the tables are now empty because the typesetter was already destroyed. So `ren->href()` is never called, and no link annotations appear in the PDF.

For regular documents, the screen-display typesetter from the original buffer is still alive with its link registrations. But for beamer export, the temporary buffer copy has no prior typesetting history — hence unclickable hyperlinks.

### How
- Inlined `typeset_as_document` in `print_doc` and deferred `delete_typesetter` until after the rendering loop, so link registrations persist through `display_links` calls
- Added a free-function `typeset(typesetter)` wrapper in `typesetter.hpp`/`typesetter.cpp` to avoid needing the `typesetter_rep` class definition in `edit_main.cpp`
16 changes: 15 additions & 1 deletion src/Edit/Editor/edit_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,21 @@ edit_main_rep::print_doc (url name, bool conform, int first, int last) {
}

// Typeset pages for printing
// NOTE: We inline typeset_as_document here and defer delete_typesetter
// until after the rendering loop. This is necessary because display_links
// (called during redraw for non-screen renderers) queries global link
// tables via get_ids/get_links. These tables are populated by the
// typesetter during typesetting and cleared when the typesetter is
// destroyed. If we destroy the typesetter before rendering (as
// typeset_as_document does), hyperlinks in the PDF become unclickable
// because the link registrations are already gone. This particularly
// affects beamer/slideshow export where a temporary buffer copy has no
// prior link registrations from screen display. (See issue #2843)

box the_box= typeset_as_document (env, subtree (et, rp), reverse (rp));
env->style_init_env ();
env->update ();
typesetter ttt = new_typesetter (env, subtree (et, rp), reverse (rp));
box the_box= ::typeset (ttt);

// Determine parameters for printer

Expand Down Expand Up @@ -267,6 +280,7 @@ edit_main_rep::print_doc (url name, bool conform, int first, int last) {
}
}
tm_delete (ren);
delete_typesetter (ttt);
}

void
Expand Down
5 changes: 5 additions & 0 deletions src/Typeset/Bridge/typesetter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,11 @@ exec_until (typesetter ttt, path p) {
ttt->br->exec_until (p);
}

box
typeset (typesetter ttt) {
return ttt->typeset ();
}

box
typeset (typesetter ttt, SI& x1, SI& y1, SI& x2, SI& y2) {
return ttt->typeset (x1, y1, x2, y2);
Expand Down
1 change: 1 addition & 0 deletions src/Typeset/typesetter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ void notify_assign_node (typesetter ttt, path p, tree_label op);
void notify_insert_node (typesetter ttt, path p, tree t);
void notify_remove_node (typesetter ttt, path p);
void exec_until (typesetter ttt, path p);
box typeset (typesetter ttt);
box typeset (typesetter ttt, SI& x1, SI& y1, SI& x2, SI& y2);

box typeset_as_concat (edit_env env, tree t, path ip);
Expand Down
Loading