GitHub Font Preview

A userscript that adds a font file preview

当前为 2016-07-21 提交的版本,查看 最新版本

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