Exemple #1
0
        void layoutEmoji(string text, TextStyle style, Font font, int start, int count)
        {
            for (int i = 0; i < count; i++)
            {
                char  c = text[i];
                float x = this._advance;
                if (EmojiUtils.isSingleCharNonEmptyEmoji(c) || char.IsHighSurrogate(c))
                {
                    float letterSpace          = style.letterSpacing;
                    float letterSpaceHalfLeft  = letterSpace * 0.5f;
                    float letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft;

                    x += letterSpaceHalfLeft;
                    this._advances[i] += letterSpaceHalfLeft;

                    var metrics = FontMetrics.fromFont(font, style.UnityFontSize);

                    var minX = x;
                    var maxX = metrics.descent - metrics.ascent + x;
                    var minY = metrics.ascent;
                    var maxY = metrics.descent;

                    if (this._bounds.width <= 0 || this._bounds.height <= 0)
                    {
                        this._bounds = UnityEngine.Rect.MinMaxRect(
                            minX, minY, maxX, maxY);
                    }
                    else
                    {
                        if (minX < this._bounds.x)
                        {
                            this._bounds.x = minX;
                        }

                        if (minY < this._bounds.y)
                        {
                            this._bounds.y = minY;
                        }

                        if (maxX > this._bounds.xMax)
                        {
                            this._bounds.xMax = maxX;
                        }

                        if (maxY > this._bounds.yMax)
                        {
                            this._bounds.yMax = maxY;
                        }
                    }

                    this._positions[i] = x;
                    float advance = style.fontSize;
                    x += advance;

                    this._advances[i] += advance;
                    this._advances[i] += letterSpaceHalfRight;
                    x += letterSpaceHalfRight;
                }
                else
                {
                    this._advances[i]  = 0;
                    this._positions[i] = x;
                }

                this._advance = x;
            }
        }
        public uiMeshMesh resolveMesh()
        {
            if (this._resolved)
            {
                return(this._mesh);
            }

            this._resolved = true;

            var style = this.textBlob.Value.style;

            var text     = this.textBlob.Value.text;
            var key      = MeshKey.create(this.textBlob.Value.instanceId, this.scale);
            var fontInfo = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle);
            var font     = fontInfo.font;

            _meshes.TryGetValue(key, out var meshInfo);
            if (meshInfo != null && meshInfo.textureVersion == fontInfo.textureVersion)
            {
                ObjectPool <MeshKey> .release(key);

                meshInfo.touch();
                this._mesh = meshInfo.mesh.transform(this.matrix);
                return(this._mesh);
            }

            // Handling Emoji
            char startingChar = text[this.textBlob.Value.textOffset];

            if (char.IsHighSurrogate(startingChar) || EmojiUtils.isSingleCharEmoji(startingChar))
            {
                var vert = ObjectPool <uiList <Vector3> > .alloc();

                var tri = ObjectPool <uiList <int> > .alloc();

                var uvCoord = ObjectPool <uiList <Vector2> > .alloc();

                var metrics    = FontMetrics.fromFont(font, style.UnityFontSize);
                var minMaxRect = EmojiUtils.getMinMaxRect(style.fontSize, metrics.ascent, metrics.descent);
                var minX       = minMaxRect.left;
                var maxX       = minMaxRect.right;
                var minY       = minMaxRect.top;
                var maxY       = minMaxRect.bottom;

                for (int i = 0; i < this.textBlob.Value.textSize; i++)
                {
                    char a    = text[this.textBlob.Value.textOffset + i];
                    int  code = a;
                    if (char.IsHighSurrogate(a))
                    {
                        D.assert(i + 1 < this.textBlob.Value.textSize);
                        D.assert(this.textBlob.Value.textOffset + i + 1 < this.textBlob.Value.text.Length);
                        char b = text[this.textBlob.Value.textOffset + i + 1];
                        D.assert(char.IsLowSurrogate(b));
                        code = char.ConvertToUtf32(a, b);
                    }
                    else if (char.IsLowSurrogate(a) || EmojiUtils.isEmptyEmoji(a))
                    {
                        continue;
                    }

                    var uvRect = EmojiUtils.getUVRect(code);

                    var positionX = this.textBlob.Value.getPositionX(i);

                    int baseIndex = vert.Count;
                    vert.Add(new Vector3(positionX + minX, minY, 0));
                    vert.Add(new Vector3(positionX + maxX, minY, 0));
                    vert.Add(new Vector3(positionX + maxX, maxY, 0));
                    vert.Add(new Vector3(positionX + minX, maxY, 0));
                    tri.Add(baseIndex);
                    tri.Add(baseIndex + 1);
                    tri.Add(baseIndex + 2);
                    tri.Add(baseIndex);
                    tri.Add(baseIndex + 2);
                    tri.Add(baseIndex + 3);
                    uvCoord.Add(uvRect.bottomLeft.toVector());
                    uvCoord.Add(uvRect.bottomRight.toVector());
                    uvCoord.Add(uvRect.topRight.toVector());
                    uvCoord.Add(uvRect.topLeft.toVector());

                    if (char.IsHighSurrogate(a))
                    {
                        i++;
                    }
                }

                uiMeshMesh meshMesh = uiMeshMesh.create(null, vert, tri, uvCoord);

                if (_meshes.ContainsKey(key))
                {
                    ObjectPool <MeshInfo> .release(_meshes[key]);

                    _meshes.Remove(key);
                }

                _meshes[key] = MeshInfo.create(key, meshMesh, 0);

                this._mesh = meshMesh.transform(this.matrix);
                return(this._mesh);
            }

            var length         = this.textBlob.Value.textSize;
            var fontSizeToLoad = Mathf.CeilToInt(style.UnityFontSize * this.scale);

            var vertices = ObjectPool <uiList <Vector3> > .alloc();

            vertices.SetCapacity(length * 4);

            var triangles = ObjectPool <uiList <int> > .alloc();

            triangles.SetCapacity(length * 6);

            var uv = ObjectPool <uiList <Vector2> > .alloc();

            uv.SetCapacity(length * 4);

            for (int charIndex = 0; charIndex < length; ++charIndex)
            {
                var ch = text[charIndex + this.textBlob.Value.textOffset];
                // first char as origin for mesh position
                var positionX = this.textBlob.Value.getPositionX(charIndex);
                if (LayoutUtils.isWordSpace(ch) || LayoutUtils.isLineEndSpace(ch) || ch == '\t')
                {
                    continue;
                }

                if (fontSizeToLoad == 0)
                {
                    continue;
                }

                font.getGlyphInfo(ch, out var glyphInfo, fontSizeToLoad, style.UnityFontStyle);

                var minX = glyphInfo.minX / this.scale;
                var maxX = glyphInfo.maxX / this.scale;
                var minY = -glyphInfo.maxY / this.scale;
                var maxY = -glyphInfo.minY / this.scale;

                var baseIndex = vertices.Count;

                vertices.Add(new Vector3(positionX + minX, minY, 0));
                vertices.Add(new Vector3(positionX + maxX, minY, 0));
                vertices.Add(new Vector3(positionX + maxX, maxY, 0));
                vertices.Add(new Vector3(positionX + minX, maxY, 0));

                triangles.Add(baseIndex);
                triangles.Add(baseIndex + 1);
                triangles.Add(baseIndex + 2);
                triangles.Add(baseIndex);
                triangles.Add(baseIndex + 2);
                triangles.Add(baseIndex + 3);

                uv.Add(glyphInfo.uvTopLeft);
                uv.Add(glyphInfo.uvTopRight);
                uv.Add(glyphInfo.uvBottomRight);
                uv.Add(glyphInfo.uvBottomLeft);
            }

            if (vertices.Count == 0)
            {
                this._mesh = null;
                ObjectPool <uiList <Vector3> > .release(vertices);

                ObjectPool <uiList <Vector2> > .release(uv);

                ObjectPool <uiList <int> > .release(triangles);

                ObjectPool <MeshKey> .release(key);

                return(null);
            }

            uiMeshMesh mesh = vertices.Count > 0 ? uiMeshMesh.create(null, vertices, triangles, uv) : null;

            if (_meshes.ContainsKey(key))
            {
                ObjectPool <MeshInfo> .release(_meshes[key]);

                _meshes.Remove(key);
            }

            _meshes[key] = MeshInfo.create(key, mesh, fontInfo.textureVersion);

            this._mesh = mesh.transform(this.matrix);
            return(this._mesh);
        }
