Skip to content

feat: replace render-time text wrapping with browser-native wrapping#191

Open
hyperfinitism wants to merge 1 commit intostats-organization:masterfrom
hyperfinitism:fix/use-foreign-obj
Open

feat: replace render-time text wrapping with browser-native wrapping#191
hyperfinitism wants to merge 1 commit intostats-organization:masterfrom
hyperfinitism:fix/use-foreign-obj

Conversation

@hyperfinitism
Copy link
Copy Markdown

@hyperfinitism hyperfinitism commented Apr 27, 2026

This PR uses foreignObject with CSS line-clamp to let the browser handle text wrapping natively instead of manually wrapping on the server. This provides better font-aware wrapping while keeping server-side line estimation for SVG height calculation.

Fixes anuraghazra#4862.

Example card

hyperfinitism-sample-repo

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 27, 2026

@hyperfinitism is attempting to deploy a commit to the martin-mfg's projects Team on Vercel.

A member of the Team first needs to authorize it.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
github-stats-extended-backend Ready Ready Preview, Comment Apr 28, 2026 9:43am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
github-stats-extended-frontend Ignored Ignored Apr 28, 2026 9:43am

Copy link
Copy Markdown
Member

@martin-mfg martin-mfg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR!

Please ignore the failing "Backend E2E test" pipeline. It determined that this PR generates different output than the master branch, which is expected here.

Currently the changes in this PR lead to visual changes in generated cards regardless of line wrapping. Especially the line height is increased. Could you please ensure there are no visual changes apart from the line wrapping? Maybe it makes sense to simply copy the HTML code which GitHub itself uses in the original repo pins on user profiles?

Doing this would also raise the question if it makes sense to use foreignObject for the whole content of gist/pin cards. I.e. not only the description, but also the title, star count, etc. What do you think?

Comment thread packages/core/src/cards/repo.js Outdated
Comment thread packages/core/src/cards/repo.js
: ""
}

<text class="description" x="${X_OFFSET}" y="-5">
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does removing the "-5" here not change the layout of the final card?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly speaking it does; the new position depends on the browser's HTML/font metrics rather than the SVG baseline, so the first-line baseline can drift by about < 1px. In practice the visual top of the description lines up at the same place and the appearance is nearly identical.

Comment thread packages/core/src/common/render.js Outdated
return 1;
}

const spaceWidth = measureText(" ", fontSize);
Copy link
Copy Markdown
Member

@martin-mfg martin-mfg Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently the code assumes that only singular, normal whitespaces are used. Could you please make it work with arbitrary whitespace?

You can use e.g. this repo description for testing:

"One                 two three four five six seven eight nine ten."

It contains different whitespace characters of different widths and I chose the length so that it actually wraps to 2 lines, but the current logic determines this will only be 1 line.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. countWrappedLines now tokenizes into alternate words/whitespaces and measures each whitespace at its rendered width (ASCII whitespace collapsed to a single space per CSS white-space: normal; non-ASCII whitespace preserved, with U+3000 special-cased to 1 em since the width table is ASCII-only).

This is still an approximation: measureText reads from a fixed ASCII-only width table for a specific font and ignores kerning and other factors. More precise widths for all characters would require hard-coding a large (char, width) table, which felt out of scope. The estimate may still be off by a few percent for non-ASCII-heavy text, but it's used only to size the SVG; the browser does the real wrap inside the foreignObject.

@hyperfinitism hyperfinitism force-pushed the fix/use-foreign-obj branch 2 times, most recently from f9af708 to 0c6113b Compare April 28, 2026 14:16
Use `foreignObject` with CSS line-clamp to let the browser handle text
wrapping natively instead of manually wrapping on the server.
This provides better font-aware wrapping while keeping server-side line
estimation for SVG height calculation.

Signed-off-by: Takuma IMAMURA <209989118+hyperfinitism@users.noreply.github.com>
@hyperfinitism
Copy link
Copy Markdown
Author

Doing this would also raise the question if it makes sense to use foreignObject for the whole content of gist/pin cards. I.e. not only the description, but also the title, star count, etc. What do you think?

While this approach maintains the greatest visual consistency, it has the following problems:

  • A core feature of github-readme-stats is letting users override title_color, text_color, bg_color, presets, etc. With our own SVG markup we own every fill/color and substitute cleanly. When using GitHub templates, you need to completely redesign the theme.
  • The current cards are SVG with inlined Octicons paths, so they render identically anywhere, e.g. offline previews. Pulling in GitHub-hosted icons or HTML scaffolds would make the cards depend on GitHub's CDN and DOM structure and break those uses.
  • Using foreignObject for a single multi-line text node is a small, contained departure from pure SVG. Embedding GitHub's full HTML pin would, in effect, make this an HTML-snippet generator rather than an SVG generator. This would be a fairly fundamental change.

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.

Repository Pin/Gist Card Description Overflow

2 participants