charWidth() and non-monospace fonts

I did end up coming with a solution to measure the position of a column in a line from renderLine and without relying on the defailtCharWidth. I don’t know how good it is but it works so I’ll post it here so others can use it maybe.

Basically I cut renderLine’s elt.innerHTML of at the given position preserving any tags. I then put that html in a separate measurement div and measure it (I tried to reuse cm.display.measure but it didn’t work). A caching can be added to improve performance:

  var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
    lineNumbers: true,
    lineWrapping: true,
    mode: "text/html"
  });

  function htmlOffset(html, pos) {
    
    function cutHTML(html, pos) { // cut html text at pos preserving all tags
      var tag=false, symbol=false; res='', count=0;

      for(var i=0; i<html.length; i++ ) {
          if(html[i] === '<') tag = true;
          if (tag || count<pos) res += html[i];

          if (!tag) {
            if (html[i] === '&') symbol=true;
            if (symbol && html[i] === ';') symbol=false;
            if (!symbol) count++; // note this counts the ';' of a symbol
          }

          if(html[i] === '>') tag = false;
      }
      return res;
    }

    var span=document.getElementById('htmlOffsetMeasurementSpan');
    if (!span) { // create a 'dummy' span as the last child of 'CodeMirror'
      span=document.createElement('span');
      span.setAttribute("id", "htmlOffsetMeasurementSpan");
      span.style.cssText = 'border:0;padding:0;'; // offsetWidth includes padding and border, explicitly override the style:
      document.getElementsByClassName('CodeMirror')[0].appendChild(span);
    }

    // measure offset
    span.innerHTML = cutHTML(html, pos).split(' ').join('\xA0'); // replace spaces with hard spaces
    span.style.display = 'inline'; // show
    var prefixWidth=span.offsetWidth;
    span.style.display = 'none'; // hide
    return prefixWidth;
  }

  var basePadding = 4;
  
  editor.on("renderLine", function(cm, line, elt) {
    var offstCol=CodeMirror.countColumn(line.text, null, cm.getOption("tabSize"));
    var prefixWidth=htmlOffset(elt.innerHTML, offstCol);

    elt.style.textIndent = "-" + prefixWidth + "px";
    elt.style.paddingLeft = (basePadding + prefixWidth) + "px";
  });

  editor.refresh();