The names of fonts are rendered using the corresponding font.
Each font's name is rendered to an OffscreenCanvas, and that image is cached in OPFS. There's a list of links to download font files in the preview.
This commit is contained in:
parent
51d57259b0
commit
a4b046a784
3 changed files with 165 additions and 15 deletions
27
index.html
27
index.html
|
@ -9,21 +9,24 @@
|
|||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Fontsource font preview</h1>
|
||||
</header>
|
||||
<main>
|
||||
<section id="intro" class="fixed-font">
|
||||
<p>This page shows fonts loaded from <a href="https://fontsource.org">Fontsource</a>.</p>
|
||||
</section>
|
||||
|
||||
<section id="controls" class="fixed-font">
|
||||
<nav>
|
||||
<section id="controls">
|
||||
<label for="font-family">Font:</label>
|
||||
<select id="font-family">
|
||||
</select>
|
||||
<button type="button" id="random-font">I'm feeling lucky</button>
|
||||
</section>
|
||||
|
||||
<ul id="font-list"></ul>
|
||||
</nav>
|
||||
<main>
|
||||
<header>
|
||||
<h1>Fontsource font preview</h1>
|
||||
</header>
|
||||
<section id="intro">
|
||||
<p>This page shows fonts loaded from <a href="https://fontsource.org">Fontsource</a>.</p>
|
||||
</section>
|
||||
|
||||
<section id="info">
|
||||
<h2>About this font</h2>
|
||||
|
||||
|
@ -52,6 +55,12 @@
|
|||
|
||||
</section>
|
||||
|
||||
<section id="files">
|
||||
<h2>Files</h2>
|
||||
<ul id="file-links">
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section id="code">
|
||||
<h2>CSS</h2>
|
||||
<pre id="css-display"></pre>
|
||||
|
|
84
script.js
84
script.js
|
@ -79,7 +79,8 @@ const css_template = (info,range,weight,style,variable_info) => {
|
|||
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 `
|
||||
|
@ -113,6 +114,52 @@ async function fetch_json(url) {
|
|||
}
|
||||
|
||||
|
||||
async function draw_font_preview(f) {
|
||||
const d = await navigator.storage.getDirectory();
|
||||
const filename = `${f.family}.png`;
|
||||
let fh;
|
||||
try {
|
||||
fh = await d.getFileHandle(filename);
|
||||
} catch(e) {
|
||||
const style = document.createElement('style');
|
||||
try {
|
||||
document.head.append(style);
|
||||
const info = await fetch_json(font_info_url(f.id));
|
||||
const variable_info = info.variable ? await fetch_json(variable_info_url(info.id)) : {};
|
||||
const css = css_template(info,info.defSubset,info.weights[0],'normal',variable_info);
|
||||
style.textContent += css;
|
||||
await new Promise((resolve,reject) => {
|
||||
setTimeout(async () => {
|
||||
const ffs = Array.from(document.fonts).filter(ff=>ff.family == `"${f.family}"`);
|
||||
await Promise.all(ffs.map(ff => ff.load())).catch(reject)
|
||||
resolve();
|
||||
},1);
|
||||
});
|
||||
} catch(e) {
|
||||
return;
|
||||
}
|
||||
const cv = new OffscreenCanvas(1,1);
|
||||
const ctx = cv.getContext('2d');
|
||||
const font_style = `100px "${f.family}"`;
|
||||
ctx.font = font_style;
|
||||
const {width, fontBoundingBoxAscent, fontBoundingBoxDescent} = ctx.measureText(f.family);
|
||||
cv.width = width;
|
||||
cv.height = fontBoundingBoxAscent + fontBoundingBoxDescent;
|
||||
ctx.font = font_style;
|
||||
ctx.fillText(f.family,0,fontBoundingBoxAscent);
|
||||
const blob = await cv.toBlob();
|
||||
fh = await d.getFileHandle(filename,{create:true});
|
||||
const w = await fh.createWritable();
|
||||
await w.write(blob);
|
||||
await w.close();
|
||||
window.blob = blob;
|
||||
style.parentElement.removeChild(style);
|
||||
}
|
||||
const file = await fh.getFile();
|
||||
return URL.createObjectURL(file);
|
||||
|
||||
}
|
||||
|
||||
|
||||
async function go() {
|
||||
let [fonts, axes_info] = await Promise.all([
|
||||
|
@ -120,14 +167,20 @@ async function go() {
|
|||
fetch_json('https://api.fontsource.org/v1/axis-registry')
|
||||
]);
|
||||
|
||||
window.available_fonts = fonts;
|
||||
|
||||
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);
|
||||
|
||||
const preview = document.querySelector(`[data-font="${name}"]`);
|
||||
if(preview) {
|
||||
preview.scrollIntoView({block: 'center'});
|
||||
document.body.scrollTop = 0;
|
||||
}
|
||||
|
||||
document.body.style.setProperty('--family',info.family);
|
||||
|
||||
|
@ -153,7 +206,6 @@ async function go() {
|
|||
if(info.variable) {
|
||||
const variable_axes = document.getElementById('variable-axes');
|
||||
variable_axes.innerHTML = '';
|
||||
console.log(variable_info);
|
||||
|
||||
const ranges = {};
|
||||
|
||||
|
@ -208,7 +260,6 @@ async function go() {
|
|||
document.body.style['font-variation-settings'] = settings;
|
||||
}
|
||||
update_variables();
|
||||
|
||||
}
|
||||
|
||||
const css_declarations = [];
|
||||
|
@ -240,7 +291,7 @@ async function go() {
|
|||
}
|
||||
|
||||
|
||||
fonts = fonts.filter(f=>!f.id.includes('noto'));
|
||||
fonts = fonts.filter(f=>!f.id.match(/noto|playwrite/i));
|
||||
select.innerHTML = '';
|
||||
for(let font of fonts) {
|
||||
const option = document.createElement('option');
|
||||
|
@ -260,6 +311,27 @@ async function go() {
|
|||
document.getElementById('random-font').addEventListener('click', random_font);
|
||||
|
||||
random_font();
|
||||
|
||||
const fl = document.getElementById('font-list');
|
||||
const step = 10;
|
||||
for(let i=0;i<fonts.length;i+=step) {
|
||||
await Promise.all(fonts.slice(i+0,i+step).map(async f => {
|
||||
const li = document.createElement('li');
|
||||
fl.append(li);
|
||||
const a = document.createElement('a');
|
||||
li.append(a);
|
||||
a.addEventListener('click', () => {
|
||||
select.value = f.id;
|
||||
use_font();
|
||||
})
|
||||
const img = document.createElement('img');
|
||||
img.dataset.font = f.id;
|
||||
a.append(img);
|
||||
img.src = await draw_font_preview(f);
|
||||
img.style.height = '1em';
|
||||
img.alt = f.family;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
go();
|
69
style.css
69
style.css
|
@ -2,10 +2,59 @@
|
|||
--spacing: 1em;
|
||||
--family: sans-serif;
|
||||
|
||||
--background: white;
|
||||
--color: black;
|
||||
|
||||
color-scheme: light dark;
|
||||
height: 100svh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
--background: black;
|
||||
--color: white;
|
||||
}
|
||||
|
||||
#font-list img {
|
||||
filter: invert(100%);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
display: grid;
|
||||
grid-template-columns: 15em 1fr;
|
||||
gap: var(--spacing);
|
||||
width: 100svw;
|
||||
height: 100svh;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
|
||||
color: var(--color);
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
|
||||
nav {
|
||||
height: 100svh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#controls {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: var(--background);
|
||||
padding: var(--spacing);
|
||||
|
||||
& select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
font-family: var(--family);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.fixed-font {
|
||||
|
@ -30,3 +79,23 @@ dt {
|
|||
max-height: 3em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
#font-list {
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
width:100%;
|
||||
height:100%;
|
||||
|
||||
font-size: 2rem;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
& li {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
#css-display {
|
||||
margin: 0;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue