Skip to content

feat(svg): let an SVG icon state the text it stands for - #697

Merged
DemchaAV merged 1 commit into
2.5-devfrom
feat/emoji-copy-text
Sep 18, 2026
Merged

DemchaAV merged 1 commit into
2.5-devfrom
feat/emoji-copy-text

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

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 an SvgIcon and 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. withText returns a copy sharing the layers and frame; null or 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 every svgIcon(icon, size) / inlineSvgIcon(icon, size) overload carries it with no new overloads — a String overload next to svgIcon(icon, size, alignment) would also have made svgIcon(icon, size, null) ambiguous.
  • EmojiLibrary sets 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 from Character.isEmoji / isEmojiPresentation; Java 17 has neither, so the table cannot be computed at runtime.
  • No emoji-set release needed: the published graph-compose-emoji 1.0.0, which the core tests resolve against, yields the same text.

Verification

  • Full CI reactor gate (./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 give null. 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, withText copies and leaves the original unchanged, null/blank clear, text is kept verbatim.
  • Knowledge pack regenerated (authoring: +text(), +withText(String)).

Notes

  • @since 2.5.0 — this adds public API while the in-progress CHANGELOG heading reads v2.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.

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.
@DemchaAV
DemchaAV changed the base branch from develop to 2.5-dev September 18, 2026 10:28
@DemchaAV
DemchaAV merged commit 6ceaeee into 2.5-dev Sep 18, 2026
12 checks passed
@DemchaAV
DemchaAV deleted the feat/emoji-copy-text branch September 18, 2026 11:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant