import TextMeshPro, { TmpOverflow } from "../TextMeshPro";
import TmpFontConfig, { TmpFontLetter } from "./TmpFontConfig";

const gfx = cc["gfx"];
const vfmt = new gfx.VertexFormat([
    { name: gfx.ATTR_POSITION, type: gfx.ATTR_TYPE_FLOAT32, num: 2 },
    { name: gfx.ATTR_UV0, type: gfx.ATTR_TYPE_FLOAT32, num: 2 },
    { name: gfx.ATTR_COLOR, type: gfx.ATTR_TYPE_UINT8, num: 4, normalize: true },
    { name: "a_color_extra", type: gfx.ATTR_TYPE_UINT8, num: 4, normalize: true },
    { name: "a_texture_idx", type: gfx.ATTR_TYPE_FLOAT32, num: 1 },
]);

const WHITE = cc.Color.WHITE;
/** 斜体倾斜弧度值 */
const ITALIC_REDIANS = cc.misc.degreesToRadians(15);
/** 下划线字符code */
const UNDERLINE_CODE = 95;
/** 省略号字符code */
const ELLIPSIS_CODE = 46;
const ELLIPSIS_CHAR = ".";
const ELLIPSIS_NUM = 3;

// share data of bmfont
let shareLabelInfo = {
    fontAtlas: null as TmpFontConfig,

    fontSize: 0,
    lineHeight: 0,
    hAlign: 0,
    vAlign: 0,

    hash: "",
    margin: 0,
};

let _comp: TextMeshPro = null;
let _tmpUvRect = cc.rect();
let _tmpPosRect = cc.rect();
let _horizontalKernings = [];
let _linesWidth = [];
let _linesOffsetX = [];

let _fntConfig: TmpFontConfig = null;
let _numberOfLines = 0;
let _textDesiredHeight = 0;
let _letterOffsetY = 0;
let _tailoredTopY = 0;
let _tailoredBottomY = 0;
let _bmfontScale = 1.0;

let _lineBreakWithoutSpaces = false;
let _lineSpacing = 0;
let _contentSize = cc.size(0, 0);
let _string = "";
let _fontSize = 0;
let _originFontSize = 0;
let _hAlign = 0;
let _vAlign = 0;
let _spacingX = 0;
let _lineHeight = 0;
let _overflow: TmpOverflow = 0;
let _isWrapText = false;
let _labelWidth = 0;
let _labelHeight = 0;
let _maxLineWidth = 0;

let _dataOffset = 0;

/** 斜体计算向量 */
let _italicVec = cc.v2();
/** 画下划线、删除线所需的数据 */
let _extraLinesData: { [lineIndex: number]: { lineIndex: number, first: any, last: any } } = {};
let _extraLineDef: TmpFontLetter = null;
/** 省略号所需的数据 */
let _ellipsisDef: TmpFontLetter = null;
let _ellipsisWidth: number = 0;

/**
 * 字符渲染数据
 */
class LetterInfo {
    /** 标记字符是否需要渲染 */
    public valid = true;
    public char = "";
    public x = 0;
    public y = 0;
    public line = 0;
    public hash = "";
    /** 标记处于需要渲染的字符中的第几位 */
    public quadsIndex = 0;
    /** 主动设置字符是否可见 */
    public visible = true;
}

/**
 * TextMeshPro顶点数据管理
 */
export default class TmpAssembler extends cc["Assembler"] {
    /** 每个顶点的数据长度 */
    protected floatsPerVert: number = 7;
    protected verticesCount: number = 4;
    protected indicesCount: number = 6;
    protected uvOffset: number = 2;
    protected colorOffset: number = 4;
    protected colorExtraOffset: number = 5;
    protected textureIdxOffset: number = 6;

    protected _renderData = null;
    protected _local = [];
    protected get verticesFloats() { return this.verticesCount * this.floatsPerVert; }

    /** 每个字符的渲染数据 */
    private _lettersInfo: LetterInfo[] = [];

    constructor() {
        super();

        this._renderData = new cc["RenderData"]();
        this._renderData.init(this);

        this.initData();
        this.initLocal();
    }

    public initData(): void {
        let data = this._renderData;
        // createFlexData支持创建指定格式的renderData
        data.createFlexData(0, this.verticesCount, this.indicesCount, this.getVfmt());

        // createFlexData不会填充顶点索引信息，手动补充一下
        let indices = data.iDatas[0];
        let count = indices.length / 6;
        for (let i = 0, idx = 0; i < count; i++) {
            let vertextID = i * 4;
            indices[idx++] = vertextID;
            indices[idx++] = vertextID + 1;
            indices[idx++] = vertextID + 2;
            indices[idx++] = vertextID + 1;
            indices[idx++] = vertextID + 3;
            indices[idx++] = vertextID + 2;
        }
    }

    public initLocal(): void {
        this._local = [];
        this._local.length = 4;
    }

    public getBuffer(v) {
        return cc.renderer["_handle"].getBuffer("mesh", this.getVfmt());
    }

    public getVfmt() {
        return vfmt;
    }

    public fillBuffers(comp, renderer): void {
        if (renderer.worldMatDirty) {
            this.updateWorldVerts(comp);
        }

        let renderData = this._renderData;
        let vData = renderData.vDatas[0];
        let iData = renderData.iDatas[0];

        let buffer = this.getBuffer(renderer);
        let offsetInfo = buffer.request(this.verticesCount, this.indicesCount);

        // buffer data may be realloc, need get reference after request.

        // fill vertices
        let vertexOffset = offsetInfo.byteOffset >> 2,
            vbuf = buffer._vData;

        if (vData.length + vertexOffset > vbuf.length) {
            vbuf.set(vData.subarray(0, vbuf.length - vertexOffset), vertexOffset);
        } else {
            vbuf.set(vData, vertexOffset);
        }

        // fill indices
        let ibuf = buffer._iData,
            indiceOffset = offsetInfo.indiceOffset,
            vertexId = offsetInfo.vertexOffset;
        for (let i = 0, l = iData.length; i < l; i++) {
            ibuf[indiceOffset++] = vertexId + iData[i];
        }
    }

