1
1
mirror of https://github.com/rsms/inter.git synced 2024-12-02 07:46:55 +03:00
inter/docs/lab/index.html
Rasmus Andersson c57a2aa4f7 Release v3.19
2021-06-18 14:26:55 -07:00

1281 lines
42 KiB
HTML

<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>lab</title>
<link rel="icon" type="image/png" href="favicon.png" />
<link href="../inter.css" rel="stylesheet">
<script type="text/javascript">
// dark color scheme by default?
let defaultColorSceme = "light"
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
defaultColorSceme = "dark"
if (document.location.search.indexOf("invert-colors") == -1) {
document.documentElement.classList.add("color-scheme-dark")
}
}
// Safari?
(function(u){ if (
u.indexOf('Safari/') != -1 &&
u.indexOf('Chrome/') == -1 &&
u.indexOf('Chromium/') == -1
) {
document.documentElement.classList.add('safari')
} })(navigator.userAgent);
</script>
<script type="text/javascript" src="samples.js?v=3.19"></script>
<script type="text/javascript" src="build-version.js?v=3.19"></script>
<script type="text/javascript" src="font-files.js?v=3.19"></script>
<link href="lab.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Roboto:400,400i,500,500i,700,700i,900,900i&subset=cyrillic,cyrillic-ext,greek,greek-ext,latin-ext,vietnamese">
</head>
<body class="init-anim">
<div id="sidebar-button"></div>
<div class="options">
<div class="flex-x">
<label title="Use variable font instead of constant font files">
<span>VF</span>
<input type="checkbox" name="varfont">
</label>
<label class="italic-setting" title="Italic">
<span>I</span>
<input type="checkbox" name="italic">
</label>
<label title="Swap color scheme">
<b></b>
<input type="checkbox" name="invert-colors">
</label>
<label title="Draw background behind samples">
<b></b>
<input type="checkbox" name="draw-sample-bg">
</label>
</div>
<label class="label-and-value">
<span>Size:</span>
<input type="range" value="22" step="1" min="4" max="128" name="_sizeRange">
<input type="number" value="22" step="1" min="4" max="1024" name="size">
<!-- <note><span class="unit">dp</span></note> -->
</label>
<!-- Variable font controls (hidden when not using variable fonts) -->
<label class="label-and-value varfontControl">
<span>Weight:</span>
<input type="range" value="400" step="1" min="100" max="900" name="varWeight">
<input type="number" value="400" step="1" min="100" max="900" name="varWeightNum">
</label>
<label class="label-and-value varfontControl">
<span>Slant:</span>
<input type="range" value="0" step="0.01" min="0" max="10" name="varSlant">
<input type="number" value="0" step="0.01" min="0" max="10" name="varSlantNum">
</label>
<label class="label-and-value constfontControl">
<span>Weight:</span>
<select name="weight" style="max-width:100px">
<option value="100">Thin (100)</option>
<option value="200">Extra Light (200)</option>
<option value="300">Light (300)</option>
<option value="400" selected>Regular (400)</option>
<option value="500">Medium (500)</option>
<option value="600">Semi Bold (600)</option>
<option value="700">Bold (700)</option>
<option value="800">Extra Bold (800)</option>
<option value="900">Black (900)</option>
</select>
</label>
<label class="label-and-value">
<span>Family:</span>
<select name="family">
<option value="text" selected>Inter</option>
<option value="display">Inter Display</option>
</select>
</label>
<label class="label-and-value">
<span>Sample text:</span>
<select name="sample"></select>
</label>
<div class="checkbox-group repertoireControl">
<label class="label-and-value">
<span>Repertoire order:</span>
<select name="repertoireOrder">
<option value="" selected>Original</option>
<option value="u">Unicode</option>
</select>
</label>
</div>
<label class="label-and-value">
<span>Letter spacing:</span>
<input type="number" value="" placeholder="" step="0.1" name="letterSpacing">
<note><span class="unit">%</span><div class="img-button reset reset-letter-spacing" tabindex="0"></div>
</note>
</label>
<label class="label-and-value">
<span>Line height:</span>
<input type="number" value="" placeholder="" step="1" min="0" max="1000" name="lineHeight">
<note><span class="unit">dp</span><div class="img-button reset reset-line-height" tabindex="0"></div>
</note>
</label>
<label class="label-and-value">
<span>Transform:</span>
<select name="text-transform">
<option value="none" selected>none</option>
<option value="capitalize">Capitalize</option>
<option value="uppercase">UPPERCASE</option>
<option value="lowercase">lowercase</option>
</select></label>
<label class="label-and-value">
<span>Decoration:</span>
<select name="text-decoration">
<option value="none" selected>none</option>
<option value="underline" style="text-decoration:underline">underline</option>
<option value="overline">overline</option>
<option value="underline overline">underline &amp; overline</option>
<option value="line-through">line-through</option>
<option value="wavy underline">wavy underline</option>
</select></label>
<!-- <h3>Display</h3> -->
<label class="label-and-value">
<span>Anti-alias:</span>
<select name="antialias">
<option value="greyscale" selected>Greyscale</option>
<option value="subpixel">Subpixel</option>
<option value="default">Browser default</option>
</select>
</label>
<label class="label-and-value">
<span>Compare:</span>
<select name="compare">
<option value="-" selected>Nothing</option>
<option value="inter-other">Other Inter family</option>
<option value="system">System font</option>
</select>
</label>
<h3>Features</h3>
<div class="checkbox-group">
<!-- case --><label title="Upper case adjustments"><input type="checkbox" class="featopt" name="feat:case"><span>case &nbsp;(Case alternates)</span></label>
<!-- cpsp --><label title='Capital spacing (adds 16 UPM to each sidebearing)'><input type="checkbox" class="featopt" name="feat:cpsp"><span>cpsp &nbsp;(Capital spacing)</span></label>
<!-- dlig --><label title="Discretionary ligatures, e.g. !? -> interrobang"><input type="checkbox" class="featopt" name="feat:dlig"><span>dlig &nbsp;(Discretionary ligatures)</span></label>
<!-- frac --><label title="Contextual automatic fractions"><input type="checkbox" class="featopt" name="feat:frac"><span>frac &nbsp;(Auto fractions)</span></label>
<!-- dnom --><label title="Convert all numbers to denominators"><input type="checkbox" class="featopt" name="feat:dnom"><span>dnom &nbsp;(Denominators)</span></label>
<!-- numr --><label title="Convert all numbers to numerators"><input type="checkbox" class="featopt" name="feat:numr"><span>numr &nbsp;(Numerators)</span></label>
<!-- salt --><label title='Stylistic Alternates'><input type="checkbox" class="featopt" name="feat:salt"><span>salt &nbsp;(Stylistic Alternates)</span></label>
<!-- subs --><label title="Subscript"><input type="checkbox" class="featopt" name="feat:subs"><span>subs &nbsp;(Subscript)</span></label>
<!-- sups --><label title="Superscript"><input type="checkbox" class="featopt" name="feat:sups"><span>sups &nbsp;(Superscript)</span></label>
<!-- tnum --><label title="Tabular numbers (fixed width)"><input type="checkbox" class="featopt" name="feat:tnum"><span>tnum &nbsp;(Tabular numbers)</span></label>
<!-- zero --><label title="Slashed zero"><input type="checkbox" class="featopt" name="feat:zero"><span>zero &nbsp;(Slashed zero)</span></label>
<label title='Stylistic set 1 "Open Digits"'><input type="checkbox" class="featopt" name="feat:ss01"><span>ss01 &nbsp;(Open Digits)</span></label>
<label title='Stylistic set 2 "Disambiguation"'><input type="checkbox" class="featopt" name="feat:ss02"><span>ss02 &nbsp;(Disambiguation)</span></label>
<label title='Stylistic set 3 "Lower case r curves into round neighbors"'><input type="checkbox" class="featopt" name="feat:ss03"><span>ss03 &nbsp;(Curved r)</span></label>
<label title='Stylistic set 4 "Disambiguation without slashed zero"'><input type="checkbox" class="featopt" name="feat:ss04"><span>ss04 &nbsp;(Disambiguation w/o zero)</span></label>
<label title='Character Variant 1 "Alternate one"'><input type="checkbox" class="featopt" name="feat:cv01"><span>cv01 &nbsp;(Alternate one)</span></label>
<label title='Character Variant 2 "Open four"'><input type="checkbox" class="featopt" name="feat:cv02"><span>cv02 &nbsp;(Open four)</span></label>
<label title='Character Variant 3 "Open six"'><input type="checkbox" class="featopt" name="feat:cv03"><span>cv03 &nbsp;(Open six)</span></label>
<label title='Character Variant 4 "Open nine"'><input type="checkbox" class="featopt" name="feat:cv04"><span>cv04 &nbsp;(Open nine)</span></label>
<label title='Character Variant 5 "Lower case L with tail")'><input type="checkbox" class="featopt" name="feat:cv05"><span>cv05 &nbsp;(Lower case L with tail)</span></label>
<label title='Character Variant 6 "Lower case r with curved tail")'><input type="checkbox" class="featopt" name="feat:cv06"><span>cv06 &nbsp;(Curved lower case r)</span></label>
<label title='Character Variant 7 "Alternate German double-s")'><input type="checkbox" class="featopt" name="feat:cv07"><span>cv07 &nbsp;(German double-s)</span></label>
<label title='Character Variant 8 "Upper-case i with serif")'><input type="checkbox" class="featopt" name="feat:cv08"><span>cv08 &nbsp;(Upper-case i with serif)</span></label>
<label title='Character Variant 9 "Flat top three")'><input type="checkbox" class="featopt" name="feat:cv09"><span>cv09 &nbsp;(Flat top three)</span></label>
<label title='Character Variant 10 "Capital G with spur")'><input type="checkbox" class="featopt" name="feat:cv10"><span>cv10 &nbsp;(Capital G with spur)</span></label>
<label title='Character Variant 11 "Single-storey a")'><input type="checkbox" class="featopt" name="feat:cv11"><span>cv11 &nbsp;(Single-storey a)</span></label>
</div>
<div class="checkbox-group">
<span>Default-on features:</span>
<label title="Contextual alternates"><input type="checkbox" class="featopt" name="feat:calt=0"> Disable calt &nbsp;(Contextual alternates)</label>
<label title="Glyph Composition/Decomposition"><input type="checkbox" class="featopt" name="feat:ccmp=0"> Disable ccmp</label>
<label title="Kerning"><input type="checkbox" class="featopt" name="feat:kern=0"> Disable kern &nbsp;(Kerning)</label>
</div>
<div>
<a href="var.html">Variable test page</a>
</div>
</div>
<boxes>
<box contenteditable spellcheck="false" class="primaryFont "><span>Rectangle</span></box>
<box contenteditable spellcheck="false" class="primaryFont positive"><span>Rectangle</span></box>
<box contenteditable spellcheck="false" class="primaryFont positive tight"><span>Rectangle</span></box>
<box contenteditable spellcheck="false" class="primaryFont centered positive"><span>Rectangle</span></box>
<sep></sep>
<box contenteditable spellcheck="false" class="secondaryFont showOnlyWithSecondarySample"><span>Rectangle</span></box>
<box contenteditable spellcheck="false" class="secondaryFont positive showOnlyWithSecondarySample"><span>Rectangle</span></box>
<box contenteditable spellcheck="false" class="secondaryFont positive tight showOnlyWithSecondarySample"><span>Rectangle</span></box>
<box contenteditable spellcheck="false" class="secondaryFont centered positive showOnlyWithSecondarySample"><span>Rectangle</span></box>
</boxes>
<div class="preview">
<samples>
<sample contenteditable spellcheck="false" class="primary inter"></sample>
<sample contenteditable spellcheck="false" class="secondary"></sample>
</samples>
</div>
<div id="measure" class="inter">Åj</div>
</body>
<script type="text/javascript">(function(){
function InterDynamicTracking(fontSize, family /* :"text"|"display" */) {
var a = -0.0223, b = 0.185, c = -0.1745;
if (family == "display") {
a = -0.015; b = 0.17; c = -0.12
}
// tracking = a + b * e ^ (c * fontSize)
return a + b * Math.pow(Math.E, c * fontSize)
}
// provide hinted=1 to use TTF hinted fonts.
// not exposed in UI as it only works when serving the site locally
// with fonts in the build/fonts directory.
const hinted = location.search.indexOf('hinted=1') != -1
const familyName = hinted ? fontFamilyNameHinted : fontFamilyName;
if (hinted) {
document.body.classList.add('hinted')
}
document.body.style.fontFamily = familyName
function parseQueryString(qs) {
return new Map(
qs.replace(/^\?+/g,'')
.split('&')
.filter(s => s.trim())
.map(s => s.split('=').map(decodeURIComponent))
)
}
function fmtQueryString(mapLikeIterable) {
let pairs = []
for (let kv of mapLikeIterable) {
let k = kv[0]
let v = kv[1]
if (v === undefined) {
pairs.push(encodeURIComponent(k))
} else {
v = (v === true) ? 1 : (v === false || v === null) ? 0 : v
pairs.push(encodeURIComponent(k) + '=' + encodeURIComponent(v))
}
}
return pairs.sort().join('&')
}
function setCurrentQueryString(queryString) {
replaceURL(() => {
if (typeof queryString == "function") {
queryString = queryString()
}
return document.location.pathname + (queryString ? "?" + queryString : "")
})
}
function setCurrentQueryStringParam(key, value, zeroValue) {
setCurrentQueryString(() => {
let qs = parseQueryString(document.location.search)
if (value === undefined || value === zeroValue) {
qs.delete(key)
} else {
qs.set(key, value)
}
return fmtQueryString(qs)
})
}
// replaceURL(url :string|Function, title? :string, meta? :any)
var replaceURL = (()=>{
// We employ some backoff on calling history.replaceState as
// Chrome will start to throttle (meaning ignoring) calls to
// history.replaceState if the frequency gets too high.
// Safari 13 provides a budget of 200 replaceState calls in a 30 second sliding time window.
// Chrome instead uses an IPC message flooding prevention (https://crbug.com/882238)
let timer = null
let queued = null // {url, title, meta} when queued, null when not
let isRetrying = false
function flush() {
clearTimeout(timer)
timer = null
if (queued) {
let url = typeof queued.url == "function" ? queued.url() : queued.url
try {
history.replaceState(queued.meta, queued.title, url)
queued = null
isRetrying = false
} catch (err) {
if (!isRetrying) {
console.warn(String(err))
}
// back off
isRetrying = true
timer = setTimeout(flush, 1000)
}
}
}
return function replaceURL(url, title, meta) {
queued = { url, title, meta }
if (timer === null) {
flush()
timer = setTimeout(flush, 200)
}
}
})()
class BoundVar {
constructor(name, e, valueGetter, valueSetter, parentVars) {
this.name = name
this.e = e
this.valueGetter = valueGetter
this.valueSetter = valueSetter
this.isCheckbox = e.type == 'checkbox'
this.isNumber = e.type == 'number' || e.type == 'range'
this.lastValue = this.getValue()
this.parentVars = parentVars
this.defaultValue = this.lastValue
this._changeCallbacks = []
this._isInitialSetValue = true
}
refreshValue(ev) {
let value = this.getValue(ev)
this.setValue(value)
return value
}
getValue(ev) {
return this.valueGetter ? this.valueGetter(this.e, this.lastValue, ev)
: this.isCheckbox ? (this.e.checked || null)
: this.isNumber ? this.e.valueAsNumber
: this.e.value
}
onChange(callback) {
this._changeCallbacks.push(callback)
}
removeChangeListener(callback) {
let i = 0
for (; i < this._changeCallbacks.length; i++) {
if (this._changeCallbacks[i] === callback) {
this._changeCallbacks.splice(i, 1)
return true
}
}
return false
}
setValue(value) {
if (this.isCheckbox && typeof value != 'boolean') {
value = parseInt(value)
if (isNaN(value)) {
value = 0
}
}
if (this.valueSetter) {
let isInitial = this._isInitialSetValue
this._isInitialSetValue = false
const v = this.valueSetter(this.e, value, isInitial)
if (v !== undefined) {
value = v
}
}
if (this.isCheckbox) {
if ((this.e.checked = !!value)) {
this.e.setAttribute("checked", "")
} else {
this.e.removeAttribute("checked")
}
} else if (this.isNumber && typeof value == 'number' && 'valueAsNumber' in this.e) {
if (isNaN(value)) {
this.e.value = null
} else if (this.e.valueAsNumber != value) {
this.e.valueAsNumber = value
}
} else if (this.e.value != value) {
this.e.value = value
}
this.lastValue = value
for (let f of this._changeCallbacks) {
f(value, this)
}
}
}
class Vars {
constructor(queryString) {
this.values = parseQueryString(queryString)
this.vars = new Map()
// this._historyReplaceStateTimer = null
// this._needsHistoryReplaceState = false
}
getValue(name) {
return this.values.get(name)
}
setValue(name, value) {
let v = this.vars.get(name)
if (!v) {
return null
}
v.setValue(value)
this._storeValue(name, value)
return value
}
_storeValue(name, value) {
// if (this.values.get(name) === value) {
// return
// }
if (value === null || value === undefined) {
this.values.delete(name)
} else {
this.values.set(name, value)
}
setCurrentQueryString(() => this.getQueryString())
}
// _performHistoryReplaceState() {
// // let qs = mergeQueryString(document.location.search, this.getQueryString())
// setCurrentQueryString(this.getQueryString())
// this._needsHistoryReplaceState = false
// }
// setNeedsHistoryReplaceState() {
// // We employ some backoff on calling history.replaceState as
// // Chrome will start to throttle (meaning ignoring) calls to
// // history.replaceState if the frequency gets too high.
// // Safari 13 provides a budget of 200 replaceState calls in a 30 second sliding time window.
// // Chrome instead uses an IPC message flooding prevention (https://crbug.com/882238)
// if (this._historyReplaceStateTimer === null) {
// this._performHistoryReplaceState()
// this._historyReplaceStateTimer = setTimeout(() => {
// this._historyReplaceStateTimer = null
// if (this._needsHistoryReplaceState) {
// this._performHistoryReplaceState()
// }
// }, 200)
// } else {
// this._needsHistoryReplaceState = true
// }
// }
refreshValue(name) {
let v = this.vars.get(name)
return v ? this._refreshValue(v) : null
}
_refreshValue(v, ev) {
let value = v.refreshValue(ev)
this._storeValue(v.name, value)
return value
}
getQueryString() {
let qs = parseQueryString(document.location.search)
// patch qs: set or remove any attributes that are registered in this.values
for (let e of this.vars) {
let k = e[0], vr = e[1], v = this.values.get(k)
if (v === undefined || vr.defaultValue == v || k[0] == '_') {
qs.delete(k)
} else {
qs.set(k, v)
}
}
return fmtQueryString(qs)
}
// bind(name :string,
// sel :Element|string,
// valueSetter? :(e:Element, value:any, isInitial:bool)=>void,
// valueGetter? :(e:Element)=>any)
// bind(name :string,
// valueSetter? :(e:Element, value:any, isInitial:bool)=>void,
// valueGetter? :(e:Element, prevValue:any, ev?:Event)=>any)
bind(name, sel, valueSetter, valueGetter) {
if (typeof sel == 'function' || sel === undefined) {
valueGetter = valueSetter
valueSetter = sel
sel = '[name="'+name+'"]'
}
let e = typeof sel == 'string' ? document.querySelector(sel) : sel;
let v = new BoundVar(name, e, valueGetter, valueSetter, this)
this.vars.set(name, v)
if (v.isNumber) {
// SHIFT-ARROW = 10 increments
// SHIFT-ALT-ARROW = x2 increments
e.addEventListener('keydown', ev => {
if (!ev.shiftKey) {
return
}
switch (ev.key) {
case 'ArrowRight':
case 'ArrowUp': {
if (ev.key == 'ArrowRight' && e.type != "range") {
return
}
if (ev.altKey) {
ev.target.valueAsNumber *= 2
} else {
ev.target.valueAsNumber += 10
}
ev.preventDefault()
ev.stopPropagation()
this._refreshValue(v, ev)
break
}
case 'ArrowLeft':
case 'ArrowDown': {
if (ev.key == 'ArrowLeft' && e.type != "range") {
return
}
if (ev.altKey) {
ev.target.valueAsNumber /= 2
} else {
ev.target.valueAsNumber -= 10
}
ev.preventDefault()
ev.stopPropagation()
this._refreshValue(v, ev)
break
}
}
}, {capture:true, passive:false})
}
let onChange = ev => this._refreshValue(v, ev)
e.addEventListener('input', onChange)
if (v.isCheckbox) {
e.addEventListener('change', onChange)
}
let existingValue = this.values.get(name)
if (existingValue !== null && existingValue !== undefined) {
if (v.isNumber) {
existingValue = parseFloat(existingValue)
} else if (v.isCheckbox) {
existingValue = existingValue != '0' && existingValue != 'false' && existingValue != 'off'
}
v.setValue(existingValue)
} else {
onChange(null)
}
return v
}
}
// function mergeQueryString(a, b) {
// a = a.replace(/^[\?\&]+/, "").split('&').sort().map(v => v.split('='))
// b = b.replace(/^[\?\&]+/, "").split('&').sort().map(v => v.split('='))
// let s = new Map(a)
// for (let kv of b) {
// s.set(kv[0], kv[1])
// }
// return Array.from(s).map(kv => kv[1] ? kv.join('=') : kv[0]).join('&')
// }
function main() {
const vars = new Vars(document.location.search)
const $ = (q, el) => (el || document).querySelector(q)
const $$ = (q, el) => [].slice.call((el || document).querySelectorAll(q))
let interUISample = $('sample.inter');
let secondarySample = $('sample.secondary');
secondarySample.innerText = interUISample.innerText;
const measureDiv = $('#measure')
const secondaryFontElements = $$('.secondaryFont')
const primaryFontElements = $$('.primaryFont')
const repertoireControl = $('.repertoireControl')
const samplesElement = $('samples')
const boxes = $('boxes')
let keyPressed = ""
let shiftKeyPressed = false
const checkKeys = ev => {
keyPressed = ev.key
shiftKeyPressed = ev.shiftKey
}
window.addEventListener('keydown', checkKeys)
window.addEventListener('keyup', checkKeys)
// sidebar show/hide
const sidebarButton = $("#sidebar-button")
let sidebarMinimized = false
function setSidebarMinimized(minimized) {
sidebarMinimized = minimized
document.body.classList.toggle("sidebar-minimized", sidebarMinimized)
setCurrentQueryStringParam("nosidebar", sidebarMinimized ? 1 : undefined)
sidebarButton.title = sidebarMinimized ? "Show controls" : "Hide controls"
}
sidebarButton.onclick = () => setSidebarMinimized(!sidebarMinimized)
setSidebarMinimized(document.location.search.indexOf('nosidebar') != -1)
// setInterval(()=>{ setSidebarMinimized(!sidebarMinimized) },2000)
// filter paste to match style
$$('[contenteditable]').forEach(el => {
el.addEventListener('paste', ev => {
ev.preventDefault()
let text = ev.clipboardData.getData("text/plain")
document.execCommand("insertHTML", false, text)
}, {capture:true,passive:false})
})
// prevent clicks on img-button from changing focus
$$('.img-button').forEach(el => {
el.addEventListener('pointerdown', ev => {
ev.preventDefault()
}, {passive:false, capture:true})
})
function forEachGlyphlist(fn) {
let elements = samplesElement.querySelectorAll('.glyphlist')
if (elements) {
for (let i = 0; i < elements.length; ++i) {
fn(elements[i], i)
}
}
}
function setGlyphlistClass(className, add) {
forEachGlyphlist(gl => {
if (add) {
gl.classList.add(className)
} else {
gl.classList.remove(className)
}
})
}
const lineHeightInput = $('[name="lineHeight"]')
let measurePending = false
const measure = () => {
const r = measureDiv.getBoundingClientRect()
measurePending = false
lineHeightInput.placeholder = r.height
document.documentElement.style.setProperty("--line-height", r.height + "px")
}
window.addEventListener('load', measure)
const cssAffectedElements = [
interUISample,
secondarySample,
measureDiv
].concat(secondaryFontElements).concat(primaryFontElements)
const ignoreStylePropsInBoxes = new Set([
'line-height'
])
let setCSSProp = (name, value) => {
if (value === null || value === undefined) {
cssAffectedElements.forEach(e => e.style.removeProperty(name))
} else {
cssAffectedElements.forEach(e => {
if (e.nodeName != 'BOX' || !ignoreStylePropsInBoxes.has(name)) {
e.style.setProperty(name, value)
}
})
}
if (!measurePending) {
measurePending = true
window.requestAnimationFrame(measure)
}
}
let setVendorPrefixedCSSProp = (name, value) => {
setCSSProp(name, value)
setCSSProp('-webkit-' + name, value)
setCSSProp('-moz-' + name, value)
setCSSProp('-ms-' + name, value)
}
let feats = new Map()
let featVars = new Map()
let updateFeaturesStyleTimer = null
function updateFeaturesStyle() {
let css = Array.from(feats).map(f => `"${f[0]}" ${f[1]}`).join(', ')
setCSSProp('font-feature-settings', css)
}
function scheduleUpdateFeaturesStyle() {
if (updateFeaturesStyleTimer === null) {
updateFeaturesStyleTimer = setTimeout(() => {
updateFeaturesStyleTimer = null
updateFeaturesStyle()
}, 1)
}
}
function setFeature(feat, val, dontUpdateVar/*=false*/) {
if (typeof val == 'boolean') {
val = val ? 1 : 0
}
let prevVal = feats.get(feat)
if (prevVal !== val) {
feats.set(feat, val)
scheduleUpdateFeaturesStyle()
if (!dontUpdateVar) {
let vr = featVars.get(feat)
if (vr) {
vr.setValue(val)
}
}
}
}
// sample text
const samplesSelect = $('select[name="sample"]')
for (let [k,v] of samples) {
const opt = document.createElement('option')
opt.innerText = k
if (v) {
opt.value = k
} else {
opt.disabled = true
}
samplesSelect.appendChild(opt)
}
vars.bind('repertoireOrder', (e, v) => {
let currOrder = repertoireOrder
if (v == 'u') {
repertoireOrder = RepertoireOrderUnicode
} else {
repertoireOrder = RepertoireOrderGlyphList
}
if (sampleVar && currOrder != repertoireOrder) {
sampleVar.refreshValue(null)
}
})
var sizeVar, varSizeRange
var varSizeSettingValueImpl = false
sizeVar = vars.bind('size', (e, v, isInitial) => {
boxes.style.display = (v > 20) ? 'none' : null
setCSSProp('font-size', v + 'px')
document.documentElement.style.setProperty("--font-size", v + "px")
setGlyphlistClass('hideNames', v < 36)
if (varSizeRange && !varSizeSettingValueImpl) {
varSizeSettingValueImpl = true
varSizeRange.setValue(v)
varSizeSettingValueImpl = false
}
return v
})
varSizeRange = vars.bind('_sizeRange', (e, v, isInitial) => {
if (isInitial) {
return sizeVar.getValue()
} else if (!varSizeSettingValueImpl) {
varSizeSettingValueImpl = true
vars.setValue("size", v)
varSizeSettingValueImpl = false
}
}, (e, prevValue, ev) => {
v = e.valueAsNumber
if (shiftKeyPressed && (keyPressed == "" || keyPressed == "Shift")) {
v = Math.round(v / 8) * 8
}
return v
})
let usingVarFont = false
let usingFontFamily = "text"
var varWeightRange, varSlantRange
var varWeightSettingValueImpl = false
var varSlantSettingValueImpl = false
function getFontFamily(overrideFamily) {
return (
(overrideFamily || usingFontFamily) == "text" ? (
usingVarFont ? (hinted ? fontFamilyNameVarHinted : fontFamilyNameVar) :
hinted ? fontFamilyNameHinted : fontFamilyName
) : (
usingVarFont ? (hinted ? fontFamilyNameDisplayVarHinted : fontFamilyNameDisplayVar) :
hinted ? fontFamilyNameDisplayHinted : fontFamilyNameDisplay
)
)
}
let currentBodyWeightClass = null
let varWeight = vars.bind('weight', (e, v) => {
setCSSProp('font-weight', v)
if (currentBodyWeightClass) {
document.body.classList.remove(currentBodyWeightClass)
}
document.body.classList.add(currentBodyWeightClass = 'font-weight-'+v)
})
var italicVar = vars.bind('italic', (e, on) => {
document.body.classList[on ? 'add' : 'remove']('italic')
if (usingVarFont && !varSlantSettingValueImpl) {
if (varSlantRange) {
varSlantRange.setValue(on ? 100 : 0)
}
updateVarFont()
}
})
let varState = {
weight: 400, // 400..900
slant: 0, // 0..-10
}
function updateVarFont() {
if (usingVarFont) {
varSlantSettingValueImpl = true
if (varState.slant <= 0.1) {
varState.slant = 0
italicVar.setValue(false)
} else {
italicVar.setValue(true)
}
varSlantSettingValueImpl = false
setCSSProp(
"font-variation-settings",
`"wght" ${varState.weight}, "slnt" ${-varState.slant}`
)
} else {
setCSSProp("font-variation-settings", null)
}
}
vars.bind('varfont', (e, on, isInitial) => {
usingVarFont = on
document.body.classList.toggle('varfont', on)
if (on) {
if (!isInitial) {
// copy value of const weight to var weight
let w = parseInt(varWeight.getValue())
if (!isNaN(w) && varWeightRange && !varWeightSettingValueImpl) {
varWeightRange.setValue(w)
}
}
// document.body.style.fontFamily = (
// hinted ? fontFamilyNameVarHinted :
// fontFamilyNameVar
// )
} else {
if (!isInitial && varWeightRange) {
// copy value of var weight to const weight
let w = varWeightRange.getValue()
if (!isNaN(w)) {
vars.setValue("weight", Math.round(w / 100) * 100)
}
}
// document.body.style.fontFamily = (
// hinted ? fontFamilyNameHinted :
// fontFamilyName
// );
}
document.body.style.fontFamily = getFontFamily()
updateVarFont()
})
let varWeightNum = vars.bind('varWeightNum', (e, v) => {
if (varWeightRange && !varWeightSettingValueImpl) {
varWeightRange.setValue(v)
}
})
let varSlantNum = vars.bind('varSlantNum', (e, v) => {
if (varSlantRange && !varSlantSettingValueImpl) {
varSlantRange.setValue(v)
}
})
varWeightRange = vars.bind('varWeight', (e, v) => {
varState.weight = v
varWeightSettingValueImpl = true
varWeightNum.setValue(v)
varWeightSettingValueImpl = false
updateVarFont()
}, (e, prevValue, ev) => {
if (prevValue === undefined) {
return 400
}
v = e.valueAsNumber
if (shiftKeyPressed && (keyPressed == "" || keyPressed == "Shift")) {
v = Math.round(v / 100) * 100
}
return v
})
varSlantRange = vars.bind('varSlant', (e, v) => {
varState.slant = v
varSlantSettingValueImpl = true
varSlantNum.setValue(v)
varSlantSettingValueImpl = false
updateVarFont()
}, (e, prevValue, ev) => {
if (prevValue === undefined) {
return 0
}
return e.valueAsNumber !== undefined ? e.valueAsNumber : e.value
})
// compare
let secondarySampleClassNameAddition = null
let currentSecondarySampleClassName = null
function updateSecondarySample() {
setSecondarySampleClassName(currentSecondarySampleClassName)
}
const setSecondarySampleClassName = className => {
currentSecondarySampleClassName = className
if (secondarySampleClassNameAddition) {
secondarySample.style.fontFamily = null
secondarySample.classList.remove(secondarySampleClassNameAddition)
secondaryFontElements.forEach(e =>
e.classList.remove(secondarySampleClassNameAddition))
}
if (className) {
let explicitFontFamily = null
if (className == "inter-other-font") {
let otherFamily = usingFontFamily == "text" ? "display" : "text"
explicitFontFamily = getFontFamily(otherFamily)
}
secondarySample.style.fontFamily = explicitFontFamily
secondarySample.classList.add(className)
secondaryFontElements.forEach(e => e.classList.add(className))
}
secondarySampleClassNameAddition = className || null
}
const enableSecondarySample = className => {
setSecondarySampleClassName(className)
secondarySample.style.display = null
document.body.classList.remove('secondarySampleDisabled')
}
const disableSecondarySample = className => {
setSecondarySampleClassName(null)
secondarySample.style.display = 'none'
document.body.classList.add('secondarySampleDisabled')
}
vars.bind('compare', (e, v) => {
disableSecondarySample()
switch (v) {
// case 'roboto': enableSecondarySample('robotoFont'); break
case 'system': enableSecondarySample('systemFont'); break
case 'inter-other': enableSecondarySample('inter-other-font'); break
default: return '-';
}
}, e => (e.value && e.value != '-') ? e.value : null)
vars.bind('family', (el, family, isInitial) => {
usingFontFamily = family
document.body.style.fontFamily = getFontFamily()
updateSecondarySample()
if (!isInitial) {
updateImplicitLetterSpacing()
}
})
let resetLineHeightButton = $('.reset-line-height')
let resetLetterSpacingButton = $('.reset-letter-spacing')
function calcImplicitLetterSpacing(size, el) {
let t = InterDynamicTracking(size, usingFontFamily)
let v = parseFloat((t * 100).toFixed(1))
;(el || letterSpacingVar.e).placeholder = v
return v
}
let letterSpacingVar = vars.bind('letterSpacing', (el, v) => {
resetLetterSpacingButton.classList.toggle("disabled", !v)
if (v) {
resetLetterSpacingButton.tabIndex = null
} else {
resetLetterSpacingButton.tabIndex = -1
}
if (!v) {
v = calcImplicitLetterSpacing(sizeVar.getValue(), el)
}
setCSSProp('letter-spacing', (v / 100) + 'em')
}, (el, prevValue, ev) => {
if (ev && !ev.inputType && !prevValue) {
// step increment/decrement
let delta = el.valueAsNumber == 0 ? -1 : el.valueAsNumber
return parseFloat(el.placeholder) + delta
}
return el.value || ""
})
function updateImplicitLetterSpacing() {
if (!letterSpacingVar.getValue()) {
v = calcImplicitLetterSpacing(sizeVar.getValue())
setCSSProp('letter-spacing', (v / 100) + 'em')
}
}
// update implicit letter spacing when size changes
sizeVar.onChange(size => {
let t = letterSpacingVar.lastValue
if (!t || isNaN(t)) {
updateImplicitLetterSpacing()
}
})
resetLetterSpacingButton.addEventListener('click', ev => {
vars.setValue('letterSpacing', '')
ev.stopPropagation()
ev.preventDefault()
}, {passive:false,capture:true})
vars.bind('lineHeight', lineHeightInput, (e, v) => {
setCSSProp('line-height', v ? v + 'px' : null)
resetLineHeightButton.classList.toggle("disabled", !v)
if (v) {
resetLineHeightButton.tabIndex = null
} else {
resetLineHeightButton.tabIndex = -1
}
}, (e, prevValue, ev) => {
if (ev && !ev.inputType && !prevValue) {
// step increment/decrement
let delta = e.valueAsNumber == 0 ? -1 : e.valueAsNumber
return parseFloat(e.placeholder) + delta
}
if (e.valueAsNumber < 0) {
return Math.abs(e.valueAsNumber)
}
return e.value || ""
})
resetLineHeightButton.addEventListener('click', ev => {
vars.setValue('lineHeight', '')
ev.stopPropagation()
ev.preventDefault()
}, {passive:false,capture:true})
vars.bind("invert-colors", (e, on) => {
if (defaultColorSceme == "dark")
on = !on
document.documentElement.classList.toggle('color-scheme-dark', on)
})
vars.bind("draw-sample-bg", (e, on) => {
document.documentElement.classList.toggle('draw-sample-background', on)
})
let spaaSelect = vars.bind('antialias', (e, v) => {
switch (v) {
case 'subpixel': {
setCSSProp('-webkit-font-smoothing', 'subpixel-antialiased')
setCSSProp('-moz-osx-font-smoothing', 'auto')
setCSSProp('font-smooth', 'always')
break
}
case 'greyscale': {
setCSSProp('-webkit-font-smoothing', 'antialiased')
setCSSProp('-moz-osx-font-smoothing', 'grayscale')
setCSSProp('font-smooth', null)
break
}
default: {
setCSSProp('-webkit-font-smoothing', 'initial')
setCSSProp('-moz-osx-font-smoothing', 'unset')
setCSSProp('font-smooth', null)
break
}
}
})
const ua = navigator.userAgent.toLowerCase()
if (ua.indexOf('win64') != -1 || ua.indexOf('win32') != -1) {
// Can't disable on Windows
vars.setValue('antialias', 'default')
spaaSelect.e.disabled = true
spaaSelect.e.parentElement.title = 'In Chrome, visit chrome:flags#lcd-text-aa to disable'
}
vars.bind('text-decoration', (e, v) => {
setVendorPrefixedCSSProp('text-decoration', e.value = v)
})
vars.bind('text-transform', (e, v) => {
setCSSProp('text-transform', e.value = v)
})
// vars.bind('variantCaps', (e, v) => {
// setCSSProp('font-variant-caps', e.value = v)
// })
// vars.bind('variantLigatures', (e, v) => {
// setCSSProp('font-variant-ligatures', e.value = v)
// })
// vars.bind('variantNumeric', (e, v) => {
// setCSSProp('font-variant-numeric', e.value = v)
// })
for (let e of $$('input.featopt')) {
let p = e.name.replace(/^feat\:/, '').split('=')
let name = p[0]
let valueOn = parseInt(p[1] || '1')
let valueOff = valueOn == 0 ? 1 : 0
let vr = vars.bind('feat-' + name, e, (e, on) => {
setFeature(name, on ? valueOn : valueOff, /*dontUpdateVar=*/true)
})
featVars.set(name, vr)
}
updateFeaturesStyle()
sampleVar = vars.bind('sample', samplesSelect, (e, v) => {
let sampleText = samples.get(v) || ''+v
if (v == 'Repertoire') {
repertoireControl.style.display = null
} else {
repertoireControl.style.display = 'none'
}
if (typeof sampleText == 'object' && sampleText.toHTML) {
const html = sampleText.toHTML()
interUISample.innerHTML = html
secondarySample.innerHTML = html
} else {
// look for directive
// #!directive:value
// #!directive
sampleText = String(sampleText).replace(/^[\s\r\n\r]+|[\s\r\n\r]+$/g, '')
let m = /(?:^|\n)#\!([\w_\-]+)(?::(.+)|)(?:\n|$)/.exec(sampleText)
if (m) {
// parse directive
sampleText = (
sampleText.substring(0, m.index) +
sampleText.substr(m.index + m[0].length)
)
let directive = m[1].toLowerCase()
// console.log('dir', m[1], '=>', m[2])
if (directive == 'enablefeatures') {
// #!enableFeatures:tnum,dlig
for (let feat of m[2].toLowerCase().split(/\s*,\s*/)) {
setFeature(feat, 1)
}
} else if (directive == 'notrim') {
// #!notrim
// noop
} else {
console.warn(`ignoring unknown directive ${m[0]} in sample text`)
}
}
if (sampleText) {
interUISample.innerText = sampleText
secondarySample.innerText = sampleText
}
}
if (v == 'Repertoire') {
requestAnimationFrame(() => {
if (sizeVar) {
sizeVar.refreshValue(null)
}
})
}
})
// ESC clears keyboard focus
document.addEventListener('keydown', ev => {
if (ev.key == "Escape") {
document.activeElement.blur()
}
}/*, {capture:true, passive:false}*/)
requestAnimationFrame(()=>{
$$('input').forEach(el => {
if (el.type == "checkbox") {
el.addEventListener("click", ev => {
el.blur()
})
el.addEventListener('keypress', ev => {
if (ev.key == "Enter") {
el.value = !el.checked
el.checked = !el.checked
el.dispatchEvent(new Event("change"))
ev.stopPropagation()
ev.preventDefault()
}
},{passive:false, capture:true})
} else {
// RETURN on control clears keyboard focus
el.addEventListener('keypress', ev => {
if (ev.keyCode == 13) {
document.activeElement.blur()
}
})
}
})
})
// clear keyboard focus when a select control changes
$$('select').forEach(el => el.addEventListener('change', ev => {
document.activeElement.blur()
window.requestAnimationFrame(() => document.activeElement.blur())
}))
}
main();
document.title = (
(new Date()).toTimeString().split(':').slice(0,2).join(':') +
' — ' + (new Date()).toDateString()
);
})();</script>
</html>