123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944 |
- import TextMeshPro from "./TextMeshPro";
- import { HtmlTextParser } from "./utils/HtmlParser";
- import TmpUtils from "./utils/TmpUtils";
- const { ccclass, property, disallowMultiple, executeInEditMode, menu } = cc._decorator;
- const RichTextChildName = "RICHTEXT_CHILD";
- const RichTextChildImageName = "RICHTEXT_Image_CHILD";
- const _htmlTextParser = new HtmlTextParser();
- let pool = new cc.js.Pool(function (node) {
- if (CC_EDITOR) {
- cc.isValid(node) && node.destroy();
- return false;
- }
- if (CC_DEV) {
- cc["assert"](!node._parent, "Recycling node\'s parent should be null!");
- }
- if (!cc.isValid(node)) {
- return false;
- }
- return true;
- }, 20);
- pool.get = function (string: string, richtext: TmpRichText) {
- let labelNode = this._get();
- if (!labelNode) {
- labelNode = new cc.PrivateNode(RichTextChildName);
- labelNode._objFlags |= cc.Object["Flags"].DontSave;
- }
- labelNode.setPosition(0, 0);
- labelNode.setAnchorPoint(0.5, 0.5);
- let labelComponent: TextMeshPro = labelNode.getComponent(TextMeshPro);
- if (!labelComponent) {
- labelComponent = labelNode.addComponent(TextMeshPro);
- }
- labelComponent.string = "";
- labelComponent.horizontalAlign = cc.Label.HorizontalAlign.LEFT;
- labelComponent.verticalAlign = cc.Label.VerticalAlign.CENTER;
- return labelNode;
- };
- /**
- * TextMeshPro富文本组件
- */
- @ccclass
- @disallowMultiple
- @executeInEditMode
- @menu("TextMeshPro组件/TmpRichText")
- export default class TmpRichText extends cc.Component {
- @property
- private _string: string = "";
- @property({ multiline: true })
- public get string(): string { return this._string; }
- public set string(v: string) {
- if (this._string === v) { return; }
- this._string = v;
- // this._layoutDirty = true;
- this._updateRichText();
- }
- @property(cc.JsonAsset)
- private _font: cc.JsonAsset = null;
- @property({ tooltip: CC_DEV && "字体资源\n依赖的纹理请勿打入图集\n在编辑器内拖拽此文件时,纹理必须和此文件处于同一目录下", type: cc.JsonAsset })
- private get font(): cc.JsonAsset { return this._font; }
- private set font(v: cc.JsonAsset) {
- if (this._font === v) { return; }
- this._font = v;
- if (CC_EDITOR) {
- this.editorInit();
- } else {
- this._layoutDirty = true;
- this._updateRichText();
- }
- }
- @property({ type: cc.Label.HorizontalAlign })
- private _horizontalAlign: cc.Label.HorizontalAlign = cc.Label.HorizontalAlign.LEFT;
- @property({ type: cc.Label.HorizontalAlign })
- public get horizontalAlign(): cc.Label.HorizontalAlign { return this._horizontalAlign; }
- public set horizontalAlign(v: cc.Label.HorizontalAlign) {
- if (this._horizontalAlign === v) { return; }
- this._horizontalAlign = v;
- this._layoutDirty = true;
- this._updateRichText();
- }
- @property({ type: cc.Label.VerticalAlign })
- private _verticalAlign: cc.Label.VerticalAlign = cc.Label.VerticalAlign.TOP;
- @property({ type: cc.Label.VerticalAlign })
- public get verticalAlign(): cc.Label.VerticalAlign { return this._verticalAlign; }
- public set verticalAlign(v: cc.Label.VerticalAlign) {
- if (this._verticalAlign === v) { return; }
- this._verticalAlign = v;
- this._layoutDirty = true;
- this._updateRichText();
- }
- @property
- private _fontSize: number = 32;
- @property({ range: [0, 1024] })
- public get fontSize(): number { return this._fontSize; }
- public set fontSize(v: number) {
- if (this._fontSize === v) { return; }
- this._fontSize = v;
- this._layoutDirty = true;
- this._updateRichText();
- }
- @property
- private _maxWidth: number = 0;
- @property({ tooltip: CC_DEV && "富文本的最大宽度" })
- public get maxWidth(): number { return this._maxWidth; }
- public set maxWidth(v: number) {
- if (this._maxWidth === v) { return; }
- this._maxWidth = v;
- this._layoutDirty = true;
- this._updateRichText();
- }
- @property
- private _lineHeight: number = 32;
- @property
- public get lineHeight(): number { return this._lineHeight; }
- public set lineHeight(v: number) {
- if (this._lineHeight === v) { return; }
- this._lineHeight = v;
- this._layoutDirty = true;
- this._updateRichText();
- }
- @property(cc.SpriteAtlas)
- private _imageAtlas: cc.SpriteAtlas = null;
- @property(cc.SpriteAtlas)
- public get imageAtlas(): cc.SpriteAtlas { return this._imageAtlas; }
- public set imageAtlas(v: cc.SpriteAtlas) {
- if (this._imageAtlas === v) { return; }
- this._imageAtlas = v;
- this._layoutDirty = true;
- this._updateRichText();
- }
- @property
- private _handleTouchEvent: boolean = true;
- @property
- public get handleTouchEvent(): boolean { return this._handleTouchEvent; }
- public set handleTouchEvent(v: boolean) {
- if (this._handleTouchEvent === v) { return; }
- this._handleTouchEvent = v;
- if (this.enabledInHierarchy) {
- this.handleTouchEvent ? this._addEventListeners() : this._removeEventListeners();
- }
- }
- @property(cc.Material)
- public material: cc.Material = null;
- @property({ tooltip: CC_DEV && "字体所依赖的纹理", type: cc.Texture2D, readonly: true })
- public textures: cc.Texture2D[] = [];
- private _textArray = null;
- private _labelSegments: cc.PrivateNode[] = [];
- private _labelSegmentsCache: cc.PrivateNode[] = [];
- private _linesWidth: number[] = [];
- private _lineOffsetX: number = 0;
- private _lineCount: number = 1;
- private _labelWidth: number = 0;
- private _labelHeight: number = 0;
- private _layoutDirty: boolean = true;
- // 文本父节点
- private _labelContent: cc.PrivateNode = null;
- private get labelContent(): cc.PrivateNode {
- if (!this._labelContent) {
- const content = "TMP_LABEL_CONTENT";
- this._labelContent = this.node.getChildByName(content) ?? new cc.PrivateNode(content);
- this._labelContent["_objFlags"] |= cc.Object["Flags"].DontSave;
- this.node.insertChild(this._labelContent, this._imageContent ? 1 : 0);
- }
- return this._labelContent;
- }
- // 图片父节点
- private _imageContent: cc.PrivateNode = null;
- private get imageContent(): cc.PrivateNode {
- if (!this._imageContent) {
- const content = "TMP_IMAGE_CONTENT";
- this._imageContent = this.node.getChildByName(content) ?? new cc.PrivateNode(content);
- this._imageContent["_objFlags"] |= cc.Object["Flags"].DontSave;
- this.node.insertChild(this._imageContent, 0);
- }
- return this._imageContent;
- }
- private editorInit(): void {
- if (CC_EDITOR) {
- // 加载图集
- if (!this._font || !this._font["_uuid"]) {
- this.textures = [];
- this._layoutDirty = true;
- this._updateRichText();
- return;
- }
- Editor.assetdb.queryUrlByUuid(this._font["_uuid"], (error: any, url: string) => {
- if (!url) {
- return;
- }
- let start = 12;
- let end = url.lastIndexOf("/");
- let dir = url.slice(start, end + 1);
- let arr: Promise<cc.Texture2D>[] = [];
- this._font.json.pageData.forEach((v) => {
- let imgUrl = dir + v.file;
- arr.push(TmpUtils.load<cc.Texture2D>(imgUrl));
- });
- Promise.all(arr).then((v) => {
- this.textures = v;
- this._layoutDirty = true;
- this._updateRichText();
- });
- });
- }
- }
- protected resetInEditor(): void {
- if (CC_EDITOR) {
- TmpUtils.load<cc.Material>(TmpUtils.TMP_MAT).then((mat) => {
- if (mat) {
- this.material = mat;
- }
- });
- }
- }
- public onRestore(): void {
- if (CC_EDITOR) {
- // Because undo/redo will not call onEnable/onDisable,
- // we need call onEnable/onDisable manually to active/disactive children nodes.
- if (this.enabledInHierarchy) {
- this.onEnable();
- }
- else {
- this.onDisable();
- }
- }
- }
- protected onEnable(): void {
- if (this.handleTouchEvent) {
- this._addEventListeners();
- }
- this._onFontLoaded();
- this._activateChildren(true);
- }
- protected onDisable(): void {
- if (this.handleTouchEvent) {
- this._removeEventListeners();
- }
- this._activateChildren(false);
- }
- protected onDestroy(): void {
- for (let i = 0; i < this._labelSegments.length; ++i) {
- this._labelSegments[i].removeFromParent();
- // @ts-ignore
- pool.put(this._labelSegments[i]);
- }
- }
- private _onColorChanged(parentColor): void {
- this.node.children.forEach((content) => {
- content.children.forEach((childNode) => {
- childNode.color = parentColor;
- });
- });
- }
- private _addEventListeners(): void {
- this.node.on(cc.Node.EventType.TOUCH_END, this._onTouchEnded, this);
- this.node.on(cc.Node.EventType.COLOR_CHANGED, this._onColorChanged, this);
- }
- private _removeEventListeners(): void {
- this.node.off(cc.Node.EventType.TOUCH_END, this._onTouchEnded, this);
- this.node.off(cc.Node.EventType.COLOR_CHANGED, this._onColorChanged, this);
- }
- private _updateLabelSegmentTextAttributes(): void {
- this._labelSegments.forEach(function (item) {
- this._applyTextAttribute(item, null, true);
- }.bind(this));
- }
- private _createFontLabel(string: string): cc.Node {
- let node = pool.get(string, this);
- let tmp: TextMeshPro = node.getComponent(TextMeshPro);
- if (tmp && tmp.getMaterial(0) !== this.material) {
- tmp.setMaterial(0, this.material);
- }
- return node;
- }
- private _onFontLoaded(): void {
- this._layoutDirty = true;
- this._updateRichText();
- }
- private _measureText(styleIndex: number, string?: string): number | ((s: string) => number) {
- let self = this;
- let func = function (string) {
- let label: cc.Node;
- if (self._labelSegmentsCache.length === 0) {
- label = self._createFontLabel(string);
- self._labelSegmentsCache.push(label);
- } else {
- label = self._labelSegmentsCache[0];
- }
- label["_styleIndex"] = styleIndex;
- self._applyTextAttribute(label, string, true);
- let labelSize = label.getContentSize();
- return labelSize.width;
- };
- if (string) {
- return func(string);
- } else {
- return func;
- }
- }
- private _onTouchEnded(event): void {
- let components = this.node.getComponents(cc.Component);
- for (let i = 0; i < this._labelSegments.length; ++i) {
- let labelSegment = this._labelSegments[i];
- let clickHandler = labelSegment["_clickHandler"];
- let clickParam = labelSegment["_clickParam"];
- if (clickHandler && this._containsTouchLocation(labelSegment, event.touch.getLocation())) {
- components.forEach(function (component) {
- if (component.enabledInHierarchy && component[clickHandler]) {
- component[clickHandler](event, clickParam);
- }
- });
- event.stopPropagation();
- }
- }
- }
- private _containsTouchLocation(label: cc.Node, point: cc.Vec2): boolean {
- let myRect = label.getBoundingBoxToWorld();
- return myRect.contains(point);
- }
- private _resetContent(node: cc.Node): void {
- if (!node) {
- return;
- }
- const children = node.children;
- for (let i = children.length - 1; i >= 0; i--) {
- const child = children[i];
- if (child.name === RichTextChildName || child.name === RichTextChildImageName) {
- if (child.parent === node) {
- child.parent = null;
- }
- else {
- // In case child.parent !== this.node, child cannot be removed from children
- children.splice(i, 1);
- }
- if (child.name === RichTextChildName) {
- // @ts-ignore
- pool.put(child);
- }
- }
- }
- }
- private _resetState(): void {
- for (let i = 0; i < this.node.childrenCount; i++) {
- this._resetContent(this.node.children[i]);
- }
- this._labelSegments.length = 0;
- this._labelSegmentsCache.length = 0;
- this._linesWidth.length = 0;
- this._lineOffsetX = 0;
- this._lineCount = 1;
- this._labelWidth = 0;
- this._labelHeight = 0;
- this._layoutDirty = true;
- }
- private _activateChildren(active: boolean): void {
- this.node.children.forEach((content) => {
- for (let i = content.children.length - 1; i >= 0; i--) {
- let child = content.children[i];
- if (child.name === RichTextChildName || child.name === RichTextChildImageName) {
- child.active = active;
- }
- }
- });
- }
- private _addLabelSegment(stringToken: string, styleIndex: number): cc.Node {
- let labelSegment;
- if (this._labelSegmentsCache.length === 0) {
- labelSegment = this._createFontLabel(stringToken);
- } else {
- labelSegment = this._labelSegmentsCache.pop();
- }
- const tmp: TextMeshPro = labelSegment.getComponent(TextMeshPro);
- if (tmp.verticalAlign !== this._verticalAlign) {
- tmp.verticalAlign = this._verticalAlign;
- }
- labelSegment._styleIndex = styleIndex;
- labelSegment._lineCount = this._lineCount;
- labelSegment.active = this.node.active;
- labelSegment.setAnchorPoint(0, 0);
- this._applyTextAttribute(labelSegment, stringToken, !!CC_EDITOR);
- this.labelContent.addChild(labelSegment);
- this._labelSegments.push(labelSegment);
- return labelSegment;
- }
- private _updateRichTextWithMaxWidth(labelString, labelWidth, styleIndex): void {
- let fragmentWidth = labelWidth;
- let labelSegment;
- if (this._lineOffsetX > 0 && fragmentWidth + this._lineOffsetX > this.maxWidth) {
- //concat previous line
- let checkStartIndex = 0;
- while (this._lineOffsetX <= this.maxWidth) {
- let checkEndIndex = this._getFirstWordLen(labelString,
- checkStartIndex,
- labelString.length);
- let checkString = labelString.substr(checkStartIndex, checkEndIndex);
- let checkStringWidth: number = this._measureText(styleIndex, checkString) as number;
- if (this._lineOffsetX + checkStringWidth <= this.maxWidth) {
- this._lineOffsetX += checkStringWidth;
- checkStartIndex += checkEndIndex;
- }
- else {
- if (checkStartIndex > 0) {
- let remainingString = labelString.substr(0, checkStartIndex);
- this._addLabelSegment(remainingString, styleIndex);
- labelString = labelString.substr(checkStartIndex, labelString.length);
- fragmentWidth = this._measureText(styleIndex, labelString);
- }
- this._updateLineInfo();
- break;
- }
- }
- }
- if (fragmentWidth > this.maxWidth) {
- let fragments = cc["textUtils"].fragmentText(labelString,
- fragmentWidth,
- this.maxWidth,
- this._measureText(styleIndex));
- for (let k = 0; k < fragments.length; ++k) {
- let splitString = fragments[k];
- labelSegment = this._addLabelSegment(splitString, styleIndex);
- let labelSize = labelSegment.getContentSize();
- this._lineOffsetX += labelSize.width;
- if (fragments.length > 1 && k < fragments.length - 1) {
- this._updateLineInfo();
- }
- }
- }
- else {
- this._lineOffsetX += fragmentWidth;
- this._addLabelSegment(labelString, styleIndex);
- }
- }
- private _isLastComponentCR(stringToken: string): boolean {
- return stringToken.length - 1 === stringToken.lastIndexOf("\n");
- }
- private _updateLineInfo(): void {
- this._linesWidth.push(this._lineOffsetX);
- this._lineOffsetX = 0;
- this._lineCount++;
- }
- private _needsUpdateTextLayout(newTextArray): boolean {
- if (this._layoutDirty || !this._textArray || !newTextArray) {
- return true;
- }
- if (this._textArray.length !== newTextArray.length) {
- return true;
- }
- for (let i = 0; i < this._textArray.length; ++i) {
- let oldItem = this._textArray[i];
- let newItem = newTextArray[i];
- if (oldItem.text !== newItem.text) {
- return true;
- }
- else {
- let oldStyle = oldItem.style, newStyle = newItem.style;
- if (oldStyle) {
- if (newStyle) {
- if (!oldStyle.outline !== !newStyle.outline) {
- return true;
- }
- if (oldStyle.size !== newStyle.size
- || !oldStyle.italic !== !newStyle.italic
- || oldStyle.isImage !== newStyle.isImage) {
- return true;
- }
- if (oldStyle.src !== newStyle.src ||
- oldStyle.imageAlign !== newStyle.imageAlign ||
- oldStyle.imageHeight !== newStyle.imageHeight ||
- oldStyle.imageWidth !== newStyle.imageWidth ||
- oldStyle.imageOffset !== newStyle.imageOffset) {
- return true;
- }
- }
- else {
- if (oldStyle.size || oldStyle.italic || oldStyle.isImage || oldStyle.outline) {
- return true;
- }
- }
- }
- else {
- if (newStyle) {
- if (newStyle.size || newStyle.italic || newStyle.isImage || newStyle.outline) {
- return true;
- }
- }
- }
- }
- }
- return false;
- }
- private _addRichTextImageElement(richTextElement): void {
- let spriteFrameName = richTextElement.style.src;
- let spriteFrame = this.imageAtlas.getSpriteFrame(spriteFrameName);
- if (spriteFrame) {
- let spriteNode = new cc.PrivateNode(RichTextChildImageName);
- spriteNode["_objFlags"] |= cc.Object["Flags"].DontSave;
- let spriteComponent = spriteNode.addComponent(cc.Sprite);
- switch (richTextElement.style.imageAlign) {
- case "top":
- spriteNode.setAnchorPoint(0, 1);
- break;
- case "center":
- spriteNode.setAnchorPoint(0, 0.5);
- break;
- default:
- spriteNode.setAnchorPoint(0, 0);
- break;
- }
- if (richTextElement.style.imageOffset) spriteNode["_imageOffset"] = richTextElement.style.imageOffset;
- spriteComponent.type = cc.Sprite.Type.SLICED;
- spriteComponent.sizeMode = cc.Sprite.SizeMode.CUSTOM;
- this.imageContent.addChild(spriteNode);
- this._labelSegments.push(spriteNode);
- let spriteRect = spriteFrame.getRect();
- let scaleFactor = 1;
- let spriteWidth = spriteRect.width;
- let spriteHeight = spriteRect.height;
- let expectWidth = richTextElement.style.imageWidth;
- let expectHeight = richTextElement.style.imageHeight;
- if (expectHeight > 0) {
- scaleFactor = expectHeight / spriteHeight;
- spriteWidth = spriteWidth * scaleFactor;
- spriteHeight = spriteHeight * scaleFactor;
- }
- else {
- scaleFactor = this.lineHeight / spriteHeight;
- spriteWidth = spriteWidth * scaleFactor;
- spriteHeight = spriteHeight * scaleFactor;
- }
- if (expectWidth > 0) spriteWidth = expectWidth;
- if (this.maxWidth > 0) {
- if (this._lineOffsetX + spriteWidth > this.maxWidth) {
- this._updateLineInfo();
- }
- this._lineOffsetX += spriteWidth;
- }
- else {
- this._lineOffsetX += spriteWidth;
- if (this._lineOffsetX > this._labelWidth) {
- this._labelWidth = this._lineOffsetX;
- }
- }
- spriteComponent.spriteFrame = spriteFrame;
- spriteNode.setContentSize(spriteWidth, spriteHeight);
- spriteNode["_lineCount"] = this._lineCount;
- if (richTextElement.style.event) {
- if (richTextElement.style.event.click) {
- spriteNode["_clickHandler"] = richTextElement.style.event.click;
- }
- if (richTextElement.style.event.param) {
- spriteNode["_clickParam"] = richTextElement.style.event.param;
- }
- else {
- spriteNode["_clickParam"] = "";
- }
- }
- else {
- spriteNode["_clickHandler"] = null;
- }
- }
- else {
- cc["warnID"](4400);
- }
- }
- private _updateRichText(): void {
- if (!this.enabledInHierarchy) return;
- let newTextArray = _htmlTextParser.parse(this.string);
- if (!this._needsUpdateTextLayout(newTextArray)) {
- this._textArray = newTextArray.slice();
- this._updateLabelSegmentTextAttributes();
- return;
- }
- this._textArray = newTextArray.slice();
- this._resetState();
- let lastEmptyLine = false;
- let label;
- let labelSize;
- for (let i = 0; i < this._textArray.length; ++i) {
- let richTextElement = this._textArray[i];
- let text = richTextElement.text;
- //handle <br/> <img /> tag
- if (text === "") {
- if (richTextElement.style && richTextElement.style.newline) {
- this._updateLineInfo();
- continue;
- }
- if (richTextElement.style && richTextElement.style.isImage && this.imageAtlas) {
- this._addRichTextImageElement(richTextElement);
- continue;
- }
- }
- let multilineTexts = text.split("\n");
- for (let j = 0; j < multilineTexts.length; ++j) {
- let labelString = multilineTexts[j];
- if (labelString === "") {
- //for continues \n
- if (this._isLastComponentCR(text)
- && j === multilineTexts.length - 1) {
- continue;
- }
- this._updateLineInfo();
- lastEmptyLine = true;
- continue;
- }
- lastEmptyLine = false;
- if (this.maxWidth > 0) {
- let labelWidth = this._measureText(i, labelString);
- this._updateRichTextWithMaxWidth(labelString, labelWidth, i);
- if (multilineTexts.length > 1 && j < multilineTexts.length - 1) {
- this._updateLineInfo();
- }
- }
- else {
- label = this._addLabelSegment(labelString, i);
- labelSize = label.getContentSize();
- this._lineOffsetX += labelSize.width;
- if (this._lineOffsetX > this._labelWidth) {
- this._labelWidth = this._lineOffsetX;
- }
- if (multilineTexts.length > 1 && j < multilineTexts.length - 1) {
- this._updateLineInfo();
- }
- }
- }
- }
- if (!lastEmptyLine) {
- this._linesWidth.push(this._lineOffsetX);
- }
- if (this.maxWidth > 0) {
- this._labelWidth = this.maxWidth;
- }
- this._labelHeight = (this._lineCount + cc["textUtils"].BASELINE_RATIO) * this.lineHeight;
- // trigger "size-changed" event
- this.node.setContentSize(this._labelWidth, this._labelHeight);
- this._updateRichTextPosition();
- this._layoutDirty = false;
- }
- private _getFirstWordLen(text, startIndex, textLen): number {
- let character = text.charAt(startIndex);
- if (cc["textUtils"].isUnicodeCJK(character)
- || cc["textUtils"].isUnicodeSpace(character)) {
- return 1;
- }
- let len = 1;
- for (let index = startIndex + 1; index < textLen; ++index) {
- character = text.charAt(index);
- if (cc["textUtils"].isUnicodeSpace(character)
- || cc["textUtils"].isUnicodeCJK(character)) {
- break;
- }
- len++;
- }
- return len;
- }
- private _updateRichTextPosition(): void {
- let nextTokenX = 0;
- let nextLineIndex = 1;
- let totalLineCount = this._lineCount;
- for (let i = 0; i < this._labelSegments.length; ++i) {
- let label = this._labelSegments[i];
- let lineCount = label["_lineCount"];
- if (lineCount > nextLineIndex) {
- nextTokenX = 0;
- nextLineIndex = lineCount;
- }
- let lineOffsetX = 0;
- switch (this.horizontalAlign) {
- case cc.Label.HorizontalAlign.LEFT:
- lineOffsetX = - this._labelWidth / 2;
- break;
- case cc.Label.HorizontalAlign.CENTER:
- lineOffsetX = - this._linesWidth[lineCount - 1] / 2;
- break;
- case cc.Label.HorizontalAlign.RIGHT:
- lineOffsetX = this._labelWidth / 2 - this._linesWidth[lineCount - 1];
- break;
- default:
- break;
- }
- label.x = nextTokenX + lineOffsetX;
- label.y = this.lineHeight * (totalLineCount - lineCount) - this._labelHeight / 2;
- if (lineCount === nextLineIndex) {
- let labelSize = label.getContentSize();
- nextTokenX += labelSize.width;
- // 排版根据TextMeshPro字符信息适配
- let tmp: TextMeshPro = label.getComponent(TextMeshPro);
- if (tmp && tmp.richTextDeltaX) {
- nextTokenX += tmp.richTextDeltaX;
- }
- }
- let sprite = label.getComponent(cc.Sprite);
- if (sprite) {
- // adjust img align (from <img align=top|center|bottom>)
- let lineHeightSet = this.lineHeight;
- let lineHeightReal = this.lineHeight * (1 + cc["textUtils"].BASELINE_RATIO); //single line node height
- switch (label.anchorY) {
- case 1:
- label.y += (lineHeightSet + ((lineHeightReal - lineHeightSet) / 2));
- break;
- case 0.5:
- label.y += (lineHeightReal / 2);
- break;
- default:
- label.y += ((lineHeightReal - lineHeightSet) / 2);
- break;
- }
- // adjust img offset (from <img offset=12|12,34>)
- if (label["_imageOffset"]) {
- let offsets = label["_imageOffset"].split(",");
- if (offsets.length === 1 && offsets[0]) {
- let offsetY = parseFloat(offsets[0]);
- if (Number.isInteger(offsetY)) label.y += offsetY;
- }
- else if (offsets.length === 2) {
- let offsetX = parseFloat(offsets[0]);
- let offsetY = parseFloat(offsets[1]);
- if (Number.isInteger(offsetX)) label.x += offsetX;
- if (Number.isInteger(offsetY)) label.y += offsetY;
- }
- }
- }
- //adjust y for label with outline
- // let outline = label.getComponent(cc.LabelOutline);
- // if (outline && outline.width) label.y = label.y - outline.width;
- }
- }
- /**
- * 16进制颜色转换
- * @param color
- */
- private _convertLiteralColorValue(color: string): cc.Color {
- let colorValue = color.toUpperCase();
- if (cc.Color[colorValue]) {
- return cc.Color[colorValue];
- }
- else {
- let hexString = (color.indexOf("#") === 0) ? color.substring(1) : color;
- let r = parseInt(hexString.substring(0, 2), 16) || 0;
- let g = parseInt(hexString.substring(2, 4), 16) || 0;
- let b = parseInt(hexString.substring(4, 6), 16) || 0;
- let a = parseInt(hexString.substring(6, 8), 16);
- if (Number.isNaN(a)) {
- a = 255;
- }
- return cc.color(r, g, b, a);
- }
- }
- /**
- * 更新字体样式
- * @param labelNode
- * @param string
- * @param force
- * @param needSpaceW 临时计算宽度时需要考虑空格字符串的宽度
- */
- private _applyTextAttribute(labelNode: cc.Node, string: string, force: boolean): void {
- let labelComponent: TextMeshPro = labelNode.getComponent(TextMeshPro);
- if (!labelComponent) {
- return;
- }
- let index = labelNode["_styleIndex"];
- let textStyle = null;
- if (this._textArray[index]) {
- textStyle = this._textArray[index].style;
- }
- if (textStyle && textStyle.color) {
- labelNode.color = this._convertLiteralColorValue(textStyle.color);
- } else {
- labelNode.color = this.node.color;
- }
- labelComponent.setFont(this.font, this.textures);
- labelComponent.lineHeight = this.lineHeight;
- labelComponent.colorGradient = Boolean(textStyle && textStyle.colorGradient);
- if (labelComponent.colorGradient) {
- labelComponent.colorLB = this._convertLiteralColorValue(textStyle.colorGradient.lb);
- labelComponent.colorRB = this._convertLiteralColorValue(textStyle.colorGradient.rb);
- labelComponent.colorLT = this._convertLiteralColorValue(textStyle.colorGradient.lt);
- labelComponent.colorRT = this._convertLiteralColorValue(textStyle.colorGradient.rt);
- }
- if (textStyle && textStyle.face) {
- labelComponent.tmpUniform.faceColor = this._convertLiteralColorValue(textStyle.face.color);
- labelComponent.tmpUniform.faceDilate = textStyle.face.dilate;
- labelComponent.tmpUniform.faceSoftness = textStyle.face.softness;
- } else {
- labelComponent.tmpUniform.faceColor = cc.Color.WHITE;
- labelComponent.tmpUniform.faceDilate = 0.5;
- labelComponent.tmpUniform.faceSoftness = 0.01;
- }
- labelComponent.enableItalic = Boolean(textStyle && textStyle.italic);
- labelComponent.enableUnderline = Boolean(textStyle && textStyle.underline);
- if (labelComponent.enableUnderline) {
- labelComponent.underlineOffset = textStyle.offset || 0;
- }
- labelComponent.enableStrikethrough = Boolean(textStyle && textStyle.strikethrough);
- if (labelComponent.enableStrikethrough) {
- labelComponent.strikethroughOffset = textStyle.offset || 0;
- }
- labelComponent.tmpUniform.enableOutline = Boolean(textStyle && textStyle.outline);
- if (textStyle && textStyle.outline) {
- labelComponent.tmpUniform.outlineColor = this._convertLiteralColorValue(textStyle.outline.color);
- labelComponent.tmpUniform.outlineThickness = textStyle.outline.thickness;
- }
- labelComponent.tmpUniform.enableUnderlay = Boolean(textStyle && textStyle.underlay);
- if (labelComponent.tmpUniform.enableUnderlay) {
- labelComponent.tmpUniform.underlayColor = this._convertLiteralColorValue(textStyle.underlay.color);
- labelComponent.tmpUniform.underlayOffset = cc.v2(textStyle.underlay.x, textStyle.underlay.y);
- labelComponent.tmpUniform.underlayDilate = textStyle.underlay.dilate;
- labelComponent.tmpUniform.underlaySoftness = textStyle.underlay.softness;
- }
- labelComponent.tmpUniform.enableGlow = Boolean(textStyle && textStyle.glow);
- if (labelComponent.tmpUniform.enableGlow) {
- labelComponent.tmpUniform.glowColor = this._convertLiteralColorValue(textStyle.glow.color);
- labelComponent.tmpUniform.glowOffset = textStyle.glow.offset;
- labelComponent.tmpUniform.glowInner = textStyle.glow.inner;
- labelComponent.tmpUniform.glowOuter = textStyle.glow.outer;
- labelComponent.tmpUniform.glowPower = textStyle.glow.power;
- }
- if (textStyle && textStyle.size) {
- labelComponent.fontSize = textStyle.size;
- } else {
- labelComponent.fontSize = this.fontSize;
- }
- if (string !== null) {
- if (typeof string !== "string") {
- string = "" + string;
- }
- labelComponent.string = string;
- }
- force && labelComponent.forceUpdateRenderData();
- if (textStyle && textStyle.event) {
- if (textStyle.event.click) {
- labelNode["_clickHandler"] = textStyle.event.click;
- }
- if (textStyle.event.param) {
- labelNode["_clickParam"] = textStyle.event.param;
- }
- else {
- labelNode["_clickParam"] = "";
- }
- }
- else {
- labelNode["_clickHandler"] = null;
- }
- }
- }
|