    /**
     * 执行一次渲染数据更新
     */
    public updateRenderData(comp: TextMeshPro): void {
        if (!comp["_vertsDirty"]) { return; }
        if (_comp === comp) { return; }
        if (!comp.fontConfig) { return; }

        _comp = comp;

        this._lettersInfo.length = 0;
        this._updateProperties(comp);
        this._updateContent();
        this.updateWorldVerts(comp);

        _comp["_actualFontSize"] = _fontSize;
        _comp.node.setContentSize(_contentSize);

        _comp["_vertsDirty"] = false;
        _comp = null;
        this._resetProperties();
    }

    /**
     * 更新渲染所需的前置数据
     */
    private _updateProperties(comp: TextMeshPro): void {
        _fntConfig = comp.fontConfig;
        _string = comp.string.toString();
        _fontSize = comp.fontSize;
        _originFontSize = _fntConfig ? _fntConfig.json.size : comp.fontSize;
        _bmfontScale = _fontSize / _originFontSize;
        _hAlign = comp.horizontalAlign;
        _vAlign = comp.verticalAlign;
        _spacingX = comp.spacingX;
        _overflow = comp.overflow;
        _lineHeight = comp.lineHeight;

        _contentSize.width = comp.node.width;
        _contentSize.height = comp.node.height;

        shareLabelInfo.fontAtlas = comp.fontConfig;
        shareLabelInfo.lineHeight = _lineHeight;
        shareLabelInfo.fontSize = _fontSize;
        shareLabelInfo.hash = "";
        shareLabelInfo.margin = 0;

        // should wrap text
        if (_overflow === TmpOverflow.NONE) {
            _isWrapText = false;
            _contentSize.width += shareLabelInfo.margin * 2;
            _contentSize.height += shareLabelInfo.margin * 2;
        } else if (_overflow === TmpOverflow.RESIZE_HEIGHT) {
            _isWrapText = true;
            _contentSize.height += shareLabelInfo.margin * 2;
        } else if (_overflow === TmpOverflow.SHRINK) {
            _isWrapText = false;
        } else {
            _isWrapText = comp.enableWrapText;
        }

        this._setupBMFontOverflowMetrics();

        // 斜体计算
        if (comp.enableItalic) {
            _italicVec.x = 0;
            _italicVec.y = _contentSize.height / 2;
            _italicVec.rotateSelf(ITALIC_REDIANS);
            _contentSize.width += Math.abs(_italicVec.x) * 2;
            _contentSize.height -= Math.abs(_contentSize.height / 2 - _italicVec.y) * 2;
        }
        // 下划线、删除线
        if (comp.enableUnderline || comp.enableStrikethrough) {
            _extraLineDef = shareLabelInfo.fontAtlas.getLetter(UNDERLINE_CODE + shareLabelInfo.hash);
            if (!_extraLineDef) {
                cc.log(`Can't find letter definition in textures. letter: _`);
            }
        }
        // 省略号
        if (comp.overflow === TmpOverflow.ELLIPSIS) {
            _ellipsisDef = shareLabelInfo.fontAtlas.getLetter(ELLIPSIS_CODE + shareLabelInfo.hash);
            if (_ellipsisDef) {
                _ellipsisWidth = (_ellipsisDef.xAdvance * _bmfontScale + _spacingX) * ELLIPSIS_NUM;
            } else {
                _ellipsisWidth = 0;
                cc.log(`Can't find letter definition in textures. letter: ${ELLIPSIS_CHAR}`);
            }
        }
    }

    private _resetProperties(): void {
        _fntConfig = null;
        shareLabelInfo.hash = "";
        shareLabelInfo.margin = 0;
    }

    private _updateContent(): void {
        this._computeHorizontalKerningForText();
        this._alignText();
    }

    private _computeHorizontalKerningForText(): void {
        let string = _string;
        let stringLen = string.length;

        let horizontalKernings = _horizontalKernings;
        let kerningDict;
        // _fntConfig && (kerningDict = _fntConfig.kerningDict);
        // if (kerningDict && !cc.js.isEmptyObject(kerningDict)) {
        //     let prev = -1;
        //     for (let i = 0; i < stringLen; ++i) {
        //         let key = string.charCodeAt(i);
        //         let kerningAmount = kerningDict[(prev << 16) | (key & 0xffff)] || 0;
        //         if (i < stringLen - 1) {
        //             horizontalKernings[i] = kerningAmount;
        //         } else {
        //             horizontalKernings[i] = 0;
        //         }
        //         prev = key;
        //     }
        // } else {
        horizontalKernings.length = 0;
        // }
    }

    private _alignText(): void {
        _textDesiredHeight = 0;
        _linesWidth.length = 0;
        _extraLinesData = {};

        if (!_lineBreakWithoutSpaces) {
            this._multilineTextWrapByWord();
        } else {
            this._multilineTextWrapByChar();
        }

        // shrink
        if (_overflow === TmpOverflow.SHRINK && _fontSize > 0) {
            let scaleHeight = _bmfontScale;
            let scaleWidth = _bmfontScale;
            let needReset = false;
            if (_textDesiredHeight > _contentSize.height) {
                scaleHeight = (_contentSize.height / _textDesiredHeight) * _bmfontScale;
                needReset = true;
            }

            let maxWidth = 0;
            _linesWidth.forEach((v) => {
                if (v > maxWidth) {
                    maxWidth = v;
                }
            });
            if (maxWidth > _contentSize.width) {
                scaleWidth = (_contentSize.width / maxWidth) * _bmfontScale;
                needReset = true;
            }
            _bmfontScale = Math.min(scaleHeight, scaleWidth);

            if (needReset) {
                _fontSize = _bmfontScale * _originFontSize;
                _textDesiredHeight = 0;
                _linesWidth.length = 0;
                _extraLinesData = {};
                if (!_lineBreakWithoutSpaces) {
                    this._multilineTextWrapByWord();
                } else {
                    this._multilineTextWrapByChar();
                }
            }
        }

        this._computeAlignmentOffset();

        // 顶点数据填充
        this._reserveQuads(_comp, this._lettersInfo.length);
        this._updateQuads();
    }

