diff options
author | Rasmus Andersson <rasmus@notion.se> | 2017-08-28 12:36:29 +0300 |
---|---|---|
committer | Rasmus Andersson <rasmus@notion.se> | 2017-08-28 12:36:29 +0300 |
commit | 4f790d0ae83d490e5b06563bc42f9bc5d633c8a9 (patch) | |
tree | ea579516603323d56630622696f72ef1328abccd /docs/glyphs/glyphs.js | |
parent | f582d7205955af3e3bdd04d37797eca1f29e613a (diff) | |
download | inter-4f790d0ae83d490e5b06563bc42f9bc5d633c8a9.tar.xz |
website
Diffstat (limited to 'docs/glyphs/glyphs.js')
-rw-r--r-- | docs/glyphs/glyphs.js | 757 |
1 files changed, 757 insertions, 0 deletions
diff --git a/docs/glyphs/glyphs.js b/docs/glyphs/glyphs.js new file mode 100644 index 000000000..d2212424b --- /dev/null +++ b/docs/glyphs/glyphs.js @@ -0,0 +1,757 @@ +// constants +var kSVGScale = 0.1 // how bmuch metrics are scaled in the SVGs +var kGlyphSize = 346 // at kSVGScale. In sync with CSS and SVGs +var kUPM = 2816 + +if (!isMac) { + Array.prototype.forEach.call(document.querySelectorAll('kbd'), function(e) { + if (e.innerText == '\u2318') { + e.innerText = 'Ctrl' + } + }) +} + +function httpget(url, cb) { + var req = new XMLHttpRequest(); + req.addEventListener("load", function() { + cb(this.responseText, this) + }) + req.open("GET", url) + req.send() +} + +function fetchGlyphInfo(cb) { + httpget("../lab/glyphinfo.json", function(res) { + cb(JSON.parse(res).glyphs) + }) +} + +function fetchMetrics(cb) { + httpget("metrics.json", function(res) { + cb(JSON.parse(res)) + }) +} + +var glyphInfo = null +var glyphMetrics = null + +function initMetrics(data) { + // metrics.json uses a compact format with numerical glyph indentifiers + // in order to minimize file size. + // We expand the glyph IDs to glyph names here. + var nameIds = data.nameids + // console.log(data) + + var metrics = {} + var metrics0 = data.metrics + Object.keys(metrics0).forEach(function (id) { + var name = nameIds[id] + var v = metrics0[id] + // v : [width, advance, left, right] + metrics[name] = { + width: v[0], + advance: v[1], + left: v[2], + right: v[3] + } + }) + + var kerningLeft = {} // { glyphName: {rightGlyphName: kerningValue, ...}, ... } + var kerningRight = {} + data.kerning.forEach(function (t) { + // each entry looks like this: + // [leftGlyphId, rightGlyphId, kerningValue] + var leftName = nameIds[t[0]] + if (!leftName) { + console.error('nameIds missing', t[0]) + } + var rightName = nameIds[t[1]] + if (!rightName) { + console.error('nameIds missing', t[1]) + } + var kerningValue = t[2] + + var lm = kerningLeft[leftName] + if (!lm) { + kerningLeft[leftName] = lm = {} + } + lm[rightName] = kerningValue + + var rm = kerningRight[rightName] + if (!rm) { + kerningRight[rightName] = rm = {} + } + rm[leftName] = kerningValue + }) + + glyphMetrics = { + metrics: metrics, + kerningLeft: kerningLeft, + kerningRight: kerningRight, + } + // console.log('glyphMetrics', glyphMetrics) +} + +function fetchAll(cb) { + var i = 0 + var res = {} + var decr = function(){ + if (--i == 0) { + cb(res) + } + } + i++ + fetchGlyphInfo(function(r){ glyphInfo = r; decr() }) + i++ + fetchMetrics(function(r){ + initMetrics(r) + decr() + }) +} + +fetchAll(render) + + +var styleSheet = document.styleSheets[document.styleSheets.length-1] +var glyphRule, baselineRule, zeroWidthAdvRule +var currentScale = 0 +var defaultSingleScale = 1 +var currentSingleScale = 1 +var defaultGridScale = 0.4 +var currentGridScale = defaultGridScale +var glyphs = document.getElementById('glyphs') + +function updateLayoutAfterChanges() { + var height = parseInt(window.getComputedStyle(glyphs).height) + var margin = height - (height * currentScale) + // console.log('scale:', currentScale, 'height:', height, 'margin:', margin) + if (currentScale > 1) { + glyphs.style.marginBottom = null + } else { + glyphs.style.marginBottom = -margin + 'px' + } +} + +function setScale(scale) { + if (queryString.iframe !== undefined) { + scale = 0.11 + } else if (queryString.g) { + scale = Math.min(Math.max(1, scale), 3) + } else { + scale = Math.min(Math.max(0.05, scale), 3) + } + + if (currentScale == scale) { + return + } + + currentScale = scale + if (queryString.g) { + currentSingleScale = scale + } else { + currentGridScale = scale + } + + var hairline = Math.ceil(1 / window.devicePixelRatio / scale) + var spacing = Math.ceil(6 / scale) + + var s = glyphs.style + if (queryString.g || queryString.iframe !== undefined) { + s.paddingLeft = null + } else { + s.paddingLeft = spacing + 'px' + } + s.width = (100 / scale) + '%' + s.transform = 'scale(' + scale + ')' + + if (!glyphRule) { + glyphRule = styleSheet.cssRules[styleSheet.insertRule('#glyphs .glyph {}', styleSheet.cssRules.length)] + baselineRule = styleSheet.cssRules[styleSheet.insertRule('#glyphs .glyph .baseline {}', styleSheet.cssRules.length)] + zeroWidthAdvRule = styleSheet.cssRules[styleSheet.insertRule('#glyphs .glyph.zero-width .advance {}', styleSheet.cssRules.length)] + } + + if (queryString.g) { + glyphRule.style.marginRight = null + glyphRule.style.marginBottom = null + } else { + glyphRule.style.marginRight = Math.ceil(6 / scale) + 'px'; + glyphRule.style.marginBottom = Math.ceil(6 / scale) + 'px'; + if (queryString.iframe !== undefined) { + glyphRule.style.marginBottom = Math.ceil(16 / scale) + 'px'; + } + } + baselineRule.style.height = hairline + 'px' + zeroWidthAdvRule.style.borderWidth = (hairline) + 'px' + + updateLayoutAfterChanges() + requestAnimationFrame(updateLayoutAfterChanges) +} + + +function encodeQueryString(q) { + return Object.keys(q).map(function(k) { + if (k) { + var v = q[k] + return encodeURIComponent(k) + (v ? '=' + encodeURIComponent(v) : '') + } + }).filter(function(s) { return !!s }).join('&') +} + +function parseQueryString(qs) { + var q = {} + qs.replace(/^\?/,'').split('&').forEach(function(c) { + var kv = c.split('=') + var k = decodeURIComponent(kv[0]) + if (k) { + q[k] = kv[1] ? decodeURIComponent(kv[1]) : null + } + }) + return q +} + + +var queryString = parseQueryString(location.search) +var glyphNameEl = null +var baseTitle = document.title +var flippedLocationHash = false + +var singleInfo = document.querySelector('#single-info') +singleInfo.parentElement.removeChild(singleInfo) +singleInfo.style.display = 'block' + +function updateLocation() { + queryString = parseQueryString(location.search) + + // var glyphs = document.getElementById('glyphs') + var h1 = document.querySelector('h1') + if (queryString.g) { + if (!glyphNameEl) { + glyphNameEl = document.createElement('span') + glyphNameEl.className = 'glyph-name' + } + document.title = queryString.g + ' – ' + baseTitle + glyphNameEl.innerText = queryString.g + h1.appendChild(glyphNameEl) + document.body.classList.add('single') + setScale(currentSingleScale) + } else { + document.title = baseTitle + if (glyphNameEl) { + h1.removeChild(glyphNameEl) + } + document.body.classList.remove('single') + setScale(currentGridScale) + } + + document.querySelector('.row.intro').style.display = ( + queryString.iframe !== undefined ? 'none' : null + ) + if (queryString.iframe !== undefined) { + document.body.classList.add('iframe') + } else { + document.body.classList.remove('iframe') + } + + render() +} + +window.onpopstate = function(ev) { + updateLocation() +} + +function navto(url) { + history.pushState({}, "Glyphs", url) + updateLocation() + window.scrollTo(0,0) +} + +function wrapIntLink(a) { + a.addEventListener('click', function(ev) { + if (!ev.metaKey && !ev.ctrlKey && !ev.shiftKey) { + ev.preventDefault() + navto(a.href) + } + }) +} + +wrapIntLink(document.querySelector('h1 > a')) + + +// keep refs to svgs so we don't have to refcount while using +var svgRepository = {} +;(function(){ + var svgs = document.getElementById('svgs'), svg, name + for (var i = 0; i < svgs.children.length; ++i) { + svg = svgs.children[i] + name = svg.id.substr(4) // strip "svg-" prefix + svgRepository[name] = svg + } +})() + + +// Maps glyphname to glyphInfo. Only links to first found entry for a flyph. +var glyphInfoMap = {} +var needsUpdateGlyphInfoMap = true + + +function render() { + if (!glyphInfo) { + return + } + + var rootEl = document.getElementById('glyphs') + rootEl.style.display = 'none' + rootEl.innerText = '' + + // glyphInfo: { + // "glyphs": [ + // [name :string, unicode? :int|null, unicodeName? :string, color? :string|null], + // ["A", 65, "LATIN CAPITAL LETTER A", "#dbeaf7"], + // ... + // ], + // } + // + // Note: Glyph names might appear multiple times (always adjacent) when a glyph is + // represented by multiple Unicode code points. For example: + // + // ["Delta", 916, "GREEK CAPITAL LETTER DELTA"], + // ["Delta", 8710, "INCREMENT"], + // + + var glyphs = glyphInfo + var singleGlyph = null + var lastGlyphEl = null + var lastGlyphName = '' + + glyphInfo.forEach(function(g, i) { + var name = g[0] + + if (needsUpdateGlyphInfoMap && !glyphInfoMap[name]) { + glyphInfoMap[name] = g + } + + if (queryString.g && name != queryString.g) { + // ignore + return + } + + var glyph = renderGlyphGraphicG(g, lastGlyphName, lastGlyphEl, singleGlyph) + + if (glyph) { + rootEl.appendChild(glyph.element) + lastGlyphEl = glyph.element + lastGlyphName = name + if (queryString.g) { + singleGlyph = glyph + } + } + }) + + needsUpdateGlyphInfoMap = false + + if (singleGlyph) { + renderSingleInfo(singleGlyph) + rootEl.appendChild(singleInfo) + } + + rootEl.style.display = null + updateLayoutAfterChanges() +} + + +function renderGlyphGraphic(glyphName) { + var g = glyphInfoMap[glyphName] + return g ? renderGlyphGraphicG(g) : null +} + + +function renderGlyphGraphicG(g, lastGlyphName, lastGlyphEl, singleGlyph) { + var name = g[0] + var names, glyph + var svg = svgRepository[name] + + if (!svg) { + // ignore + return null + } + + var metrics = glyphMetrics.metrics[name] + if (!metrics) { + console.error('missing metrics for', name) + } + + var info = { + name: name, + unicode: g[1], + unicodeName: g[2], + color: g[3], + + // These are all in 1:1 UPM (not scaled) + advance: metrics.advance, + left: metrics.left, + right: metrics.right, + width: metrics.width, + + element: null, + } + + if (name == lastGlyphName) { + // additional Unicode code point for same glyph + glyph = lastGlyphEl + names = glyph.querySelector('.names') + names.innerText += ',' + if (info.unicode) { + var ucid = ' U+' + fmthex(info.unicode) + names.innerText += ' U+' + fmthex(info.unicode) + if (!queryString.g) { + glyph.title += ucid + } + } + if (info.unicodeName) { + names.innerText += ' ' + info.unicodeName + if (!queryString.g) { + glyph.title += ' (' + info.unicodeName + ')' + } + } + + if (queryString.g) { + if (singleGlyph) { + if (!singleGlyph.alternates) { + singleGlyph.alternates = [] + } + singleGlyph.alternates.push(info) + } else { + throw new Error('alternate glyph UC, but appears first in glyphinfo data') + } + } + + return + } + + // console.log('svg for', name, svg.width.baseVal.value, '->', svg, '\n', info) + + glyph = document.createElement('a') + glyph.className = 'glyph' + glyph.href = '?g=' + encodeURIComponent(name) + wrapIntLink(glyph) + + info.element = glyph + + if (!queryString.g) { + glyph.title = name + } + + var baseline = document.createElement('div') + baseline.className = 'baseline' + glyph.appendChild(baseline) + + names = document.createElement('div') + names.className = 'names' + names.innerText = name + if (info.unicode) { + var ucid = ' U+' + fmthex(info.unicode) + names.innerText += ' U+' + fmthex(info.unicode, 4) + if (!queryString.g) { + glyph.title += ucid + } + } + if (info.unicodeName) { + names.innerText += ' ' + info.unicodeName + if (!queryString.g) { + glyph.title += ' (' + info.unicodeName + ')' + } + } + glyph.appendChild(names) + + var scaledAdvance = info.advance * kSVGScale + + if (scaledAdvance > kGlyphSize) { + glyph.style.width = scaledAdvance.toFixed(2) + 'px' + } + + var adv = document.createElement('div') + adv.className = 'advance' + glyph.appendChild(adv) + + adv.appendChild(svg) + svg.style.left = (info.left * kSVGScale).toFixed(2) + 'px' + + if (info.advance <= 0) { + glyph.className += ' zero-width' + } else { + adv.style.width = scaledAdvance.toFixed(2) + 'px' + } + + return info +} + + +function renderSingleInfo(g) { + var e = singleInfo + e.querySelector('.name').innerText = g.name + + function setv(el, name, textValue) { + el.querySelector('.' + name).innerText = textValue + } + + var unicode = e.querySelector('.unicode') + + function configureUnicodeView(el, g) { + setv(el, 'unicodeCodePoint', g.unicode ? 'U+' + fmthex(g.unicode, 4) : '–') + setv(el, 'unicodeName', g.unicodeName || '') + } + + // remove any previous alternate unicode list items + var rmli = unicode + while ((rmli = rmli.nextSibling)) { + if (rmli.nodeType == Node.ELEMENT_NODE) { + if (!rmli.parentElement || !rmli.classList.contains('unicode')) { + break + } + rmli.parentElement.removeChild(rmli) + } + } + + configureUnicodeView(unicode, g) + + if (g.alternates) { + var refnode = unicode.nextSibling + g.alternates.forEach(function(g) { + var li = unicode.cloneNode(true) + configureUnicodeView(li, g) + if (refnode) { + unicode.parentElement.insertBefore(li, unicode.nextSibling) + } else { + unicode.parentElement.appendChild(li) + } + }) + } + + e.querySelector('.advanceWidth').innerText = g.advance + e.querySelector('.marginLeft').innerText = g.left + e.querySelector('.marginRight').innerText = g.right + + var colorMark = e.querySelector('.colorMark') + if (g.color) { + colorMark.title = g.color.toUpperCase() + colorMark.style.background = g.color + colorMark.classList.remove('none') + } else { + colorMark.title = '(None)' + colorMark.style.background = null + colorMark.classList.add('none') + } + + var svg = svgRepository[g.name] + var svgFile = e.querySelector('.svgFile') + svgFile.download = g.name + '.svg' + svgFile.href = getSvgDataURI(svg) + + renderSingleKerning(g) +} + + +var cachedSVGDataURIs = {} + +function getSvgDataURI(svg) { + var cached = cachedSVGDataURIs[svg.id] + if (!cached) { + cached = 'data:image/svg+xml,' + svg.outerHTML.replace(/[\r\n]+/g, '') + cachedSVGDataURIs[svg.id] = cached + } + return cached +} + + +function pxround(n) { + return Math.round(n * 2) / 2 +} + + +function selectKerningPair(id, directly) { + Array.prototype.forEach.call( + document.querySelectorAll('.kernpair.selected'), + function(e) { + e.classList.remove('selected') + } + ) + var el = document.getElementById(id) + if (el) { + el.classList.add('selected') + if (!directly) { + el.scrollIntoViewIfNeeded() + } + } + history.replaceState({}, '', location.search + '#' + id) +} + + +// return true if some kerning was rendered +function renderSingleKerning(g) { + var kerningList = document.getElementById('kerning-list') + kerningList.style.display = 'none' + kerningList.innerText = '' + var thisSvg = svgRepository[g.name] + var thisSvgURI = getSvgDataURI(thisSvg) + + if (!thisSvg) { + kerningList.style.display = null + return false + } + + var kerningAsLeft = glyphMetrics.kerningLeft[g.name] || {} + var kerningAsRight = glyphMetrics.kerningRight[g.name] || {} + + var kerningAsLeftKeys = Object.keys(kerningAsLeft) + var kerningAsRightKeys = Object.keys(kerningAsRight) + + if (kerningAsLeftKeys.length == 0 && kerningAsRightKeys.length == 0) { + var p = document.createElement('p') + p.className = 'empty' + p.innerText = 'No kerning' + kerningList.appendChild(p) + kerningList.style.display = null + return false + } + + var lilGlyphSize = 128 + var lilScale = lilGlyphSize / kGlyphSize + + function mkpairGlyph(glyphName, side, kerningValue, svgURI) { + var metrics = glyphMetrics.metrics[glyphName] + var svgWidth = pxround(metrics.width * kSVGScale * lilScale) + var el = document.createElement('div') + + var leftMargin = metrics.left + var rightMargin = metrics.right + if (side == 'left') { + rightMargin += kerningValue + } else { + leftMargin += kerningValue + } + leftMargin = leftMargin * kSVGScale * lilScale + rightMargin = rightMargin * kSVGScale * lilScale + + el.className = side + el.style.backgroundImage = "url('" + svgURI + "')" + el.style.backgroundSize = svgWidth + 'px ' + lilGlyphSize + 'px' + el.style.width = svgWidth + 'px' + el.style.height = lilGlyphSize + 'px' + el.style.marginLeft = pxround(leftMargin) + 'px' + el.style.marginRight = pxround(rightMargin) + 'px' + return el + } + + function mkpairs(kerningInfo, selfName, self, side) { + var asLeftSide = side == 'left' + var idPrefix = asLeftSide ? 'kernl-' : 'kernr-' + var keys = Object.keys(kerningInfo) + var otherSide = asLeftSide ? 'right' : 'left' + + keys.forEach(function(glyphName) { + var kerningValue = kerningInfo[glyphName] + var otherSvg = svgRepository[glyphName] + + var pair = document.createElement('a') + pair.className = 'kernpair' + pair.title = ( + asLeftSide ? selfName + '/' + glyphName + ' ' + kerningValue : + glyphName + '/' + selfName + ' ' + kerningValue + ) + pair.id = idPrefix + glyphName + pair.href = '#' + pair.id + pair.onclick = function(ev) { + selectKerningPair(pair.id) + ev.preventDefault() + ev.stopPropagation() + } + + var otherEl = mkpairGlyph(glyphName, otherSide, kerningValue, getSvgDataURI(otherSvg)) + + if (asLeftSide) { + if (self.parentNode) { + pair.appendChild(self.cloneNode(true)) + } else { + pair.appendChild(self) + } + pair.appendChild(otherEl) + } else { + pair.appendChild(otherEl) + if (self.parentNode) { + pair.appendChild(self.cloneNode(true)) + } else { + pair.appendChild(self) + } + } + + var kern = document.createElement('div') + kern.className = 'kern ' + (kerningValue < 0 ? 'neg' : 'pos') + + var absKernVal = Math.abs(kerningValue) + var kernWidth = Math.max(1, pxround(absKernVal * kSVGScale * lilScale)) + kern.style.width = kernWidth + 'px' + + var leftMetrics = glyphMetrics.metrics[asLeftSide ? selfName : glyphName] + var leftAdvance = leftMetrics.advance + kern.style.left = pxround((leftAdvance - absKernVal) * kSVGScale * lilScale) + 'px' + pair.appendChild(kern) + + var label = document.createElement('div') + label.className = 'label' + label.innerText = kerningValue + kern.appendChild(label) + + kerningList.appendChild(pair) + }) + + return keys.length != 0 + } + + var selfLeft = mkpairGlyph(g.name, 'left', 0, thisSvgURI) + var selfRight = mkpairGlyph(g.name, 'right', 0, thisSvgURI) + + if (mkpairs(kerningAsLeft, g.name, selfLeft, 'left')) { + var div = document.createElement('div') + div.className = 'divider' + kerningList.appendChild(div) + } + + mkpairs(kerningAsRight, g.name, selfRight, 'right') + + if (location.hash && location.hash.indexOf('#kern') == 0) { + var id = location.hash.substr(1) + setTimeout(function(){ + selectKerningPair(id) + },1) + } + + kerningList.style.display = null + return true +} + + +function fmthex(cp, minWidth) { + let s = cp.toString(16).toUpperCase() + while (s.length < minWidth) { + s = '0' + s + } + return s +} + + +document.addEventListener('keydown', function(ev) { + if (!queryString.g && (ev.metaKey || ev.ctrlKey)) { + if (ev.keyCode == 187 || ev.key == '+') { + setScale(parseFloat((currentScale + 0.1).toFixed(2))) + ev.preventDefault() + } else if (ev.keyCode == 189 || ev.key == '-') { + setScale(parseFloat((currentScale - 0.1).toFixed(2))) + ev.preventDefault() + } else if (ev.keyCode == 48 || ev.key == '0') { + setScale(queryString.g ? defaultSingleScale : defaultGridScale) + ev.preventDefault() + } + } +}) + +updateLocation()
\ No newline at end of file |