265 lines
7.6 KiB
JavaScript
265 lines
7.6 KiB
JavaScript
![]() |
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}: <p class="char-range">${chars_in_range(info.unicodeRange[v])}</p>`);
|
||
|
|
||
|
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();
|