Exemple #3
0
        public void layout(ParagraphConstraints constraints)
        {
            if (!this._needsLayout && this._width == constraints.width)
            {
                return;
            }

            this._tabStops.setFont(
                FontManager.instance.getOrCreate(
                    this._paragraphStyle.fontFamily ?? TextStyle.kDefaultFontFamily,
                    this._paragraphStyle.fontWeight ?? TextStyle.kDefaultFontWeight,
                    this._paragraphStyle.fontStyle ?? TextStyle.kDefaultfontStyle).font,
                (int)(this._paragraphStyle.fontSize ?? TextStyle.kDefaultFontSize));

            this._needsLayout = false;
            this._width       = Mathf.Floor(constraints.width);

            int lineStyleRunsCount = this._computeLineBreak();

            if (this._glyphLines == null || this._glyphLines.Length < this._lineRangeCount)
            {
                this._glyphLines = new GlyphLine[LayoutUtils.minPowerOfTwo(this._lineRangeCount)];
            }

            if (this._lineHeights == null || this._lineHeights.Length < this._lineRangeCount)
            {
                this._lineHeights = new float[LayoutUtils.minPowerOfTwo(this._lineRangeCount)];
            }

            if (this._paintRecords == null || this._paintRecords.Length < lineStyleRunsCount)
            {
                this._paintRecords = new PaintRecord[LayoutUtils.minPowerOfTwo(lineStyleRunsCount)];
            }

            this._paintRecordsCount = 0;

            if (this._codeUnitRuns == null || this._codeUnitRuns.Length < lineStyleRunsCount)
            {
                this._codeUnitRuns = new CodeUnitRun[LayoutUtils.minPowerOfTwo(lineStyleRunsCount)];
            }

            this._codeUnitRunsCount = 0;

            int styleMaxLines = this._paragraphStyle.maxLines ?? int.MaxValue;

            this._didExceedMaxLines = this._lineRangeCount > styleMaxLines;

            var   lineLimit     = Mathf.Min(styleMaxLines, this._lineRangeCount);
            int   styleRunIndex = 0;
            float yOffset       = 0;
            float preMaxDescent = 0;
            float maxWordWidth  = 0;

            TextBlobBuilder builder          = new TextBlobBuilder();
            int             ellipsizedLength = this._text.Length + (this._paragraphStyle.ellipsis?.Length ?? 0);

            // All text blobs share a single position buffer, which is big enough taking ellipsis into consideration
            if (this._textBlobPositionXs == null || this._textBlobPositionXs.Length < ellipsizedLength)
            {
                this._textBlobPositionXs = new float[LayoutUtils.minPowerOfTwo(ellipsizedLength)];
            }

            builder.setPositionXs(this._textBlobPositionXs);
            // this._glyphLines and this._codeUnitRuns will refer to this array for glyph positions
            if (this._glyphPositions == null || this._glyphPositions.Length < ellipsizedLength)
            {
                this._glyphPositions = new GlyphPosition[LayoutUtils.minPowerOfTwo(ellipsizedLength)];
            }

            // Pointer to the _glyphPositions array, to keep track of where the next glyph is stored
            int pGlyphPositions = 0;

            // Compute max(NumberOfWords(line) for line in lines), to determine the size of word buffers
            int maxWordCount = this._computeMaxWordCount();

            if (_wordsBuffer == null || _wordsBuffer.Length < maxWordCount)
            {
                _wordsBuffer = new Range <int> [maxWordCount < 4 ? 4: maxWordCount];
            }

            // Iterate through line ranges
            for (int lineNumber = 0; lineNumber < lineLimit; ++lineNumber)
            {
                var   lineRange      = this._lineRanges[lineNumber];
                int   wordIndex      = 0;
                float runXOffset     = 0;
                float justifyXOffset = 0;

                // Break the line into words if justification should be applied.
                bool justifyLine = this._paragraphStyle.textAlign == TextAlign.justify &&
                                   lineNumber != lineLimit - 1 && !lineRange.hardBreak &&
                                   // Do not apply justify if ellipsis should be added, or the ellipsis may be pushed
                                   // out of the border.
                                   // This is still not taken care of in the flutter engine.
                                   !(this._paragraphStyle.ellipsized() && this._paragraphStyle.maxLines == null);

                int   wordCount    = this._findWords(lineRange.start, lineRange.end, _wordsBuffer);
                float wordGapWidth = !(justifyLine && wordCount > 1)
                    ? 0
                    : (this._width - this._lineWidths[lineNumber]) / (wordCount - 1);

                // Count the number of style runs, and compute the character number of the longest run by the way
                int lineStyleRunCount = this._countLineStyleRuns(lineRange, styleRunIndex, out int maxTextCount);

                string ellipsis  = this._paragraphStyle.ellipsis;
                bool   hardBreak = lineRange.hardBreak;

                if (!string.IsNullOrEmpty(ellipsis) && !hardBreak && !this._width.isInfinite() &&
                    (lineNumber == lineLimit - 1 || this._paragraphStyle.maxLines == null))
                {
                    maxTextCount += ellipsis.Length;
                }

                // Allocate the advances and positions to store the layout result
                // TODO: find a way to compute the maxTextCount for the entire paragraph, so that this allocation
                //       happens only once
                if (_advancesBuffer == null || _advancesBuffer.Length < maxTextCount)
                {
                    _advancesBuffer = new float[LayoutUtils.minPowerOfTwo(maxTextCount)];
                }

                if (_positionsBuffer == null || _positionsBuffer.Length < maxTextCount)
                {
                    _positionsBuffer = new float[LayoutUtils.minPowerOfTwo(maxTextCount)];
                }

                // Keep of the position in _glyphPositions before evaluating this line
                int glyphPositionLineStart = pGlyphPositions;

                if (lineStyleRunCount != 0)
                {
                    // Exclude trailing whitespace from right-justified lines so the last
                    // visible character in the line will be flush with the right margin.
                    int lineEndIndex = this._paragraphStyle.textAlign == TextAlign.right ||
                                       this._paragraphStyle.textAlign == TextAlign.center
                        ? lineRange.endExcludingWhitespace
                        : lineRange.end;
                    int lineStyleRunIndex = 0;

                    // Instead of computing all lineStyleRuns at once and store into an array and iterate through them,
                    // compute each lineStyleRun and deal with it on the fly, to save the storage for the runs
                    while (styleRunIndex < this._runs.size)
                    {
                        var styleRun = this._runs.getRun(styleRunIndex);
                        // Compute the intersection between current style run intersects and the line
                        int start = Mathf.Max(styleRun.start, lineRange.start);
                        int end   = Mathf.Min(styleRun.end, lineEndIndex);
                        // Make sure that each run is not empty
                        if (start < end)
                        {
                            var    style     = styleRun.style;
                            string text      = this._text;
                            int    textStart = start;
                            int    textEnd   = end;
                            int    textCount = textEnd - textStart;
                            // Keep track of the pointer to _glyphPositions in the start of this run
                            int glyphPositionStyleRunStart = pGlyphPositions;

                            // Ellipsize the text if ellipsis string is set, and this is the last lineStyleRun of
                            // the current line, and this is the last line or max line is not set
                            if (!string.IsNullOrEmpty(ellipsis) && !hardBreak && !this._width.isInfinite() &&
                                lineStyleRunIndex == lineStyleRunCount - 1 &&
                                (lineNumber == lineLimit - 1 || this._paragraphStyle.maxLines == null))
                            {
                                float ellipsisWidth = Layout.measureText(ellipsis, style);

                                // Find the minimum number of characters to truncate, so that the truncated text
                                // appended with ellipsis is within the constraints of line width
                                int truncateCount = Layout.computeTruncateCount(runXOffset, text, textStart,
                                                                                textCount, style, this._width - ellipsisWidth, this._tabStops);

                                // If all the positions have not changed, use the cached ellipsized text
                                // else update the cache
                                if (!(this._ellipsizedText != null &&
                                      this._ellipsizedLength == textStart + textCount - truncateCount &&
                                      this._ellipsizedText.Length == this._ellipsizedLength + ellipsis.Length &&
                                      this._ellipsizedText.EndsWith(ellipsis)))
                                {
                                    this._ellipsizedText =
                                        text.Substring(0, textStart + textCount - truncateCount) + ellipsis;
                                    this._ellipsizedLength = this._ellipsizedText.Length - ellipsis.Length;
                                }

                                text      = this._ellipsizedText;
                                textCount = text.Length - textStart;
                                D.assert(textCount != 0);
                                if (this._paragraphStyle.maxLines == null)
                                {
                                    lineLimit = lineNumber + 1;
                                    this._didExceedMaxLines = true;
                                }
                            }

                            float advance = Layout.doLayout(runXOffset, text, textStart, textCount, style,
                                                            _advancesBuffer, _positionsBuffer, this._tabStops, out var bounds);

                            builder.allocRunPos(style, text, textStart, textCount);
                            builder.setBounds(bounds);

                            // Update the max width of the words
                            // Fill in the glyph positions, and the positions of the text blob builder
                            float wordStartPosition = float.NaN;
                            for (int glyphIndex = 0; glyphIndex < textCount; ++glyphIndex)
                            {
                                float glyphXOffset = _positionsBuffer[glyphIndex] + justifyXOffset;
                                float glyphAdvance = _advancesBuffer[glyphIndex];
                                builder.setPositionX(glyphIndex, glyphXOffset);
                                this._glyphPositions[pGlyphPositions++] = new GlyphPosition(runXOffset + glyphXOffset,
                                                                                            glyphAdvance, textStart + glyphIndex);
                                if (wordIndex < wordCount)
                                {
                                    Range <int> word = _wordsBuffer[wordIndex];
                                    // Run into the start of current word, record the start position of this word
                                    if (word.start == start + glyphIndex)
                                    {
                                        wordStartPosition = runXOffset + glyphXOffset;
                                    }

                                    // Run into the end of current word
                                    if (word.end == start + glyphIndex + 1)
                                    {
                                        if (justifyLine)
                                        {
                                            justifyXOffset += wordGapWidth;
                                        }

                                        // Update the current word
                                        wordIndex++;
                                        // If the start position of this word has been recorded, calculate the
                                        // width of this word, and update the entire word
                                        if (!float.IsNaN(wordStartPosition))
                                        {
                                            maxWordWidth = Mathf.Max(maxWordWidth,
                                                                     this._glyphPositions[pGlyphPositions - 1].xPos.end - wordStartPosition);
                                            wordStartPosition = float.NaN;
                                        }
                                    }
                                }
                            }

                            // Create paint record
                            var font = FontManager.instance.getOrCreate(style.fontFamily,
                                                                        style.fontWeight, style.fontStyle).font;
                            var         metrics     = FontMetrics.fromFont(font, style.UnityFontSize);
                            PaintRecord paintRecord = new PaintRecord(style, runXOffset, 0, builder.make(),
                                                                      metrics, advance);
                            this._paintRecords[this._paintRecordsCount++] = paintRecord;
                            runXOffset += advance;

                            // Create code unit run
                            this._codeUnitRuns[this._codeUnitRunsCount++] = new CodeUnitRun(
                                new Range <int>(start, end),
                                new Range <float>(this._glyphPositions[glyphPositionStyleRunStart].xPos.start,
                                                  this._glyphPositions[pGlyphPositions - 1].xPos.end),
                                lineNumber, TextDirection.ltr, glyphPositionStyleRunStart, textCount);

                            lineStyleRunIndex++;
                        }

                        if (styleRun.end >= lineEndIndex)
                        {
                            break;
                        }

                        styleRunIndex++;
                    }
                }

                float maxLineSpacing = 0;
                float maxDescent     = 0;

                void updateLineMetrics(FontMetrics metrics, float styleHeight)
                {
                    float lineSpacing = lineNumber == 0
                        ? -metrics.ascent * styleHeight
                        : (-metrics.ascent + metrics.leading) * styleHeight;

                    if (lineSpacing > maxLineSpacing)
                    {
                        maxLineSpacing = lineSpacing;
                        if (lineNumber == 0)
                        {
                            this._alphabeticBaseline  = lineSpacing;
                            this._ideographicBaseline =
                                (metrics.underlinePosition ?? 0.0f - metrics.ascent) * styleHeight;
                        }
                    }

                    float descent = metrics.descent * styleHeight;

                    maxDescent = Mathf.Max(descent, maxDescent);
                }

                if (lineStyleRunCount != 0)
                {
                    for (int i = 0; i < lineStyleRunCount; i++)
                    {
                        var paintRecord = this._paintRecords[this._paintRecordsCount - i - 1];
                        updateLineMetrics(paintRecord.metrics, paintRecord.style.height);
                    }
                }
                else
                {
                    var defaultFont = FontManager.instance.getOrCreate(
                        this._paragraphStyle.fontFamily ?? TextStyle.kDefaultFontFamily,
                        this._paragraphStyle.fontWeight ?? TextStyle.kDefaultFontWeight,
                        this._paragraphStyle.fontStyle ?? TextStyle.kDefaultfontStyle).font;
                    var metrics = FontMetrics.fromFont(defaultFont,
                                                       (int)(this._paragraphStyle.fontSize ?? TextStyle.kDefaultFontSize));
                    updateLineMetrics(metrics, this._paragraphStyle.height ?? TextStyle.kDefaultHeight);
                }

                this._lineHeights[lineNumber] = ((lineNumber == 0 ? 0 : this._lineHeights[lineNumber - 1])
                                                 + Mathf.Round(maxLineSpacing + maxDescent));
                yOffset      += Mathf.Round(maxLineSpacing + preMaxDescent);
                preMaxDescent = maxDescent;
                float lineXOffset = this.getLineXOffset(runXOffset);
                int   count       = pGlyphPositions - glyphPositionLineStart;
                if (lineXOffset != 0 && this._glyphPositions != null)
                {
                    for (int i = 0; i < count; ++i)
                    {
                        this._glyphPositions[this._glyphPositions.Length - i - 1].shiftSelf(lineXOffset);
                    }
                }

                this._glyphLines[lineNumber] = new GlyphLine(glyphPositionLineStart, count);
                for (int i = 0; i < lineStyleRunCount; i++)
                {
                    var paintRecord = this._paintRecords[this._paintRecordsCount - 1 - i];
                    paintRecord.shift(lineXOffset, yOffset);
                    this._paintRecords[this._paintRecordsCount - 1 - i] = paintRecord;
                }
            }

            this._lineCount = lineLimit;

            // min intrinsic width := minimum width this paragraph has to take, which equals the maximum word width
            if (this._paragraphStyle.maxLines == 1 || (this._paragraphStyle.maxLines == null &&
                                                       this._paragraphStyle.ellipsized()))
            {
                this._minIntrinsicWidth = this.maxIntrinsicWidth;
            }
            else
            {
                this._minIntrinsicWidth = Mathf.Min(maxWordWidth, this.maxIntrinsicWidth);
            }
        }