    private _multilineTextWrapByWord(): boolean {
        return this._multilineTextWrap(this._getFirstWordLen);
    }

    private _multilineTextWrapByChar(): boolean {
        return this._multilineTextWrap(this._getFirstCharLen);
    }

    private _multilineTextWrap(nextTokenFunc: Function): boolean {
        // 省略号处理
        let ellipsisMaxLines = 0;
        let useEllipsis = false;
        if (_overflow === TmpOverflow.ELLIPSIS && _ellipsisDef) {
            ellipsisMaxLines = Math.max(1, Math.floor(_contentSize.height / _lineHeight));
        }

        let textLen = _string.length;

        let lineIndex = 0;
        let nextTokenX = 0;
        let nextTokenY = 0;
        let longestLine = 0;
        let letterRight = 0;

        let highestY = 0;
        let lowestY = 0;
        let letterDef: TmpFontLetter = null;
        let letterPosition = cc.v2(0, 0);

        for (let index = 0; index < textLen;) {
            let character = _string.charAt(index);
            if (character === "\n") {
                // 省略号处理
                if (_overflow === TmpOverflow.ELLIPSIS && _ellipsisDef && lineIndex + 1 >= ellipsisMaxLines) {
                    this._recordEllipsis(nextTokenY, letterPosition, lineIndex);
                    useEllipsis = true;
                    // 更新_linesWidth
                    let ellipsisInfo = this._lettersInfo[this._lettersInfo.length - 1];
                    // letterRight = ellipsisInfo.x + _ellipsisDef.w * _bmfontScale - shareLabelInfo.margin;
                    letterRight = ellipsisInfo.x + (_ellipsisDef.xAdvance - _ellipsisDef.offsetX) * _bmfontScale + _spacingX - shareLabelInfo.margin * 2;
                    break;
                }

                _linesWidth.push(letterRight);
                letterRight = 0;
                lineIndex++;
                nextTokenX = 0;
                nextTokenY -= _lineHeight * this._getFontScale() + _lineSpacing;
                this._recordPlaceholderInfo(index, character);
                index++;
                continue;
            }

            let tokenLen = nextTokenFunc(_string, index, textLen);
            let tokenHighestY = highestY;
            let tokenLowestY = lowestY;
            let tokenRight = letterRight;
            let nextLetterX = nextTokenX;
            let newLine = false;

            for (let tmp = 0; tmp < tokenLen; ++tmp) {
                let letterIndex = index + tmp;
                character = _string.charAt(letterIndex);
                if (character === "\r") {
                    this._recordPlaceholderInfo(letterIndex, character);
                    continue;
                }
                letterDef = shareLabelInfo.fontAtlas.getLetterDefinitionForChar(character);
                if (!letterDef) {
                    this._recordPlaceholderInfo(letterIndex, character);
                    cc.log(`Can't find letter definition in textures. letter: ${character}`);
                    continue;
                }

                let letterX = nextLetterX + letterDef.offsetX * _bmfontScale - shareLabelInfo.margin;

                // 斜边处理
                if ((_comp as TextMeshPro).enableItalic) {
                    _italicVec.x = 0;
                    _italicVec.y = letterDef.h * _bmfontScale / 2;
                    _italicVec.rotateSelf(ITALIC_REDIANS);
                    letterX += Math.abs(_italicVec.x);
                }

                // 省略号处理
                if (_overflow === TmpOverflow.ELLIPSIS && _ellipsisDef) {
                    if (letterX + (letterDef.xAdvance - letterDef.offsetX) * _bmfontScale > _maxLineWidth) {
                        if (!_isWrapText || lineIndex + 1 >= ellipsisMaxLines) {
                            this._recordEllipsis(nextTokenY, letterPosition, lineIndex);
                            useEllipsis = true;
                            // 更新_linesWidth
                            let ellipsisInfo = this._lettersInfo[this._lettersInfo.length - 1];
                            // letterRight = ellipsisInfo.x + _ellipsisDef.w * _bmfontScale - shareLabelInfo.margin;
                            letterRight = ellipsisInfo.x + (_ellipsisDef.xAdvance - _ellipsisDef.offsetX) * _bmfontScale + _spacingX - shareLabelInfo.margin * 2;
                            break;
                        }
                    }
                }

                if (_isWrapText
                    && _maxLineWidth > 0
                    && nextTokenX > 0
                    && letterX + (letterDef.xAdvance - letterDef.offsetX) * _bmfontScale > _maxLineWidth
                    && !cc["textUtils"].isUnicodeSpace(character)) {
                    _linesWidth.push(letterRight);
                    letterRight = 0;
                    lineIndex++;
                    nextTokenX = 0;
                    nextTokenY -= (_lineHeight * this._getFontScale() + _lineSpacing);
                    newLine = true;
                    break;
                } else {
                    letterPosition.x = letterX;
                }

                letterPosition.y = nextTokenY - letterDef.offsetY * _bmfontScale + shareLabelInfo.margin;
                this._recordLetterInfo(letterPosition, character, letterIndex, lineIndex);

                if (letterIndex + 1 < _horizontalKernings.length && letterIndex < textLen - 1) {
                    nextLetterX += _horizontalKernings[letterIndex + 1];
                }

                nextLetterX += letterDef.xAdvance * _bmfontScale + _spacingX - shareLabelInfo.margin * 2;

                // tokenRight = letterPosition.x + letterDef.w * _bmfontScale - shareLabelInfo.margin;
                tokenRight = nextLetterX;
                // 斜边处理
                if ((_comp as TextMeshPro).enableItalic) {
                    _italicVec.x = 0;
                    _italicVec.y = letterDef.h * _bmfontScale / 2;
                    _italicVec.rotateSelf(ITALIC_REDIANS);
                    tokenRight += Math.abs(_italicVec.x);
                }

                if (tokenHighestY < letterPosition.y) {
                    tokenHighestY = letterPosition.y;
                }

                if (tokenLowestY > letterPosition.y - letterDef.h * _bmfontScale) {
                    tokenLowestY = letterPosition.y - letterDef.h * _bmfontScale;
                }

            } //end of for loop

            if (useEllipsis) { break; }

            if (newLine) { continue; }

            nextTokenX = nextLetterX;
            letterRight = tokenRight;

            if (highestY < tokenHighestY) {
                highestY = tokenHighestY;
            }
            if (lowestY > tokenLowestY) {
                lowestY = tokenLowestY;
            }
            if (longestLine < letterRight) {
                longestLine = letterRight;
            }

            index += tokenLen;
        } //end of for loop

        _linesWidth.push(letterRight);

        _numberOfLines = lineIndex + 1;
        _textDesiredHeight = _numberOfLines * _lineHeight * this._getFontScale();
        if (_numberOfLines > 1) {
            _textDesiredHeight += (_numberOfLines - 1) * _lineSpacing;
        }

        _contentSize.width = _labelWidth;
        _contentSize.height = _labelHeight;
        if (_labelWidth <= 0) {
            _contentSize.width = parseFloat(longestLine.toFixed(2)) + shareLabelInfo.margin * 2;
        }
        if (_labelHeight <= 0) {
            _contentSize.height = parseFloat(_textDesiredHeight.toFixed(2)) + shareLabelInfo.margin * 2;
        }

        _tailoredTopY = _contentSize.height;
        _tailoredBottomY = 0;

        if (_overflow !== TmpOverflow.CLAMP) {
            if (highestY > 0) {
                _tailoredTopY = _contentSize.height + highestY;
            }

            if (lowestY < -_textDesiredHeight) {
                _tailoredBottomY = _textDesiredHeight + lowestY;
            }
        }

        // 记录letterRight与nextTokenX的差值，供富文本排版使用
        _comp["_richTextDeltaX"] = nextTokenX - letterRight;

        return true;
    }

