GitHub Font Preview

A userscript that adds a font file preview

当前为 2017-03-25 提交的版本,查看 最新版本

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