diff options
author | Rasmus Andersson <rasmus@notion.se> | 2018-02-20 12:38:51 +0300 |
---|---|---|
committer | Rasmus Andersson <rasmus@notion.se> | 2018-02-20 12:38:51 +0300 |
commit | 9f367901ef4e6df00eb786ac99fcdc21ed5e69f0 (patch) | |
tree | 44ea3e320dd96f541ed94f5a032bff70050afc51 /docs/dynmetrics/index.html | |
parent | 761638ef42ec443429889c548b758c06a516839a (diff) | |
download | inter-9f367901ef4e6df00eb786ac99fcdc21ed5e69f0.tar.xz |
website: major update
Diffstat (limited to 'docs/dynmetrics/index.html')
-rw-r--r-- | docs/dynmetrics/index.html | 522 |
1 files changed, 522 insertions, 0 deletions
diff --git a/docs/dynmetrics/index.html b/docs/dynmetrics/index.html new file mode 100644 index 000000000..21bd95d82 --- /dev/null +++ b/docs/dynmetrics/index.html @@ -0,0 +1,522 @@ +--- +layout: default +title: Dynamic Metrics +--- +{% + +capture url_root + %}{% if site.safe == false %}/{% else %}/inter/{% endif +%}{% +endcapture %}{% + +for file in site.static_files %}{% + assign _path = file.path | remove_first: "/inter" %}{% + if _path == "/dynmetrics/index.css" %}{% + assign index_css_v = file.modified_time | date: "%Y%m%d%H%M%S" %}{% + elsif _path == "/res/bindings.js" %}{% + assign bindings_js_v = file.modified_time | date: "%Y%m%d%H%M%S" %}{% + elsif _path == "/res/graphplot.js" %}{% + assign graphplot_js_v = file.modified_time | date: "%Y%m%d%H%M%S" %}{% + endif %}{% +endfor + +%} +<link rel="stylesheet" href="index.css?v={{ index_css_v }}"> +<script src="{{url_root}}res/bindings.js?v={{ bindings_js_v }}"></script> +<script src="{{url_root}}res/graphplot.js?v={{ graphplot_js_v }}"></script> + +<div class="row first"><div> + <h1>Dynamic Metrics</h1> + <p> + There's of course no absolute right or wrong when it comes to expressing + yourself with typography, but Inter UI Dynamic Metrics provides guidelines + for <em>good</em> typography. + You simply provide the optical font size, and the tracking and leading + (letter spacing and line height) will be calculated using the following + formula: + </p> + <p> + <formula> + tracking = + <const>a</const> + <const>b</const> × + <const>e</const><sup>(<const>c</const> × fontSize)</sup> + </formula> + <formula> + leading = <const>l</const> × fontSize + </formula> + <formula> + <const>e</const> ≈ <num>2.718</num> + </formula> + </p> + <p class="small-window"> + <small>View this on a larger screen to enable interactive control.</small> + </p> +</div></div> + +<!-- <div class="row small-window"><div> + Hello +</div></div> --> + +<div class="white full-width row with-sidebar"> + + <div class="samples"> + <p contenteditable class="sample"> + <span class="di"><span></span></span> + <span class="info" + title="size, tracking, (ideal tracking) — progress bar shows distance from ideal" + >15 0.0 ( 0.0 )</span> + Such a riot of sea and wind strews the whole extent of beach with whatever has been lost or thrown overboard, or torn out of sunken ships. Many a man has made a good week’s work in a single day by what he has found while walking along the Beach when the surf was down. + </p> + </div> + + <div class="sidebar controls"> + <div class="control"> + <label title="Number of ideal matches">ni</label> + <input title="Number of ideal matches" type="number" readonly data-binding="ideal-count"> + <label title="Distance from ideal">di</label> + <input title="Distance from ideal" type="number" class="wide" readonly data-binding="ideal-distance"> + </div> + <div class="control"> + <img title="Style" class="icon" src="../samples/icons/style.svg"> + <select data-binding="style"> + <option value="regular" default>Regular</option> + <option value="italic">Italic</option> + <option value="medium" default>Medium</option> + <option value="medium-italic">Medium Italic</option> + <option value="bold" default>Bold</option> + <option value="bold-italic">Bold Italic</option> + <option value="black" default>Black</option> + <option value="black-italic">Black Italic</option> + </select> + </div> + <div class="control"> + <img title="Base tracking" class="icon" src="../samples/icons/letter-spacing.svg"> + <input type="range" min="-0.05" max="0.06" step="0.001" data-binding="base-tracking"> + <input type="number" min="-0.15" max="1" step="0.001" data-binding="base-tracking"> + </div> + <hr> + <div class="control"> + <label title="Constant a">a</label> + <input type="range" min="-0.05" max="0" step="0.001" data-binding="var-a"> + <input type="number" min="-0.05" max="0" step="0.0001" data-binding="var-a"> + </div> + <div class="control"> + <label title="Constant b">b</label> + <input type="range" min="0" max="1" step="0.01" data-binding="var-b"> + <input type="number" min="0" max="1" step="0.001" data-binding="var-b"> + </div> + <div class="control"> + <label title="Constant c">c</label> + <input type="range" min="-1" max="0" step="0.01" data-binding="var-c"> + <input type="number" min="-1" max="0" step="0.001" data-binding="var-c"> + </div> + <hr> + <div class="control"> + <label title="Constant l controls line height">l</label> + <input type="range" min="1" max="2" step="0.1" data-binding="var-l"> + <input type="number" min="1" max="2" step="0.1" data-binding="var-l"> + </div> + <hr class="without-bottom-margin"> + <canvas class="graphplot">Canvas not Supported</canvas> + <hr class="without-top-margin"> + <h3>Ideal values</h3> + <textarea id="ideal-values"></textarea> + <hr class="without-top-margin"> + </div> + +</div> + + +<script type="text/javascript">(function(){ + +// internal variables that are user-controllable, but are not part of +// Dynamic Metrics +var baseTracking = 0 +var weightClass = 400 + +// Ideal values designed one by one, by hand. +// font size => tracking +var idealValues = {} +var idealValuesList = [] +var idealValuesTextArea = $('textarea#ideal-values') + +function setIdealValues(valueMap) { + idealValues = valueMap + idealValuesList = [] + Object.keys(idealValues).forEach(function(fontSize) { + idealValuesList.push([fontSize, idealValues[fontSize]]) + }) + if (idealValuesTextArea.value == '') { + idealValuesTextArea.value = idealValuesList.map(function(t){ + return t[0] + '\t' + t[1] + }).join('\n') + } +} + +function parseValues(s) { + var values = {} + s = s.replace(/^[\s\r\t\n]+|[\s\r\t\n]+$/g, '') // trim whitespace + s.split(/\s*\r?\n\s*/).map(function(line) { + var t = line.split(/\s+/) + if (t.length == 2) { + t[0] = parseInt(t[0]) + t[1] = parseFloat(t[1]) + values[t[0]] = t[1] + } + }) + return values +} + +setIdealValues({ + // set-2018-02-20 + 6: 0.055, + 7: 0.044, + 8: 0.034, + 9: 0.024, + 10: 0.018, + 11: 0.012, + 12: 0.007, + 13: 0.003, + 14: 0.001, + 15: -0.002, + 16: -0.004, + 17: -0.006, + 18: -0.008, + 20: -0.01, + 24: -0.013, +}) + +// setIdealValues({ +// // set-2018-02-19 +// 6: 0.059, +// 7: 0.043, +// 8: 0.032, +// 9: 0.022, +// 10: 0.014, +// 11: 0.009, +// 12: 0.004, +// 13: 0, +// 14: -0.003, +// 15: -0.005, +// 16: -0.0075, +// 17: -0.0084, +// 18: -0.0095, +// 20: -0.012, +// 24: -0.014, +// }) + +// setIdealValues({ +// // set-2018-02-18 +// 6: 0.06, +// 7: 0.04, +// 8: 0.03, +// 9: 0.02, +// 10: 0.01, +// 11: 0.005, +// 12: 0.002, +// 13: 0.001, +// 14: 0, +// 15: -0.002, +// 16: -0.005, +// 17: -0.007, +// 18: -0.009, +// 20: -0.011, +// 24: -0.011, +// }) + + +// Variables for constants involved in Dynamic Metrics +var a = -0.013 +// -0.0119 +// -0.0101 +// -0.0092 +// -0.012 +// -0.013 + +var b = 0.23 +// 0.455 +// 0.421 +// 0.26 +// 0.23 +// 0.251 + +var c = -0.21 +// -0.29 +// -0.23 +// -0.21 +// -0.222 + +// a = -0.012; b = 0.23; c = -0.21; // di=0.002514 on set-2018-02-18 +// a = -0.013; b = 0.251; c = -0.222 // di=0.001742 on set-2018-02-18 +// a = -0.015; b = 0.283; c = -0.23; // di=0.00221 on set-2018-02-18 +// a = -0.0149; b = 0.298; c = -0.23; // di=0.000484 on set-2018-02-19 +a = -0.016; b = 0.21; c = -0.18; // di=0.000532 on set-2018-02-20 + + +// these have a short di, but stray far away from larger font sizes +// a = -0.0077 +// b = 0.377 + +var l = 1.4 + + +// InterUIDynamicTracking takes the font size in points or pixels and returns +// the compensating tracking in EM. +// +function InterUIDynamicTracking(fontSize, weightClass) { + // if (!weightClass) { -- currently unused + // weightClass = 400 + // } + // + // y = -0.01021241 + 0.3720623 * e ^ (-0.2808687 * x) + // [6-35] 0.05877 .. -0.0101309 (13==0; stops growing around 30-35) + // See https://gist.github.com/rsms/8efdbca5f8145a584ed08a7c3d6e5788 + // + return a + b * Math.pow(Math.E, c * fontSize) + // [6 - 38] 0.05798 .. -0.01099 (midpoint = 12.533) + + // y = 0.025 - (ln(x) * 0.01) + // return 0.025 - Math.log(fontSize) * 0.01 +} + + +function InterUIDynamicLineHeight(fontSize, weightClass) { + return Math.round(fontSize * l) +} + + +var NPOS = Number.MAX_SAFE_INTEGER + +function Sample(fontSize) { + this.rootEl = sampleTemplate.cloneNode(true) + this.infoEl = $('.info', this.rootEl) + this.diEl = $('.di', this.rootEl) + this.diEl.classList.add('unavailable') + this.style = this.rootEl.style + this.maxBoxWidth = 0 + this.fontSize = 0 + this.tracking = 0 + this.lineHeight = 0 + this.idealTracking = NPOS + this.matchesIdeal = false + this.setFontSize(fontSize) + this.render() +} + +Sample.prototype.idealDistance = function(fontSize) { + // returns the distance from the ideal (or NPOS if no "ideal" data available) + if (this.idealTracking == NPOS) { + return NPOS + } + return ( + Math.max(this.tracking, this.idealTracking) - + Math.min(this.tracking, this.idealTracking) + ) +} + +Sample.prototype.setFontSize = function(fontSize) { + this.fontSize = fontSize + this.tracking = baseTracking + InterUIDynamicTracking(fontSize, weightClass) + this.lineHeight = InterUIDynamicLineHeight(fontSize, weightClass) + this.maxBoxWidth = Math.round(fontSize * (this.tracking + 1) * 25) + + var idealTracking = idealValues[this.fontSize] + if (idealTracking === undefined) { + if (this.idealTracking != NPOS) { + this.diEl.classList.add('unavailable') + } + this.idealTracking = NPOS + this.matchesIdeal = false + } else { + if (this.idealTracking == NPOS) { + this.diEl.classList.remove('unavailable') + } + this.idealTracking = idealTracking + // match to a precision of 3 + this.matchesIdeal = this.tracking.toFixed(3) == idealTracking.toFixed(3) + var di = 1 + if (this.matchesIdeal) { + this.diEl.classList.add('match') + } else { + this.diEl.classList.remove('match') + di = 1 - Math.min(1, this.idealDistance() * 50) + } + this.diEl.firstChild.style.width = (di * this.maxBoxWidth) + 'px' + } +} + +Sample.prototype.render = function() { + this.style.fontSize = this.fontSize + 'px' + this.style.letterSpacing = this.tracking + 'em' + this.style.lineHeight = this.lineHeight + 'px' + + var SP = '\u00A0\u00A0\u00A0' + this.infoEl.innerText = ( + this.fontSize + + SP + this.tracking.toFixed(3) + + // SP + this.lineHeight.toFixed(3) + + ( + this.idealTracking != NPOS ? ( + SP + '( ' + this.idealTracking + + (this.matchesIdeal ? ' \u2713' : '') + + ' )' + ) : '' + ) + ) + + this.style.maxWidth = this.maxBoxWidth + 'px' +} + + +var bindings = new Bindings() +var sampleTemplate +var samples = [] // Sample[] +var graph = new GraphPlot($('canvas.graphplot')) +graph.setOrigin(-0.2, 0.8) +graph.setScale(0.049, 5) + +function addChildren(targetNode, children) { + targetNode.style.visibility = 'hidden' + var i = 0; + for (; i < children.length; i++) { + targetNode.appendChild(children[i]) + } + targetNode.style.visibility = null +} + +function initSamples() { + var samplesEl = $('.samples') + sampleTemplate = $('.sample', samplesEl) + samplesEl.removeChild(sampleTemplate) + + // small and medium sizes + var i, fontSize = 6, endFontSize = 16 + for (; fontSize <= endFontSize; fontSize++) { + samples.push(new Sample(fontSize)) + } + + // a few select large samples + samples.push(new Sample(17)) + samples.push(new Sample(18)) + samples.push(new Sample(20)) + samples.push(new Sample(24)) + samples.push(new Sample(30)) + samples.push(new Sample(40)) + + // add to dom in one go + addChildren(samplesEl, samples.map(function(s) { return s.rootEl })) +} + + +function updateSample(sample) { + sample.setFontSize(sample.fontSize) // updates derived values + sample.render() +} + +function updateSamples() { + samples.forEach(updateSample) + updateIdealMatches() + updateGraphPlot() +} + +function updateIdealMatches() { + // ideal-distance + var idealCount = 0 + var distance = 0 + var ndistances = 0 + samples.forEach(function(sample) { + if (sample.matchesIdeal) { + idealCount++ + } + var di = sample.idealDistance() + if (di != NPOS) { + distance += di + ndistances++ + } + }) + + distance = distance / ndistances + + bindings.setValue('ideal-distance', distance.toFixed(6)) + bindings.setValue('ideal-count', idealCount) +} + +function updateGraphPlot() { + graph.clear() + graph.plotLine(idealValuesList, '#0d3') + graph.plotf(function(x) { + return InterUIDynamicTracking(x, weightClass) + }) +} + + +bindings.configure('base-tracking', 0, 'float', function (tracking) { + baseTracking = tracking + updateSamples() +}) + +bindings.configure('var-a', a, 'float', function (v) { + a = v + updateSamples() +}) + +bindings.configure('var-b', b, 'float', function (v) { + b = v + updateSamples() +}) + +bindings.configure('var-c', c, 'float', function (v) { + c = v + updateSamples() +}) + +bindings.configure('var-l', l, 'float', function (v) { + l = v + updateSamples() +}) + +bindings.configure('ideal-count', 0, 'int', function (v) {}) +bindings.configure('ideal-distance', 0, 'float', function (v) {}) + +bindings.configure('style', null, null, function (style) { + var cl = $('.samples').classList + cl.remove('font-style-regular') + cl.remove('font-style-italic') + cl.remove('font-style-medium') + cl.remove('font-style-medium-italic') + cl.remove('font-style-bold') + cl.remove('font-style-bold-italic') + cl.remove('font-style-black') + cl.remove('font-style-black-italic') + cl.add('font-style-' + style) + weightClass = ( + style.indexOf('black') != -1 ? 900 : + style.indexOf('bold') != -1 ? 700 : + style.indexOf('medium') != -1 ? 500 : + 400 + ) + updateSamples() +}) + + +bindings.bindAllInputs('.control input') +bindings.bindAllInputs('.control select') + +// double-click base tracking to reset +$('input[type="range"][data-binding="base-tracking"]').addEventListener( + 'dblclick', + function(ev) { bindings.setValue('base-tracking', 0) } +) + +// allow editing of ideal values +idealValuesTextArea.addEventListener('input', function(ev) { + setIdealValues(parseValues(idealValuesTextArea.value)) + updateSamples() +}) + + +// start +initSamples() +updateSamples() + +})();</script> |