    private _getFirstCharLen(): number {
        return 1;
    }

    private _getFontScale(): number {
        return _overflow === TmpOverflow.SHRINK ? _bmfontScale : 1;
    }

    private _getFirstWordLen(text: string, startIndex: number, textLen: number): number {
        let character = text.charAt(startIndex);
        if (cc["textUtils"].isUnicodeCJK(character)
            || character === "\n"
            || cc["textUtils"].isUnicodeSpace(character)) {
            return 1;
        }

        let len = 1;
        let letterDef = shareLabelInfo.fontAtlas.getLetterDefinitionForChar(character);
        if (!letterDef) {
            return len;
        }
        let nextLetterX = letterDef.xAdvance * _bmfontScale + _spacingX;
        let letterX;
        for (let index = startIndex + 1; index < textLen; ++index) {
            character = text.charAt(index);

            letterDef = shareLabelInfo.fontAtlas.getLetterDefinitionForChar(character);
            if (!letterDef) {
                break;
            }
            letterX = nextLetterX + letterDef.offsetX * _bmfontScale;

            if (letterX + (letterDef.xAdvance - letterDef.offsetX) * _bmfontScale > _maxLineWidth
                && !cc["textUtils"].isUnicodeSpace(character)
                && _maxLineWidth > 0) {
                return len;
            }
            nextLetterX += letterDef.xAdvance * _bmfontScale + _spacingX;
            if (character === "\n"
                || cc["textUtils"].isUnicodeSpace(character)
                || cc["textUtils"].isUnicodeCJK(character)) {
                break;
            }
            len++;
        }

        return len;
    }

    /**
     * 从已记录的字符中倒退，直到能放下省略号
     */
    private _recordEllipsis(nextTokenY: number, letterPosition: cc.Vec2, lineIndex: number): void {
        let nextX = 0;
        let lastIndex = this._lettersInfo.length - 1;
        while (lastIndex >= 0) {
            let lastInfo = this._lettersInfo[lastIndex];
            let lastDef = shareLabelInfo.fontAtlas.getLetterDefinitionForChar(lastInfo.char);
            let lastW = lastDef ? lastDef.w : 0;
            let lastXAdvance = lastDef ? lastDef.xAdvance : 0;
            let lastOffsetX = lastDef ? lastDef.offsetX : 0;
            let lastRightX = lastInfo.x + lastW * _bmfontScale - shareLabelInfo.margin;
            nextX = lastInfo.x + (lastXAdvance - lastOffsetX) * _bmfontScale + _spacingX - shareLabelInfo.margin * 2;
            if (_maxLineWidth >= lastRightX + _ellipsisWidth) {
                break;
            }
            lastIndex--;
            this._lettersInfo.pop();
        }
        if (lastIndex < 0) {
            nextX = 0;
        }
        // 记录省略号
        letterPosition.y = nextTokenY - _ellipsisDef.offsetY * _bmfontScale + shareLabelInfo.margin;
        for (let i = 1; i <= ELLIPSIS_NUM; i++) {
            letterPosition.x = nextX + _ellipsisDef.offsetX * _bmfontScale - shareLabelInfo.margin;
            this._recordLetterInfo(letterPosition, ELLIPSIS_CHAR, lastIndex + i, lineIndex);
            nextX += _ellipsisDef.xAdvance * _bmfontScale + _spacingX - shareLabelInfo.margin * 2;
        }
    }

    /**
     * 记录无需渲染的占位符
     */
    private _recordPlaceholderInfo(letterIndex: number, char: string): void {
        if (letterIndex >= this._lettersInfo.length) {
            let tmpInfo = new LetterInfo();
            this._lettersInfo.push(tmpInfo);
        }

        this._lettersInfo[letterIndex].char = char;
        this._lettersInfo[letterIndex].hash = char.charCodeAt(0) + shareLabelInfo.hash;
        this._lettersInfo[letterIndex].valid = false;
    }

