diff --git a/.gitignore b/.gitignore index 433aea75..d491c39e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,7 @@ node_modules .env .env.* !.env.example +venv vite.config.js.timestamp-* vite.config.ts.timestamp-* +src/lib/assets/icons.min.woff2 diff --git a/README.md b/README.md index 63403372..d4e6f17b 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,5 @@ # dot i/o V2 -Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). +## Development -## Creating a project - -If you're seeing this, you've probably already done this step. Congrats! - -```bash -# create a new project in the current directory -npm create svelte@latest - -# create a new project in my-app -npm create svelte@latest my-app -``` - -## Developing - -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: - -```bash -npm run dev - -# or start the server and open the app in a new browser tab -npm run dev -- --open -``` - -## Building - -To create a production version of your app: - -```bash -npm run build -``` - -You can preview the production build with `npm run preview`. - -> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. +Create a python virtual environment diff --git a/icons.config.js b/icons.config.js new file mode 100644 index 00000000..864ec2fa --- /dev/null +++ b/icons.config.js @@ -0,0 +1,23 @@ +export default { + inputPath: + "node_modules/@fontsource-variable/material-symbols-rounded/files/material-symbols-rounded-latin-full-normal.woff2", + outputPath: "src/lib/assets/icons.min.woff2", + icons: [ + "piano", + "keyboard", + "settings", + "music_note", + "avg_pace", + "lyrics", + "speed", + "cognition", + "update", + "offline_pin", + "warning", + "cable", + "person", + ], + codePoints: { + speed: "e9e4", + }, +} diff --git a/package-lock.json b/package-lock.json index 59d196da..acaeb339 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@theaninova/prettier-config": "^1.0.0", "@types/w3c-web-serial": "^1.0.3", "@vite-pwa/sveltekit": "^0.2.5", + "fontkit": "^2.0.2", "prettier": "^2.8.0", "prettier-plugin-svelte": "^2.10.1", "sass": "^1.63.6", @@ -2402,6 +2403,15 @@ "vite": "^4.0.0" } }, + "node_modules/@swc/helpers": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", + "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@theaninova/prettier-config": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@theaninova/prettier-config/-/prettier-config-1.0.0.tgz", @@ -2679,6 +2689,26 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -2710,6 +2740,15 @@ "node": ">=8" } }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "dev": true, + "dependencies": { + "base64-js": "^1.1.2" + } + }, "node_modules/browserslist": { "version": "4.21.9", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", @@ -2890,6 +2929,15 @@ "fsevents": "~2.3.2" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/code-red": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.3.tgz", @@ -3134,6 +3182,12 @@ "integrity": "sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==", "dev": true }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "dev": true + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -3565,6 +3619,23 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "node_modules/fontkit": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.2.tgz", + "integrity": "sha512-jc4k5Yr8iov8QfS6u8w2CnHWVmbOGtdBtOXMze5Y+QD966Rx6PEVWXSEGwXlsDlKtu1G12cJjcsybnqhSk/+LA==", + "dev": true, + "dependencies": { + "@swc/helpers": "^0.4.2", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -4984,6 +5055,12 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5507,6 +5584,12 @@ "node": ">=4" } }, + "node_modules/restructure": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.0.tgz", + "integrity": "sha512-Xj8/MEIhhfj9X2rmD9iJ4Gga9EFqVlpMj3vfLnV2r/Mh5jRMryNV+6lWh9GdJtDBcBSPIqzRdfBQ3wDtNFv/uw==", + "dev": true + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -6481,6 +6564,12 @@ "node": ">=10" } }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "dev": true + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -6529,6 +6618,12 @@ "node": ">=8" } }, + "node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "dev": true + }, "node_modules/type-fest": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", @@ -6626,6 +6721,16 @@ "node": ">=4" } }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "dev": true, + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, "node_modules/unicode-property-aliases-ecmascript": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", @@ -6635,6 +6740,16 @@ "node": ">=4" } }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "dev": true, + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, "node_modules/unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", diff --git a/package.json b/package.json index 4a1d9aad..ad9fe5b8 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch", + "minify-icons": "node tools/minify-icon-font.js", "lint": "prettier --plugin-search-dir . --check .", "format": "prettier --plugin-search-dir . --write ." }, @@ -26,6 +27,7 @@ "@sveltejs/adapter-static": "^2.0.2", "@sveltejs/kit": "^1.20.4", "@material/material-color-utilities": "^0.2.7", + "fontkit": "^2.0.2", "prettier": "^2.8.0", "prettier-plugin-svelte": "^2.10.1", "svelte": "^4.0.0", diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..5e74e3f3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Brotli==1.0.9 +fonttools[woff]==4.40.0 +zopfli==0.2.2 diff --git a/src/lib/fonts/material-symbols-rounded.scss b/src/lib/fonts/material-symbols-rounded.scss index 51a45d0a..34de0a7f 100644 --- a/src/lib/fonts/material-symbols-rounded.scss +++ b/src/lib/fonts/material-symbols-rounded.scss @@ -1,10 +1,8 @@ -/* fallback */ @font-face { font-family: "Material Symbols Rounded"; font-weight: 100 700; font-style: normal; - src: url("@fontsource-variable/material-symbols-rounded/files/material-symbols-rounded-latin-full-normal.woff2") - format("woff2"); + src: url("$lib/assets/icons.min.woff2") format("woff2"); } .icon { diff --git a/src/lib/fonts/noto-sans-mono.scss b/src/lib/fonts/noto-sans-mono.scss index 59d6df81..def4e205 100644 --- a/src/lib/fonts/noto-sans-mono.scss +++ b/src/lib/fonts/noto-sans-mono.scss @@ -1,5 +1,6 @@ /* noto-sans-mono-cyrillic-ext-wght-normal */ -@font-face { + +/* @font-face { font-family: "Noto Sans Mono"; font-weight: 100 900; font-style: normal; @@ -8,10 +9,11 @@ src: url("@fontsource-variable/noto-sans-mono/files/noto-sans-mono-cyrillic-ext-wght-normal.woff2") format("woff2-variations"); unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; -} +} */ /* noto-sans-mono-cyrillic-wght-normal */ -@font-face { + +/* @font-face { font-family: "Noto Sans Mono"; font-weight: 100 900; font-style: normal; @@ -20,10 +22,11 @@ src: url("@fontsource-variable/noto-sans-mono/files/noto-sans-mono-cyrillic-wght-normal.woff2") format("woff2-variations"); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; -} +} */ /* noto-sans-mono-greek-ext-wght-normal */ -@font-face { + +/* @font-face { font-family: "Noto Sans Mono Variable"; font-weight: 100 900; font-style: normal; @@ -32,10 +35,11 @@ src: url("@fontsource-variable/noto-sans-mono/files/noto-sans-mono-greek-ext-wght-normal.woff2") format("woff2-variations"); unicode-range: U+1F00-1FFF; -} +} */ /* noto-sans-mono-greek-wght-normal */ -@font-face { + +/* @font-face { font-family: "Noto Sans Mono"; font-weight: 100 900; font-style: normal; @@ -44,10 +48,11 @@ src: url("@fontsource-variable/noto-sans-mono/files/noto-sans-mono-greek-wght-normal.woff2") format("woff2-variations"); unicode-range: U+0370-03FF; -} +} */ /* noto-sans-mono-vietnamese-wght-normal */ -@font-face { + +/* @font-face { font-family: "Noto Sans Mono"; font-weight: 100 900; font-style: normal; @@ -57,7 +62,7 @@ format("woff2-variations"); unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; -} +} */ /* noto-sans-mono-latin-ext-wght-normal */ @font-face { diff --git a/src/lib/icons.js b/src/lib/icons.js new file mode 100644 index 00000000..5a42d295 --- /dev/null +++ b/src/lib/icons.js @@ -0,0 +1,3 @@ +export const Icon = { + close: 0xe5cd, +} diff --git a/src/routes/config/+layout.svelte b/src/routes/config/+layout.svelte index 69a41346..7ae1de30 100644 --- a/src/routes/config/+layout.svelte +++ b/src/routes/config/+layout.svelte @@ -31,9 +31,6 @@ border-radius: 32px; } - .icon { - } - a { display: flex; gap: 4px; diff --git a/tools/minify-icon-font.js b/tools/minify-icon-font.js new file mode 100644 index 00000000..2df02148 --- /dev/null +++ b/tools/minify-icon-font.js @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2022 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import {openSync} from "fontkit" +import {exec} from "child_process" +import config from "../icons.config.js" +import {statSync, existsSync} from "fs" + +/** + * @param command {string[] | string} + * @returns {Promise} + */ +async function run(command) { + const fullCommand = Array.isArray(command) ? command.join(" ") : command + console.log(`>> ${fullCommand}`) + + return new Promise((resolve, reject) => { + exec(fullCommand, (error, stdout, stderr) => { + if (error) { + reject(error) + } else if (stderr) { + reject(stderr) + } else { + resolve(stdout.trim()) + } + }) + }) +} + +/** @type {Set} */ +const icons = new Set(config.icons) + +console.log("Icons used:", [...icons.values()].sort()) +const font = openSync(config.inputPath) + +const glyphs = ["5f-7a", "30-39"] +for (const icon of icons) { + const iconGlyphs = font.layout(icon).glyphs + if (iconGlyphs.length === 0) { + console.error(`${icon} not found in font. Typo?`) + process.exit(-1) + } + + const codePoints = iconGlyphs + .flatMap(it => font.stringsForGlyph(it.id)) + .flatMap(it => [...it]) + .map(it => it.codePointAt(0).toString(16)) + + if (codePoints.length === 0) { + if (config.codePoints?.[icon]) { + glyphs.push(config.codePoints[icon]) + } else { + console.log() + console.error(`${icon} code point could not be determined. Add it to config.codePoints.`) + process.exit(-1) + } + } + + glyphs.push(...codePoints) +} +glyphs.sort() + +const pythonPath = "./venv/bin/python" +if (!existsSync(pythonPath)) { + throw new Error(`Expected a python virtual environment at ${pythonPath}`) +} +console.log(await run(`${pythonPath} --version`)) +console.log( + await run([ + pythonPath, + "-m fontTools.subset", + `"${config.inputPath}"`, + `--unicodes=${glyphs.join(",")}`, + "--no-layout-closure", + `--output-file="${config.outputPath}"`, + "--flavor=woff2", + ]), +) + +console.log(`${glyphs.length} Used Icons Total`) +console.log(`Minified font saved to ${config.outputPath}`) +const result = statSync(config.outputPath).size +const before = statSync(config.inputPath).size + +console.log( + `${toByteUnit(before)} > ${toByteUnit(result)} (${(((before - result) / before) * 100).toFixed( + 2, + )}% Reduction)`, +) + +/** + * Bytes to respective units + * + * @param value {number} + * @returns {string} + */ +function toByteUnit(value) { + if (value < 1024) { + return `${value}B` + } else if (value < 1024 * 1024) { + return `${(value / 1024).toFixed(2)}KB` + } else { + return `${(value / 1024 / 1024).toFixed(2)}MB` + } +} diff --git a/vite.config.js b/vite.config.js index c03b7aa8..2e16d6fa 100644 --- a/vite.config.js +++ b/vite.config.js @@ -14,6 +14,9 @@ export default defineConfig({ scope: "/", base: "/", includeAssets: ["favicon.png"], + workbox: { + globPatterns: ["**/*.{js,css,html,woff2,json,csv,png,svg}"], + }, manifest: { name: "dot i/o", id: "dot_io_v2",