UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

154 lines (120 loc) 3.59 kB
/* This replaces THREE's font, so that we can have one shape per character */ import {Shape, ShapePath} from 'three'; import {PolyDictionary} from '../../../types/GlobalTypes'; interface Glyph { _cachedOutline: any[]; ha: number; o: string; } export interface FontData { resolution: number; boundingBox: { yMax: number; yMin: number; }; underlineThickness: number; glyphs: PolyDictionary<Glyph>; familyName: string; } export interface FontGenerateShapesOptions { size?: number; isCCW?: boolean; // noHoles?: boolean; } export class Font { public readonly isFont = true; public readonly type = 'font'; constructor(private data: FontData) {} generateShapes(text: string, options: FontGenerateShapesOptions = {}) { if (options.size == null) { options.size = 100; } if (options.isCCW == null) { options.isCCW = false; } // if (options.noHoles == null) { // options.noHoles = false; // } const allShapes: Array<Shape[]> = []; const allPaths = createPaths(text, options.size, this.data); for (const pathsForChar of allPaths) { const shapesForChar: Shape[] = []; for (const path of pathsForChar) { const shapes = path.toShapes(options.isCCW); shapesForChar.push(...shapes); } allShapes.push(shapesForChar); } return allShapes; } } function createPaths(text: string, size: number, data: FontData) { const chars = Array.from(text); const scale = size / data.resolution; const lineHeight = (data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness) * scale; const allPaths: Array<ShapePath[]> = []; let offsetX = 0, offsetY = 0; for (let i = 0; i < chars.length; i++) { const char = chars[i]; const pathsForChar: ShapePath[] = []; if (char === '\n') { offsetX = 0; offsetY -= lineHeight; } else { const ret = createPath(char, scale, offsetX, offsetY, data); if (ret) { offsetX += ret.offsetX; pathsForChar.push(ret.path); } } allPaths.push(pathsForChar); } return allPaths; } function createPath(char: string, scale: number, offsetX: number, offsetY: number, data: FontData) { const glyph = data.glyphs[char] || data.glyphs['?']; if (!glyph) { console.error('THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.'); return; } const path = new ShapePath(); let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2; if (glyph.o) { const outline = glyph._cachedOutline || (glyph._cachedOutline = glyph.o.split(' ')); for (let i = 0, l = outline.length; i < l; ) { const action = outline[i++]; switch (action) { case 'm': // moveTo x = outline[i++] * scale + offsetX; y = outline[i++] * scale + offsetY; path.moveTo(x, y); break; case 'l': // lineTo x = outline[i++] * scale + offsetX; y = outline[i++] * scale + offsetY; path.lineTo(x, y); break; case 'q': // quadraticCurveTo cpx = outline[i++] * scale + offsetX; cpy = outline[i++] * scale + offsetY; cpx1 = outline[i++] * scale + offsetX; cpy1 = outline[i++] * scale + offsetY; path.quadraticCurveTo(cpx1, cpy1, cpx, cpy); break; case 'b': // bezierCurveTo cpx = outline[i++] * scale + offsetX; cpy = outline[i++] * scale + offsetY; cpx1 = outline[i++] * scale + offsetX; cpy1 = outline[i++] * scale + offsetY; cpx2 = outline[i++] * scale + offsetX; cpy2 = outline[i++] * scale + offsetY; path.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, cpx, cpy); break; } } } return {offsetX: glyph.ha * scale, path: path}; }