    /**
     * 记录需要渲染的字符
     */
    private _recordLetterInfo(letterPosition: cc.Vec2, character: string, letterIndex: number, lineIndex: number): void {
        if (letterIndex >= this._lettersInfo.length) {
            let tmpInfo = new LetterInfo();
            this._lettersInfo.push(tmpInfo);
        }
        let char = character.charCodeAt(0);
        let key = char + shareLabelInfo.hash;

        this._lettersInfo[letterIndex].line = lineIndex;
        this._lettersInfo[letterIndex].char = character;
        this._lettersInfo[letterIndex].hash = key;
        this._lettersInfo[letterIndex].valid = shareLabelInfo.fontAtlas.getLetter(key).valid;
        this._lettersInfo[letterIndex].x = letterPosition.x;
        this._lettersInfo[letterIndex].y = letterPosition.y;
    }

    private _computeAlignmentOffset(): void {
        _linesOffsetX.length = 0;

        switch (_hAlign) {
            case cc.Label.HorizontalAlign.LEFT:
                for (let i = 0; i < _numberOfLines; ++i) {
                    _linesOffsetX.push(0);
                }
                break;
            case cc.Label.HorizontalAlign.CENTER:
                for (let i = 0, l = _linesWidth.length; i < l; i++) {
                    _linesOffsetX.push((_contentSize.width - _linesWidth[i]) / 2);
                }
                break;
            case cc.Label.HorizontalAlign.RIGHT:
                for (let i = 0, l = _linesWidth.length; i < l; i++) {
                    _linesOffsetX.push(_contentSize.width - _linesWidth[i]);
                }
                break;
            default:
                break;
        }

        // TOP
        _letterOffsetY = _contentSize.height;
        if (_vAlign !== cc.Label.VerticalAlign.TOP) {
            let blank = _contentSize.height - _textDesiredHeight + _lineHeight * this._getFontScale() - _originFontSize * _bmfontScale;
            if (_vAlign === cc.Label.VerticalAlign.BOTTOM) {
                // BOTTOM
                _letterOffsetY -= blank;
            } else {
                // CENTER:
                _letterOffsetY -= blank / 2;
            }
        }
    }

    private _setupBMFontOverflowMetrics(): void {
        let newWidth = _contentSize.width;
        let newHeight = _contentSize.height;

        if (_overflow === TmpOverflow.RESIZE_HEIGHT) {
            newHeight = 0;
        }

        if (_overflow === TmpOverflow.NONE) {
            newWidth = 0;
            newHeight = 0;
        }

        _labelWidth = newWidth;
        _labelHeight = newHeight;
        _maxLineWidth = newWidth;
    }

    /**
     * 更新所有顶点数据
     */
    private _updateQuads(): void {
        let node = _comp.node;

        this.verticesCount = this.indicesCount = 0;

        // Need to reset dataLength in Canvas rendering mode.
        this._renderData && (this._renderData.dataLength = 0);

        let contentSize = _contentSize,
            appx = node["_anchorPoint"].x * contentSize.width,
            appy = node["_anchorPoint"].y * contentSize.height;

        let quadsIndex = 0;
        for (let i = 0, l = this._lettersInfo.length; i < l; ++i) {
            let letterInfo = this._lettersInfo[i];
            if (!letterInfo) break;
            if (!letterInfo.valid) continue;
            letterInfo.quadsIndex = quadsIndex;
            let letterDef = shareLabelInfo.fontAtlas.getLetter(letterInfo.hash);

            _tmpUvRect.height = letterDef.h;
            _tmpUvRect.width = letterDef.w;
            _tmpUvRect.x = letterDef.u;
            _tmpUvRect.y = letterDef.v;

            let py = letterInfo.y + _letterOffsetY;

            if (_labelHeight > 0) {
                if (_overflow === TmpOverflow.CLAMP) {
                    if (py > _tailoredTopY) {
                        let clipTop = py - _tailoredTopY;
                        _tmpUvRect.y += clipTop / _bmfontScale;
                        _tmpUvRect.height -= clipTop / _bmfontScale;
                        py = py - clipTop;
                    }

                    if ((py - _tmpUvRect.height * _bmfontScale < _tailoredBottomY)) {
                        _tmpUvRect.height = (py < _tailoredBottomY) ? 0 : (py - _tailoredBottomY) / _bmfontScale;
                    }
                }
            }

            let px = letterInfo.x + _linesOffsetX[letterInfo.line];
            if (_labelWidth > 0) {
                if (_overflow === TmpOverflow.CLAMP) {
                    if (px < 0) {
                        _tmpUvRect.x += -px / _bmfontScale;
                        _tmpUvRect.width -= -px / _bmfontScale;
                        px = 0;
                    }
                    if (px + _tmpUvRect.width * _bmfontScale > _contentSize.width) {
                        let clipRight = px + _tmpUvRect.width * _bmfontScale - _contentSize.width;
                        _tmpUvRect.width -= clipRight / _bmfontScale;
                    }
                }
            }

            if (_tmpUvRect.height > 0 && _tmpUvRect.width > 0) {
                _tmpPosRect.x = px - appx;
                _tmpPosRect.y = py - appy;
                _tmpPosRect.width = _tmpUvRect.width * _bmfontScale;
                _tmpPosRect.height = _tmpUvRect.height * _bmfontScale;
                this.appendQuad(letterDef.textureId, _tmpUvRect, _tmpPosRect);

                quadsIndex++;
                // 下划线数据记录
                if (_extraLineDef && ((_comp as TextMeshPro).enableUnderline || (_comp as TextMeshPro).enableStrikethrough)) {
                    if (!cc["textUtils"].isUnicodeSpace(letterInfo.char)) {
                        let lineData = _extraLinesData[letterInfo.line];
                        if (!lineData) {
                            _extraLinesData[letterInfo.line] = {
                                lineIndex: letterInfo.line,
                                first: i,
                                last: i
                            }
                        } else {
                            if (lineData.last < i) {
                                lineData.last = i;
                            }
                        }
                    }
                }
            }
        }

        if (_extraLineDef) {
            // 下划线
            if ((_comp as TextMeshPro).enableUnderline) {
                this._updateLineQuads(appx, appy, -_fontSize + (_comp as TextMeshPro).underlineOffset * _bmfontScale);
            }
            // 删除线
            if ((_comp as TextMeshPro).enableStrikethrough) {
                this._updateLineQuads(appx, appy, -_fontSize / 2 + (_comp as TextMeshPro).strikethroughOffset * _bmfontScale);
            }
        }

        this.updateColorExtra(_comp);
        this._quadsUpdated();
    }

