mh-two-thousand-and-two
2024-03-25 b8c93990f3fa5e50a8aca16bdc9c2758168aa0fd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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<string, number> {
    const map: Record<string, number> = {};
    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+)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<Platform>) {
    for (let key in platformApi) {
        // Don't assign unknown methods.
        if ((newPlatformApis as any)[key]) {
            (platformApi as any)[key] = (newPlatformApis as any)[key];
        }
    }
}