mirror of
https://github.com/rsms/inter.git
synced 2024-12-02 07:46:55 +03:00
1281 lines
42 KiB
HTML
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 & 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 (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 (Capital spacing)</span></label>
|
|
<!-- dlig --><label title="Discretionary ligatures, e.g. !? -> interrobang"><input type="checkbox" class="featopt" name="feat:dlig"><span>dlig (Discretionary ligatures)</span></label>
|
|
<!-- frac --><label title="Contextual automatic fractions"><input type="checkbox" class="featopt" name="feat:frac"><span>frac (Auto fractions)</span></label>
|
|
<!-- dnom --><label title="Convert all numbers to denominators"><input type="checkbox" class="featopt" name="feat:dnom"><span>dnom (Denominators)</span></label>
|
|
<!-- numr --><label title="Convert all numbers to numerators"><input type="checkbox" class="featopt" name="feat:numr"><span>numr (Numerators)</span></label>
|
|
<!-- salt --><label title='Stylistic Alternates'><input type="checkbox" class="featopt" name="feat:salt"><span>salt (Stylistic Alternates)</span></label>
|
|
<!-- subs --><label title="Subscript"><input type="checkbox" class="featopt" name="feat:subs"><span>subs (Subscript)</span></label>
|
|
<!-- sups --><label title="Superscript"><input type="checkbox" class="featopt" name="feat:sups"><span>sups (Superscript)</span></label>
|
|
<!-- tnum --><label title="Tabular numbers (fixed width)"><input type="checkbox" class="featopt" name="feat:tnum"><span>tnum (Tabular numbers)</span></label>
|
|
<!-- zero --><label title="Slashed zero"><input type="checkbox" class="featopt" name="feat:zero"><span>zero (Slashed zero)</span></label>
|
|
<label title='Stylistic set 1 "Open Digits"'><input type="checkbox" class="featopt" name="feat:ss01"><span>ss01 (Open Digits)</span></label>
|
|
<label title='Stylistic set 2 "Disambiguation"'><input type="checkbox" class="featopt" name="feat:ss02"><span>ss02 (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 (Curved r)</span></label>
|
|
<label title='Stylistic set 4 "Disambiguation without slashed zero"'><input type="checkbox" class="featopt" name="feat:ss04"><span>ss04 (Disambiguation w/o zero)</span></label>
|
|
<label title='Character Variant 1 "Alternate one"'><input type="checkbox" class="featopt" name="feat:cv01"><span>cv01 (Alternate one)</span></label>
|
|
<label title='Character Variant 2 "Open four"'><input type="checkbox" class="featopt" name="feat:cv02"><span>cv02 (Open four)</span></label>
|
|
<label title='Character Variant 3 "Open six"'><input type="checkbox" class="featopt" name="feat:cv03"><span>cv03 (Open six)</span></label>
|
|
<label title='Character Variant 4 "Open nine"'><input type="checkbox" class="featopt" name="feat:cv04"><span>cv04 (Open nine)</span></label>
|
|
<label title='Character Variant 5 "Lower case L with tail")'><input type="checkbox" class="featopt" name="feat:cv05"><span>cv05 (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 (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 (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 (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 (Flat top three)</span></label>
|
|
<label title='Character Variant 10 "Capital G with spur")'><input type="checkbox" class="featopt" name="feat:cv10"><span>cv10 (Capital G with spur)</span></label>
|
|
<label title='Character Variant 11 "Single-storey a")'><input type="checkbox" class="featopt" name="feat:cv11"><span>cv11 (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 (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 (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>
|