    /**
     * 更新下划线、删除线的顶点数据
     */
    private _updateLineQuads(appx: number, appy: number, offsetY: number): void {
        for (let key in _extraLinesData) {
            let underlineInfo = _extraLinesData[key];
            let lineIdx = underlineInfo.lineIndex;
            let first = underlineInfo.first;
            let last = underlineInfo.last > 0 ? underlineInfo.last : first;
            let firstInfo = this._lettersInfo[first];
            if (!firstInfo.valid) {
                continue;
            }
            let lastInfo = this._lettersInfo[last];
            let firstDef = shareLabelInfo.fontAtlas.getLetter(firstInfo.hash);
            let lastDef = shareLabelInfo.fontAtlas.getLetter(lastInfo.hash);

            let maxWidth = lastInfo.x + lastDef.w * _bmfontScale - firstInfo.x;

            let wLeft = maxWidth >= _extraLineDef.w * _bmfontScale ? _extraLineDef.w * _bmfontScale / 3 : maxWidth / 2;
            let wRight = wLeft;
            let wMid = maxWidth - wLeft - wRight;
            let leftX = firstInfo.x + _linesOffsetX[lineIdx];
            let rightX = leftX + wLeft + wMid;
            let midX = leftX + wLeft;

            // 左
            _tmpUvRect.height = _extraLineDef.h;
            _tmpUvRect.width = wLeft / _bmfontScale;
            _tmpUvRect.x = _extraLineDef.u;
            _tmpUvRect.y = _extraLineDef.v;

            let py = firstInfo.y + _letterOffsetY + firstDef.offsetY * _bmfontScale + offsetY;

            if (_labelHeight > 0) {
                if (py > _tailoredTopY) {
                    let clipTop = py - _tailoredTopY;
                    _tmpUvRect.y += clipTop;
                    _tmpUvRect.height -= clipTop;
                    py = py - clipTop;
                }

                if ((py - _extraLineDef.h * _bmfontScale < _tailoredBottomY) && _overflow === TmpOverflow.CLAMP) {
                    _tmpUvRect.height = (py < _tailoredBottomY) ? 0 : (py - _tailoredBottomY) / _bmfontScale;
                }
            }

            if (_tmpUvRect.height > 0 && _tmpUvRect.width > 0) {
                _tmpPosRect.x = leftX - appx;
                _tmpPosRect.y = py - appy;
                _tmpPosRect.width = wLeft;
                _tmpPosRect.height = _tmpUvRect.height * _bmfontScale;
                this.appendQuad(_extraLineDef.textureId, _tmpUvRect, _tmpPosRect);
            }

            // 右
            _tmpUvRect.width = wRight / _bmfontScale;
            _tmpUvRect.x = _extraLineDef.u + _extraLineDef.w - _tmpUvRect.width;

            if (_tmpUvRect.height > 0 && _tmpUvRect.width > 0) {
                _tmpPosRect.x = rightX - appx;
                _tmpPosRect.y = py - appy;
                _tmpPosRect.width = wRight;
                _tmpPosRect.height = _tmpUvRect.height * _bmfontScale;
                this.appendQuad(_extraLineDef.textureId, _tmpUvRect, _tmpPosRect);
            }

            // 中
            if (wMid > 0) {
                _tmpUvRect.width = _extraLineDef.w - wLeft * 2 / _bmfontScale;
                _tmpUvRect.x = _extraLineDef.u + _tmpUvRect.width;

                if (_tmpUvRect.height > 0 && _tmpUvRect.width > 0) {
                    _tmpPosRect.x = midX - appx;
                    _tmpPosRect.y = py - appy;
                    _tmpPosRect.width = wMid;
                    _tmpPosRect.height = _tmpUvRect.height * _bmfontScale;
                    this.appendQuad(_extraLineDef.textureId, _tmpUvRect, _tmpPosRect);
                }
            }
        }
    }

    /**
     * 顶点数据、索引数据初始化
     */
    private _reserveQuads(comp: TextMeshPro, count: number): void {
        let extra = 0;
        if (comp.enableUnderline) {
            extra++;
        }
        if (comp.enableStrikethrough) {
            extra++;
        }
        count = count + extra * _numberOfLines * 3;

        let verticesCount = count * 4;
        let indicesCount = count * 6;
        let flexBuffer = this._renderData._flexBuffer;
        flexBuffer.reserve(verticesCount, indicesCount);
        flexBuffer.used(verticesCount, indicesCount);
        let iData = this._renderData.iDatas[0];
        for (let i = 0, vid = 0, l = indicesCount; i < l; i += 6, vid += 4) {
            iData[i] = vid;
            iData[i + 1] = vid + 1;
            iData[i + 2] = vid + 2;
            iData[i + 3] = vid + 1;
            iData[i + 4] = vid + 3;
            iData[i + 5] = vid + 2;
        }
        _dataOffset = 0;
    }

    /**
     * 更新实际使用的顶点数据、索引数据长度
     */
    private _quadsUpdated(): void {
        _dataOffset = 0;
        let flexBuffer = this._renderData._flexBuffer;
        flexBuffer.used(this.verticesCount, this.indicesCount);
    }

