feat(svg): let an SVG icon state the text it stands for - #697
Merged
Merged
Conversation
A colour emoji is drawn from SVG paths, so the page has no character where it sits: copying a line with 🚀 out of the PDF and pasting it into a messenger loses the emoji. The engine cannot fix that at render time because the text is gone before it gets there: RichText.emoji resolves the shortcode to an SvgIcon and keeps only the drawing. SvgIcon.withText(String) returns a copy of the icon carrying the text it stands for, read back with SvgIcon.text(). EmojiLibrary sets it on every icon it resolves, spelled from the glyph key. The key drops U+FE0F because Noto's file names do, and without it a text-default emoji such as U+2764 can paste as a plain black symbol. EmojiSequences restores the fully-qualified form of UTS #51: U+FE0F after every character whose default presentation is text, unless a skin-tone modifier or a selector already follows. The 219 such characters are held as a table generated from the JDK's Unicode data; the published graph-compose-emoji 1.0.0 set resolves to the same text, so no emoji-set release is needed. Nothing renders differently yet. The PDF backend reads the text in a following change. EmojiSequencesTest pins the spelling (plain, text-default, ZWJ, skin tone, keycap, existing selector, malformed keys) and compares the table with Character.isEmoji / isEmojiPresentation on JDK 21+ (it ran on JDK 24: 219 codepoints, identical on JDK 23). EmojiLibraryTest checks :rocket:, :heart: and :woman_technologist: carry their text and that a glyph named by anything but codepoints resolves without one; SvgIconTextTest checks withText copies and leaves the original as it was.
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.
Why
A colour emoji is drawn from SVG paths, so the page has no character where it sits: a line with
:rocket:in it, copied out of a PDF into a messenger, arrives without the rocket. The renderer cannot put the emoji back, because it never sees it —RichText.emoji(":rocket:")resolves the shortcode to anSvgIconand keeps only the drawing. The text has to travel with the icon first; the PDF backend writes it in a following PR.What changed
SvgIcon.text()/SvgIcon.withText(String)(@since 2.5.0) — the text an icon drawn inline stands for when the page is read as text.withTextreturns a copy sharing the layers and frame;nullor blank clears it. A block icon (addSvgIcon,SvgIcon.node) does not carry it. The text lives on the icon rather than on the run, so a custom icon states it once (SvgIcon.read(path).withText("✓")) and everysvgIcon(icon, size)/inlineSvgIcon(icon, size)overload carries it with no new overloads — aStringoverload next tosvgIcon(icon, size, alignment)would also have madesvgIcon(icon, size, null)ambiguous.EmojiLibrarysets the text on every icon it resolves, spelled from the glyph key.EmojiSequences(package-private) restores what the key drops. Keys follow Noto's file names, which omit U+FE0F; without it a text-default emoji such as U+2764 can paste as a plain black symbol. The text is the fully-qualified form of UTS docs: reorganise into categorised folders + real docs index + link audit #51: U+FE0F after every character whose default presentation is text, unless a skin-tone modifier or a selector already follows. The 219 such characters are a sorted table generated fromCharacter.isEmoji/isEmojiPresentation; Java 17 has neither, so the table cannot be computed at runtime.graph-compose-emoji1.0.0, which the core tests resolve against, yields the same text.Verification
./mvnw -B -ntp clean verify -pl <the 10 CI modules> -am) → BUILD SUCCESS, 13 modules, 3246 tests, 0 failures../mvnw -P japicmp verify -pl :graph-compose-core→ BUILD SUCCESS (additions only).javadoc:javadoc -pl :graph-compose-core→ BUILD SUCCESS, 0 warnings.EmojiSequencesTest(9) pins the spelling: rocket plain, heart and © gain FE0F, ZWJ sequences (woman technologist, man health worker, eye in speech bubble), a skin-tone modifier suppresses the selector, a keycap base is qualified before U+20E3, an existing FE0F/FE0E is not doubled, malformed keys givenull. It also compares the table with the running JDK's Unicode data on JDK 21+ — ran on JDK 24 (219 codepoints, identical on JDK 23); CI's 21 and 25 legs run it too.EmojiLibraryTest+2::rocket:,:heart:,:woman_technologist:carry their text; a glyph named by anything but codepoints resolves and draws without text.SvgIconTextTest(4): parsed icons state no text,withTextcopies and leaves the original unchanged, null/blank clear, text is kept verbatim.authoring: +text(), +withText(String)).Notes
@since 2.5.0— this adds public API while the in-progress CHANGELOG heading readsv2.4.1 — Planned; the next cut is a minor under semver.Lane: canonical (
document.svg,document.emoji) — no render change in this PR.Merge order: first; the PDF text layer (stacked PR) builds on it.