GitHub Font Preview

A userscript that adds a font file preview

目前为 2016-06-11 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GitHub Font Preview
  3. // @version 1.0.0
  4. // @description A userscript that adds a font file preview
  5. // @license https://creativecommons.org/licenses/by-sa/4.0/
  6. // @namespace http://github.com/Mottie
  7. // @include https://github.com/*
  8. // @run-at document-idle
  9. // @grant GM_addStyle
  10. // @grant GM_xmlhttpRequest
  11. // @connect github.com
  12. // @connect githubusercontent.com
  13. // @require https://greasyfork.org/scripts/20469-opentype-js/code/opentypejs.js?version=130870
  14. // @author Rob Garrison
  15. // ==/UserScript==
  16. /* global GM_addStyle, GM_xmlhttpRequest, opentype */
  17. /*jshint unused:true, esnext:true */
  18. (function() {
  19. 'use strict';
  20.  
  21. let timer, targets, font,
  22. busy = false;
  23.  
  24. // supported font types
  25. const fontExt = /\.(otf|ttf|woff)$/i,
  26.  
  27. // canvas colors
  28. bigGlyphStrokeColor = '#111111', // (big) stroke color
  29. bigGlyphFillColor = '#808080', // (big) fill color
  30. bigGlyphMarkerColor = '#f00', // (big) min & max width marker
  31. miniGlyphMarkerColor = '#606060', // (mini) glyph index (bottom left corner)
  32. glyphRulerColor = '#a0a0a0'; // (mini) min & max width marker & (big) glyph horizontal lines
  33.  
  34. function getFont(url) {
  35. if (url) {
  36. GM_xmlhttpRequest({
  37. method: 'GET',
  38. url: url,
  39. responseType: 'arraybuffer',
  40. onload : function(response) {
  41. setupFont(response.response);
  42. }
  43. });
  44. }
  45. }
  46.  
  47. function setupFont(data) {
  48. busy = true;
  49. let target = document.querySelector('.file .image');
  50. if (target) {
  51. addHTML(target);
  52. try {
  53. font = opentype.parse(data);
  54. showErrorMessage('');
  55. onFontLoaded(font);
  56. } catch (err) {
  57. showErrorMessage(err.toString());
  58. if (err.stack) {
  59. console.error(err.stack);
  60. }
  61. throw(err);
  62. }
  63. }
  64. busy = false;
  65. }
  66.  
  67. function addHTML(target) {
  68. let name = document.querySelector('.final-path').textContent || '';
  69. target.innerHTML = `
  70. <div id="gfp-wrapper">
  71. <span class="gfp-info" id="gfp-font-name">${name}</span>
  72. <div id="gfp-message"></div>
  73. <hr>
  74. <div id="gfp-font-data">
  75. <div class="gfp-collapsed">Font Header table <a href="https://www.microsoft.com/typography/OTSPEC/head.htm" target="_blank">head</a></div>
  76. <dl id="gfp-head-table"></dl>
  77. <div class="gfp-collapsed">Horizontal Header table <a href="https://www.microsoft.com/typography/OTSPEC/hhea.htm" target="_blank">hhea</a></div>
  78. <dl id="gfp-hhea-table"></dl>
  79. <div class="gfp-collapsed">Maximum Profile table <a href="https://www.microsoft.com/typography/OTSPEC/maxp.htm" target="_blank">maxp</a></div>
  80. <dl id="gfp-maxp-table"></dl>
  81. <div class="gfp-collapsed">Naming table <a href="https://www.microsoft.com/typography/OTSPEC/name.htm" target="_blank">name</a></div>
  82. <dl id="gfp-name-table"></dl>
  83. <div class="gfp-collapsed">OS/2 and Windows Metrics table <a href="https://www.microsoft.com/typography/OTSPEC/os2.htm" target="_blank">OS/2</a></div>
  84. <dl id="gfp-os2-table"></dl>
  85. <div class="gfp-collapsed">PostScript table <a href="https://www.microsoft.com/typography/OTSPEC/post.htm" target="_blank">post</a></div>
  86. <dl id="gfp-post-table"></dl>
  87. <div class="gfp-collapsed">Character To Glyph Index Mapping Table <a href="https://www.microsoft.com/typography/OTSPEC/cmap.htm" target="_blank">cmap</a></div>
  88. <dl id="gfp-cmap-table"></dl>
  89. <div class="gfp-collapsed">Font Variations table <a href="https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6fvar.html" target="_blank">fvar</a></div>
  90. <dl id="gfp-fvar-table"></dl>
  91. </div>
  92. <hr>
  93. <div>
  94. Glyphs <span id="gfp-pagination"></span>
  95. <br>
  96. <div id="gfp-glyph-list-end"></div>
  97. </div>
  98. <div style="position: relative">
  99. <div id="gfp-glyph-display">
  100. <canvas id="gfp-glyph-bg" class="ghd-invert" width="500" height="500"></canvas>
  101. <canvas id="gfp-glyph" class="ghd-invert" width="500" height="500"></canvas>
  102. </div>
  103. <div id="gfp-glyph-data"></div>
  104. <div style="clear: both"></div>
  105. </div>
  106. <span style="font-size:0.8em">Powered by <a href="https://github.com/nodebox/opentype.js">opentype.js</a></span>
  107. </div>
  108. `;
  109. prepareGlyphList();
  110. // Add bindings for collapsible font data
  111. let tableHeaders = document.getElementById('gfp-font-data').getElementsByTagName('div'),
  112. indx = tableHeaders.length;
  113. while (indx--) {
  114. tableHeaders[indx].addEventListener('click', function(e) {
  115. e.target.classList.toggle('gfp-collapsed');
  116. }, false);
  117. }
  118. }
  119.  
  120. function init() {
  121. let name,
  122. el = document.querySelector('.file .image');
  123. if (el) {
  124. name = document.querySelector('.final-path').textContent || '';
  125. if (name && fontExt.test(name)) {
  126. getFont(el.querySelector('a').href || '');
  127. }
  128. }
  129. }
  130.  
  131. // DOM targets - to detect GitHub dynamic ajax page loading
  132. targets = document.querySelectorAll([
  133. '#js-repo-pjax-container',
  134. '.context-loader-container',
  135. '[data-pjax-container]'
  136. ].join(','));
  137.  
  138. Array.prototype.forEach.call(targets, function(target) {
  139. new MutationObserver(function(mutations) {
  140. mutations.forEach(function(mutation) {
  141. // preform checks before adding code wrap to minimize function calls
  142. if (!busy && mutation.target === target) {
  143. clearTimeout(timer);
  144. timer = setTimeout(init, 200);
  145. }
  146. });
  147. }).observe(target, {
  148. childList: true,
  149. subtree: true
  150. });
  151. });
  152.  
  153. init();
  154.  
  155. /* Code modified from http://opentype.js.org/ demos */
  156. GM_addStyle(`
  157. #gfp-wrapper { text-align:left; }
  158. #gfp-wrapper canvas { background-image:none !important; background-color:transparent !important; }
  159. #gfp-message { position:relative; top:-3px; background:red; color:white; padding:1px 5px; font-weight:bold; border-radius:2px; display:none; clear:both; }
  160. #gfp-glyphs { width:950px; }
  161. .gfp-info { float:right; font-size:11px; color:#999; }
  162. hr { clear:both; border:none; border-bottom:1px solid #ccc; margin:20px 0 20px 0; padding:0; }
  163. /* Font Inspector */
  164. #gfp-font-data div { font-weight:normal; margin:0; cursor:pointer; }
  165. #gfp-font-data div:before { font-size:85%; content:'▼ '; }
  166. #gfp-font-data div.gfp-collapsed:before { font-size:85%; content:'► '; }
  167. #gfp-font-data div.gfp-collapsed + dl { display:none; }
  168. #gfp-font-data dl { margin-top:0; padding-left:2em; color:#707070; }
  169. #gfp-font-data dt { float:left; }
  170. #gfp-font-data dd { margin-left: 12em; }
  171. #gfp-font-data .gfp-langtag { font-size:85%; color:#999; white-space:nowrap; }
  172. #gfp-font-data .gfp-langname { padding-right:0.5em; }
  173. /* Glyph Inspector */
  174. #gfp-pagination span { margin:0 0.3em; color:#505050; cursor:pointer; }
  175. #gfp-pagination span.gfp-page-selected { font-weight:bold; -webkit-filter:brightness(150%); filter:brightness(150%); }
  176. canvas.gfp-item { float:left; border:solid 1px #a0a0a0; margin-right:-1px; margin-bottom:-1px; cursor:pointer; }
  177. canvas.gfp-item:hover { opacity:.8; }
  178. #gfp-glyph-list-end { clear:both; height:20px; }
  179. #gfp-glyph-display { float:left; border:solid 1px #a0a0a0; position:relative; width:500px; height:500px; }
  180. #gfp-glyph, #gfp-glyph-bg { position:absolute; top:0; left:0; border:0; }
  181. #gfp-glyph-data { float:left; margin-left:2em; }
  182. #gfp-glyph-data dl { margin:0; }
  183. #gfp-glyph-data dt { float:left; }
  184. #gfp-glyph-data dd { margin-left:12em; }
  185. #gfp-glyph-data pre { font-size:11px; }
  186. pre.gfp-path { margin:0; }
  187. pre.gfp-contour { margin:0 0 1em 2em; border-bottom:solid 1px #a0a0a0; }
  188. span.gfp-oncurve { color:blue; }
  189. span.gfp-offcurve { color:red; }
  190. `);
  191.  
  192. /* Code copied from http://opentype.js.org/font-inspector.html */
  193. function escapeHtml(unsafe) {
  194. return unsafe
  195. .replace(/&/g, '&amp;')
  196. .replace(/</g, '&lt;')
  197. .replace(/>/g, '&gt;')
  198. .replace(/\u0022/g, '&quot;')
  199. .replace(/\u0027/g, '&#039;');
  200. }
  201.  
  202. function displayNames(names) {
  203. let indx, property, translations, langs, lang, langIndx, langLen, esclang,
  204. html = '',
  205. properties = Object.keys(names),
  206. len = properties.length;
  207. for (indx = 0; indx < len; indx++) {
  208. property = properties[indx];
  209. html += '<dt>' + escapeHtml(property) + '</dt><dd>';
  210. translations = names[property];
  211. langs = Object.keys(translations);
  212. langLen = langs.length;
  213. for (langIndx = 0; langIndx < langLen; langIndx++) {
  214. lang = langs[langIndx];
  215. esclang = escapeHtml(lang);
  216. html += '<span class="gfp-langtag">' + esclang +
  217. '</span> <span class="gfp-langname" lang=' + esclang + '>' +
  218. escapeHtml(translations[lang]) + '</span> ';
  219. }
  220. html += '</dd>';
  221. }
  222. document.getElementById('gfp-name-table').innerHTML = html;
  223. }
  224.  
  225. function displayFontData() {
  226. let html, tablename, table, property, value, element;
  227. for (tablename in font.tables) {
  228. if (font.tables.hasOwnProperty(tablename)) {
  229. table = font.tables[tablename];
  230. if (tablename === 'name') {
  231. displayNames(table);
  232. continue;
  233. }
  234. html = '';
  235. for (property in table) {
  236. if (table.hasOwnProperty(property)) {
  237. value = table[property];
  238. html += '<dt>' + property + '</dt><dd>';
  239. if (Array.isArray(value) && typeof value[0] === 'object') {
  240. html += value.map(function(item) {
  241. return JSON.stringify(item);
  242. }).join('<br>');
  243. } else if (typeof value === 'object') {
  244. html += JSON.stringify(value);
  245. } else {
  246. html += value;
  247. }
  248. html += '</dd>';
  249. }
  250. }
  251. element = document.getElementById('gfp-' + tablename + '-table');
  252. if (element) {
  253. element.innerHTML = html;
  254. }
  255. }
  256. }
  257. }
  258.  
  259. /* Code copied from http://opentype.js.org/glyph-inspector.html */
  260. const cellCount = 100,
  261. cellWidth = 44,
  262. cellHeight = 40,
  263. cellMarginTop = 1,
  264. cellMarginBottom = 8,
  265. cellMarginLeftRight = 1,
  266. glyphMargin = 5,
  267. pixelRatio = window.devicePixelRatio || 1,
  268. arrowLength = 10,
  269. arrowAperture = 4;
  270.  
  271. let pageSelected, fontScale, fontSize, fontBaseline, glyphScale, glyphSize, glyphBaseline;
  272.  
  273. function enableHighDPICanvas(canvas) {
  274. let pixelRatio, oldWidth, oldHeight;
  275. if (typeof canvas === 'string') {
  276. canvas = document.getElementById(canvas);
  277. }
  278. pixelRatio = window.devicePixelRatio || 1;
  279. if (pixelRatio === 1) { return; }
  280. oldWidth = canvas.width;
  281. oldHeight = canvas.height;
  282. canvas.width = oldWidth * pixelRatio;
  283. canvas.height = oldHeight * pixelRatio;
  284. canvas.style.width = oldWidth + 'px';
  285. canvas.style.height = oldHeight + 'px';
  286. canvas.getContext('2d').scale(pixelRatio, pixelRatio);
  287. }
  288.  
  289. function showErrorMessage(message) {
  290. let el = document.getElementById('gfp-message');
  291. el.style.display = (!message || message.trim().length === 0) ? 'none' : 'block';
  292. el.innerHTML = message;
  293. }
  294.  
  295. function pathCommandToString(cmd) {
  296. let str = '<strong>' + cmd.type + '</strong> ' +
  297. ((cmd.x !== undefined) ? 'x=' + cmd.x + ' y=' + cmd.y + ' ' : '') +
  298. ((cmd.x1 !== undefined) ? 'x1=' + cmd.x1 + ' y1=' + cmd.y1 + ' ' : '') +
  299. ((cmd.x2 !== undefined) ? 'x2=' + cmd.x2 + ' y2=' + cmd.y2 : '');
  300. return str;
  301. }
  302.  
  303. function contourToString(contour) {
  304. return '<pre class="gfp-contour">' + contour.map(function(point) {
  305. // ".alert.tip" class modified by GitHub Dark style - more readable blue
  306. // ".cdel" class modified by GitHub Dark style - more readable red
  307. return '<span class="gfp-' + (point.onCurve ? 'oncurve alert tip' : 'offcurve cdel') +
  308. '">x=' + point.x + ' y=' + point.y + '</span>';
  309. }).join('\n') + '</pre>';
  310. }
  311.  
  312. function formatUnicode(unicode) {
  313. unicode = unicode.toString(16);
  314. if (unicode.length > 4) {
  315. return ('000000' + unicode.toUpperCase()).substr(-6);
  316. } else {
  317. return ('0000' + unicode.toUpperCase()).substr(-4);
  318. }
  319. }
  320.  
  321. function displayGlyphData(glyphIndex) {
  322. let glyph, contours, html,
  323. container = document.getElementById('gfp-glyph-data');
  324. if (glyphIndex < 0) {
  325. container.innerHTML = '';
  326. return;
  327. }
  328. glyph = font.glyphs.get(glyphIndex);
  329. html = '<dt>name</dt><dd>' + glyph.name + '</dd>';
  330. if (glyph.unicodes.length > 0) {
  331. html += '<dt>unicode</dt><dd>' + glyph.unicodes.map(formatUnicode).join(', ') + '</dd>';
  332. }
  333. html += '<dl><dt>index</dt><dd>' + glyph.index + '</dd>';
  334. if (glyph.xMin !== 0 || glyph.xMax !== 0 || glyph.yMin !== 0 || glyph.yMax !== 0) {
  335. html += '<dt>xMin</dt><dd>' + glyph.xMin + '</dd>' +
  336. '<dt>xMax</dt><dd>' + glyph.xMax + '</dd>' +
  337. '<dt>yMin</dt><dd>' + glyph.yMin + '</dd>' +
  338. '<dt>yMax</dt><dd>' + glyph.yMax + '</dd>';
  339. }
  340. html += '<dt>advanceWidth</dt><dd>' + glyph.advanceWidth + '</dd>';
  341. if (glyph.leftSideBearing !== undefined) {
  342. html += '<dt>leftSideBearing</dt><dd>' + glyph.leftSideBearing + '</dd>';
  343. }
  344. html += '</dl>';
  345. if (glyph.numberOfContours > 0) {
  346. contours = glyph.getContours();
  347. html += 'contours:<br>' + contours.map(contourToString).join('\n');
  348. } else if (glyph.isComposite) {
  349. html += '<br>This composite glyph is a combination of :<ul><li>' +
  350. glyph.components.map(function(component) {
  351. return 'glyph ' + component.glyphIndex + ' at dx=' + component.dx +
  352. ', dy=' + component.dy;
  353. }).join('</li><li>') + '</li></ul>';
  354. } else if (glyph.path) {
  355. html += 'path:<br><pre class="gfp-path"> ' +
  356. glyph.path.commands.map(pathCommandToString).join('\n ') + '\n</pre>';
  357. }
  358. container.innerHTML = html;
  359. }
  360.  
  361. function drawArrow(ctx, x1, y1, x2, y2) {
  362. let dx = x2 - x1,
  363. dy = y2 - y1,
  364. segmentLength = Math.sqrt(dx * dx + dy * dy),
  365. unitx = dx / segmentLength,
  366. unity = dy / segmentLength,
  367. basex = x2 - arrowLength * unitx,
  368. basey = y2 - arrowLength * unity,
  369. normalx = arrowAperture * unity,
  370. normaly = -arrowAperture * unitx;
  371. ctx.beginPath();
  372. ctx.moveTo(x2, y2);
  373. ctx.lineTo(basex + normalx, basey + normaly);
  374. ctx.lineTo(basex - normalx, basey - normaly);
  375. ctx.lineTo(x2, y2);
  376. ctx.closePath();
  377. ctx.fill();
  378. }
  379.  
  380. /**
  381. * This function is Path.prototype.draw with an arrow
  382. * at the end of each contour.
  383. */
  384. function drawPathWithArrows(ctx, path) {
  385. let indx, cmd, x1, y1, x2, y2,
  386. arrows = [],
  387. len = path.commands.length;
  388. ctx.beginPath();
  389. for (indx = 0; indx < len; indx++) {
  390. cmd = path.commands[indx];
  391. if (cmd.type === 'M') {
  392. if (x1 !== undefined) {
  393. arrows.push([ctx, x1, y1, x2, y2]);
  394. }
  395. ctx.moveTo(cmd.x, cmd.y);
  396. } else if (cmd.type === 'L') {
  397. ctx.lineTo(cmd.x, cmd.y);
  398. x1 = x2;
  399. y1 = y2;
  400. } else if (cmd.type === 'C') {
  401. ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y);
  402. x1 = cmd.x2;
  403. y1 = cmd.y2;
  404. } else if (cmd.type === 'Q') {
  405. ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y);
  406. x1 = cmd.x1;
  407. y1 = cmd.y1;
  408. } else if (cmd.type === 'Z') {
  409. arrows.push([ctx, x1, y1, x2, y2]);
  410. ctx.closePath();
  411. }
  412. x2 = cmd.x;
  413. y2 = cmd.y;
  414. }
  415. if (path.fill) {
  416. ctx.fillStyle = path.fill;
  417. ctx.fill();
  418. }
  419. if (path.stroke) {
  420. ctx.strokeStyle = path.stroke;
  421. ctx.lineWidth = path.strokeWidth;
  422. ctx.stroke();
  423. }
  424. ctx.fillStyle = bigGlyphStrokeColor;
  425. arrows.forEach(function(arrow) {
  426. drawArrow.apply(null, arrow);
  427. });
  428. }
  429.  
  430. function displayGlyph(glyphIndex) {
  431. let glyph, glyphWidth, xmin, xmax, x0, markSize, path,
  432. canvas = document.getElementById('gfp-glyph'),
  433. ctx = canvas.getContext('2d'),
  434. width = canvas.width / pixelRatio,
  435. height = canvas.height / pixelRatio;
  436. ctx.clearRect(0, 0, width, height);
  437. if (glyphIndex < 0) { return; }
  438. glyph = font.glyphs.get(glyphIndex);
  439. glyphWidth = glyph.advanceWidth * glyphScale;
  440. xmin = (width - glyphWidth)/2;
  441. xmax = (width + glyphWidth)/2;
  442. x0 = xmin;
  443. markSize = 10;
  444.  
  445. ctx.fillStyle = bigGlyphMarkerColor;
  446. ctx.fillRect(xmin-markSize+1, glyphBaseline, markSize, 1);
  447. ctx.fillRect(xmin, glyphBaseline, 1, markSize);
  448. ctx.fillRect(xmax, glyphBaseline, markSize, 1);
  449. ctx.fillRect(xmax, glyphBaseline, 1, markSize);
  450. ctx.textAlign = 'center';
  451. ctx.fillText('0', xmin, glyphBaseline + markSize + 10);
  452. ctx.fillText(glyph.advanceWidth, xmax, glyphBaseline + markSize + 10);
  453.  
  454. ctx.fillStyle = bigGlyphStrokeColor;
  455. path = glyph.getPath(x0, glyphBaseline, glyphSize);
  456. path.fill = bigGlyphFillColor;
  457. path.stroke = bigGlyphStrokeColor;
  458. path.strokeWidth = 1.5;
  459. drawPathWithArrows(ctx, path);
  460. glyph.drawPoints(ctx, x0, glyphBaseline, glyphSize);
  461. }
  462.  
  463. function renderGlyphItem(canvas, glyphIndex) {
  464. const cellMarkSize = 4,
  465. ctx = canvas.getContext('2d');
  466. ctx.clearRect(0, 0, cellWidth, cellHeight);
  467. if (glyphIndex >= font.numGlyphs) { return; }
  468.  
  469. ctx.fillStyle = miniGlyphMarkerColor;
  470. ctx.font = '9px sans-serif';
  471. ctx.fillText(glyphIndex, 1, cellHeight-1);
  472. let glyph = font.glyphs.get(glyphIndex),
  473. glyphWidth = glyph.advanceWidth * fontScale,
  474. xmin = (cellWidth - glyphWidth) / 2,
  475. xmax = (cellWidth + glyphWidth) / 2,
  476. x0 = xmin;
  477.  
  478. ctx.fillStyle = glyphRulerColor;
  479. ctx.fillRect(xmin-cellMarkSize+1, fontBaseline, cellMarkSize, 1);
  480. ctx.fillRect(xmin, fontBaseline, 1, cellMarkSize);
  481. ctx.fillRect(xmax, fontBaseline, cellMarkSize, 1);
  482. ctx.fillRect(xmax, fontBaseline, 1, cellMarkSize);
  483.  
  484. ctx.fillStyle = '#000000';
  485. glyph.draw(ctx, x0, fontBaseline, fontSize);
  486. }
  487.  
  488. function displayGlyphPage(pageNum) {
  489. pageSelected = pageNum;
  490. document.getElementById('gfp-p' + pageNum).className = 'gfp-page-selected';
  491. let indx,
  492. firstGlyph = pageNum * cellCount;
  493. for (indx = 0; indx < cellCount; indx++) {
  494. renderGlyphItem(document.getElementById('gfp-g' + indx), firstGlyph + indx);
  495. }
  496. }
  497.  
  498. function pageSelect(event) {
  499. document.getElementsByClassName('gfp-page-selected')[0].className = '';
  500. displayGlyphPage((event.target.id || '').replace('gfp-p', ''));
  501. }
  502.  
  503. function initGlyphDisplay() {
  504. let glyphBgCanvas = document.getElementById('gfp-glyph-bg'),
  505. w = glyphBgCanvas.width / pixelRatio,
  506. h = glyphBgCanvas.height / pixelRatio,
  507. glyphW = w - glyphMargin*2,
  508. glyphH = h - glyphMargin*2,
  509. head = font.tables.head,
  510. maxHeight = head.yMax - head.yMin,
  511. ctx = glyphBgCanvas.getContext('2d');
  512.  
  513. glyphScale = Math.min(glyphW/(head.xMax - head.xMin), glyphH/maxHeight);
  514. glyphSize = glyphScale * font.unitsPerEm;
  515. glyphBaseline = glyphMargin + glyphH * head.yMax / maxHeight;
  516.  
  517. function hline(text, yunits) {
  518. let ypx = glyphBaseline - yunits * glyphScale;
  519. ctx.fillText(text, 2, ypx + 3);
  520. ctx.fillRect(80, ypx, w, 1);
  521. }
  522.  
  523. ctx.clearRect(0, 0, w, h);
  524. ctx.fillStyle = glyphRulerColor;
  525. hline('Baseline', 0);
  526. hline('yMax', font.tables.head.yMax);
  527. hline('yMin', font.tables.head.yMin);
  528. hline('Ascender', font.tables.hhea.ascender);
  529. hline('Descender', font.tables.hhea.descender);
  530. hline('Typo Ascender', font.tables.os2.sTypoAscender);
  531. hline('Typo Descender', font.tables.os2.sTypoDescender);
  532. }
  533.  
  534. function onFontLoaded(font) {
  535. let indx, link, lastIndex,
  536. w = cellWidth - cellMarginLeftRight * 2,
  537. h = cellHeight - cellMarginTop - cellMarginBottom,
  538. head = font.tables.head,
  539. maxHeight = head.yMax - head.yMin,
  540. pagination = document.getElementById('gfp-pagination'),
  541. fragment = document.createDocumentFragment(),
  542. numPages = Math.ceil(font.numGlyphs / cellCount);
  543.  
  544. fontScale = Math.min(w/(head.xMax - head.xMin), h/maxHeight);
  545. fontSize = fontScale * font.unitsPerEm;
  546. fontBaseline = cellMarginTop + h * head.yMax / maxHeight;
  547. pagination.innerHTML = '';
  548.  
  549. for (indx = 0; indx < numPages; indx++) {
  550. link = document.createElement('span');
  551. lastIndex = Math.min(font.numGlyphs - 1, (indx + 1) * cellCount - 1);
  552. link.textContent = indx * cellCount + '-' + lastIndex;
  553. link.id = 'gfp-p' + indx;
  554. link.addEventListener('click', pageSelect, false);
  555. fragment.appendChild(link);
  556. // A white space allows to break very long lines into multiple lines.
  557. // This is needed for fonts with thousands of glyphs.
  558. fragment.appendChild(document.createTextNode(' '));
  559. }
  560. pagination.appendChild(fragment);
  561.  
  562. displayFontData();
  563. initGlyphDisplay();
  564. displayGlyphPage(0);
  565. displayGlyph(-1);
  566. displayGlyphData(-1);
  567. }
  568.  
  569. function cellSelect(event) {
  570. if (!font) { return; }
  571. let firstGlyphIndex = pageSelected * cellCount,
  572. cellIndex = +event.target.id.replace('gfp-g', ''),
  573. glyphIndex = firstGlyphIndex + cellIndex;
  574. if (glyphIndex < font.numGlyphs) {
  575. displayGlyph(glyphIndex);
  576. displayGlyphData(glyphIndex);
  577. }
  578. }
  579.  
  580. function prepareGlyphList() {
  581. let indx, canvas,
  582. marker = document.getElementById('gfp-glyph-list-end'),
  583. parent = marker.parentElement;
  584. for (indx = 0; indx < cellCount; indx++) {
  585. canvas = document.createElement('canvas');
  586. canvas.width = cellWidth;
  587. canvas.height = cellHeight;
  588. canvas.className = 'gfp-item ghd-invert';
  589. canvas.id = 'gfp-g' + indx;
  590. canvas.addEventListener('click', cellSelect, false);
  591. enableHighDPICanvas(canvas);
  592. parent.insertBefore(canvas, marker);
  593. }
  594. }
  595.  
  596. })();