Monospace font characters are not fixed width
Asked Answered
J

2

6

I'm trying to align some characters to draw a box in html. I've picked a monospace font so that characters are aligned, and drew the box with an equal number of characters for each line.

pre {
  font-family: 'Roboto Mono';
  white-space: pre;
}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap" rel="stylesheet">
<pre>
    ╭─────────────────────────────────────────────────────────────────────────╮
    │.........................................................................│
    │-------------------------------------------------------------------------│
    │                                                                         │
    ╰─────────────────────────────────────────────────────────────────────────╯                         
</pre>

When I render this in the browser though, the rightmost edge can sometimes be misaligned depending on the monospaced font used.

When I use Roboto Mono:

misaligned box roboto mono

When I use Space Mono:

misaligned box space mono

For some reason, my monospace characters aren't monospaced. Why is this happening, and how do I enforce monospace in order to align characters?

Jot answered 21/1, 2022 at 6:19 Comment(2)
Browsers will use fallback fonts for characters that don't exist in the preferred font. The period and hyphen characters likely exist in your desired fonts, but do those graphical characters exist in those fonts or do they only exist in the fallback font?Parsaye
yeah that's it! not all characters use the same font. I had overlooked this since I had tested it out first by rendering the box in a terminal and my text editor with the fonts used, and it was all monospaced correctly. I guess both my terminal and text editor are doing some extra work to make sure all characters are aligned?Jot
P
9

tl;dr: Google's font CDN isn't serving the fonts in their fullness. Download the zip they offer and host the fonts yourself.


Google's font CDN doesn't seem to be providing the full character range of some fonts. For example, Google's @import is:

@import url('https://fonts.googleapis.com/css2?family=Fira+Code&display=swap');

Looking at the CSS inside:

/* cyrillic-ext */
@font-face {
  font-family: 'Fira Code';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/firacode/v14/uU9eCBsR6Z2vfE9aq3bL0fxyUs4tcw4W_D1sJV37Nv7g.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Fira Code';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/firacode/v14/uU9eCBsR6Z2vfE9aq3bL0fxyUs4tcw4W_D1sJVT7Nv7g.woff2) format('woff2');
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* more chunks like this omitted */

So the font is broken up into several files and they use unicode-range to map glyphs to specific files.

These are all the ranges they provide in that file:

unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
unicode-range: U+1F00-1FFF;
unicode-range: U+0370-03FF;
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;

The range for the box drawing characters is U+2500-257F which doesn't show up in the above list.

I tried adding them in but, as far as I can tell, the served files do not include that range.

However, you can download the original font files from Google (as a zip of .ttf files). Converting one of those font files into a data url and using it does render the glyphs correctly:

pre {
  font-family: 'Fira Code stripped';
}
@font-face {
  font-family: 'Fira Code stripped';
  src: url('data:application/octet-stream;base64,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');
}
<pre>
╭────────────────────────────────────────────╮
│............................................│
│--------------------------------------------│
│                                            │
╰────────────────────────────────────────────╯
</pre>

NOTE: In order to fit this example into an answer, I've stripped the font down to just the 9 characters needed to render the sample and converted it to woff2. External testing with the full ttf file produces the same results.

I conclude that Google simply isn't serving the complete font via their CDN.

Parsaye answered 23/1, 2022 at 3:46 Comment(3)
yep. this makes sense! I was offline for a while, so I downloaded the font locally instead of using google fonts to continue debugging this issue, and all of a sudden everything was lining up. really appreciate the detailed explanation here!Jot
I love you! Spent the entire day trying to figure this out. Countless stack exchange and git hub issues viewed until I finally came upon this.Peanut
When I wrote this answer, I did submit a bug report on their github repo. Since they haven't gotten around to fixing it, here's the link.Parsaye
M
4

Go to Google Fonts and write your character used for border. There will be shown empty glyph, so that means font does not have glyphs for border (top and bottom) and browser uses fallback font.

enter image description here

So you have few options:

  1. Use only monospace font
  2. Find font that supports all of your characters
  3. Use CSS to actually draw elements and not "ASCII-art"

pre {
  font-family: monospace;
  white-space: pre;
}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap" rel="stylesheet">
<pre>
    ╭─────────────────────────────────────────────────────────────────────────╮
    │.........................................................................│
    │-------------------------------------------------------------------------│
    │                                                                         │
    ╰─────────────────────────────────────────────────────────────────────────╯                         
</pre>
Marlo answered 21/1, 2022 at 6:59 Comment(11)
yeah that's it—like Ouroborus said above—not all characters use the same font. I had overlooked this since I had tested it out first by rendering the box in a terminal and my text editor with the fonts used, and it was all monospaced correctly. I guess both my terminal and text editor are doing some extra work to make sure all characters are aligned? If the terminal and text editor can do it, can we also do the same thing in browser? I can think of wrapping every single character in <pre>—but that seems excessiveJot
@PauloCosta Have you checked my edit? font-family: monospace does it's job correctlyMarlo
@Marlo Sort of. In Firefox, on Window 10, it still appears to be using two different fonts even though you specified just monospace. i.imgur.com/Y4WrNfW.pngParsaye
like @Parsaye mine is a bit misaligned as well (Chrome on macOS). additionally terminals and text editors are doing something that is allows the user to use fonts with partial character coverage—yet still keep things aligned. For example this is Roboto Mono on my terminal. was wondering if we could replicate what it's doing in htmlJot
@PauloCosta The main issue is fonts. Pick a font that contains all the characters you intend to use. For example, this font contains all the ASCII and extended ASCII characters. That aside, terminals (and certain text editors) force a fixed width and height for every character. There isn't a direct way to do that in a browser. An indirect way is wrap every character in a <span> and style the spans as inline-block with fixed width and height.Parsaye
@Parsaye I tried to go with a font with high character coverage, but it's still misaligned. I tried Fira Code and Noto Sans Mono, and both fira and noto were misalignedJot
@Parsaye codepen for easy viewing of above issue. Thanks so far by the way for all of the guidance!Jot
I suspect this is a bug with the font, specifically with the "Box Drawings Light Arc" characters. If you use the square corners, they match up. A lot of the Google fonts have github repos and, searching their issues, a number of them had or have issues with the rounded corners. I'd say, at this point, your options are to find a font where this has been fixed or to submit an issue to their repo and wait for them to fix it.Parsaye
@Parsaye i've tried three monospaced fonts that include the characters—fira code, noto sans mono, and jetbrains mono. we sure this is a font-level solution? or am i just choosing the wrong fonts here? it seems unlikely that all of them would have the same bug.Jot
Those fonts seem to be using the same glyphs for the box drawing range. I suspect they either copied those glyphs from each other or from yet another font. If you're on Windows and use consolas, everything lines up. DejaVu Sans Mono (home, CDN, permissive license) works correctly as well.Parsaye
Alright, I think I pinned down what the problem really is. See my answer.Parsaye

© 2022 - 2024 — McMap. All rights reserved.