feat(action): Hyperlinks 2.0 & Click Actions — issue #21 epic#61
Merged
Merged
Conversation
Closes #21. Authors a unified, accessible API for hyperlinks, ScreenTips, click/hover actions, and accessibility surfaces on shapes, runs, and pictures. Lands eight sub-features as a single coherent surface so the "hyperlink on a run" and "click action on a shape" stories share one Hyperlink/ActionSetting/CT_Hyperlink core. Sub-features: 1. ScreenTip on a run hyperlink — Run.hyperlink.tooltip getter/setter, read/write the `tooltip` attribute on the run's a:hlinkClick. 2. Color override on a hyperlink run — Run.hyperlink.color delegates to the same lazy Font.color machinery; closes scanny/python-pptx scanny#940 and scanny#821 (theme-hyperlink-color override without materialising a:solidFill on read). 3. Click actions for shapes — - ActionSetting.run_macro(macro_name) — emits ppaction://macro?name=<urlquoted> - ActionSetting.target_program(file_path) — emits ppaction://program with an external HYPERLINK relationship - ActionSetting.play_sound(audio_file) — embeds a WAV via the AUDIO relationship under a:snd, with endSnd=True; uses RT.AUDIO (relationships/audio), NOT relationships/media — the latter triggers PowerPoint's Repair dialog on load. 4. Independent hover action — Shape.hover_action exposes an ActionSetting bound to a:hlinkHover so click and hover behaviors can co-exist without overwriting each other. 5. Jump to slide from a text run — Run.click_action gives runs the same ActionSetting surface shapes have; target_slide=other_slide emits ppaction://hlinksldjump with a SLIDE relationship. 6. Picture hyperlinks — Picture.hyperlink exposes the same Hyperlink surface (address, tooltip, color), backed by the picture's cNvPr. 7. Accessibility surface on every shape — - BaseShape.alt_text (cNvPr/@Descr) - BaseShape.alt_title (cNvPr/@title) - BaseShape.is_decorative (adec:decorative ext, Office 2019+) - BaseShape.is_hidden_from_accessibility (alias of is_decorative) 8. Unified click/hover API — runs and shapes share the same ActionSetting/Hyperlink objects so target_slide/run_macro/ target_program/play_sound/tooltip/address work the same on either surface. PowerPoint compatibility notes (load-bearing — verified by Repair-dialog testing): - Every a:hlinkClick / a:hlinkHover we create gets r:id="" as a default even when no relationship is needed. PowerPoint's load-time validator strips a:hlinkClick elements that omit @r:id, even though ECMA-376 marks it optional. Real PowerPoint output always carries r:id, empty when there is no relationship — mirroring the precedent in oxml/shapes/picture.py for ppaction://media. - For an otherwise-empty (tooltip-only or inert) a:hlinkClick, we add action="ppaction://noaction". This matches real PowerPoint's own emission for inert hlinks. The marker is added by _ensure_noaction_if_inert and pruned in lockstep with the rest of the hlink's contents by _prune_hlink_if_empty. - play_sound uses RT.AUDIO, not RT.MEDIA. The Microsoft-2007 relationships/media rel under CT_EmbeddedWAVAudioFile/@r:embed triggers PowerPoint's Repair dialog and strips the entire hlinkClick plus the WAV part. relationships/audio (ECMA-376) is the rel PowerPoint actually expects for embedded WAV under a:snd. KNOWN LIMITATION — no-click-action hover ScreenTip not rendered: PowerPoint does not surface a hover ScreenTip in slideshow mode for shapes that have no click action. This applies to BOTH paths: - cNvPr/a:hlinkClick/@ToolTip without a navigation target - cNvPr/@Descr (alt_text) Both round-trip correctly through save/reload at the XML layer, but PowerPoint's slideshow runtime only activates hover-ScreenTip rendering when the hyperlink carries a real navigation target — URL, slide jump, macro, or program. There is no known fully-supported OOXML workaround for a pure no-click-action hover ScreenTip. ScreenTips on hyperlinks WITH a navigation target work correctly. alt_text remains useful for accessibility / screen reader text. The relevant docstrings (Hyperlink.tooltip, ActionSetting.tooltip, Run.hyperlink.tooltip) all document this limitation. Surface: - src/pptx/action.py: +229 lines — target_program, run_macro, play_sound, tooltip (read/write) on both ActionSetting and Hyperlink; _get_or_add_hlink default r:id=""; helpers _prune_hlink_if_empty, _ensure_noaction_if_inert, _ensure_noaction_pruned - src/pptx/oxml/action.py: +63 lines — CT_Hyperlink expanded with a:snd (CT_EmbeddedWAVAudioFile) child, endSnd attribute, tooltip attribute, tgtFrame attribute; CT_EmbeddedWAVAudioFile element class with @r:embed and @name - src/pptx/oxml/__init__.py: registration for a:snd - src/pptx/shapes/base.py: alt_text, alt_title, is_decorative, is_hidden_from_accessibility properties on BaseShape - src/pptx/shapes/picture.py: hyperlink property - src/pptx/text/text.py: Run.hyperlink.tooltip, Run.hyperlink.color, Run.click_action (parallel to Shape.click_action) - tests/test_issue21_hyperlinks.py: 38 new unit tests covering every sub-feature - features/iss-21-hyperlinks-clickactions.feature: 9 acceptance scenarios (run ScreenTip, alt_text round-trip, run hyperlink color, run-macro, play-sound, hover action, run jump-to-slide, picture hyperlink, tooltip-only XML round-trip) - features/steps/iss21.py: behave step implementations - .gitignore: .DS_Store (macOS Finder noise) Tests: 4021 passed (was 3983 before #21; +38 new). Lint: ruff format + check clean. Behave: 1139 scenarios, 0 failed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #21.
Unified hyperlinks + click/hover actions + accessibility surface, across runs / shapes / pictures.
Sub-features
Run.hyperlink.tooltipRun.hyperlink.color(closes Set/change font color when working with Hyperlinks is impossible scanny/python-pptx#940, How to change the color of hyperlink text? scanny/python-pptx#821)ActionSetting.run_macro,target_program,play_soundShape.hover_actionparallel toclick_actionRun.click_action.target_slide = ...Picture.hyperlink(address, tooltip, color)alt_text,alt_title,is_decorative,is_hidden_from_accessibilityActionSetting/HyperlinkcorePowerPoint-compatibility load-bearing notes
These were each verified by Repair-dialog testing in PowerPoint:
a:hlinkClick/a:hlinkHoverwe create getsr:id=""by default. Without it, PowerPoint's load-time validator strips the element even though ECMA-376 marks@r:idoptional. Real PowerPoint output always carriesr:id(empty when no relationship), matching the precedent already inoxml/shapes/picture.py.action=\"ppaction://noaction\"to match real PowerPoint's own emission.play_soundusesRT.AUDIO(ECMA-376relationships/audio), NOTRT.MEDIA— the Microsoft-2007relationships/mediarel undera:snd/@r:embedtriggers a Repair dialog and strips the entire hlinkClick.Known limitation — no-click-action hover ScreenTip not rendered
PowerPoint does not surface a hover ScreenTip in slideshow mode for shapes that have no click action. This applies to both paths:
cNvPr/a:hlinkClick/@tooltip(no nav target)cNvPr/@descr(shape.alt_text)PowerPoint's slideshow runtime only activates hover-ScreenTip rendering when the hyperlink carries a real navigation target (URL, slide jump, macro, program). There is no known fully-supported OOXML workaround.
Mitigations:
alt_textremains useful for accessibility/screen-reader text, which is its primary purpose.Hyperlink.tooltip,ActionSetting.tooltip,Run.hyperlink.tooltip) document this limitation.This limitation is shipped as-is rather than hidden — the API is honest about what PowerPoint will and won't render.
Verification
Baseline before #21: 3983 unit tests, 1130 behave scenarios. Net add: +38 unit tests, +9 behave scenarios.
UAT (per CLAUDE.md §6a — maintainer signoff)
uat/uat_issue21_hyperlinks.pybuilds a 2-slide deck exercising every sub-feature with byte-level round-trip assertions (17/17 PASS). Visual sign-off done by maintainer:Closes #21.