feat: init
This commit is contained in:
21
node_modules/google-fonts-helper/LICENSE
generated
vendored
Normal file
21
node_modules/google-fonts-helper/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Datalogix
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
158
node_modules/google-fonts-helper/README.md
generated
vendored
Normal file
158
node_modules/google-fonts-helper/README.md
generated
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
# google-fonts-helper
|
||||
|
||||
[![npm version][npm-version-src]][npm-version-href]
|
||||
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
||||
[![Github Actions CI][github-actions-ci-src]][github-actions-ci-href]
|
||||
[![Codecov][codecov-src]][codecov-href]
|
||||
[![License][license-src]][license-href]
|
||||
|
||||
> [Google Fonts Helper](https://developers.google.com/fonts)
|
||||
|
||||
[📖 **Release Notes**](./CHANGELOG.md)
|
||||
|
||||
## Install
|
||||
|
||||
Install using npm, yarn or pnpm:
|
||||
|
||||
```bash
|
||||
npm install google-fonts-helper
|
||||
# or
|
||||
yarn add google-fonts-helper
|
||||
# or
|
||||
pnpm add google-fonts-helper
|
||||
```
|
||||
|
||||
Import into your Node.js project:
|
||||
|
||||
```js
|
||||
const {
|
||||
constructURL,
|
||||
merge,
|
||||
isValidURL,
|
||||
parse,
|
||||
download,
|
||||
} = require("google-fonts-helper");
|
||||
// or
|
||||
import {
|
||||
constructURL,
|
||||
merge,
|
||||
isValidURL,
|
||||
parse,
|
||||
download,
|
||||
} from "google-fonts-helper";
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### `constructURL(): string`
|
||||
|
||||
```ts
|
||||
constructURL({ families: { Roboto: true } });
|
||||
// https://fonts.googleapis.com/css2?family=Roboto
|
||||
|
||||
constructURL({ families: { Roboto: true, Lato: true } });
|
||||
// https://fonts.googleapis.com/css2?family=Roboto&family=Lato
|
||||
|
||||
constructURL({ families: { "Crimson Pro": { wght: "200..400" } } });
|
||||
// https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@200..400
|
||||
|
||||
constructURL({
|
||||
families: {
|
||||
Roboto: true,
|
||||
Lato: {
|
||||
wght: 100,
|
||||
},
|
||||
Raleway: {
|
||||
wght: [400],
|
||||
ital: [100, 400],
|
||||
},
|
||||
},
|
||||
});
|
||||
// https://fonts.googleapis.com/css2?family=Roboto&family=Lato:wght@100&family=Raleway:ital,wght@0,400;1,100;1,400
|
||||
```
|
||||
|
||||
### `merge(...fonts: GoogleFonts[]): GoogleFonts`
|
||||
|
||||
```ts
|
||||
merge({ families: { Roboto: true } }, { families: { Lato: true } });
|
||||
// { families: { Roboto: true, Lato: true } }
|
||||
|
||||
merge({ families: { Roboto: true } }, { families: { Roboto: [300, 400] } });
|
||||
// { families: { Roboto: [300, 400] } }
|
||||
```
|
||||
|
||||
### `isValidURL(url: string): boolean`
|
||||
|
||||
```ts
|
||||
isValidURL("https://fonts.googleapis.com/css2?family=Roboto");
|
||||
// true
|
||||
|
||||
isValidURL("https://foo.bar");
|
||||
// false
|
||||
```
|
||||
|
||||
### `parse(url: string): GoogleFonts`
|
||||
|
||||
```ts
|
||||
parse("https://fonts.googleapis.com/css2?family=Roboto");
|
||||
// { families: { Roboto: true } }
|
||||
|
||||
parse("https://fonts.googleapis.com/css2?family=Roboto&family=Lato");
|
||||
// { families: { Roboto: true, Lato: true } }
|
||||
|
||||
parse("https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@200..400");
|
||||
// { families: { 'Crimson Pro': { wght: '200..400' } }
|
||||
|
||||
parse("https://foo.bar");
|
||||
// {}
|
||||
```
|
||||
|
||||
### `download(url: string, option?: DownloadOptions): Downloader`
|
||||
|
||||
```ts
|
||||
const downloader = download('https://fonts.googleapis.com/css2?family=Roboto', {
|
||||
base64: false,
|
||||
overwriting: false,
|
||||
outputDir: './',
|
||||
stylePath: 'fonts.css',
|
||||
fontsDir: 'fonts',
|
||||
fontsPath: './fonts'
|
||||
})
|
||||
|
||||
downloader.hook('download-font:before', (font: FontInputOutput) {
|
||||
console.log(font)
|
||||
})
|
||||
|
||||
downloader.hook('download-font:done', (font: FontInputOutput) {
|
||||
console.log(font)
|
||||
})
|
||||
|
||||
downloader.hook('download:start', () => {
|
||||
console.log('Downloading fonts...')
|
||||
})
|
||||
|
||||
downloader.hook('download:complete', () => {
|
||||
console.log('Download fonts completed.')
|
||||
})
|
||||
|
||||
await downloader.execute()
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[MIT License](./LICENSE)
|
||||
|
||||
Copyright (c) Datalogix
|
||||
|
||||
<!-- Badges -->
|
||||
|
||||
[npm-version-src]: https://img.shields.io/npm/v/google-fonts-helper/latest.svg
|
||||
[npm-version-href]: https://npmjs.com/package/google-fonts-helper
|
||||
[npm-downloads-src]: https://img.shields.io/npm/dt/google-fonts-helper.svg
|
||||
[npm-downloads-href]: https://npmjs.com/package/google-fonts-helper
|
||||
[github-actions-ci-src]: https://github.com/datalogix/google-fonts-helper/workflows/ci/badge.svg
|
||||
[github-actions-ci-href]: https://github.com/datalogix/google-fonts-helper/actions?query=workflow%3Aci
|
||||
[codecov-src]: https://img.shields.io/codecov/c/github/datalogix/google-fonts-helper.svg
|
||||
[codecov-href]: https://codecov.io/gh/datalogix/google-fonts-helper
|
||||
[license-src]: https://img.shields.io/npm/l/google-fonts-helper.svg
|
||||
[license-href]: https://npmjs.com/package/google-fonts-helper
|
||||
420
node_modules/google-fonts-helper/dist/index.cjs
generated
vendored
Normal file
420
node_modules/google-fonts-helper/dist/index.cjs
generated
vendored
Normal file
@@ -0,0 +1,420 @@
|
||||
'use strict';
|
||||
|
||||
const ufo = require('ufo');
|
||||
const node_fs = require('node:fs');
|
||||
const node_path = require('node:path');
|
||||
const ofetch = require('ofetch');
|
||||
const hookable = require('hookable');
|
||||
const deepmerge = require('deepmerge');
|
||||
|
||||
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
||||
|
||||
const deepmerge__default = /*#__PURE__*/_interopDefaultCompat(deepmerge);
|
||||
|
||||
const GOOGLE_FONTS_DOMAIN = "fonts.googleapis.com";
|
||||
function isValidDisplay(display) {
|
||||
return ["auto", "block", "swap", "fallback", "optional"].includes(display);
|
||||
}
|
||||
function parseStyle(style) {
|
||||
if (["wght", "normal", "regular"].includes(style.toLowerCase())) {
|
||||
return "wght";
|
||||
}
|
||||
if (["ital", "italic", "i"].includes(style.toLowerCase())) {
|
||||
return "ital";
|
||||
}
|
||||
return style;
|
||||
}
|
||||
function cartesianProduct(...a) {
|
||||
return a.length < 2 ? a : a.reduce((a2, b) => a2.flatMap((d) => b.map((e) => [d, e].flat())));
|
||||
}
|
||||
function parseFamilyName(name) {
|
||||
return decodeURIComponent(name).replace(/\+/g, " ");
|
||||
}
|
||||
|
||||
function constructURL({
|
||||
families,
|
||||
display,
|
||||
subsets,
|
||||
text
|
||||
} = {}) {
|
||||
const _subsets = (Array.isArray(subsets) ? subsets : [subsets]).filter(Boolean);
|
||||
const family = convertFamiliesToArray(families ?? {});
|
||||
if (family.length < 1) {
|
||||
return false;
|
||||
}
|
||||
const query = {
|
||||
family
|
||||
};
|
||||
if (display && isValidDisplay(display)) {
|
||||
query.display = display;
|
||||
}
|
||||
if (_subsets.length > 0) {
|
||||
query.subset = _subsets.join(",");
|
||||
}
|
||||
if (text) {
|
||||
query.text = text;
|
||||
}
|
||||
return ufo.withHttps(ufo.withQuery(ufo.resolveURL(GOOGLE_FONTS_DOMAIN, "css2"), query));
|
||||
}
|
||||
function convertFamiliesToArray(families) {
|
||||
const result = [];
|
||||
Object.entries(families).forEach(([name, values]) => {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
name = parseFamilyName(name);
|
||||
if (typeof values === "string" && String(values).includes("..")) {
|
||||
result.push(`${name}:wght@${values}`);
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(values) && values.length > 0) {
|
||||
result.push(`${name}:wght@${values.join(";")}`);
|
||||
return;
|
||||
}
|
||||
if (Object.keys(values).length > 0) {
|
||||
const axes = {};
|
||||
let italicWeights = [];
|
||||
Object.entries(values).sort(([styleA], [styleB]) => styleA.localeCompare(styleB)).forEach(([style, weight]) => {
|
||||
const parsedStyle = parseStyle(style);
|
||||
if (parsedStyle === "ital") {
|
||||
axes[parsedStyle] = ["0", "1"];
|
||||
if (weight === true || weight === 400 || weight === 1) {
|
||||
italicWeights = ["*"];
|
||||
} else {
|
||||
italicWeights = Array.isArray(weight) ? weight.map((w) => String(w)) : [weight];
|
||||
}
|
||||
} else {
|
||||
axes[parseStyle(style)] = Array.isArray(weight) ? weight.map((w) => String(w)) : [weight];
|
||||
}
|
||||
});
|
||||
const strictlyItalic = [];
|
||||
if (Object.keys(axes).length === 1 && Object.hasOwn(axes, "ital")) {
|
||||
if (!(italicWeights.includes("*") || italicWeights.length === 1 && italicWeights.includes("400"))) {
|
||||
axes.wght = italicWeights;
|
||||
strictlyItalic.push(...italicWeights);
|
||||
}
|
||||
} else if (Object.hasOwn(axes, "wght") && !italicWeights.includes("*")) {
|
||||
strictlyItalic.push(...italicWeights.filter((w) => !axes.wght.includes(w)));
|
||||
axes.wght = [.../* @__PURE__ */ new Set([...axes.wght, ...italicWeights])];
|
||||
}
|
||||
const axisTagList = Object.keys(axes).sort((axisA, axisB) => {
|
||||
const isLowerA = axisA[0] === axisA[0].toLowerCase();
|
||||
const isLowerB = axisB[0] === axisB[0].toLowerCase();
|
||||
if (isLowerA && !isLowerB) {
|
||||
return -1;
|
||||
}
|
||||
if (!isLowerA && isLowerB) {
|
||||
return 1;
|
||||
}
|
||||
return axisA.localeCompare(axisB);
|
||||
});
|
||||
if (axisTagList.length === 1 && axisTagList.includes("ital")) {
|
||||
result.push(`${name}:ital@1`);
|
||||
return;
|
||||
}
|
||||
let axisTupleArrays = cartesianProduct(...axisTagList.map((tag) => axes[tag]), [[]]);
|
||||
const italicIndex = axisTagList.findIndex((i) => i === "ital");
|
||||
if (italicIndex !== -1) {
|
||||
const weightIndex = axisTagList.findIndex((i) => i === "wght");
|
||||
if (weightIndex !== -1) {
|
||||
axisTupleArrays = axisTupleArrays.filter((axisTuple) => axisTuple[italicIndex] === "0" && !strictlyItalic.includes(axisTuple[weightIndex]) || axisTuple[italicIndex] === "1" && italicWeights.includes(axisTuple[weightIndex]));
|
||||
}
|
||||
}
|
||||
const axisTupleList = axisTupleArrays.sort((axisTupleA, axisTupleB) => {
|
||||
for (let i = 0; i < axisTupleA.length; i++) {
|
||||
const compareResult = parseInt(axisTupleA[i]) - parseInt(axisTupleB[i]);
|
||||
if (compareResult !== 0) {
|
||||
return compareResult;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}).map((axisTuple) => axisTuple.join(",")).join(";");
|
||||
result.push(`${name}:${axisTagList.join(",")}@${axisTupleList}`);
|
||||
return;
|
||||
}
|
||||
if (values) {
|
||||
result.push(name);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function isValidURL(url) {
|
||||
return RegExp(GOOGLE_FONTS_DOMAIN).test(url);
|
||||
}
|
||||
|
||||
var __defProp = Object.defineProperty;
|
||||
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
||||
var __publicField = (obj, key, value) => {
|
||||
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
||||
return value;
|
||||
};
|
||||
class Downloader extends hookable.Hookable {
|
||||
constructor(url, options) {
|
||||
super();
|
||||
this.url = url;
|
||||
__publicField(this, "config");
|
||||
this.config = {
|
||||
base64: false,
|
||||
overwriting: false,
|
||||
outputDir: "./",
|
||||
stylePath: "fonts.css",
|
||||
fontsDir: "fonts",
|
||||
fontsPath: "./fonts",
|
||||
headers: [["user-agent", [
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko)",
|
||||
"Chrome/98.0.4758.102 Safari/537.36"
|
||||
].join(" ")]],
|
||||
...options
|
||||
};
|
||||
}
|
||||
async execute() {
|
||||
if (!isValidURL(this.url)) {
|
||||
throw new Error("Invalid Google Fonts URL");
|
||||
}
|
||||
const { outputDir, stylePath, headers, fontsPath } = this.config;
|
||||
const cssPath = node_path.resolve(outputDir, stylePath);
|
||||
let overwriting = this.config.overwriting;
|
||||
if (!overwriting && node_fs.existsSync(cssPath)) {
|
||||
const currentCssContent = node_fs.readFileSync(cssPath, "utf-8");
|
||||
const currentUrl = (currentCssContent.split(/\r?\n/, 1).shift() || "").replace("/*", "").replace("*/", "").trim();
|
||||
if (currentUrl === this.url) {
|
||||
return false;
|
||||
}
|
||||
overwriting = true;
|
||||
}
|
||||
await this.callHook("download:start");
|
||||
const { searchParams } = new URL(this.url);
|
||||
const subsets = searchParams.get("subset") ? searchParams.get("subset")?.split(",") : void 0;
|
||||
await this.callHook("download-css:before", this.url);
|
||||
const _css = await ofetch.ofetch(this.url, { headers });
|
||||
const { fonts: fontsFromCss, css: cssContent } = parseFontsFromCss(_css, fontsPath, subsets);
|
||||
await this.callHook("download-css:done", this.url, cssContent, fontsFromCss);
|
||||
const fonts = await this.downloadFonts(fontsFromCss);
|
||||
await this.callHook("write-css:before", cssPath, cssContent, fonts);
|
||||
const newContent = this.writeCss(cssPath, `/* ${this.url} */
|
||||
${cssContent}`, fonts);
|
||||
await this.callHook("write-css:done", cssPath, newContent, cssContent);
|
||||
await this.callHook("download:complete");
|
||||
return true;
|
||||
}
|
||||
async downloadFonts(fonts) {
|
||||
const { headers, base64, outputDir, fontsDir } = this.config;
|
||||
const downloadedFonts = [];
|
||||
const _fonts = [];
|
||||
for (const font of fonts) {
|
||||
const downloadedFont = downloadedFonts.find((f) => f.inputFont === font.inputFont);
|
||||
if (downloadedFont) {
|
||||
if (base64) {
|
||||
font.outputText = downloadedFont.outputText;
|
||||
} else {
|
||||
node_fs.copyFileSync(
|
||||
node_path.resolve(outputDir, fontsDir, downloadedFont.outputFont),
|
||||
node_path.resolve(outputDir, fontsDir, font.outputFont)
|
||||
);
|
||||
}
|
||||
_fonts.push(font);
|
||||
continue;
|
||||
}
|
||||
await this.callHook("download-font:before", font);
|
||||
const response = await ofetch.ofetch.raw(font.inputFont, { headers, responseType: "arrayBuffer" });
|
||||
if (!response?._data) {
|
||||
_fonts.push(font);
|
||||
continue;
|
||||
}
|
||||
const buffer = Buffer.from(response?._data);
|
||||
if (base64) {
|
||||
const mime = response.headers.get("content-type") ?? "font/woff2";
|
||||
font.outputText = `url('data:${mime};base64,${buffer.toString("base64")}')`;
|
||||
} else {
|
||||
const fontPath = node_path.resolve(outputDir, fontsDir, font.outputFont);
|
||||
node_fs.mkdirSync(node_path.dirname(fontPath), { recursive: true });
|
||||
node_fs.writeFileSync(fontPath, buffer);
|
||||
}
|
||||
_fonts.push(font);
|
||||
await this.callHook("download-font:done", font);
|
||||
downloadedFonts.push(font);
|
||||
}
|
||||
return _fonts;
|
||||
}
|
||||
writeCss(path, content, fonts) {
|
||||
for (const font of fonts) {
|
||||
content = content.replace(font.inputText, font.outputText);
|
||||
}
|
||||
node_fs.mkdirSync(node_path.dirname(path), { recursive: true });
|
||||
node_fs.writeFileSync(path, content, "utf-8");
|
||||
return content;
|
||||
}
|
||||
}
|
||||
function parseFontsFromCss(content, fontsPath, subsets) {
|
||||
const css = [];
|
||||
const fonts = [];
|
||||
const re = {
|
||||
face: /\s*(?:\/\*\s*(.*?)\s*\*\/)?[^@]*?@font-face\s*{(?:[^}]*?)}\s*/gi,
|
||||
family: /font-family\s*:\s*(?:'|")?([^;]*?)(?:'|")?\s*;/i,
|
||||
style: /font-style\s*:\s*([^;]*?)\s*;/i,
|
||||
weight: /font-weight\s*:\s*([^;]*?)\s*;/i,
|
||||
url: /url\s*\(\s*(?:'|")?\s*([^]*?)\s*(?:'|")?\s*\)\s*?/gi
|
||||
};
|
||||
let match1;
|
||||
while ((match1 = re.face.exec(content)) !== null) {
|
||||
const [fontface, subset] = match1;
|
||||
const familyRegExpArray = re.family.exec(fontface);
|
||||
const family = familyRegExpArray ? familyRegExpArray[1] : "";
|
||||
const styleRegExpArray = re.style.exec(fontface);
|
||||
const style = styleRegExpArray ? styleRegExpArray[1] : "";
|
||||
const weightRegExpArray = re.weight.exec(fontface);
|
||||
const weight = weightRegExpArray ? weightRegExpArray[1] : "";
|
||||
if (subsets && subsets.length && !subsets.includes(subset)) {
|
||||
continue;
|
||||
}
|
||||
css.push(fontface);
|
||||
let match2;
|
||||
while ((match2 = re.url.exec(fontface)) !== null) {
|
||||
const [forReplace, url] = match2;
|
||||
const ext = node_path.extname(url).replace(/^\./, "") || "woff2";
|
||||
const newFilename = formatFontFileName("{family}-{style}-{weight}-{subset}.{ext}", {
|
||||
family: family.replace(/\s+/g, "_"),
|
||||
style: style.replace(/\s+/g, "_") || "normal",
|
||||
weight: weight.replace(/\s+/g, "_") || "",
|
||||
subset: subset || "text",
|
||||
ext
|
||||
}).replace(/\.$/, "");
|
||||
fonts.push({
|
||||
inputFont: url,
|
||||
outputFont: newFilename,
|
||||
inputText: forReplace,
|
||||
outputText: `url('${node_path.posix.join(fontsPath, newFilename)}')`
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
css: css.join("\n"),
|
||||
fonts
|
||||
};
|
||||
}
|
||||
function formatFontFileName(template, values) {
|
||||
return Object.entries(values).filter(([key]) => /^[a-z0-9_-]+$/gi.test(key)).map(([key, value]) => [new RegExp(`([^{]|^){${key}}([^}]|$)`, "g"), `$1${value}$2`]).reduce((str, [regexp, replacement]) => str.replace(regexp, String(replacement)), template).replace(/({|}){2}/g, "$1");
|
||||
}
|
||||
|
||||
function download(url, options) {
|
||||
return new Downloader(url, options);
|
||||
}
|
||||
|
||||
function merge(...fonts) {
|
||||
return deepmerge__default.all(fonts);
|
||||
}
|
||||
|
||||
function parse(url) {
|
||||
const result = {};
|
||||
if (!isValidURL(url)) {
|
||||
return result;
|
||||
}
|
||||
const { searchParams, pathname } = new URL(url);
|
||||
if (!searchParams.has("family")) {
|
||||
return result;
|
||||
}
|
||||
const families = convertFamiliesObject(searchParams.getAll("family"), pathname.endsWith("2"));
|
||||
if (Object.keys(families).length < 1) {
|
||||
return result;
|
||||
}
|
||||
result.families = families;
|
||||
const display = searchParams.get("display");
|
||||
if (display && isValidDisplay(display)) {
|
||||
result.display = display;
|
||||
}
|
||||
const subsets = searchParams.get("subset");
|
||||
if (subsets) {
|
||||
result.subsets = subsets.split(",");
|
||||
}
|
||||
const text = searchParams.get("text");
|
||||
if (text) {
|
||||
result.text = text;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function convertFamiliesObject(families, v2 = true) {
|
||||
const result = {};
|
||||
families.flatMap((family) => family.split("|")).forEach((family) => {
|
||||
if (!family) {
|
||||
return;
|
||||
}
|
||||
if (!family.includes(":")) {
|
||||
result[family] = true;
|
||||
return;
|
||||
}
|
||||
const parts = family.split(":");
|
||||
if (!parts[1]) {
|
||||
return;
|
||||
}
|
||||
const values = {};
|
||||
if (!v2) {
|
||||
parts[1].split(",").forEach((style) => {
|
||||
const styleParsed = parseStyle(style);
|
||||
if (styleParsed === "wght") {
|
||||
values.wght = true;
|
||||
}
|
||||
if (styleParsed === "ital") {
|
||||
values.ital = true;
|
||||
}
|
||||
if (styleParsed === "bold" || styleParsed === "b") {
|
||||
values.wght = 700;
|
||||
}
|
||||
if (styleParsed === "bolditalic" || styleParsed === "bi") {
|
||||
values.ital = 700;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (v2) {
|
||||
let [styles, weights] = parts[1].split("@");
|
||||
if (!weights) {
|
||||
weights = String(styles).replace(",", ";");
|
||||
styles = "wght";
|
||||
}
|
||||
styles.split(",").forEach((style) => {
|
||||
const styleParsed = parseStyle(style);
|
||||
values[styleParsed] = weights.split(";").map((weight) => {
|
||||
if (/^\+?\d+$/.test(weight)) {
|
||||
return parseInt(weight);
|
||||
}
|
||||
const [pos, w] = weight.split(",");
|
||||
const index = styleParsed === "wght" ? 0 : 1;
|
||||
if (!w) {
|
||||
return weight;
|
||||
}
|
||||
if (parseInt(pos) !== index) {
|
||||
return 0;
|
||||
}
|
||||
if (/^\+?\d+$/.test(w)) {
|
||||
return parseInt(w);
|
||||
}
|
||||
return w;
|
||||
}).filter((v) => parseInt(v.toString()) > 0 || v.toString().includes(".."));
|
||||
if (!values[styleParsed].length) {
|
||||
values[styleParsed] = true;
|
||||
return;
|
||||
}
|
||||
if (values[styleParsed].length > 1) {
|
||||
return;
|
||||
}
|
||||
const first = values[styleParsed][0];
|
||||
if (String(first).includes("..")) {
|
||||
values[styleParsed] = first;
|
||||
}
|
||||
if (first === 1 || first === true) {
|
||||
values[styleParsed] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
result[parseFamilyName(parts[0])] = values;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
exports.Downloader = Downloader;
|
||||
exports.constructURL = constructURL;
|
||||
exports.download = download;
|
||||
exports.isValidURL = isValidURL;
|
||||
exports.merge = merge;
|
||||
exports.parse = parse;
|
||||
15178
node_modules/google-fonts-helper/dist/index.d.cts
generated
vendored
Normal file
15178
node_modules/google-fonts-helper/dist/index.d.cts
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
15178
node_modules/google-fonts-helper/dist/index.d.mts
generated
vendored
Normal file
15178
node_modules/google-fonts-helper/dist/index.d.mts
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
15178
node_modules/google-fonts-helper/dist/index.d.ts
generated
vendored
Normal file
15178
node_modules/google-fonts-helper/dist/index.d.ts
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
409
node_modules/google-fonts-helper/dist/index.mjs
generated
vendored
Normal file
409
node_modules/google-fonts-helper/dist/index.mjs
generated
vendored
Normal file
@@ -0,0 +1,409 @@
|
||||
import { withHttps, withQuery, resolveURL } from 'ufo';
|
||||
import { existsSync, readFileSync, copyFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
||||
import { resolve, dirname, extname, posix } from 'node:path';
|
||||
import { ofetch } from 'ofetch';
|
||||
import { Hookable } from 'hookable';
|
||||
import deepmerge from 'deepmerge';
|
||||
|
||||
const GOOGLE_FONTS_DOMAIN = "fonts.googleapis.com";
|
||||
function isValidDisplay(display) {
|
||||
return ["auto", "block", "swap", "fallback", "optional"].includes(display);
|
||||
}
|
||||
function parseStyle(style) {
|
||||
if (["wght", "normal", "regular"].includes(style.toLowerCase())) {
|
||||
return "wght";
|
||||
}
|
||||
if (["ital", "italic", "i"].includes(style.toLowerCase())) {
|
||||
return "ital";
|
||||
}
|
||||
return style;
|
||||
}
|
||||
function cartesianProduct(...a) {
|
||||
return a.length < 2 ? a : a.reduce((a2, b) => a2.flatMap((d) => b.map((e) => [d, e].flat())));
|
||||
}
|
||||
function parseFamilyName(name) {
|
||||
return decodeURIComponent(name).replace(/\+/g, " ");
|
||||
}
|
||||
|
||||
function constructURL({
|
||||
families,
|
||||
display,
|
||||
subsets,
|
||||
text
|
||||
} = {}) {
|
||||
const _subsets = (Array.isArray(subsets) ? subsets : [subsets]).filter(Boolean);
|
||||
const family = convertFamiliesToArray(families ?? {});
|
||||
if (family.length < 1) {
|
||||
return false;
|
||||
}
|
||||
const query = {
|
||||
family
|
||||
};
|
||||
if (display && isValidDisplay(display)) {
|
||||
query.display = display;
|
||||
}
|
||||
if (_subsets.length > 0) {
|
||||
query.subset = _subsets.join(",");
|
||||
}
|
||||
if (text) {
|
||||
query.text = text;
|
||||
}
|
||||
return withHttps(withQuery(resolveURL(GOOGLE_FONTS_DOMAIN, "css2"), query));
|
||||
}
|
||||
function convertFamiliesToArray(families) {
|
||||
const result = [];
|
||||
Object.entries(families).forEach(([name, values]) => {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
name = parseFamilyName(name);
|
||||
if (typeof values === "string" && String(values).includes("..")) {
|
||||
result.push(`${name}:wght@${values}`);
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(values) && values.length > 0) {
|
||||
result.push(`${name}:wght@${values.join(";")}`);
|
||||
return;
|
||||
}
|
||||
if (Object.keys(values).length > 0) {
|
||||
const axes = {};
|
||||
let italicWeights = [];
|
||||
Object.entries(values).sort(([styleA], [styleB]) => styleA.localeCompare(styleB)).forEach(([style, weight]) => {
|
||||
const parsedStyle = parseStyle(style);
|
||||
if (parsedStyle === "ital") {
|
||||
axes[parsedStyle] = ["0", "1"];
|
||||
if (weight === true || weight === 400 || weight === 1) {
|
||||
italicWeights = ["*"];
|
||||
} else {
|
||||
italicWeights = Array.isArray(weight) ? weight.map((w) => String(w)) : [weight];
|
||||
}
|
||||
} else {
|
||||
axes[parseStyle(style)] = Array.isArray(weight) ? weight.map((w) => String(w)) : [weight];
|
||||
}
|
||||
});
|
||||
const strictlyItalic = [];
|
||||
if (Object.keys(axes).length === 1 && Object.hasOwn(axes, "ital")) {
|
||||
if (!(italicWeights.includes("*") || italicWeights.length === 1 && italicWeights.includes("400"))) {
|
||||
axes.wght = italicWeights;
|
||||
strictlyItalic.push(...italicWeights);
|
||||
}
|
||||
} else if (Object.hasOwn(axes, "wght") && !italicWeights.includes("*")) {
|
||||
strictlyItalic.push(...italicWeights.filter((w) => !axes.wght.includes(w)));
|
||||
axes.wght = [.../* @__PURE__ */ new Set([...axes.wght, ...italicWeights])];
|
||||
}
|
||||
const axisTagList = Object.keys(axes).sort((axisA, axisB) => {
|
||||
const isLowerA = axisA[0] === axisA[0].toLowerCase();
|
||||
const isLowerB = axisB[0] === axisB[0].toLowerCase();
|
||||
if (isLowerA && !isLowerB) {
|
||||
return -1;
|
||||
}
|
||||
if (!isLowerA && isLowerB) {
|
||||
return 1;
|
||||
}
|
||||
return axisA.localeCompare(axisB);
|
||||
});
|
||||
if (axisTagList.length === 1 && axisTagList.includes("ital")) {
|
||||
result.push(`${name}:ital@1`);
|
||||
return;
|
||||
}
|
||||
let axisTupleArrays = cartesianProduct(...axisTagList.map((tag) => axes[tag]), [[]]);
|
||||
const italicIndex = axisTagList.findIndex((i) => i === "ital");
|
||||
if (italicIndex !== -1) {
|
||||
const weightIndex = axisTagList.findIndex((i) => i === "wght");
|
||||
if (weightIndex !== -1) {
|
||||
axisTupleArrays = axisTupleArrays.filter((axisTuple) => axisTuple[italicIndex] === "0" && !strictlyItalic.includes(axisTuple[weightIndex]) || axisTuple[italicIndex] === "1" && italicWeights.includes(axisTuple[weightIndex]));
|
||||
}
|
||||
}
|
||||
const axisTupleList = axisTupleArrays.sort((axisTupleA, axisTupleB) => {
|
||||
for (let i = 0; i < axisTupleA.length; i++) {
|
||||
const compareResult = parseInt(axisTupleA[i]) - parseInt(axisTupleB[i]);
|
||||
if (compareResult !== 0) {
|
||||
return compareResult;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}).map((axisTuple) => axisTuple.join(",")).join(";");
|
||||
result.push(`${name}:${axisTagList.join(",")}@${axisTupleList}`);
|
||||
return;
|
||||
}
|
||||
if (values) {
|
||||
result.push(name);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function isValidURL(url) {
|
||||
return RegExp(GOOGLE_FONTS_DOMAIN).test(url);
|
||||
}
|
||||
|
||||
var __defProp = Object.defineProperty;
|
||||
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
||||
var __publicField = (obj, key, value) => {
|
||||
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
||||
return value;
|
||||
};
|
||||
class Downloader extends Hookable {
|
||||
constructor(url, options) {
|
||||
super();
|
||||
this.url = url;
|
||||
__publicField(this, "config");
|
||||
this.config = {
|
||||
base64: false,
|
||||
overwriting: false,
|
||||
outputDir: "./",
|
||||
stylePath: "fonts.css",
|
||||
fontsDir: "fonts",
|
||||
fontsPath: "./fonts",
|
||||
headers: [["user-agent", [
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko)",
|
||||
"Chrome/98.0.4758.102 Safari/537.36"
|
||||
].join(" ")]],
|
||||
...options
|
||||
};
|
||||
}
|
||||
async execute() {
|
||||
if (!isValidURL(this.url)) {
|
||||
throw new Error("Invalid Google Fonts URL");
|
||||
}
|
||||
const { outputDir, stylePath, headers, fontsPath } = this.config;
|
||||
const cssPath = resolve(outputDir, stylePath);
|
||||
let overwriting = this.config.overwriting;
|
||||
if (!overwriting && existsSync(cssPath)) {
|
||||
const currentCssContent = readFileSync(cssPath, "utf-8");
|
||||
const currentUrl = (currentCssContent.split(/\r?\n/, 1).shift() || "").replace("/*", "").replace("*/", "").trim();
|
||||
if (currentUrl === this.url) {
|
||||
return false;
|
||||
}
|
||||
overwriting = true;
|
||||
}
|
||||
await this.callHook("download:start");
|
||||
const { searchParams } = new URL(this.url);
|
||||
const subsets = searchParams.get("subset") ? searchParams.get("subset")?.split(",") : void 0;
|
||||
await this.callHook("download-css:before", this.url);
|
||||
const _css = await ofetch(this.url, { headers });
|
||||
const { fonts: fontsFromCss, css: cssContent } = parseFontsFromCss(_css, fontsPath, subsets);
|
||||
await this.callHook("download-css:done", this.url, cssContent, fontsFromCss);
|
||||
const fonts = await this.downloadFonts(fontsFromCss);
|
||||
await this.callHook("write-css:before", cssPath, cssContent, fonts);
|
||||
const newContent = this.writeCss(cssPath, `/* ${this.url} */
|
||||
${cssContent}`, fonts);
|
||||
await this.callHook("write-css:done", cssPath, newContent, cssContent);
|
||||
await this.callHook("download:complete");
|
||||
return true;
|
||||
}
|
||||
async downloadFonts(fonts) {
|
||||
const { headers, base64, outputDir, fontsDir } = this.config;
|
||||
const downloadedFonts = [];
|
||||
const _fonts = [];
|
||||
for (const font of fonts) {
|
||||
const downloadedFont = downloadedFonts.find((f) => f.inputFont === font.inputFont);
|
||||
if (downloadedFont) {
|
||||
if (base64) {
|
||||
font.outputText = downloadedFont.outputText;
|
||||
} else {
|
||||
copyFileSync(
|
||||
resolve(outputDir, fontsDir, downloadedFont.outputFont),
|
||||
resolve(outputDir, fontsDir, font.outputFont)
|
||||
);
|
||||
}
|
||||
_fonts.push(font);
|
||||
continue;
|
||||
}
|
||||
await this.callHook("download-font:before", font);
|
||||
const response = await ofetch.raw(font.inputFont, { headers, responseType: "arrayBuffer" });
|
||||
if (!response?._data) {
|
||||
_fonts.push(font);
|
||||
continue;
|
||||
}
|
||||
const buffer = Buffer.from(response?._data);
|
||||
if (base64) {
|
||||
const mime = response.headers.get("content-type") ?? "font/woff2";
|
||||
font.outputText = `url('data:${mime};base64,${buffer.toString("base64")}')`;
|
||||
} else {
|
||||
const fontPath = resolve(outputDir, fontsDir, font.outputFont);
|
||||
mkdirSync(dirname(fontPath), { recursive: true });
|
||||
writeFileSync(fontPath, buffer);
|
||||
}
|
||||
_fonts.push(font);
|
||||
await this.callHook("download-font:done", font);
|
||||
downloadedFonts.push(font);
|
||||
}
|
||||
return _fonts;
|
||||
}
|
||||
writeCss(path, content, fonts) {
|
||||
for (const font of fonts) {
|
||||
content = content.replace(font.inputText, font.outputText);
|
||||
}
|
||||
mkdirSync(dirname(path), { recursive: true });
|
||||
writeFileSync(path, content, "utf-8");
|
||||
return content;
|
||||
}
|
||||
}
|
||||
function parseFontsFromCss(content, fontsPath, subsets) {
|
||||
const css = [];
|
||||
const fonts = [];
|
||||
const re = {
|
||||
face: /\s*(?:\/\*\s*(.*?)\s*\*\/)?[^@]*?@font-face\s*{(?:[^}]*?)}\s*/gi,
|
||||
family: /font-family\s*:\s*(?:'|")?([^;]*?)(?:'|")?\s*;/i,
|
||||
style: /font-style\s*:\s*([^;]*?)\s*;/i,
|
||||
weight: /font-weight\s*:\s*([^;]*?)\s*;/i,
|
||||
url: /url\s*\(\s*(?:'|")?\s*([^]*?)\s*(?:'|")?\s*\)\s*?/gi
|
||||
};
|
||||
let match1;
|
||||
while ((match1 = re.face.exec(content)) !== null) {
|
||||
const [fontface, subset] = match1;
|
||||
const familyRegExpArray = re.family.exec(fontface);
|
||||
const family = familyRegExpArray ? familyRegExpArray[1] : "";
|
||||
const styleRegExpArray = re.style.exec(fontface);
|
||||
const style = styleRegExpArray ? styleRegExpArray[1] : "";
|
||||
const weightRegExpArray = re.weight.exec(fontface);
|
||||
const weight = weightRegExpArray ? weightRegExpArray[1] : "";
|
||||
if (subsets && subsets.length && !subsets.includes(subset)) {
|
||||
continue;
|
||||
}
|
||||
css.push(fontface);
|
||||
let match2;
|
||||
while ((match2 = re.url.exec(fontface)) !== null) {
|
||||
const [forReplace, url] = match2;
|
||||
const ext = extname(url).replace(/^\./, "") || "woff2";
|
||||
const newFilename = formatFontFileName("{family}-{style}-{weight}-{subset}.{ext}", {
|
||||
family: family.replace(/\s+/g, "_"),
|
||||
style: style.replace(/\s+/g, "_") || "normal",
|
||||
weight: weight.replace(/\s+/g, "_") || "",
|
||||
subset: subset || "text",
|
||||
ext
|
||||
}).replace(/\.$/, "");
|
||||
fonts.push({
|
||||
inputFont: url,
|
||||
outputFont: newFilename,
|
||||
inputText: forReplace,
|
||||
outputText: `url('${posix.join(fontsPath, newFilename)}')`
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
css: css.join("\n"),
|
||||
fonts
|
||||
};
|
||||
}
|
||||
function formatFontFileName(template, values) {
|
||||
return Object.entries(values).filter(([key]) => /^[a-z0-9_-]+$/gi.test(key)).map(([key, value]) => [new RegExp(`([^{]|^){${key}}([^}]|$)`, "g"), `$1${value}$2`]).reduce((str, [regexp, replacement]) => str.replace(regexp, String(replacement)), template).replace(/({|}){2}/g, "$1");
|
||||
}
|
||||
|
||||
function download(url, options) {
|
||||
return new Downloader(url, options);
|
||||
}
|
||||
|
||||
function merge(...fonts) {
|
||||
return deepmerge.all(fonts);
|
||||
}
|
||||
|
||||
function parse(url) {
|
||||
const result = {};
|
||||
if (!isValidURL(url)) {
|
||||
return result;
|
||||
}
|
||||
const { searchParams, pathname } = new URL(url);
|
||||
if (!searchParams.has("family")) {
|
||||
return result;
|
||||
}
|
||||
const families = convertFamiliesObject(searchParams.getAll("family"), pathname.endsWith("2"));
|
||||
if (Object.keys(families).length < 1) {
|
||||
return result;
|
||||
}
|
||||
result.families = families;
|
||||
const display = searchParams.get("display");
|
||||
if (display && isValidDisplay(display)) {
|
||||
result.display = display;
|
||||
}
|
||||
const subsets = searchParams.get("subset");
|
||||
if (subsets) {
|
||||
result.subsets = subsets.split(",");
|
||||
}
|
||||
const text = searchParams.get("text");
|
||||
if (text) {
|
||||
result.text = text;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function convertFamiliesObject(families, v2 = true) {
|
||||
const result = {};
|
||||
families.flatMap((family) => family.split("|")).forEach((family) => {
|
||||
if (!family) {
|
||||
return;
|
||||
}
|
||||
if (!family.includes(":")) {
|
||||
result[family] = true;
|
||||
return;
|
||||
}
|
||||
const parts = family.split(":");
|
||||
if (!parts[1]) {
|
||||
return;
|
||||
}
|
||||
const values = {};
|
||||
if (!v2) {
|
||||
parts[1].split(",").forEach((style) => {
|
||||
const styleParsed = parseStyle(style);
|
||||
if (styleParsed === "wght") {
|
||||
values.wght = true;
|
||||
}
|
||||
if (styleParsed === "ital") {
|
||||
values.ital = true;
|
||||
}
|
||||
if (styleParsed === "bold" || styleParsed === "b") {
|
||||
values.wght = 700;
|
||||
}
|
||||
if (styleParsed === "bolditalic" || styleParsed === "bi") {
|
||||
values.ital = 700;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (v2) {
|
||||
let [styles, weights] = parts[1].split("@");
|
||||
if (!weights) {
|
||||
weights = String(styles).replace(",", ";");
|
||||
styles = "wght";
|
||||
}
|
||||
styles.split(",").forEach((style) => {
|
||||
const styleParsed = parseStyle(style);
|
||||
values[styleParsed] = weights.split(";").map((weight) => {
|
||||
if (/^\+?\d+$/.test(weight)) {
|
||||
return parseInt(weight);
|
||||
}
|
||||
const [pos, w] = weight.split(",");
|
||||
const index = styleParsed === "wght" ? 0 : 1;
|
||||
if (!w) {
|
||||
return weight;
|
||||
}
|
||||
if (parseInt(pos) !== index) {
|
||||
return 0;
|
||||
}
|
||||
if (/^\+?\d+$/.test(w)) {
|
||||
return parseInt(w);
|
||||
}
|
||||
return w;
|
||||
}).filter((v) => parseInt(v.toString()) > 0 || v.toString().includes(".."));
|
||||
if (!values[styleParsed].length) {
|
||||
values[styleParsed] = true;
|
||||
return;
|
||||
}
|
||||
if (values[styleParsed].length > 1) {
|
||||
return;
|
||||
}
|
||||
const first = values[styleParsed][0];
|
||||
if (String(first).includes("..")) {
|
||||
values[styleParsed] = first;
|
||||
}
|
||||
if (first === 1 || first === true) {
|
||||
values[styleParsed] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
result[parseFamilyName(parts[0])] = values;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export { Downloader, constructURL, download, isValidURL, merge, parse };
|
||||
46
node_modules/google-fonts-helper/package.json
generated
vendored
Normal file
46
node_modules/google-fonts-helper/package.json
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "google-fonts-helper",
|
||||
"version": "3.7.3",
|
||||
"description": "Google Fonts Helper",
|
||||
"repository": "datalogix/google-fonts-helper",
|
||||
"license": "MIT",
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"require": "./dist/index.cjs",
|
||||
"import": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"deepmerge": "^4.3.1",
|
||||
"hookable": "^5.5.3",
|
||||
"ofetch": "^1.4.1",
|
||||
"ufo": "^1.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxtjs/eslint-config-typescript": "latest",
|
||||
"@types/fs-extra": "latest",
|
||||
"@vitest/coverage-v8": "latest",
|
||||
"changelogen": "latest",
|
||||
"eslint": "latest",
|
||||
"tempy": "latest",
|
||||
"typescript": "latest",
|
||||
"unbuild": "latest",
|
||||
"vitest": "latest"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "unbuild",
|
||||
"dev": "vitest",
|
||||
"lint": "eslint --ext .ts,.js .",
|
||||
"release": "pnpm test && pnpm build && changelogen --release --push && pnpm publish",
|
||||
"test": "pnpm lint && vitest run --coverage"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user