Home > Net >  How to adjust kerning for the unicode flat symbol
How to adjust kerning for the unicode flat symbol

Time:12-30

I'm trying to describe a chord, B♭, but it is showing up weird in an <option> HTML tag. Instead of "B♭", it shows up with an apparent space between the "B" and the "♭".

I tried applying a CSS style, letter-spacing, to this chord in a <span> tag, but apparently, this doesn't control kerning.

Interestingly, when I select the chord from the dropdown, my OS renders the kerning correctly, but after it is selected, the flat symbol shows up by itself, set aside by a space, like the following.

unusual kerning between a letter and a flat symbol

The JS literal that I'm trying to render is,

'B\u266D'

Using the literal 'B&flat;' just renders as 'B&flat;' in the dropdown.

FWIW, I'm doing this from within Vue3, and looking at the output in Chrome, on a Mac.

CodePudding user response:

This seems to be a rendering issue in Chrome 96 on macOS (and possibly other Chromium-based browsers and other operating systems). Firefox 91 on macOS renders it correctly. The problem also only affects the flat (\u266d) and natural (\u266e) signs. The sharp, double sharp, and double flat signs are unaffected in my tests.

You could workaround the issue by adjusting the <select>'s letter-spacing with a negative value (to bring the characters closer together, thus reducing the gap), and compensating for the reduced width with padding on the right. You'd only want to do this conditionally for the affected signs, so use a computed prop that returns the necessary letter-spacing and padding:

  1. Use a v-model on the <select> to capture the selected note.

  2. Use a computed prop that returns the styles needed to adjust letter-spacing and padding. When the selected note contains the problematic signs, return the adjusted spacing/padding.

  3. Bind the computed style to the <select>'s styles.

<script setup>
const notes = [
  'B\u266d', // flat
  'B\u266e', // natural
  'B\u266f', // sharp
  'B\u{1D12B}', // double flat
  'B\u{1D12A}', // double sharp
]
1️⃣
let selectedNote = $ref(notes[0])

2️⃣
let styles = $computed(() => {
  if (selectedNote.includes('\u266d') || selectedNote.includes('\u266e')) {
    return {
      'letter-spacing': '-20px',
      'padding-right': '40px',
    }
  }
})
</script>

<template>
  <select 1️⃣ v-model="selectedNote" :style="styles" 3️⃣>
    <option v-for="note in notes">{{ note }}</option>
  </select>
</template>

Note: The <script setup> above uses the new/experimental Reactivity Transform, which supports globally defined reactivity APIs ($ref, $computed, etc.) and removes the need to unwrap refs.

demo

Result on Chrome 96 on macOS:

  • Related