    /**
     * 添加一组顶点数据（4个顶点）
     * @param textureId 渲染的字符所需纹理id
     * @param uvRect 顶点uv数据
     * @param posRect 顶点坐标数据
     */
    private appendQuad(textureId: number, uvRect: cc.Rect, posRect: cc.Rect): void {
        let renderData = this._renderData;
        let verts = renderData.vDatas[0],
            uintVerts = renderData.uintVDatas[0];

        this.verticesCount += 4;
        this.indicesCount = this.verticesCount / 2 * 3;

        let texture = shareLabelInfo.fontAtlas.getTexture(textureId);
        let texw = texture.width,
            texh = texture.height,
            rectWidth = uvRect.width,
            rectHeight = uvRect.height,
            color = _comp.node["_color"]._val;

        let l, b, r, t;
        let floatsPerVert = this.floatsPerVert;
        // uvs
        let uvDataOffset = _dataOffset + this.uvOffset;
        l = (uvRect.x) / texw;
        r = (uvRect.x + rectWidth) / texw;
        b = (uvRect.y + rectHeight) / texh;
        t = (uvRect.y) / texh;

        verts[uvDataOffset] = l;
        verts[uvDataOffset + 1] = b;
        uvDataOffset += floatsPerVert;
        verts[uvDataOffset] = r;
        verts[uvDataOffset + 1] = b;
        uvDataOffset += floatsPerVert;
        verts[uvDataOffset] = l;
        verts[uvDataOffset + 1] = t;
        uvDataOffset += floatsPerVert;
        verts[uvDataOffset] = r;
        verts[uvDataOffset + 1] = t;

        // positions
        l = posRect.x;
        r = posRect.x + posRect.width;
        b = posRect.y - posRect.height;
        t = posRect.y;
        this.appendVerts(_comp, _dataOffset, l, r, b, t);

        // colors
        let colorOffset = _dataOffset + this.colorOffset;
        for (let i = 0; i < 4; i++) {
            uintVerts[colorOffset] = color;
            colorOffset += floatsPerVert;
        }

        // colorExtra
        let colorExtraOffset = _dataOffset + this.colorExtraOffset;
        for (let i = 0; i < 4; i++) {
            uintVerts[colorExtraOffset] = WHITE["_val"];
            colorExtraOffset += floatsPerVert;
        }

        // textureId
        let idOffset = _dataOffset + this.textureIdxOffset;
        for (let i = 0; i < 4; i++) {
            verts[idOffset] = textureId;
            idOffset += this.floatsPerVert;
        }

        _dataOffset += this.floatsPerVert * 4;
    }

    private appendVerts(comp: TextMeshPro, offset, l, r, b, t): void {
        let local = this._local;
        let floatsPerVert = this.floatsPerVert;

        if (comp.enableItalic) {
            _italicVec.x = 0;
            _italicVec.y = (t - b) / 2;
            _italicVec.rotateSelf(ITALIC_REDIANS);

            local[offset] = l - Math.abs(_italicVec.x);
            local[offset + 1] = b + Math.abs((t - b) / 2 - _italicVec.y);

            offset += floatsPerVert;
            local[offset] = r - Math.abs(_italicVec.x);
            local[offset + 1] = b + Math.abs((t - b) / 2 - _italicVec.y);

            offset += floatsPerVert;
            local[offset] = l + Math.abs(_italicVec.x);
            local[offset + 1] = t - Math.abs((t - b) / 2 - _italicVec.y);

            offset += floatsPerVert;
            local[offset] = r + Math.abs(_italicVec.x);
            local[offset + 1] = t - Math.abs((t - b) / 2 - _italicVec.y);
        } else {
            local[offset] = l;
            local[offset + 1] = b;

            offset += floatsPerVert;
            local[offset] = r;
            local[offset + 1] = b;

            offset += floatsPerVert;
            local[offset] = l;
            local[offset + 1] = t;

            offset += floatsPerVert;
            local[offset] = r;
            local[offset + 1] = t;
        }
    }

    /**
     * 更新顶点世界坐标数据
     */
    public updateWorldVerts(comp: TextMeshPro): void {
        let node = comp.node;
        let local = this._local;
        let world = this._renderData.vDatas[0];
        let floatsPerVert = this.floatsPerVert;

        if (CC_NATIVERENDERER) {
            for (let offset = 0, l = local.length; offset < l; offset += floatsPerVert) {
                world[offset] = local[offset];
                world[offset + 1] = local[offset + 1];
            }
        } else {
            let matrix = node["_worldMatrix"];
            let matrixm = matrix.m,
                a = matrixm[0], b = matrixm[1], c = matrixm[4], d = matrixm[5],
                tx = matrixm[12], ty = matrixm[13];

            for (let offset = 0; offset < local.length; offset += floatsPerVert) {
                let x = local[offset];
                let y = local[offset + 1];
                world[offset] = x * a + y * c + tx;
                world[offset + 1] = x * b + y * d + ty;
            }
        }
    }

    public updateColor(comp, color?): void {
        if (CC_NATIVERENDERER) {
            this["_dirtyPtr"][0] |= cc["Assembler"]["FLAG_VERTICES_OPACITY_CHANGED"];
        }
        let uintVerts = this._renderData.uintVDatas[0];
        if (!uintVerts) return;
        color = color != null ? color : comp.node.color._val;
        let floatsPerVert = this.floatsPerVert;
        let colorOffset = this.colorOffset;
        for (let i = colorOffset, l = uintVerts.length; i < l; i += floatsPerVert) {
            uintVerts[i] = color;
        }
    }

