From 51d57259b0e1cbb784ad86325d4d4c0819d74c7b Mon Sep 17 00:00:00 2001 From: Christian Lawson-Perfect Date: Tue, 11 Feb 2025 14:20:15 +0000 Subject: [PATCH] first commit --- .gitignore | 1 + index.html | 62 +++++++++++++ script.js | 265 +++++++++++++++++++++++++++++++++++++++++++++++++++++ style.css | 32 +++++++ 4 files changed, 360 insertions(+) create mode 100644 .gitignore create mode 100644 index.html create mode 100644 script.js create mode 100644 style.css diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8bc0f76 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.make.* \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..98ad76a --- /dev/null +++ b/index.html @@ -0,0 +1,62 @@ + + + + + + Fontsource font preview + + + + + +
+

Fontsource font preview

+
+
+
+

This page shows fonts loaded from Fontsource.

+
+ +
+ + + +
+ +
+

About this font

+ +

+ +
+

Variable axes

+ +
    +
+
+ +
+
Category
+
+ +
Styles
+
    + +
    Weights
    +
      + +
      Unicode ranges
      +
        +
        + +
        + +
        +

        CSS

        +
        
        +      
        + +
        + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 0000000..6bc1f06 --- /dev/null +++ b/script.js @@ -0,0 +1,265 @@ +const select = document.getElementById('font-family'); +const link = document.getElementById('font-link'); + +const font_info_url = id => `https://api.fontsource.org/v1/fonts/${id}`; + +const variable_info_url = id => `https://api.fontsource.org/v1/variable/${id}`; + +const font_page_url = id => `https://fontsource.org/fonts/${id}`; + +function chars_in_range(v) { + if(!v) { + return ''; + } + const ranges = v.split(','); + return ranges.map(r => { + let [_,start,end] = r.match(/U\+([A-Z0-9]+)(?:-([A-Z0-9]+))?/i); + start = parseInt(start,16); + end = parseInt(end,16) || start; + let c = ''; + for(let i = start; i<=end; i++) { + c += String.fromCharCode(i); + } + return c; + }).join(' '); +} + +function element(name, attr, content) { + const el = document.createElement(name); + if(attr) { + Object.entries(attr).forEach(([k,v]) => el.setAttribute(k,v)); + } + if(content !== undefined) { + el.innerHTML = content; + } + return el; +} + +const css_template = (info,range,weight,style,variable_info) => { + + const axes = Object.keys(variable_info.axes || {}).filter(a=>a!='ital'); + function get_axis() { + if (axes.length === 1 && axes[0]=='wght') { + return 'wght'; + } + + if (axes.length === 2 && axes.includes('wght')) { + const selected = + axes.find((axis) => axis !== 'wght')?.toLowerCase() ?? 'wght'; + return selected; + } + const isStandard = axes.every((axis) => + ['wght', 'wdth', 'slnt', 'opsz'].includes(axis), + ); + + return isStandard ? 'standard' : 'full'; + } + + const axis = get_axis(); + + let urls; + + if(info.variable) { + urls = [`url(https://cdn.jsdelivr.net/fontsource/fonts/${info.id}:vf@latest/${range}-${axis}-${style}.woff2) format('woff2-variations')`]; + } else { + const variant = info.variants[weight][style][range]; + urls = Object.entries(variant.url).map(([format,url]) => { + return `url(${url}) format('${format}')`; + }); + } + + const extras = []; + if(info.variable) { + if(axes.includes('wght')) { + extras.push(` font-weight: ${variable_info.axes.wght.min} ${variable_info.axes.wght.max};`); + } + if(axes.includes('wdth')) { + extras.push(` font-stretch: ${variable_info.axes.wdth.min} ${variable_info.axes.wdth.max};`); + } + if(axes.includes('slnt')) { + extras.push(` font-style: oblique ${variable_info.axes.slnt.min}deg ${variable_info.axes.slnt.max}deg;`); + } +} + + if(info.variable) { + return ` +/* ${info.family} variable ${style} ${range} */ +@font-face ${'{'} + font-family: '${info.family}'; + font-style: ${style}; + font-display: swap; +${extras.join('\n')} + src: ${urls.join(', ')}; + unicode-range: ${info.unicodeRange[range]}; +${'}'} + `.trim(); + } else { + return ` +/* ${info.family} ${weight} ${style} ${range} */ +@font-face ${'{'} + font-family: '${info.family}'; + font-style: ${style}; + font-weight: ${weight}; + font-display: swap; + src: ${urls.join(', ')}; + unicode-range: ${info.unicodeRange[range]}; +${'}'} + `.trim(); + } +} + +async function fetch_json(url) { + return await (await fetch(url)).json(); +} + + + +async function go() { + let [fonts, axes_info] = await Promise.all([ + fetch_json('https://api.fontsource.org/v1/fonts'), + fetch_json('https://api.fontsource.org/v1/axis-registry') + ]); + + axes_info = Object.fromEntries(Object.entries(axes_info).map(([k,v]) => [k.toLowerCase(), v])); + + async function use_font() { + const name = select.value; + console.log(name); + const info = await fetch_json(font_info_url(name)); + const variable_info = info.variable ? await fetch_json(variable_info_url(info.id)) : {}; + console.log(info); + + document.body.style.setProperty('--family',info.family); + + function show_list(id, list, style_fn, text_fn) { + text_fn = text_fn || (v => v); + const ul = document.getElementById(id); + ul.innerHTML = ''; + for(let v of list) { + const li = document.createElement('li'); + ul.append(li); + li.innerHTML = text_fn(v); + style_fn(li.style, v); + } + } + + show_list('styles',info.styles, (style,v) => { style['font-style'] = v }); + show_list('weights',info.weights, (style,v) => { style['font-weight'] = v; style['font-variation-settings'] = `"wght" ${v}`; }); + show_list('unicode-ranges',info.subsets, () => {}, v => `${v}:

        ${chars_in_range(info.unicodeRange[v])}

        `); + + document.getElementById('category').textContent = info.category; + + document.getElementById('variable').classList.toggle('hidden',!info.variable); + if(info.variable) { + const variable_axes = document.getElementById('variable-axes'); + variable_axes.innerHTML = ''; + console.log(variable_info); + + const ranges = {}; + + Object.entries(variable_info.axes).filter(([axis,d]) => !['opsz','ital'].includes(axis.toLowerCase())).forEach(([axis,d]) => { + const axis_info = axes_info[axis.toLowerCase()]; + const li = document.createElement('li'); + const id = `axis-${axis}`; + variable_axes.append(li); + const label = element('label',{for: id}, axis_info.name); + li.append(label); + const range = element( + 'input', + { + type: 'range', + id: id, + min: d.min, + max: d.max, + step: d.step, + value: d.default, + list: `axis-list-${axis}` + } + ); + li.append(range); + ranges[axis] = range; + + const datalist = element('datalist',{id: `axis-list-${axis}`}) + li.append(datalist); + const default_option = element('option',{value:d.default}); + datalist.append(default_option); + + const output = element( + 'output', + { + for: id + }, + d.default + ); + li.append(output); + + const desc = element('p',{class:'help-text'},axis_info.description); + li.append(desc); + + range.addEventListener('input', e => { + output.textContent = range.value; + update_variables(); + }); + + }) + + function update_variables() { + const settings = Object.entries(ranges).map(([axis,range]) => `"${axis}" ${range.value}`).join(', '); + document.body.style['font-variation-settings'] = settings; + } + update_variables(); + + } + + const css_declarations = []; + if(info.variable) { + for(let style of info.styles) { + for(let range of info.subsets) { + const css = css_template(info, range, '', style, variable_info); + css_declarations.push(css); + } + } + + } else { + for(let style of info.styles) { + for(let weight of info.weights) { + for(let range of info.subsets) { + const css = css_template(info, range, weight, style, variable_info); + css_declarations.push(css); + } + } + } + } + + const css = css_declarations.join('\n\n'); + document.getElementById('font-style').textContent = css; + document.getElementById('css-display').textContent = css; + + link.href = font_page_url(info.id); + link.textContent = info.family; + } + + + fonts = fonts.filter(f=>!f.id.includes('noto')); + select.innerHTML = ''; + for(let font of fonts) { + const option = document.createElement('option'); + select.append(option); + option.textContent = font.family; + option.value = font.id; + } + + select.addEventListener('change', use_font); + + function random_font() { + const font = fonts[Math.floor(Math.random()*fonts.length)]; + select.value = font.id; + use_font(); + } + + document.getElementById('random-font').addEventListener('click', random_font); + + random_font(); +} + +go(); \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..125118f --- /dev/null +++ b/style.css @@ -0,0 +1,32 @@ +:root { + --spacing: 1em; + --family: sans-serif; + + color-scheme: light dark; +} +body { + font-family: var(--family); +} + +.fixed-font { + font-family: sans-serif; +} + +dt { + font-weight: bold; + text-decoration: underline; +} + +.hidden { + display: none; +} + +.help-text { + font-size: small; +} + +.char-range { + max-width: 100%; + max-height: 3em; + overflow: auto; +} \ No newline at end of file