summaryrefslogtreecommitdiff
path: root/docs/glyphs/glyphs.js
diff options
context:
space:
mode:
Diffstat (limited to 'docs/glyphs/glyphs.js')
-rw-r--r--docs/glyphs/glyphs.js757
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