Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrong spacing in thumbnail/embed SVG with Consolas font #265

Open
lionel-rowe opened this issue Aug 24, 2024 · 4 comments · May be fixed by asciinema/asciinema-server#450
Open

Wrong spacing in thumbnail/embed SVG with Consolas font #265

lionel-rowe opened this issue Aug 24, 2024 · 4 comments · May be fixed by asciinema/asciinema-server#450

Comments

@lionel-rowe
Copy link

lionel-rowe commented Aug 24, 2024

Edit: I realized this issue is in the wrong repo, feel free to move to asciinema/asciinema-server. PR at asciinema/asciinema-server#450


Describe the bug

With this recording: https://asciinema.org/a/XxTbS0aAtTA6BZ9tdzKardxJs

The recording itself (including the freeze frame of the frame used for the thumbnail) looks perfect, stunning, gorgeous.

However, the spacing is off in the SVG used for the thumbnail/embed image when displayed on Windows 11 with the Consolas font installed (from examining the SVG, the font stack is "Consolas, Menlo, 'Bitstream Vera Sans Mono', monospace, 'Powerline Symbols'").

Changing the font to Courier New fixes the problem (but it also makes it kinda ugly cuz... Courier New). Changing the font size to 15.3px (magic number) also seems to fix it, but presumably would break the other fonts.

Screenshots:

Description Screenshot
Freeze frame of the recording itself Screenshot of a freeze frame of the recording itself
SVG with Consolas font installed (broken spacing) Screenshot of SVG with Consolas font installed
SVG with the font manually changed to Courier New (fixed but ugly) Screenshot of SVG with font changed to Courier New
SVG with the font size manually changed to 15.3px (fixed but magic number) Screenshot of SVG with font changed to Courier New

Direct link to the SVG

To Reproduce
Steps to reproduce the behavior:

  1. Create a recording containing various ANSI code spans of different colors/font weight/etc
  2. View the thumbnail with the Consolas font installed

There are prominent examples at https://asciinema.org/explore that also reproduce the problem, e.g. https://asciinema.org/a/666780

Expected behavior

Thumbnail to look identical (or near-identical) to screenshotted freeze frame, just crisper due to being an SVG.

Versions:

  • OS: WSL with Ubuntu 22.04.4 LTS
  • asciinema cli: asciinema 2.1.0

Additional context
N/A

@lionel-rowe lionel-rowe changed the title Wrong spacing in thumbnail/embed SVG Wrong spacing in thumbnail/embed SVG with Consolas font Aug 24, 2024
@lionel-rowe
Copy link
Author

lionel-rowe commented Aug 24, 2024

Here's a hack that "fixes" such an SVG in any font (heck, it even works passably for most variable-width fonts), by means of wrapping each char in its own span:

  1. Open SVG in browser
  2. Run script in dev tools
  3. Copy XML of the SVG element (using outerHTML) and save it in an .svg file

Script:

for (const tspan of document.querySelectorAll('tspan')) {
    if (tspan.querySelector('*')) continue

    const tspans = [...tspan.textContent.trim()].map((char, i) => {
        const t = tspan.cloneNode(true)
        t.textContent = char

        let x = parseFloat(tspan.getAttribute('x'))
        x += i * 8.4

        if (i) t.removeAttribute('dy')
        t.setAttribute('x', x)

        return t
    })
    tspan.replaceWith(...tspans)
}

Limitations:

  • Increases file size quite a bit (in my case 11 kB -> 41 kB)
  • For fonts that have the wrong intrinsic size, such as Consolas, the spacing looks wider or narrower than normal
  • Doesn't handle "Unicode width" for non-ASCII chars (but could be made to do so with the use of something like Deno @std's cli/unicode-width)

Results applied to my thumbnail:

image

And here are the results with the font changed to the decidedly non-monospace Papyrus:

image

@lionel-rowe
Copy link
Author

lionel-rowe commented Aug 25, 2024

Less hacky fix: add a @font-face at-rule targeting Consolas inside the <style> tag:

@font-face {
    font-family: "Consolas";
    src: local("Consolas");
    size-adjust: 109.5%;
}

As before, 109.5 is a magic number (109.5% of 14px ~= 15.3px).

Works perfectly in latest Chrome, Firefox, Edge, but notably fails (has no effect) if you open the SVG in Inkscape.

Result:

Font size-adjusted version

@lionel-rowe
Copy link
Author

lionel-rowe commented Aug 25, 2024

OK, here's a third fix, much better and less hacky than either of the others.

The x property of <tspan>s can accept a space-separated list of numbers, rather than just a single scalar number, with each number corresponding to one glyph. This feature is supported even in SVG 1.1, so it has very wide support.

In addition, CSS font-size-adjust is now "newly available" in baseline, meaning it has decent (but not completely ubiquitous) browser support.

  • We use the space-separated x property to manually place each glyph (but without the need for additional elements, saving on file size and presumably rendering performance too).
  • Then, we also add a font-size-adjust of ch-width 0.6, meaning that (at font size 14 in a monospace font) each char is guaranteed to be 14 * 0.6 = 8.4 px wide. If this feature is unavailable, the result is a little less pretty (spacing wider/narrower than normal, as noted above) but still has proper column placement of chars, so it's still a passable result.

Tested in latest Chrome, FF, Edge, and it even looks fine in Inkscape (despite no font-size-adjust support). I found the second solution above suffers from a rendering bug in FF wherein zooming in and out sometimes messes up the layout, but this solution doesn't have that problem.

Also tested on an asciicast with different Unicode-width chars — no issues there either, as asciinema already renders each char with Unicode-width other than 1 in its own span, so they just stay where they are.

File size increase in my case is 10.2 -> 13.1 kB, though I cheated a little by rounding each x value to 1 decimal place (no visual difference).

Dev-tools-based demo:

document.querySelector('svg style').textContent += `\n text { font-size-adjust: ch-width 0.6; }`

// https://github.com/asciinema/asciinema-server/blob/fbbb2ce35272d516ce2a6debfbc04c9a7c60a149/lib/asciinema_web/controllers/recording_svg.ex#L399
const CELL_WIDTH = 8.42333333

for (const tspan of document.querySelectorAll('tspan')) {
    if (tspan.childElementCount) continue

    const offset = parseFloat(tspan.getAttribute('x'))

    const xPerGrapheme = [...new Intl.Segmenter('en-US', { granularity: 'grapheme' }).segment(tspan.textContent.trim())].map((_, i) => {
        return offset + (i * CELL_WIDTH)
    })

    tspan.setAttribute('x', xPerGrapheme.map((x) => x.toFixed(1)).join(' '))
}

@lionel-rowe
Copy link
Author

lionel-rowe commented Sep 6, 2024

In addition, CSS font-size-adjust is now "newly available" in baseline, meaning it has decent (but not completely ubiquitous) browser support.

Turns out it breaks in Chrome when a width or max-width is applied to the img element in which the SVG is rendered (issue) So unless there's a workaround, the least-worst option is probably the same fix but without the font-size-adjust bit.

@ku1ik ku1ik transferred this issue from asciinema/asciinema Sep 16, 2024
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 a pull request may close this issue.

1 participant