Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple fonts support #58

Merged
merged 11 commits into from
Sep 11, 2020
80 changes: 49 additions & 31 deletions _tools/generateFontData.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,37 +18,43 @@ const path = require("path");
const {
parseFontFile,
buildStylesheet,
buildFontJs
buildFontJs,
getSelector
} = require("specimen-skeleton-support");

const srcDirectory = path.resolve(__dirname, "../", "src");
const fontsDirectory = path.resolve(srcDirectory, "fonts");
const dataDirectory = path.resolve(srcDirectory, "_data");
const fontsStylesheetPath = path.resolve(srcDirectory, "css", "font.css");
const fontJsPath = path.resolve(srcDirectory, "js", "font.js");
const dataDirectory = path.resolve(srcDirectory, "_data/fonts");
const fontsStylesheetPath = path.resolve(srcDirectory, "css", "fonts.css");
const fontJsPath = path.resolve(srcDirectory, "js", "fonts.js");

const assert = (condition, message) => {
if (!condition) {
throw new Error(message);
}
};

const _appendFile = util.promisify(fs.appendFile);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of mutating things. I'd rather build up the whole result and write it at once, instead of appending things. Many things can go wrong with appending, you need to handle order etc. What do you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I'll see if I can refactor this.

const _writeFile = util.promisify(fs.writeFile);
const writeFile = (path, contents) => {
const writeFile = (path, contents, append) => {
console.info("Writing", path);
if (append) {
return _appendFile(path, contents);
}
return _writeFile(path, contents);
};

const writeDataFile = async (filename, data) => {
const dataFilePath = path.join(dataDirectory, filename);
const fileContents = JSON.stringify(data, null, 4);

return writeFile(dataFilePath, fileContents);
const writeDataFile = async (filename, fontName, data) => {
fs.mkdir(path.join(dataDirectory, fontName), { recursive: true }, () => {
const dataFilePath = path.join(dataDirectory, fontName, filename);
const fileContents = JSON.stringify(data, null, 4);
return writeFile(dataFilePath, fileContents);
});
};

const writeDataFiles = async fontData => {
const promises = Object.entries(fontData).map(([type, data]) => {
return writeDataFile(`${type}.json`, data);
const promises = Object.entries(fontData.data).map(([type, data]) => {
return writeDataFile(`${type}.json`, getSelector(fontData, true), data);
});

return Promise.all(promises);
Expand All @@ -59,46 +65,58 @@ const writeStylesheet = async (fontData, fontFilePath) => {
path.dirname(fontsStylesheetPath),
fontFilePath
);
const stylesheet = buildStylesheet(fontData, fontUrl).toString();
return writeFile(fontsStylesheetPath, stylesheet);
let stylesheet = buildStylesheet(fontData, fontUrl).toString();
stylesheet += "\n\n";
return writeFile(fontsStylesheetPath, stylesheet, true);
};

const writeFontJs = async fontData => {
const js = buildFontJs(fontData);
return writeFile(fontJsPath, js);
return writeFile(fontJsPath, js, true);
};

const findFirstFontFile = async directory => {
const findFontFile = async directory => {
const fontFiles = (await util.promisify(fs.readdir)(directory)).filter(
f => path.extname(f) == ".woff2"
);

assert(
fontFiles.length > 0,
`No font file found. Place your font in ${path.relative(
`No WOFF2 font files found. Place your WOFF2 fonts in ${path.relative(
process.cwd(),
directory
)}.`
);

assert(
fontFiles.length == 1,
"Multiple font files found. Please specify the path to your font file explicitly."
);
const paths = fontFiles.map(fontFile => ({
RoelN marked this conversation as resolved.
Show resolved Hide resolved
name: path.basename(fontFile, path.extname(fontFile)),
path: path.resolve(fontsDirectory, fontFile)
}));

return path.resolve(fontsDirectory, fontFiles[0]);
return paths;
};

const main = async () => {
const fontFilePath =
process.argv[2] || (await findFirstFontFile(fontsDirectory));
const fontData = await parseFontFile(fontFilePath);

await Promise.all([
writeDataFiles(fontData.data),
writeStylesheet(fontData, fontFilePath),
writeFontJs(fontData)
]);
const fontFiles = process.argv[2] || (await findFontFile(fontsDirectory));

// Initialise files
writeFile(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These writeFile calls are not awaited now. I think the writes should be awaited, to prevent race conditions with other writes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, will do.

fontsStylesheetPath,
`/* Generated by the Specimen Skeleton */\n`
);
writeFile(
fontJsPath,
`/* Generated by the Specimen Skeleton */\nexport const fontNames = [];\n`
);

for (const fontFile of fontFiles) {
const fontData = await parseFontFile(fontFile.path);
await Promise.all([
writeDataFiles(fontData, fontFile.name),
writeStylesheet(fontData, fontFile.path),
writeFontJs(fontData, fontFile.name)
]);
}
};

main().catch(e => {
Expand Down
6 changes: 4 additions & 2 deletions src/_includes/character-grid.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<ol class="character-grid">
{% for char in charset %}
<h2>Charset for {{ font[0] }}</h2>

<ol class="character-grid {{ font[0] }}">
{% for char in font[1].charset %}
<li>{{ char }}</li>
{% endfor %}
</ol>
Expand Down
8 changes: 4 additions & 4 deletions src/_includes/interactive-controls.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="interactive-controls">
<h2>Axes & Instances</h2>
<div class="interactive-controls {{ font[0] }}">
<h2>Axes & Instances for {{ font[0] }}</h2>
<ul class="interactive-controls-sliders">
<li>
<label for="interactive-controls-fontsize">Size</label>
Expand All @@ -14,7 +14,7 @@ <h2>Axes & Instances</h2>
class="interactive-controls-slider"
/>
</li>
{% for axis in axes %}
{% for axis in font[1].axes %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this loop over all fonts? And why [1] and not [0]?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the way 11ty returns data in _data: [0] contains the directory name, and [1] contains the actual data for each file. (In this case, axes.json). The looping happens here (prettier smashes this into one line, not happy about that, but hopefully you can parse it ;-))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah clear!

<li>
<label for="axis-{{ axis.axis }}">{{ axis.name }}</label>
<input
Expand All @@ -37,7 +37,7 @@ <h2>Axes & Instances</h2>
id="interactive-controls-instances-select"
class="interactive-controls-instances"
>
{% for instance in instances %}
{% for instance in font[1].instances %}
<option value="{{ instance.axes | json_stringify | escape }}"
>{{ instance.name }}</option
>
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion src/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ limitations under the License.

@import "./animation.css";
@import "./character-grid.css";
@import "./font.css";
@import "./fonts.css";
@import "./generic.css";
@import "./interactive-controls.css";
@import "./layout.css";
Expand Down
1 change: 1 addition & 0 deletions src/fonts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Put your WOFF2 font files here!
12 changes: 5 additions & 7 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@
</div>

<main>
{% include interactive-controls.html %}

{% for font in fonts %} {% if font[1].axes.length != 0 %} {% include
interactive-controls.html %}
<hr />

{% include character-grid.html %}

{% endif %} {% endfor %} {% for font in fonts %} {% include
character-grid.html %}
<hr />

{% include animation.html %}
{% endfor %} {% include animation.html %}
</main>
File renamed without changes.
15 changes: 10 additions & 5 deletions src/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// limitations under the License.

import "./assets.js";
import { fontName } from "./font";
import { fontNames } from "./fonts.js";
import FontFaceObserver from "fontfaceobserver";

const fontTimeOut = 5000; // In milliseconds
Expand All @@ -36,14 +36,19 @@ const throttle = (fn, wait) => {
};

// Set up FontFaceObserver
const font = new FontFaceObserver(fontName);
font.load(null, fontTimeOut).then(
let observers = [];
for (const fontName of fontNames) {
const font = new FontFaceObserver(fontName);
observers.push(font.load(null, fontTimeOut));
}

Promise.all(observers).then(
() => {
// Font has loaded
// All fonts have loaded
document.documentElement.classList.add("fonts-loaded");
},
() => {
// Font didn't load
// One or more fonts didn't load
document.documentElement.classList.add("fonts-failed");
}
);
Expand Down