dirty-variables
clone your own copy | download snapshot

Snapshots | iceberg

Inside this repository

inspector.v3.js
application/javascript

Download raw (5.3 KB)

var font;

class Font {
  constructor(xml) {
    this.xml = xml;
    let parser = new DOMParser();
    this.dom = parser.parseFromString(xml, 'text/xml');
  }

  table(name) { return this.dom.querySelector(name) };

  glyphs() { return this.table('glyf').querySelectorAll('TTGlyph'); };

  glyph(name) { return this.table('glyf').querySelector(`TTGlyph[name="${name}"]`); };

  variations() { return this.table('fvar'); };

  glyphVariations() { return this.table('gvar').querySelectorAll('glyphVariations'); };

  glyphVariation(name) { return this.table('gvar').querySelector(`glyphVariations[glyph="${name}"]`); };

  delta(name, point) { return this.glyphVariation(name).querySelectorAll(`delta[pt="${point}"]`); };

  contours(name) { return this.glyph(name).querySelectorAll('contour'); };
}

function deriveCurvePoint(p1, p2) {
  return {
    'x': p1.x + .5 * (p2.x - p1.x),
    'y': p1.y + .5 * (p2.y - p1.y),
    'on': false,
    'derived': true
  };
}

function drawGlyph(name, font) {
  var canvas = document.getElementById('canvas'),
    ctx = canvas.getContext('2d');

  ctx.clearRect(0, 0, canvas.width, canvas.height);

  var glyph = font.glyph(name);

  var xmin = glyph.getAttribute('xMin'),
    xmax = glyph.getAttribute('xMax'),
    ymin = glyph.getAttribute('yMin'),
    ymax = glyph.getAttribute('yMax'),
    glyphWidth = xmax - xmin,
    glyphHeight = ymax - ymin;

  prepareCanvas = (ctx) => {
    ctx.transform(1, 0, 0, -1, 0, canvas.height);
    ctx.translate((canvas.width - glyphWidth) / 2 - xmin, (canvas.height - glyphHeight) / 2);
  };

  ctx.save();
  prepareCanvas(ctx);

  var glyphTTYPoints = [];

  font.contours(name).forEach(contour => {
    ctx.beginPath();

    var contourTTYPoints = [];
    var curvepoints = [];

    contour.querySelectorAll('pt').forEach((point) => {
      var x = parseInt(point.getAttribute('x')),
        y = parseInt(point.getAttribute('y')),
        on = (point.getAttribute('on') === "1") ? true : false;

      contourTTYPoints.push({
        'x': x,
        'y': y,
        'on': on
      });
    });

    var prevPoint = contourTTYPoints[contourTTYPoints.length - 1];

    for (var i = 0; i < contourTTYPoints.length; i++) {
      var point = contourTTYPoints[i];

      if (!point.on) {
        if (!prevPoint.on) {
          curvepoints.push(deriveCurvePoint(point, prevPoint));
        } else if (i === 0) {
          curvepoints.push(prevPoint);
        }
      }

      curvepoints.push(point);
      prevPoint = point;
    }

    if (!curvepoints[curvepoints.length - 1].on) {
      curvepoints.push(curvepoints[0]);
    }

    for (var i = 0; i < curvepoints.length; i++) {
      var p = curvepoints[i];
      if (p.on) {
        if (i === 0) {
          ctx.moveTo(p.x, p.y);
        } else {
          ctx.lineTo(p.x, p.y);
        }
      } else {
        var cp = p,
          p = curvepoints[i + 1];

        ctx.quadraticCurveTo(cp.x, cp.y, p.x, p.y);
        i++;
      }
    }

    ctx.closePath();
    ctx.stroke();

    glyphTTYPoints = glyphTTYPoints.concat(contourTTYPoints);
  });

  // Mark points
  ctx.save();
  ctx.strokeStyle = 'darkblue';
  ctx.fillStyle = 'darkblue'
  glyphTTYPoints.forEach((p, k) => {
    ctx.beginPath();
    ctx.arc(p.x, p.y, 3, 0, 2 * Math.PI, false);
    if (p.on) {
      ctx.fill();
    } else {
      ctx.stroke();
    }

    ctx.save();
    ctx.strokeStyle = 'orange';
    ctx.fillStyle = 'orange';
    font.delta(name, k).forEach((d) => {
      var x = p.x, y = p.y, dx = parseInt(d.getAttribute('x')), dy = parseInt(d.getAttribute('y'));

      if (dx !== 0 || dy !== 0) {
        ctx.beginPath();
        ctx.moveTo(x, y);
        ctx.lineTo(x + dx, y + dy);
        ctx.stroke();
        arrowHead(ctx, x + dx, y + dy, calcAngle(dx, dy), 10);
      }
      ctx.beginPath();
      ctx.arc(x + dx, y + dy, 2, 0, Math.PI * 2, false);
      ctx.fill();
    });
    ctx.restore();
  });
  ctx.restore();

  ctx.restore();
}

function fillGlyphSelector() {
  var glyphSelector = document.getElementById('glyph-selector');
  while (glyphSelector.firstChild) {
    glyphSelector.removeChild(glyphSelector.firstChild);
  }
  font.glyphs().forEach((glyph) => {
    var option = document.createElement('option');
    option.value = glyph.getAttribute('name');
    option.appendChild(document.createTextNode(option.value));
    glyphSelector.appendChild(option);
  });
}


function arrowHead(ctx, x, y, angle, size) {
  ctx.save();
  ctx.translate(x, y);
  // ctx.arc(0, 0, 10, 0, 2 * Math.PI, false);
  // // ctx.stroke();
  ctx.rotate(angle + Math.PI * .75);
  ctx.scale(size / 10, size / 10);
  ctx.beginPath();
  ctx.moveTo(3, 10);
  ctx.lineTo(0, 0);
  ctx.lineTo(10, 3);
  // ctx.endPath();
  ctx.stroke();
  ctx.restore();
}

function calcAngle(dx, dy) {
  return Math.atan(dy / dx) + ((dx < 0) ? Math.PI : 0);
}

function loadTTX() {
  var fontSelector = document.getElementById('font-selector');

  if (fontSelector.files.length > 0) {
    var reader = new FileReader();
    reader.onload = function (event) {
      font = new Font(event.target.result);
      fillGlyphSelector();
    };
    reader.readAsText(document.getElementById('font-selector').files[0]);
  }
}

document.getElementById('font-selector').addEventListener('change', loadTTX);

loadTTX();

document.getElementById('glyph-selector').addEventListener('change', function () {
  var value = this.value;
  drawGlyph(value, font);
});