htmlcsscombining-marks

Font-coloring Combining Characters without de-combining from the base glyph


I'm trying to build a little system which highlights combining characters in a different color than regular characters. Take the following example:

* { font-size: 72px }
b { font-weight: normal; color: red }
Te&#x301;st A&#x334; B&#x353; <br/>
Te<b>&#x301;</b>st A<b>&#x334;</b> B<b>&#x353;</b>

I'd like the three combining characters (acute accent, tilde overlay, and x below) to be font-colored red, but remain precisely where they were if in the original text (rather than de-concatenating to the side). The problem is, when I wrap a combining character in an HTML element, it is no longer 'attached' to the base character and instead inline with the rest of the text.

Is there any way to accomplish this with HTML / CSS?

Note: I have reviewed the answers here and here, but they all seem to only attack the problem 'geometrically'—that is they highlight the part of a character within a certain region. This question is specifically about highlight the 'typographical' aspects of the combining characters.


Solution

  • I'm revisting this question almost a year later, and I've figured out a more satisfactory (though far more verbose) solution using SVG. Basically, this is the similar to my previous HTML/CSS-based version, but SVG gives you the power to do clip / mask out the anti-aliased edges of the underlying base character.

    The only really problem remaining is how to handle overlay characters (where the combining character is rendered directly on top of the underlying character). In this case you'd either need to render the base character on top of the overlay (not preferable in my use case) or render the overlay on an placeholder white space character which will not necessarily perfectly match the width of the base character. Here's a demonstration:

    svg text {
      x: 50px;
      y: 50px;
      alignment-baseline: middle;
      text-anchor: middle;
      font-size: 55px;
    }
    svg .backdrop {
      x: 1px;
      y: 1px;
      rx: 15px;
      ry: 15px;
      width: 98px;
      height: 98px;
      fill: url(#grad);
    }
    svg .cc-above { fill: #F00; }
    svg .cc-below { fill: #00F; }
    svg .cc-overlay { fill: #0FF; }
    svg .cc-base { fill: #000; }
    svg .cc-mask { stroke: #000; stroke-width: 3px; }
    .sample { float: left; }
    .caption { display: block; text-align: center; }
    <div class="sample">
      <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="100px" height="100px" viewBox="0 0 100 100">
        <defs>
          <text id="base" x="50" y="50">e</text>
          <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
            <stop offset="0%" style="stop-color:#DDD;stop-opacity:1" />
            <stop offset="100%" style="stop-color:#888;stop-opacity:1" />
          </linearGradient>
        </defs>
        <rect class="backdrop" />
        <mask id="mask1">
          <rect x="0" y="0" width="100%" height="100%" fill="#fff" />
          <use xlink:href="#base" class="cc-mask" />
        </mask>
        <text class="cc-above" x="50%" y="50%" mask="url(#mask1)">e&#x0300;</text>
        <use xlink:href="#base" class="cc-base" />
      </svg>
      <div class="caption">Above</div>
    </div>
    
    <div class="sample">
      <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="100px" height="100px" viewBox="0 0 100 100">
        <defs>
          <text id="base" x="50" y="50">e</text>
          <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
            <stop offset="0%" style="stop-color:#DDD;stop-opacity:1" />
            <stop offset="100%" style="stop-color:#888;stop-opacity:1" />
          </linearGradient>
        </defs>
        <rect class="backdrop" />
        <mask id="mask1">
          <rect x="0" y="0" width="100%" height="100%" fill="#fff" />
          <use xlink:href="#base" class="cc-mask" />
        </mask>
        <text class="cc-below" x="50%" y="50%" mask="url(#mask1)">e&#x031F;</text>
        <use xlink:href="#base" class="cc-base" />
      </svg>
      <div class="caption">Below</div>
    </div>
    
    <div class="sample">
      <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="100px" height="100px" viewBox="0 0 100 100">
        <defs>
          <text id="base" x="50" y="50">e</text>
          <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
            <stop offset="0%" style="stop-color:#DDD;stop-opacity:1" />
            <stop offset="100%" style="stop-color:#888;stop-opacity:1" />
          </linearGradient>
        </defs>
        <rect class="backdrop" />
        <mask id="mask1">
          <rect x="0" y="0" width="100%" height="100%" fill="#fff" />
          <use xlink:href="#base" class="cc-mask" />
        </mask>
        <use xlink:href="#base" class="cc-base" />
        <text class="cc-overlay" x="50%" y="50%">&#x2002;&#x0334;</text>
      </svg>
      <div class="caption">Overlay</div>
    </div>
    
    <div class="sample">
      <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="100px" height="100px" viewBox="0 0 100 100">
        <defs>
          <text id="base" x="50" y="50">e</text>
          <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
            <stop offset="0%" style="stop-color:#DDD;stop-opacity:1" />
            <stop offset="100%" style="stop-color:#888;stop-opacity:1" />
          </linearGradient>
        </defs>
        <rect class="backdrop" />
        <mask id="mask1">
          <rect x="0" y="0" width="100%" height="100%" fill="#fff" />
          <use xlink:href="#base" class="cc-mask" />
        </mask>
        <text class="cc-above" x="50%" y="50%" mask="url(#mask1)">e&#x0300;</text>
        <text class="cc-below" x="50%" y="50%" mask="url(#mask1)">e&#x031F;</text>
        <use xlink:href="#base" class="cc-base" />
        <text class="cc-overlay" x="50%" y="50%">&#x2002;&#x0334;</text>
      </svg>
      <div class="caption">All</div>
    </div>

    I don't have all that much experience with SVG, and someone that does may be able to find a way to improve upon this solution further.