export const DEFAULT_FONT_SIZE = 12; export const DEFAULT_FONT_FAMILY = 'sans-serif'; export const DEFAULT_FONT = `${DEFAULT_FONT_SIZE}px ${DEFAULT_FONT_FAMILY}`; interface Platform { // TODO CanvasLike? createCanvas(): HTMLCanvasElement measureText(text: string, font?: string): { width: number } loadImage( src: string, onload: () => void | HTMLImageElement['onload'], onerror: () => void | HTMLImageElement['onerror'] ): HTMLImageElement } // Text width map used for environment there is no canvas // Only common ascii is used for size concern. // Generated from following code // // ctx.font = '12px sans-serif'; // const asciiRange = [32, 126]; // let mapStr = ''; // for (let i = asciiRange[0]; i <= asciiRange[1]; i++) { // const char = String.fromCharCode(i); // const width = ctx.measureText(char).width; // const ratio = Math.round(width / 12 * 100); // mapStr += String.fromCharCode(ratio + 20)) // } // mapStr.replace(/\\/g, '\\\\'); const OFFSET = 20; const SCALE = 100; // TODO other basic fonts? // eslint-disable-next-line const defaultWidthMapStr = `007LLmW'55;N0500LLLLLLLLLL00NNNLzWW\\\\WQb\\0FWLg\\bWb\\WQ\\WrWWQ000CL5LLFLL0LL**F*gLLLL5F0LF\\FFF5.5N`; function getTextWidthMap(mapStr: string): Record { const map: Record = {}; if (typeof JSON === 'undefined') { return map; } for (let i = 0; i < mapStr.length; i++) { const char = String.fromCharCode(i + 32); const size = (mapStr.charCodeAt(i) - OFFSET) / SCALE; map[char] = size; } return map; } export const DEFAULT_TEXT_WIDTH_MAP = getTextWidthMap(defaultWidthMapStr); export const platformApi: Platform = { // Export methods createCanvas() { return typeof document !== 'undefined' && document.createElement('canvas'); }, measureText: (function () { let _ctx: CanvasRenderingContext2D; let _cachedFont: string; return (text: string, font?: string) => { if (!_ctx) { const canvas = platformApi.createCanvas(); _ctx = canvas && canvas.getContext('2d'); } if (_ctx) { if (_cachedFont !== font) { _cachedFont = _ctx.font = font || DEFAULT_FONT; } return _ctx.measureText(text); } else { text = text || ''; font = font || DEFAULT_FONT; // Use font size if there is no other method can be used. const res = /((?:\d+)?\.?\d*)px/.exec(font); const fontSize = res && +res[1] || DEFAULT_FONT_SIZE; let width = 0; if (font.indexOf('mono') >= 0) { // is monospace width = fontSize * text.length; } else { for (let i = 0; i < text.length; i++) { const preCalcWidth = DEFAULT_TEXT_WIDTH_MAP[text[i]]; width += preCalcWidth == null ? fontSize : (preCalcWidth * fontSize); } } return { width }; } }; })(), loadImage(src, onload, onerror) { const image = new Image(); image.onload = onload; image.onerror = onerror; image.src = src; return image; } }; export function setPlatformAPI(newPlatformApis: Partial) { for (let key in platformApi) { // Don't assign unknown methods. if ((newPlatformApis as any)[key]) { (platformApi as any)[key] = (newPlatformApis as any)[key]; } } }