public void RecreateQuadBatch() { if (font == null) { return; } // // first, cut into lines // // the line list is a list of pair <first char index, line text> lineList.Clear(); { int lineStartIndex = 0; int lineBreakIndex = 0; int length = text.Length; while (lineBreakIndex < length) { char c = text[lineBreakIndex]; bool antiSlashN = (c == '\n'); bool antiSlashR = (c == '\r'); if (antiSlashN || antiSlashR) { lineList.Add(new LineDescriptor(lineStartIndex, (lineBreakIndex - lineStartIndex), false)); if (antiSlashR && // if \r is found (lineBreakIndex < length - 1) && (text[lineBreakIndex + 1] == '\n')) // skip possible \n following it { lineBreakIndex++; } lineStartIndex = lineBreakIndex + 1; // start index of next line } lineBreakIndex++; } lineList.Add(new LineDescriptor(lineStartIndex, (text.Length - lineStartIndex), false)); // int startIndex = 0; // int lineBreakIndex = -1; // while ( ( lineBreakIndex = text.IndexOf ( '\n', startIndex ) ) != -1 ) // { // lineList.Add ( new LineDescriptor ( startIndex, lineBreakIndex - startIndex, false ) ); // startIndex = lineBreakIndex + 1; // start index of next line // } // lineList.Add ( new LineDescriptor ( startIndex, text.Length - startIndex, false ) ); } #if UNITY_EDITOR errorMessage = null; #endif // // new create lines geometry // quadBatch.setSize(text.Length); int lineIndex = 0; Vector3 linePosition = new Vector3(position.x, position.y, position.z); int quadStartIndex = 0; while (lineIndex < lineList.Count) { // TODO : optim : "lineList.get( lineIndex )" as local ? int startIndex = lineList[lineIndex].charStartIndexInText; int length = lineList[lineIndex].charCount; bool issuedFromLineCut = lineList[lineIndex].issuedFromLineCut; CreateTextGeometryResult result = createTextGeometry(linePosition, text, startIndex, length, quadStartIndex, issuedFromLineCut, scale, lineMaxWidth, preserveWords); if ((result.processedCharCount > 0) && (result.processedCharCount != length)) // line not fully processed ? (see line break near end of // createTextGeometry method) { // insert remaining of the line lineList.Insert(lineIndex + 1, new LineDescriptor(startIndex + result.processedCharCount, length - result.processedCharCount, true)); } // updates line description lineList[lineIndex] = new LineDescriptor(startIndex + result.spaceRemovedAtBeginning, result.processedCharCount - result.spaceRemovedAtBeginning, quadStartIndex, result.renderedCharCount, issuedFromLineCut); quadStartIndex += result.renderedCharCount; // moves into the quad buffer if (fontData != null) { if (letterSpacingMode == LetterSpacingMode.Add) { linePosition.y -= ((fontData.LineHeight + letterSpacing.y) * scale); } else { linePosition.y -= (letterSpacing.y * scale); } } // TODO : might be an option instead of lineHeight : result.boundingRectMax.Y // result.boundingRectMin.Y; lineIndex++; } #if UNITY_EDITOR if (errorMessage != null) { Debug.LogError("Invalid char found in text to render :\n" + errorMessage); } #endif quadBatch.setSize(quadStartIndex); // // align each line's geometry // bounds = new Rect(0, 0, -1, -1); if (lineList.Count > 1) { float lineWidth = lineMaxWidth; if ( (lineMaxWidth == float.MaxValue) || (alignment == TextAlignment.Center) // we also want real width when centering ) { bounds = quadBatch.getBounds(); lineWidth = bounds.width; } switch (alignment) { case TextAlignment.Left: // default alignment break; case TextAlignment.Center: for (int i = 0; i < lineList.Count; i++) { LineDescriptor lineDesc = lineList[i]; Rect boundingRect = quadBatch.getBounds(lineDesc.quadStartIndexInBatch, lineDesc.quadCount); quadBatch.translate(lineDesc.quadStartIndexInBatch, lineDesc.quadCount, new Vector3((lineWidth - boundingRect.width) / 2, 0, 0)); } break; case TextAlignment.Right: for (int i = 0; i < lineList.Count; i++) { LineDescriptor lineDesc = lineList[i]; Rect boundingRect = quadBatch.getBounds(lineDesc.quadStartIndexInBatch, lineDesc.quadCount); quadBatch.translate(lineDesc.quadStartIndexInBatch, lineDesc.quadCount, new Vector3(lineWidth - boundingRect.width, 0, 0)); } break; case TextAlignment.Justify: for (int i = 0; i < lineList.Count; i++) { LineDescriptor lineDesc = lineList[i]; // line is cut if next line is issued from the cut bool lineWasCut = (i < lineList.Count - 1) && (lineList[i + 1].issuedFromLineCut); // TODO : add right/centered aligned justify as Photoshop do ? if (lineWasCut) // only align line that are cut, others are already left aligned { // get space count in that line List <int> charCountsToProcess = new List <int> (); int noSpaceCharCount = 0; for (int j = 0; j < lineDesc.charCount; j++) { char charToRender = text[lineDesc.charStartIndexInText + j]; if (charToRender == 0xA0) { charToRender = (char)0x20; // convert unbreakable space into space } if (charToRender == 0x20) { charCountsToProcess.Add(noSpaceCharCount); noSpaceCharCount = 0; } else { noSpaceCharCount++; } } charCountsToProcess.Add(noSpaceCharCount); if (charCountsToProcess.Count > 1) { Rect boundingRect = quadBatch.getBounds(lineDesc.quadStartIndexInBatch, lineDesc.quadCount); float spaceToAdd = (lineWidth - boundingRect.width) / (charCountsToProcess.Count - 1); if (spaceToAdd > 0) { // distribute translation to each spacing int quadIndex = lineDesc.quadStartIndexInBatch; int quadCount = 0; for (int j = 0; j < charCountsToProcess.Count; j++) { quadCount = charCountsToProcess[j]; if (quadCount > 0) { // don't process first word, that stay left aligned if (j > 0) { // quadBatch.colorize ( quadIndex, 1, Color.RED ); // DEBUG : colorize first letter in red quadBatch.translate(quadIndex, quadCount, new Vector3(spaceToAdd * j, 0, 0)); } quadIndex += charCountsToProcess[j]; } quadIndex++; // skip space char, that have not to be processed } } } } } break; } } if (bounds.width == -1 || bounds.height == -1) // might have been already computed above, don't do it twice { bounds = quadBatch.getBounds(); } // if ( trimTopLeftSpace ) // remove empty top left space created by glyph xoffset/yoffset // { // position.x = quadBounds.x; // position.y = quadBounds.y; // bounds.x = quadBounds.x; // bounds.y = quadBounds.y; // } float dx = 0; switch (anchor) { case TextAnchor.UpperLeft: case TextAnchor.MiddleLeft: case TextAnchor.LowerLeft: // dx = - bounds.width / 2; dx = -bounds.xMin; break; case TextAnchor.UpperCenter: case TextAnchor.MiddleCenter: case TextAnchor.LowerCenter: // dx = - bounds.width / 2; dx = -bounds.center.x; break; case TextAnchor.UpperRight: case TextAnchor.MiddleRight: case TextAnchor.LowerRight: // dx = - bounds.width; dx = -bounds.xMax; break; } // float dy = -( bounds.y + bounds.height ); float dy = 0; switch (anchor) { case TextAnchor.UpperCenter: case TextAnchor.UpperLeft: case TextAnchor.UpperRight: // dy += bounds.height / 2; dy = -bounds.yMax; break; case TextAnchor.MiddleCenter: case TextAnchor.MiddleLeft: case TextAnchor.MiddleRight: // dy += bounds.height / 2; dy = -bounds.center.y; break; case TextAnchor.LowerCenter: case TextAnchor.LowerLeft: case TextAnchor.LowerRight: // dy += bounds.height; dy = -bounds.yMin; break; } quadBatch.translate(new Vector3(dx, dy, 0)); }
private CreateTextGeometryResult createTextGeometry(Vector3 position, string text, int startIndex, int length, int quadStartIndex, bool lineIsIssuedFromLineCut, float scaleFactor, float lineMaxWidth, bool preserveWords) { CreateTextGeometryResult result = new CreateTextGeometryResult(); if (lineIsIssuedFromLineCut) { // trim spaces at start while ((startIndex < startIndex + length) && ((text[startIndex] == 0x20) || (text[startIndex] == 0xA0))) { startIndex++; length--; result.processedCharCount++; result.spaceRemovedAtBeginning++; } } // float pageWidth = font.getRegion ().getRegionWidth (); // float pageHeight = font.getRegion ().getRegionHeight (); // Glyph glyph = null; CharacterInfo glyph; // Font.fontChar letterStruct = null; Vector3 letterPosition = new Vector3(); Vector2 letterDimension = new Vector2(); Vector2 letterTopLeftUV = new Vector2(); Vector2 letterTopRightUV = new Vector2(); Vector2 letterBottomLeftUV = new Vector2(); Vector2 letterBottomRightUV = new Vector2(); float advance = 0; int lastSpaceIndex = startIndex; for (int i = startIndex; i < startIndex + length; i++) { char charToRender = text[i]; if (charToRender == 0xA0) { charToRender = (char)0x20; // convert unbreakable space into space } if (!font.GetCharacterInfo(charToRender, out glyph)) { #if UNITY_EDITOR if (charToRender != 0x0D) { if (errorMessage == null) { errorMessage = ""; } errorMessage += "Font character unknown : '" + charToRender + "' (0x" + ((int)charToRender).ToString("X2") + "), used in text \"" + text + "\"" + "\n"; // Debug.LogWarning ( "Font character unknown : '" + charToRender + "' (0x" + ( (int)charToRender ).ToString ( "X2" ) + "), used in text \"" + text + "\"" ); if (!font.GetCharacterInfo(' ', out glyph)) { errorMessage += "Font default replacement character [space] not defined."; } // Debug.LogError ( "Font default replacement character [space] not defined." ); } #endif } // glyph = font.getData ().getGlyph ( charToRender ); // if (glyph == null) // // throw new RuntimeException ( "Character '" + charToRender + "' is not defined in font" ); // glyph = font.getData ().getGlyph ( ' ' ); int kerning = 0; // kerning to add // if (i > 0) // { // char previousCharToRender = text.charAt ( i - 1 ); // if (previousCharToRender == 0xA0) // previousCharToRender = (char) 0x20; // convert unbreakable space into space // // font.kerningTable.TryGetValue ( new KeyValuePair<char, char> ( previousCharToRender, charToRender ), out kerning ); // } if (charToRender == 0x20) { lastSpaceIndex = i; // keep index of last space // // // space special case // letterPosition.x = advance + kerning + glyph.minX; // letterPosition.y = ( glyph.minY + glyph.maxY - glyph.minY ); // letterDimension.x = (float)glyph.maxX - glyph.minX; // letterDimension.y = glyph.maxY - glyph.minY; // 0.0f; // letterTopLeftUV = glyph.uvTopLeft; // letterTopRightUV = glyph.uvTopRight; // letterBottomLeftUV = glyph.uvBottomLeft; // letterBottomRightUV = glyph.uvBottomRight; } // else { letterPosition.x = advance + kerning + glyph.minX; letterPosition.y = (glyph.minY + glyph.maxY - glyph.minY); letterDimension.x = glyph.maxX - glyph.minX; letterDimension.y = -(glyph.maxY - glyph.minY); letterTopLeftUV = glyph.uvTopLeft; letterTopRightUV = glyph.uvTopRight; letterBottomLeftUV = glyph.uvBottomLeft; letterBottomRightUV = glyph.uvBottomRight; } letterPosition *= scaleFactor; // apply scaling letterDimension *= scaleFactor; // check if text is over the max line width if (letterPosition.x + letterDimension.x > lineMaxWidth) { // generation is not finished, return count of processed characters if (preserveWords && (lastSpaceIndex > startIndex)) { // cut where the last space char was found, will truncate and discard all chars after result.processedCharCount -= (i - lastSpaceIndex); result.renderedCharCount -= (i - lastSpaceIndex); break; } else { break; // line break in the middle of a word } } letterPosition += position; // then offset by position // if ( charToRender != 0x20 ) quadBatch.setQuad(quadStartIndex + result.renderedCharCount, letterPosition, letterDimension, letterTopLeftUV, letterTopRightUV, letterBottomLeftUV, letterBottomRightUV, color); // if ( glyph.flipped ) // quadBatch.flipUvs ( quadStartIndex + result.renderedCharCount ); if (letterSpacingMode == LetterSpacingMode.Add) { advance += (glyph.advance + letterSpacing.x); } else { advance += letterSpacing.x; } result.processedCharCount++; result.renderedCharCount++; } return(result); }