/ node_modules / opentype.js / glyph-inspector.html
glyph-inspector.html
  1  <!DOCTYPE html>
  2  <html>
  3  <head>
  4      <title>opentype.js glyph inspector</title>
  5      <meta name="description" content="A JavaScript library to manipulate the letterforms of text from the browser or node.js.">
  6      <meta charset="utf-8">
  7      <link rel="stylesheet" href="site.css">
  8      <script src="dist/opentype.js"></script>
  9  </head>
 10  <body>
 11  <div class="header">
 12      <div class="container">
 13          <h1>opentype.js</h1>
 14          <nav>
 15              <a href="index.html">Home</a>
 16              <a href="font-inspector.html">Font Inspector</a>
 17              <a href="glyph-inspector.html">Glyph Inspector</a>
 18          </nav>
 19          <nav class="right">
 20              <a class="github" href="https://github.com/nodebox/opentype.js">Fork me on GitHub</a>
 21              <a class="gitter" href="https://gitter.im/nodebox/opentype.js">Chat on Gitter</a>
 22          </nav>
 23      </div>
 24  </div>
 25  
 26  <div class="container">
 27  
 28      <div class="explain">
 29          <h1>Glyph Inspector</h1>
 30          <small>opentype.js is an OpenType and TrueType font parser. Here you can inspect the raw glyph data.</small>
 31      </div>
 32  
 33      <input id="file" type="file">
 34      <span class="info" id="font-name">Roboto-Black</span>
 35      <div id="message"></div>
 36  
 37      <hr>
 38  
 39      <div>
 40          Glyphs <span id="pagination"></span>
 41          <br>
 42          <div id="glyph-list-end"></div>
 43      </div>
 44      <div style="position: relative">
 45          <div id="glyph-display">
 46              <canvas id="glyph-bg" width="500" height="500"></canvas>
 47              <canvas id="glyph" width="500" height="500"></canvas>
 48          </div>
 49          <div id="glyph-data"></div>
 50          <div style="clear: both"></div>
 51      </div>
 52  
 53      <hr>
 54  
 55      <div class="explain">
 56          <h1>Free Software</h1>
 57          <p>opentype.js is available on <a href="https://github.com/nodebox/opentype.js">GitHub</a> under the <a href="https://raw.github.com/nodebox/opentype.js/master/LICENSE">MIT License</a>.</p>
 58          <p>Copyright &copy; 2017 Frederik De Bleser.</p>
 59      </div>
 60  
 61      <hr>
 62  </div>
 63  
 64  
 65  <script>
 66  var cellCount = 100,
 67      cellWidth = 44,
 68      cellHeight = 40,
 69      cellMarginTop = 1,
 70      cellMarginBottom = 8,
 71      cellMarginLeftRight = 1,
 72      glyphMargin = 5,
 73      pixelRatio = window.devicePixelRatio || 1;
 74  
 75  var pageSelected, font, fontScale, fontSize, fontBaseline, glyphScale, glyphSize, glyphBaseline;
 76  
 77  function enableHighDPICanvas(canvas) {
 78      if (typeof canvas === 'string') {
 79          canvas = document.getElementById(canvas);
 80      }
 81      var pixelRatio = window.devicePixelRatio || 1;
 82      if (pixelRatio === 1) return;
 83      var oldWidth = canvas.width;
 84      var oldHeight = canvas.height;
 85      canvas.width = oldWidth * pixelRatio;
 86      canvas.height = oldHeight * pixelRatio;
 87      canvas.style.width = oldWidth + 'px';
 88      canvas.style.height = oldHeight + 'px';
 89      canvas.getContext('2d').scale(pixelRatio, pixelRatio);
 90  }
 91  
 92  function showErrorMessage(message) {
 93      var el = document.getElementById('message');
 94      if (!message || message.trim().length === 0) {
 95          el.style.display = 'none';
 96      } else {
 97          el.style.display = 'block';
 98      }
 99      el.innerHTML = message;
100  }
101  
102  function pathCommandToString(cmd) {
103      var str = '<strong>' + cmd.type + '</strong> ' +
104          ((cmd.x !== undefined) ? 'x='+cmd.x+' y='+cmd.y+' ' : '') +
105          ((cmd.x1 !== undefined) ? 'x1='+cmd.x1+' y1='+cmd.y1+' ' : '') +
106          ((cmd.x2 !== undefined) ? 'x2='+cmd.x2+' y2='+cmd.y2 : '');
107      return str;
108  }
109  
110  function contourToString(contour) {
111      return '<pre class="contour">' + contour.map(function(point) {
112          return '<span class="' + (point.onCurve ? 'on' : 'off') + 'curve">x=' + point.x + ' y=' + point.y + '</span>';
113      }).join('\n') + '</pre>';
114  }
115  
116  function formatUnicode(unicode) {
117      unicode = unicode.toString(16);
118      if (unicode.length > 4) {
119          return ("000000" + unicode.toUpperCase()).substr(-6)
120      } else {
121          return ("0000" + unicode.toUpperCase()).substr(-4)
122      }
123  }
124  
125  function displayGlyphData(glyphIndex) {
126      var container = document.getElementById('glyph-data');
127      if (glyphIndex < 0) {
128          container.innerHTML = '';
129          return;
130      }
131      var glyph = font.glyphs.get(glyphIndex),
132          html = '<dl>';
133      html += '<dt>name</dt><dd>'+glyph.name+'</dd>';
134  
135      if (glyph.unicodes.length > 0) {
136          html += '<dt>unicode</dt><dd>'+ glyph.unicodes.map(formatUnicode).join(', ') +'</dd>';
137      }
138      html += '<dt>index</dt><dd>'+glyph.index+'</dd>';
139  
140      if (glyph.xMin !== 0 || glyph.xMax !== 0 || glyph.yMin !== 0 || glyph.yMax !== 0) {
141          html += '<dt>xMin</dt><dd>'+glyph.xMin+'</dd>' +
142              '<dt>xMax</dt><dd>'+glyph.xMax+'</dd>' +
143              '<dt>yMin</dt><dd>'+glyph.yMin+'</dd>' +
144              '<dt>yMax</dt><dd>'+glyph.yMax+'</dd>';
145      }
146      html += '<dt>advanceWidth</dt><dd>'+glyph.advanceWidth+'</dd>';
147      if(glyph.leftSideBearing !== undefined) {
148          html += '<dt>leftSideBearing</dt><dd>'+glyph.leftSideBearing+'</dd>';
149      }
150      html += '</dl>';
151      if (glyph.numberOfContours > 0) {
152          var contours = glyph.getContours();
153          html += 'contours:<div id="glyph-contours">' + contours.map(contourToString).join('\n') + '</div>';
154      } else if (glyph.isComposite) {
155          html += '<br>This composite glyph is a combination of :<ul><li>' +
156              glyph.components.map(function(component) {
157                  if (component.matchedPoints === undefined) {
158                      return 'glyph '+component.glyphIndex+' at dx='+component.dx+', dy='+component.dy;
159                  } else {
160                      return 'glyph '+component.glyphIndex+' at matchedPoints=['+component.matchedPoints+']';
161                  }
162              }).join('</li><li>') + '</li></ul>';
163      } else if (glyph.path) {
164          html += 'path:<br><pre>  ' + glyph.path.commands.map(pathCommandToString).join('\n  ') + '\n</pre>';
165      }
166      container.innerHTML = html;
167  }
168  
169  var arrowLength = 10,
170      arrowAperture = 4;
171  
172  function drawArrow(ctx, x1, y1, x2, y2) {
173      var dx = x2 - x1,
174          dy = y2 - y1,
175          segmentLength = Math.sqrt(dx*dx + dy*dy),
176          unitx = dx / segmentLength,
177          unity = dy / segmentLength,
178          basex = x2 - arrowLength * unitx,
179          basey = y2 - arrowLength * unity,
180          normalx = arrowAperture * unity,
181          normaly = -arrowAperture * unitx;
182      ctx.beginPath();
183      ctx.moveTo(x2, y2);
184      ctx.lineTo(basex + normalx, basey + normaly);
185      ctx.lineTo(basex - normalx, basey - normaly);
186      ctx.lineTo(x2, y2);
187      ctx.closePath();
188      ctx.fill();
189  }
190  
191  /**
192   * This function is Path.prototype.draw with an arrow
193   * at the end of each contour.
194   */
195  function drawPathWithArrows(ctx, path) {
196      var i, cmd, x1, y1, x2, y2;
197      var arrows = [];
198      ctx.beginPath();
199      for (i = 0; i < path.commands.length; i += 1) {
200          cmd = path.commands[i];
201          if (cmd.type === 'M') {
202              if(x1 !== undefined) {
203                  arrows.push([ctx, x1, y1, x2, y2]);
204              }
205              ctx.moveTo(cmd.x, cmd.y);
206          } else if (cmd.type === 'L') {
207              ctx.lineTo(cmd.x, cmd.y);
208              x1 = x2;
209              y1 = y2;
210          } else if (cmd.type === 'C') {
211              ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y);
212              x1 = cmd.x2;
213              y1 = cmd.y2;
214          } else if (cmd.type === 'Q') {
215              ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y);
216              x1 = cmd.x1;
217              y1 = cmd.y1;
218          } else if (cmd.type === 'Z') {
219              arrows.push([ctx, x1, y1, x2, y2]);
220              ctx.closePath();
221          }
222          x2 = cmd.x;
223          y2 = cmd.y;
224      }
225      if (path.fill) {
226          ctx.fillStyle = path.fill;
227          ctx.fill();
228      }
229      if (path.stroke) {
230          ctx.strokeStyle = path.stroke;
231          ctx.lineWidth = path.strokeWidth;
232          ctx.stroke();
233      }
234      ctx.fillStyle = '#000000';
235      arrows.forEach(function(arrow) {
236          drawArrow.apply(null, arrow);
237      });
238  }
239  
240  function displayGlyph(glyphIndex) {
241      var canvas = document.getElementById('glyph'),
242          ctx = canvas.getContext('2d'),
243          width = canvas.width / pixelRatio,
244          height = canvas.height / pixelRatio;
245      ctx.clearRect(0, 0, width, height);
246      if(glyphIndex < 0) return;
247      var glyph = font.glyphs.get(glyphIndex),
248          glyphWidth = glyph.advanceWidth * glyphScale,
249          xmin = (width - glyphWidth)/2,
250          xmax = (width + glyphWidth)/2,
251          x0 = xmin,
252          markSize = 10;
253  
254      ctx.fillStyle = '#606060';
255      ctx.fillRect(xmin-markSize+1, glyphBaseline, markSize, 1);
256      ctx.fillRect(xmin, glyphBaseline, 1, markSize);
257      ctx.fillRect(xmax, glyphBaseline, markSize, 1);
258      ctx.fillRect(xmax, glyphBaseline, 1, markSize);
259      ctx.textAlign = 'center';
260      ctx.fillText('0', xmin, glyphBaseline+markSize+10);
261      ctx.fillText(glyph.advanceWidth, xmax, glyphBaseline+markSize+10);
262  
263      ctx.fillStyle = '#000000';
264      var path = glyph.getPath(x0, glyphBaseline, glyphSize);
265      path.fill = '#808080';
266      path.stroke = '#000000';
267      path.strokeWidth = 1.5;
268      drawPathWithArrows(ctx, path);
269      glyph.drawPoints(ctx, x0, glyphBaseline, glyphSize);
270  }
271  
272  function renderGlyphItem(canvas, glyphIndex) {
273      var cellMarkSize = 4;
274      var ctx = canvas.getContext('2d');
275      ctx.clearRect(0, 0, cellWidth, cellHeight);
276      if (glyphIndex >= font.numGlyphs) return;
277  
278      ctx.fillStyle = '#606060';
279      ctx.font = '9px sans-serif';
280      ctx.fillText(glyphIndex, 1, cellHeight-1);
281      var glyph = font.glyphs.get(glyphIndex),
282          glyphWidth = glyph.advanceWidth * fontScale,
283          xmin = (cellWidth - glyphWidth)/2,
284          xmax = (cellWidth + glyphWidth)/2,
285          x0 = xmin;
286  
287      ctx.fillStyle = '#a0a0a0';
288      ctx.fillRect(xmin-cellMarkSize+1, fontBaseline, cellMarkSize, 1);
289      ctx.fillRect(xmin, fontBaseline, 1, cellMarkSize);
290      ctx.fillRect(xmax, fontBaseline, cellMarkSize, 1);
291      ctx.fillRect(xmax, fontBaseline, 1, cellMarkSize);
292  
293      ctx.fillStyle = '#000000';
294      glyph.draw(ctx, x0, fontBaseline, fontSize);
295  }
296  
297  function displayGlyphPage(pageNum) {
298      pageSelected = pageNum;
299      document.getElementById('p'+pageNum).className = 'page-selected';
300      var firstGlyph = pageNum * cellCount;
301      for(var i = 0; i < cellCount; i++) {
302          renderGlyphItem(document.getElementById('g'+i), firstGlyph+i);
303      }
304  }
305  
306  function pageSelect(event) {
307      document.getElementsByClassName('page-selected')[0].className = '';
308      displayGlyphPage(+event.target.id.substr(1));
309  }
310  
311  function initGlyphDisplay() {
312      var glyphBgCanvas = document.getElementById('glyph-bg'),
313          w = glyphBgCanvas.width / pixelRatio,
314          h = glyphBgCanvas.height / pixelRatio,
315          glyphW = w - glyphMargin*2,
316          glyphH = h - glyphMargin*2,
317          head = font.tables.head,
318          maxHeight = head.yMax - head.yMin,
319          ctx = glyphBgCanvas.getContext('2d');
320  
321      glyphScale = Math.min(glyphW/(head.xMax - head.xMin), glyphH/maxHeight);
322      glyphSize = glyphScale * font.unitsPerEm;
323      glyphBaseline = glyphMargin + glyphH * head.yMax / maxHeight;
324  
325      function hline(text, yunits) {
326          ypx = glyphBaseline - yunits * glyphScale;
327          ctx.fillText(text, 2, ypx+3);
328          ctx.fillRect(80, ypx, w, 1);
329      }
330  
331      ctx.clearRect(0, 0, w, h);
332      ctx.fillStyle = '#a0a0a0';
333      hline('Baseline', 0);
334      hline('yMax', font.tables.head.yMax);
335      hline('yMin', font.tables.head.yMin);
336      hline('Ascender', font.tables.hhea.ascender);
337      hline('Descender', font.tables.hhea.descender);
338      hline('Typo Ascender', font.tables.os2.sTypoAscender);
339      hline('Typo Descender', font.tables.os2.sTypoDescender);
340  }
341  
342  function onFontLoaded(font) {
343      window.font = font;
344  
345      var w = cellWidth - cellMarginLeftRight * 2,
346          h = cellHeight - cellMarginTop - cellMarginBottom,
347          head = font.tables.head,
348          maxHeight = head.yMax - head.yMin;
349      fontScale = Math.min(w/(head.xMax - head.xMin), h/maxHeight);
350      fontSize = fontScale * font.unitsPerEm;
351      fontBaseline = cellMarginTop + h * head.yMax / maxHeight;
352  
353      var pagination = document.getElementById("pagination");
354      pagination.innerHTML = '';
355      var fragment = document.createDocumentFragment();
356      var numPages = Math.ceil(font.numGlyphs / cellCount);
357      for(var i = 0; i < numPages; i++) {
358          var link = document.createElement('span');
359          var lastIndex = Math.min(font.numGlyphs-1, (i+1)*cellCount-1);
360          link.textContent = i*cellCount + '-' + lastIndex;
361          link.id = 'p' + i;
362          link.addEventListener('click', pageSelect, false);
363          fragment.appendChild(link);
364          // A white space allows to break very long lines into multiple lines.
365          // This is needed for fonts with thousands of glyphs.
366          fragment.appendChild(document.createTextNode(' '));
367      }
368      pagination.appendChild(fragment);
369  
370      initGlyphDisplay();
371      displayGlyphPage(0);
372      displayGlyph(-1);
373      displayGlyphData(-1);
374  }
375  
376  function onReadFile(e) {
377      document.getElementById('font-name').innerHTML = '';
378      var file = e.target.files[0];
379      var reader = new FileReader();
380      reader.onload = function(e) {
381          try {
382              font = opentype.parse(e.target.result);
383              showErrorMessage('');
384              onFontLoaded(font);
385          } catch (err) {
386              showErrorMessage(err.toString());
387              if (err.stack) console.log(err.stack);
388              throw(err);
389          }
390      };
391      reader.onerror = function(err) {
392          showErrorMessage(err.toString());
393      };
394  
395      reader.readAsArrayBuffer(file);
396  }
397  
398  function cellSelect(event) {
399      if (!font) return;
400      var firstGlyphIndex = pageSelected*cellCount,
401          cellIndex = +event.target.id.substr(1),
402          glyphIndex = firstGlyphIndex + cellIndex;
403      if (glyphIndex < font.numGlyphs) {
404          displayGlyph(glyphIndex);
405          displayGlyphData(glyphIndex);
406      }
407  }
408  
409  function prepareGlyphList() {
410      var marker = document.getElementById('glyph-list-end'),
411          parent = marker.parentElement;
412      for(var i = 0; i < cellCount; i++) {
413          var canvas = document.createElement('canvas');
414          canvas.width = cellWidth;
415          canvas.height = cellHeight;
416          canvas.className = 'item';
417          canvas.id = 'g'+i;
418          canvas.addEventListener('click', cellSelect, false);
419          enableHighDPICanvas(canvas);
420          parent.insertBefore(canvas, marker);
421      }
422  }
423  
424  var fontFileName = 'fonts/Roboto-Black.ttf';
425  document.getElementById('font-name').innerHTML = fontFileName.split('/')[1];
426  
427  var fileButton = document.getElementById('file');
428  fileButton.addEventListener('change', onReadFile, false);
429  
430  enableHighDPICanvas('glyph-bg');
431  enableHighDPICanvas('glyph');
432  
433  prepareGlyphList();
434  opentype.load(fontFileName, function(err, font) {
435      var amount, glyph, ctx, x, y, fontSize;
436      if (err) {
437          showErrorMessage(err.toString());
438          return;
439      }
440      onFontLoaded(font);
441  });
442  </script>
443  </body>
444  </html>