GitHub Font Preview

A userscript that adds a font file preview

目前為 2018-05-10 提交的版本,檢視 最新版本

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