refactor(text-rendering): ♻️ use embeded fonts

Removed the text to path conversion, it was
too complicated to align the generated paths
with the dynamic layouts' texts. So embeded
font files using data URIs is being used,
this is experimental and a "quick-fix", because
data URIs could be not compatible and/or work
in some environments and browsers. A comment and/or
issue with more details about this will be created.
This commit is contained in:
Gustavo "Guz" L. de Mello
2024-01-16 18:55:41 -03:00
parent fc3243dfe7
commit 40c89b11c4
6 changed files with 35 additions and 34 deletions

View File

@@ -5,7 +5,6 @@
<title></title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="css/style.css" rel="stylesheet">
<!-- auto-refresh in dev -->
<!-- <script type="text/javascript" src="https://livejs.com/live.js"></script> -->
<script type="module" src="./packages/banners/src/index.js"></script>

View File

@@ -20,6 +20,7 @@
"@commitlint/config-conventional": "^18.4.3",
"@commitlint/types": "^18.4.3",
"@vercel/node": "^3.0.11",
"cal-sans": "^1.0.1",
"eslint": "^8.54.0",
"husky": "^8.0.0",
"turbo": "^1.10.16",

View File

@@ -16,8 +16,5 @@
"@types/node": "^20.10.0",
"eslint": "^8.54.0",
"linkedom": "^0.16.4"
},
"dependencies": {
"@fredli74/typr": "^0.3.8"
}
}

View File

@@ -3,7 +3,6 @@
*/
import getLocalLayout from './layouts.js';
import { isValidIcon } from './utils.js';
import fontSvg from './text-svg.js';
/**
* @param {Readonly<string>} string - The string to be converted.
@@ -106,7 +105,7 @@ function regexHelper(string) {
* @returns {[number, number]}
*/
getTranslate() {
if (!string.includes('translate')) return [ 0, 0 ];
if (!string.includes('translate')) return [0, 0];
const translateRegex = /translate\((?:[^,]+),(?:[^)]+)\)/gu;
@@ -122,6 +121,25 @@ function regexHelper(string) {
/* eslint-enable */
}
/**
* @returns {Promise<string>}
*/
async function getSvgCSS() {
const buffer = await fetch(
import.meta.resolve('/packages/banners/static/CalSans-SemiBold.ttf'),
);
// eslint-disable-next-line unicorn/prefer-code-point, max-len
const base64 = btoa(String.fromCharCode(...new Uint8Array(await buffer.arrayBuffer())));
const css = `
@font-face {
font-family: 'Cal Sans';
src: url('data:application/x-font-ttf;base64,${base64}') format('truetype');
}
`;
return css;
}
/**
* @param {BannerObject} object - The Banner Object to be generated from.
* @returns {Promise<string>} - The SVG of the banner.
@@ -134,15 +152,19 @@ async function banner(object) {
// @ts-expect-error because fetch is Readonly in Banner object;
const lFetch = object.lib?.fetch ?? globalThis.fetch;
/** @type {Readonly<string>} */
const svg = await getLocalLayout('horizontal');
const layoutSvg = await getLocalLayout('horizontal');
const dom = stringToHtml(svg, doc);
const dom = stringToHtml(layoutSvg, doc);
const helper = domHelper(dom);
await helper.asyncModify('svg > defs', async el =>
el?.appendChild(stringToHtml(`<style>${await getSvgCSS()}</style>`, doc)),
);
await helper.asyncModify('[data-banner-class="icon"]', async (el) => {
if (!el || !object.icon || !isValidIcon(object.icon)) return;
const [ iconSet, iconName ] = object.icon.split(':');
const [iconSet, iconName] = object.icon.split(':');
const res = await lFetch(`https://api.iconify.design/${iconSet}/${iconName}.svg`);
@@ -171,7 +193,6 @@ async function banner(object) {
el.parentElement?.setAttribute('transform', `${transform ?? ''} translate(${coords.join(',')})`);
const styles = el.getAttribute('style');
const size = regexHelper(styles ?? '').getFontSize();
@@ -199,8 +220,6 @@ async function banner(object) {
},
);
dom.appendChild(stringToHtml(`<g transform="translate(30, 40)"><path d="${fontSvg}" transform="scale(0.01, -0.01)" fill="#ff0000" /></g>`, doc));
return htmlToString(dom, doc);
}

View File

@@ -1,14 +0,0 @@
import * as typr from '@fredli74/typr';
const buffer = await fetch(
import.meta.resolve('/packages/banners/static/CalSans-SemiBold.ttf'),
);
const font = new typr.Font(await buffer.arrayBuffer());
const glyhps = font.stringToGlyphs('Hello world');
const path = font.glyphsToPath(glyhps);
const svg = font.pathToSVG(path);
export default svg;

15
pnpm-lock.yaml generated
View File

@@ -24,6 +24,9 @@ importers:
'@vercel/node':
specifier: ^3.0.11
version: 3.0.11
cal-sans:
specifier: ^1.0.1
version: 1.0.1
eslint:
specifier: ^8.54.0
version: 8.54.0
@@ -41,10 +44,6 @@ importers:
version: 0.34.6
packages/banners:
dependencies:
'@fredli74/typr':
specifier: ^0.3.8
version: 0.3.8
devDependencies:
'@types/node':
specifier: ^20.10.0
@@ -601,10 +600,6 @@ packages:
engines: {node: '>=14'}
dev: true
/@fredli74/typr@0.3.8:
resolution: {integrity: sha512-0Wr3arrJUOoF5HyE8/nusPsdOQaUEd9t0dq8GTHBgSakuh543VqQklIpuRWF/nEnoxkIJzHpCYYesWTyEqgrFg==}
dev: false
/@humanwhocodes/config-array@0.11.13:
resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==}
engines: {node: '>=10.10.0'}
@@ -1615,6 +1610,10 @@ packages:
engines: {node: '>=8'}
dev: true
/cal-sans@1.0.1:
resolution: {integrity: sha512-XwN3/7jez8WmFVcNnNqO2K9lh133KiIcURCyGFnSM+ZmNZ8zIcOTNfr3SpenLAkRceYsq+fQNX/PL4C1rIkEPQ==}
dev: true
/call-bind@1.0.5:
resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==}
dependencies: