Last active
February 6, 2024 20:35
-
-
Save OnCloud125252/7842f61aa11980a16f275ba9980117bf to your computer and use it in GitHub Desktop.
Subset font using opentype.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { loadSync, Font } from "opentype.js"; | |
import { readFileSync, readdirSync } from "fs"; | |
import { join } from "path"; | |
const rootDir = process.cwd(); | |
const fontDir = join(rootDir, "fonts"); | |
export default async function handler(req, res) { | |
if (req.method !== "GET") { | |
return res.status(405).send({ message: "Method Not Allowed" }); | |
} | |
try { | |
const query = req.query; | |
const { family, text } = query; | |
const decodedFamily = decodeURIComponent(family); | |
const decodedText = decodeURIComponent(text); | |
if (!query || !decodedFamily) { | |
return res.status(400).send({ message: "Bad Request" }); | |
} | |
else if (!readdirSync(fontDir).includes(`${decodedFamily}.otf`)) { | |
return res.status(404).send({ message: "Font Family Not Found" }); | |
} | |
if (!decodedText) { | |
res.setHeader("Content-Type", "application/octet-stream"); | |
res.setHeader(`Content-Disposition", "attachment; filename=${decodedFamily}.otf`); | |
return res.status(200).send(readFileSync(join(rootDir, "fonts", `${decodedFamily}.otf`))); | |
} | |
else if (decodedText) { | |
const subseted = await subsetFont(decodedFamily, decodedText); | |
res.setHeader("Content-Type", "application/octet-stream"); | |
res.setHeader("Content-Disposition", `attachment; filename=${decodedFamily}.otf`); | |
return res.status(200).send(subseted.fontBuffer); | |
} | |
else { | |
return res.status(422).send({ message: "Unprocessable Entity" }); | |
} | |
} catch (error) { | |
console.error(error); | |
return res.status(500).send({ message: "Internal Server Error" }); | |
} | |
} | |
async function subsetFont(family, requireTexts) { | |
const fontPath = join(fontDir, `${family}.otf`); | |
const glyphs = [...new Set(requireTexts.split(""))].join(""); | |
const font = loadSync(fontPath); | |
const postScriptName = font.getEnglishName("postScriptName"); | |
const [familyName, styleName] = postScriptName.split("-"); | |
const notdefGlyph = font.glyphs.get(0); | |
notdefGlyph.name = ".notdef"; | |
const subGlyphs = [notdefGlyph].concat(font.stringToGlyphs(glyphs)); | |
const subsetFont = new Font({ | |
familyName: familyName, | |
styleName: styleName, | |
unitsPerEm: font.unitsPerEm, | |
ascender: font.ascender, | |
descender: font.descender, | |
designer: font.getEnglishName("designer"), | |
designerURL: font.getEnglishName("designerURL"), | |
manufacturer: font.getEnglishName("manufacturer"), | |
manufacturerURL: font.getEnglishName("manufacturerURL"), | |
license: font.getEnglishName("license"), | |
licenseURL: font.getEnglishName("licenseURL"), | |
version: font.getEnglishName("version"), | |
description: font.getEnglishName("description"), | |
copyright: "This is a subset font of " + postScriptName + ". " + font.getEnglishName("copyright"), | |
trademark: font.getEnglishName("trademark"), | |
glyphs: subGlyphs | |
}); | |
return { | |
fontName: postScriptName, | |
fontBuffer: arrayBufferToNodeBuffer(subsetFont.toArrayBuffer()) | |
}; | |
} | |
function arrayBufferToNodeBuffer(arrayBuffer) { | |
const buffer = Buffer.alloc(arrayBuffer.byteLength); | |
const view = new Uint8Array(arrayBuffer); | |
for (let i = 0; i < buffer.length; ++i) { | |
buffer[i] = view[i]; | |
} | |
return buffer; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Original code by Ashung: https://gist.github.com/Ashung/7cbed68bdaab918fd01ff73ea1c2ab75#file-main-js */ | |
import { writeFileSync } from "fs"; | |
import { loadSync, Font } from "opentype.js"; | |
import { join } from "path"; | |
// Define the configs | |
const fontPath = ["./src/NotoSerifSC-Bold.otf"]; | |
const outputDir = "dist"; | |
const requireTexts = " 0123456789:年月日时分秒公元农历腊零初一二三四五六七八九十廿甲乙丙丁戊己庚辛壬癸子丑寅卯辰巳午未申酉戌亥立春雨水惊蛰春分清明谷雨立夏小满芒种夏至小暑大暑立秋处暑白露秋分寒露霜降立冬小雪大雪冬至小寒大寒"; | |
const glyphs = [...new Set(requireTexts.split(""))].join(""); | |
fontPath.forEach(item => { | |
const font = loadSync(item); | |
const postScriptName = font.getEnglishName("postScriptName"); | |
const [familyName, styleName] = postScriptName.split("-"); | |
const notdefGlyph = font.glyphs.get(0); | |
notdefGlyph.name = ".notdef"; | |
const subGlyphs = [notdefGlyph].concat(font.stringToGlyphs(glyphs)); | |
const subsetFont = new Font({ | |
familyName: familyName, | |
styleName: styleName, | |
unitsPerEm: font.unitsPerEm, | |
ascender: font.ascender, | |
descender: font.descender, | |
designer: font.getEnglishName("designer"), | |
designerURL: font.getEnglishName("designerURL"), | |
manufacturer: font.getEnglishName("manufacturer"), | |
manufacturerURL: font.getEnglishName("manufacturerURL"), | |
license: font.getEnglishName("license"), | |
licenseURL: font.getEnglishName("licenseURL"), | |
version: font.getEnglishName("version"), | |
description: font.getEnglishName("description"), | |
copyright: "This is a subset font of " + postScriptName + ". " + font.getEnglishName("copyright"), | |
trademark: font.getEnglishName("trademark"), | |
glyphs: subGlyphs | |
}); | |
// Configure the output file | |
const dist = join( | |
outputDir, | |
postScriptName.replace(/-/g, "_").toLowerCase() + "_subset.otf" | |
); | |
// Found this method by digging into openfont.js npm package | |
const buffer = arrayBufferToNodeBuffer(subsetFont.toArrayBuffer()); | |
writeFileSync(dist, buffer); | |
}); | |
// Credit: https://stackoverflow.com/a/12101012 | |
function arrayBufferToNodeBuffer(arrayBuffer) { | |
const buffer = Buffer.alloc(arrayBuffer.byteLength); | |
const view = new Uint8Array(arrayBuffer); | |
for (let i = 0; i < buffer.length; ++i) { | |
buffer[i] = view[i]; | |
} | |
return buffer; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
font.js is an api for next.js that can provide real-time font subsetting.
You can specify words you need, the api will respond a subseted font.
By subsetting the font, you can decrease the network usage loading font files.
The effect will be more obvious expecially when using Chinese content.
Requirements
Create a
font
folder in the root directory which contain.otf
font files.The font files should be renamed to the font family name.
For example for a font file named
Example.otf
, the result will befont/Example.otf
in root folder.Usage
The request methos is "GET" and the format is
/api/font?family=Example&text=requiredString
.Example
will be the font family name,requiredString
will be the words you want to specify, both querys are required.Note that
requiredString
should always be URL encoded.This is an example of loading a font which family name is
Example
and specify the required words to beABC123哈囉
.Start by create a style tag: