GitHub Font Preview

A userscript that adds a font file preview

目前為 2021-02-21 提交的版本,檢視 最新版本

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