<commit_before>import BoundingRect, { RectLike } from '../core/BoundingRect';
import { createCanvas } from '../core/util';
import { Dictionary, PropType, TextAlign, TextVerticalAlign, BuiltinTextPosition } from '../core/types';
import LRU from '../core/LRU';

let textWidthCache: Dictionary<LRU<number>> = {};

export const DEFAULT_FONT = '12px sans-serif';

let _ctx: CanvasRenderingContext2D;
let _cachedFont: string;

function defaultMeasureText(text: string, font?: string): { width: number } {
    if (!_ctx) {
        _ctx = createCanvas().getContext('2d');
    }
    if (_cachedFont !== font) {
        _cachedFont = _ctx.font = font || DEFAULT_FONT;
    }
    return _ctx.measureText(text);
}

let methods: {
    measureText: (text: string, font?: string) => { width: number }
} = {
    measureText: defaultMeasureText
};

export function $override(
    name: keyof typeof methods,
    fn: PropType<typeof methods, keyof typeof methods>
) {
    methods[name] = fn;
}

// let cacheMissCount = 0;
// let totalCount = 0;

export function getWidth(text: string, font: string): number {
    font = font || DEFAULT_FONT;
    let cacheOfFont = textWidthCache[font];
    if (!cacheOfFont) {
        cacheOfFont = textWidthCache[font] = new LRU(500);
    }
    let width = cacheOfFont.get(text);
    if (width == null) {
        width = methods.measureText(text, font).width;
        cacheOfFont.put(text, width);
        // cacheMissCount++;
    }
    // totalCount++;

    return width;
}

export function getBoundingRect(
    text: string,
    font: string,
    textAlign?: TextAlign,
    textBaseline?: TextVerticalAlign
): BoundingRect {
    const width = getWidth(text, font);
    const height = getLineHeight(font);

    const x = adjustTextX(0, width, textAlign);
    const y = adjustTextY(0, height, textBaseline);

    const rect = new BoundingRect(x, y, width, height);

    return rect;
}

export function adjustTextX(x: number, width: number, textAlign: TextAlign): number {
    // TODO Right to left language
    if (textAlign === 'right') {
        x -= width;
    }
    else if (textAlign === 'center') {
        x -= width / 2;
    }
    return x;
}

export function adjustTextY(y: number, height: number, verticalAlign: TextVerticalAlign): number {
    if (verticalAlign === 'middle') {
        y -= height / 2;
    }
    else if (verticalAlign === 'bottom') {
        y -= height;
    }
    return y;
}


export function getLineHeight(font?: string): number {
    // FIXME A rough approach.<commit_msg>fix: export getBoundingRect with newline<commit_after>import BoundingRect, { RectLike } from '../core/BoundingRect';
import { createCanvas } from '../core/util';
import { Dictionary, PropType, TextAlign, TextVerticalAlign, BuiltinTextPosition } from '../core/types';
import LRU from '../core/LRU';

let textWidthCache: Dictionary<LRU<number>> = {};

export const DEFAULT_FONT = '12px sans-serif';

let _ctx: CanvasRenderingContext2D;
let _cachedFont: string;

function defaultMeasureText(text: string, font?: string): { width: number } {
    if (!_ctx) {
        _ctx = createCanvas().getContext('2d');
    }
    if (_cachedFont !== font) {
        _cachedFont = _ctx.font = font || DEFAULT_FONT;
    }
    return _ctx.measureText(text);
}

let methods: {
    measureText: (text: string, font?: string) => { width: number }
} = {
    measureText: defaultMeasureText
};

export function $override(
    name: keyof typeof methods,
    fn: PropType<typeof methods, keyof typeof methods>
) {
    methods[name] = fn;
}

// let cacheMissCount = 0;
// let totalCount = 0;

export function getWidth(text: string, font: string): number {
    font = font || DEFAULT_FONT;
    let cacheOfFont = textWidthCache[font];
    if (!cacheOfFont) {
        cacheOfFont = textWidthCache[font] = new LRU(500);
    }
    let width = cacheOfFont.get(text);
    if (width == null) {
        width = methods.measureText(text, font).width;
        cacheOfFont.put(text, width);
        // cacheMissCount++;
    }
    // totalCount++;

    return width;
}

/**
 *
 * Get bounding rect for inner usage(TSpan)
 * Which not include text newline.
 */
export function innerGetBoundingRect(
    text: string,
    font: string,
    textAlign?: TextAlign,
    textBaseline?: TextVerticalAlign
): BoundingRect {
    const width = getWidth(text, font);
    const height = getLineHeight(font);

    const x = adjustTextX(0, width, textAlign);
    const y = adjustTextY(0, height, textBaseline);

    const rect = new BoundingRect(x, y, width, height);

    return rect;
}

/**
 *
 * Get bounding rect for outer usage. Compatitable with old implementation
 * Which includes text newline.
 */
export function getBoundingRect(
    text: string,
    font: string,
    textAlign?: TextAlign,
    textBaseline?: TextVerticalAlign
) {
    const textLines = (text || '').split('\n');
    const len = textLines.length;
    if (len === 1) {
        return innerGetBoundingRect(textLines[0], font, textAlign, textBaseline);
    }
    else {
        const uniondRect = new BoundingRect(0, 0, 0, 0);
        for (let i = 0; i < textLines.length; i++) {
            const rect = innerGetBoundingRect(textLines[i], font, textAlign, textBaseline);
            i === 0 ? uniondRect.copy(rect) : uniondRect.union(rect);
        }
        return uniondRect;
    }
}

export function adjustTextX(x: number, width: number, textAlign: TextAlign): number {
    // TODO Right to left language
    if (textAlign === 'right') {
        x -= width;
    }
    else if (textAlign === 'center') {
        x -= width / 2;
    }
    return x;
}

export function adjustTextY(y: number, height: number, verticalAlign: TextVerticalAlign): number {
    if (verticalAlign === 'middle') {
        y -= height / 2;
    }
    else if (verticalAlign === 'bottom') {
        y -= height;
    }
    return y;
}


export function getLineHeight(font?: string): number {
    // FIXME A rough approach.
{'fraction_non_alphanumeric': 0.13087417887822134, 'fraction_numerical': 0.005726798046151255, 'mean_word_length': 3.0754975978037065, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1415227', 'n_tokens_mistral': 1856, 'n_tokens_neox': 1800, 'n_words': 519}