    /**
     * 更新额外顶点颜色，不对下划线、删除线生效
     */
    public updateColorExtra(comp: TextMeshPro): void {
        let uintVerts = this._renderData.uintVDatas[0];
        if (!uintVerts) return;

        let tmpColor = cc.color();
        for (let i = 0; i < this._lettersInfo.length; i++) {
            let info = this._lettersInfo[i];
            if (!info.valid || cc["textUtils"].isUnicodeSpace(info.char)) {
                continue;
            }
            let alpha = info.visible ? 1 : 0;
            let offset = this.colorExtraOffset + this.floatsPerVert * info.quadsIndex * 4;
            tmpColor.set(WHITE);
            tmpColor.setA(tmpColor.a * alpha);
            comp.colorGradient && tmpColor.multiply(comp.colorLB);
            uintVerts[offset] = tmpColor["_val"];

            offset += this.floatsPerVert;
            tmpColor.set(WHITE);
            tmpColor.setA(tmpColor.a * alpha);
            comp.colorGradient && tmpColor.multiply(comp.colorRB);
            uintVerts[offset] = tmpColor["_val"];

            offset += this.floatsPerVert;
            tmpColor.set(WHITE);
            tmpColor.setA(tmpColor.a * alpha);
            comp.colorGradient && tmpColor.multiply(comp.colorLT);
            uintVerts[offset] = tmpColor["_val"];

            offset += this.floatsPerVert;
            tmpColor.set(WHITE);
            tmpColor.setA(tmpColor.a * alpha);
            comp.colorGradient && tmpColor.multiply(comp.colorRT);
            uintVerts[offset] = tmpColor["_val"];
        }
    }

    //#region 顶点数据操作接口

    /**
     * 根据字符下标判断此字符是否可见
     */
    public isVisble(index: number): boolean {
        let info = this._lettersInfo[index];
        return info && info.valid && info.visible && !cc["textUtils"].isUnicodeSpace(info.char);
    }

    /**
     * 根据字符下标设置字符是否可见
     */
    public setVisible(comp: TextMeshPro, index: number, visible: boolean): void {
        let info = this._lettersInfo[index];
        if (!info || this.isVisble(index) === visible || info.visible === visible || cc["textUtils"].isUnicodeSpace(info.char)) {
            return;
        }

        info.visible = visible;
        let offset = this.colorExtraOffset + this.floatsPerVert * info.quadsIndex * 4;
        let color = cc.color();
        let alpha = (visible ? 1 : 0);

        let uintVerts = this._renderData.uintVDatas[0];

        color.set(WHITE);
        color.setA(color.a * alpha);
        comp.colorGradient && color.multiply(comp.colorLB);
        uintVerts[offset] = color["_val"];
        offset += this.floatsPerVert;

        color.set(WHITE);
        color.setA(color.a * alpha);
        comp.colorGradient && color.multiply(comp.colorRB);
        uintVerts[offset] = color["_val"];
        offset += this.floatsPerVert;

        color.set(WHITE);
        color.setA(color.a * alpha);
        comp.colorGradient && color.multiply(comp.colorLT);
        uintVerts[offset] = color["_val"];
        offset += this.floatsPerVert;

        color.set(WHITE);
        color.setA(color.a * alpha);
        comp.colorGradient && color.multiply(comp.colorRT);
        uintVerts[offset] = color["_val"];
    }

    /**
     * 根据字符下标获取颜色顶点数据，顺序为[左下, 右下, 左上, 右上]
     */
    public getColorExtraVertices(index: number): [cc.Color, cc.Color, cc.Color, cc.Color] | null {
        let info = this._lettersInfo[index];
        if (!info || !info.valid) {
            return null;
        }
        let result: [cc.Color, cc.Color, cc.Color, cc.Color] = [] as any;
        let uintVerts = this._renderData.uintVDatas[0];
        let offset = this.colorExtraOffset + this.floatsPerVert * info.quadsIndex * 4;
        for (let i = 0; i < 4; i++) {
            let color = cc.color();
            color["_val"] = uintVerts[offset];
            result.push(color);
            offset += this.floatsPerVert;
        }
        return result;
    }

    /**
     * 根据字符下标设置颜色顶点数据，顺序为[左下, 右下, 左上, 右上]
     */
    public setColorExtraVertices(index: number, data: [cc.Color, cc.Color, cc.Color, cc.Color]): void {
        let info = this._lettersInfo[index];
        if (!info || !info.valid || data.length !== 4 || cc["textUtils"].isUnicodeSpace(info.char)) {
            return;
        }

        let uintVerts = this._renderData.uintVDatas[0];
        let offset = this.colorExtraOffset + this.floatsPerVert * info.quadsIndex * 4;
        for (let i = 0; i < 4; i++) {
            uintVerts[offset] = data[i]["_val"];
            offset += this.floatsPerVert;
        }
    }

    /**
     * 根据字符下标获取坐标顶点数据，顺序为[左下, 右下, 左上, 右上]
     */
    public getPosVertices(index: number): [cc.Vec2, cc.Vec2, cc.Vec2, cc.Vec2] | null {
        let info = this._lettersInfo[index];
        if (!info || !info.valid) {
            return null;
        }
        let result: [cc.Vec2, cc.Vec2, cc.Vec2, cc.Vec2] = [] as any;
        let local = this._local;
        let offset = this.floatsPerVert * info.quadsIndex * 4;
        for (let i = 0; i < 4; i++) {
            result.push(cc.v2(local[offset], local[offset + 1]));
            offset += this.floatsPerVert;
        }
        return result;
    }

    /**
     * 根据字符下标设置坐标顶点数据，顺序为[左下, 右下, 左上, 右上]
     */
    public setPosVertices(index: number, data: [cc.Vec2, cc.Vec2, cc.Vec2, cc.Vec2]): void {
        let info = this._lettersInfo[index];
        if (!info || !info.valid || data.length !== 4 || cc["textUtils"].isUnicodeSpace(info.char)) {
            return;
        }
        let local = this._local;
        let offset = this.floatsPerVert * info.quadsIndex * 4;
        for (let i = 0; i < 4; i++) {
            local[offset] = data[i].x;
            local[offset + 1] = data[i].y;
            offset += this.floatsPerVert;
        }
    }

    //#endregion
}
