Skip to content

Instantly share code, notes, and snippets.

@runevision
Last active August 20, 2024 06:42
Show Gist options
  • Save runevision/2df4c291c0717a483f7f4b42e01c0313 to your computer and use it in GitHub Desktop.
Save runevision/2df4c291c0717a483f7f4b42e01c0313 to your computer and use it in GitHub Desktop.

Revisions

  1. runevision revised this gist Jun 14, 2021. 1 changed file with 78 additions and 59 deletions.
    137 changes: 78 additions & 59 deletions Text2.cs
    Original file line number Diff line number Diff line change
    @@ -51,69 +51,88 @@ protected override void OnPopulateMesh (VertexHelper toFill) {
    Vector2 extents = rectTransform.rect.size;

    string text2 = text;
    // Replace Hyphenation Point characters with soft hyphens.
    text2 = text2.Replace ("\u2027", shy);
    // Add spaces after soft hyphens.
    text2 = text2.Replace (shy, shySpace);
    // Add zero-width non-joiner characters and spaces after hyphens.
    text2 = text2.Replace ("-", "-" + zwnjSpace);

    var settings = GetGenerationSettings (extents);
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);

    int fixLine = 0;
    while (fixLine < cachedTextGenerator.lineCount) {

    var line = cachedTextGenerator.lines[fixLine];
    int charA = line.startCharIdx;
    int charB = GetLineEndCharacterIndex (text2, fixLine);

    // Remove soft hyphen and space combos from current line.
    int lengthBefore = text2.Length;
    text2 =
    text2.Substring (0, charA) +
    text2.Substring (charA, charB - charA).Replace (shySpace, "").Replace (zwnjSpace, "") +
    text2.Substring (charB);

    // If anything was removed, handle the same line again.
    if (text2.Length != lengthBefore) {
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);
    continue;
    }

    // If nothing changed, check if there is room for adding one last bit
    // to this line by removing the hyphen at the end of the line.
    bool notLastLine = (fixLine < cachedTextGenerator.lineCount - 1);
    if (notLastLine && (text2[charB] == shyChar || text2[charB] == zwnjChar)) {
    // Remove hyphen and space combo at end of line.
    string text3 = text2.Substring (0, charB) + text2.Substring (charB + 2);
    cachedTextGenerator.PopulateWithErrors (text3, settings, gameObject);

    int newLineIndex = cachedTextGenerator.lines[fixLine + 1].startCharIdx;
    int newCharB = GetLineEndCharacterIndex (text3, fixLine);
    // If we managed to add more to this line
    // AND the last character of the line without space and hyphens is smaller
    // than the actual last character (meaning we didn't cut a word improperly)
    // then accept this modification.
    if (newCharB >= charB && (newCharB < newLineIndex || text3[newCharB] == '-'))
    text2 = text3;

    // Otherwise just proceed to next line.
    // We need to first set the text generator back to using the unmodified string.
    else
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);
    }
    if (this.horizontalOverflow == HorizontalWrapMode.Overflow) {
    // Remove Hyphenation Point characters.
    text2 = text2.Replace ("\u2027", string.Empty);
    // Remove soft hyphens since Unity otherwise renders them.
    text2 = text2.Replace (shy, string.Empty);

    fixLine++;
    var settings = GetGenerationSettings (extents);
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);
    }
    else {
    // Replace Hyphenation Point characters with soft hyphens.
    text2 = text2.Replace ("\u2027", shy);
    // Add spaces after soft hyphens.
    text2 = text2.Replace (shy, shySpace);
    // Add zero-width non-joiner characters and spaces after hyphens.
    text2 = text2.Replace ("-", "-" + zwnjSpace);

    var settings = GetGenerationSettings (extents);
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);

    int fixLine = 0;
    while (fixLine < cachedTextGenerator.lineCount) {

    var line = cachedTextGenerator.lines[fixLine];
    int charA = line.startCharIdx;
    int charB = GetLineEndCharacterIndex (text2, fixLine);

    // Remove soft hyphen and space combos from current line.
    int lengthBefore = text2.Length;
    text2 =
    text2.Substring (0, charA) +
    text2.Substring (charA, charB - charA).Replace (shySpace, "").Replace (zwnjSpace, "") +
    text2.Substring (charB);

    // If anything was removed, handle the same line again.
    if (text2.Length != lengthBefore) {
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);
    continue;
    }

    // If nothing changed, check if there is room for adding one last bit
    // to this line by removing the hyphen at the end of the line.
    bool notLastLine = (fixLine < cachedTextGenerator.lineCount - 1);
    if (notLastLine && (text2[charB] == shyChar || text2[charB] == zwnjChar)) {
    // Remove hyphen and space combo at end of line.
    string text3 = text2.Substring (0, charB) + text2.Substring (charB + 2);
    cachedTextGenerator.PopulateWithErrors (text3, settings, gameObject);

    if (fixLine == cachedTextGenerator.lineCount - 1) {
    // We managed to fit the text on one less line, so clearly
    // we did manage to add more text tot his line.
    text2 = text3;
    }
    else {
    int newLineIndex = cachedTextGenerator.lines[fixLine + 1].startCharIdx;
    int newCharB = GetLineEndCharacterIndex (text3, fixLine);
    // If we managed to add more to this line
    // AND the last character of the line without space and hyphens is smaller
    // than the actual last character (meaning we didn't cut a word improperly)
    // then accept this modification.
    if (newCharB >= charB && (newCharB < newLineIndex || text3[newCharB] == '-'))
    text2 = text3;

    // Otherwise just proceed to next line.
    // We need to first set the text generator back to using the unmodified string.
    else
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);
    }
    }

    fixLine++;
    }

    {
    // Remove the zero-width non-joiner characters we added
    // since Unity actually does render them.
    int lengthBefore = text2.Length;
    text2 = text2.Replace ("-" + zwnj, "-");
    if (text2.Length < lengthBefore)
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);
    {
    // Remove the zero-width non-joiner characters we added
    // since Unity actually does render them.
    int lengthBefore = text2.Length;
    text2 = text2.Replace ("-" + zwnj, "-");
    if (text2.Length < lengthBefore)
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);
    }
    }

    // The rest of the code is unmodified from the Text class.
  2. runevision revised this gist Jun 11, 2021. 1 changed file with 75 additions and 42 deletions.
    117 changes: 75 additions & 42 deletions Text2.cs
    Original file line number Diff line number Diff line change
    @@ -9,82 +9,115 @@
    // https://www.compart.com/en/unicode/U+2027
    public class Text2 : Text {

    const char shyChar = '\u00AD';
    const string shy = "\u00AD";
    const string shySpace = "\u00AD ";
    const char zwnjChar = '\u200C';
    const string zwnj = "\u200C";
    const string zwnjSpace = "\u200C ";

    int LastIndexOfInSubString (string input, int start, int length, string value) {
    int pos = input.Substring (start, length).LastIndexOf (value);
    if (pos < 0)
    return pos;
    return pos + start;
    }

    int GetLineEndCharacterIndex (string text, int line) {
    int index = line == cachedTextGenerator.lineCount - 1 ?
    text.Length :
    cachedTextGenerator.lines[line + 1].startCharIdx;
    if (text[index - 1] == '\n')
    index--;
    if (text[index - 1] == ' ')
    index--;
    if (text[index - 1] == shyChar)
    index--;
    if (text[index - 1] == zwnjChar)
    index--;
    return index;
    }

    readonly UIVertex[] m_TempVerts = new UIVertex[4];
    protected override void OnPopulateMesh (VertexHelper toFill) {
    if (font == null)
    return;

    string text2 = text;
    string shy = "\u00AD";
    // Replace Hyphenation Point characters with soft hyphens.
    text2 = text2.Replace ("\u2027", shy);

    // We don't care if we the font Texture changes while we are doing our Update.
    // The end result of cachedTextGenerator will be valid for this instance.
    // Otherwise we can get issues like Case 619238.
    m_DisableFontTextureRebuiltCallback = true;

    Vector2 extents = rectTransform.rect.size;

    string text2 = text;
    // Replace Hyphenation Point characters with soft hyphens.
    text2 = text2.Replace ("\u2027", shy);
    // Add spaces after soft hyphens.
    text2 = text2.Replace (shy, shySpace);
    // Add zero-width non-joiner characters and spaces after hyphens.
    text2 = text2.Replace ("-", "-" + zwnjSpace);

    var settings = GetGenerationSettings (extents);
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);

    int fixLine = 0;
    while (fixLine < cachedTextGenerator.lineCount) {

    var line = cachedTextGenerator.lines[fixLine];
    int charA = line.startCharIdx;
    int charB = fixLine == cachedTextGenerator.lineCount - 1 ?
    text2.Length :
    cachedTextGenerator.lines[fixLine + 1].startCharIdx;
    int regularHyphen = LastIndexOfInSubString (text2, charA, charB - charA, "-");
    int softHyphen = LastIndexOfInSubString (text2, charA, charB - charA, shy);
    int lastHyphen = Mathf.Max (softHyphen, regularHyphen);
    if (lastHyphen < 0) {
    // No hyphens so this line is ok.
    fixLine++;
    int charB = GetLineEndCharacterIndex (text2, fixLine);

    // Remove soft hyphen and space combos from current line.
    int lengthBefore = text2.Length;
    text2 =
    text2.Substring (0, charA) +
    text2.Substring (charA, charB - charA).Replace (shySpace, "").Replace (zwnjSpace, "") +
    text2.Substring (charB);

    // If anything was removed, handle the same line again.
    if (text2.Length != lengthBefore) {
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);
    continue;
    }
    int space = LastIndexOfInSubString (text2, lastHyphen, charB - lastHyphen, " ");
    if (space >= 0) {
    if (softHyphen >= 0) {
    // First we need to test if more of this line could be put
    // on the previous line if we introduce a word break.
    string text3 = text2.Substring (0, softHyphen) + "- " + text2.Substring (softHyphen + 1);
    cachedTextGenerator.PopulateWithErrors (text3, settings, gameObject);
    if (cachedTextGenerator.lines[fixLine].startCharIdx > charA) {
    text2 = text3;
    continue;
    }

    // Remove the soft hyphen so it's not rendered.
    // Since this may make more room on the line,
    // we have to process the same line again.
    text2 = text2.Substring (0, softHyphen) + text2.Substring (softHyphen + 1);
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);
    continue;
    }

    // There are no soft hyphens, and there is a space
    // after the last regular hyphen so this line is ok.
    fixLine++;
    continue;
    // If nothing changed, check if there is room for adding one last bit
    // to this line by removing the hyphen at the end of the line.
    bool notLastLine = (fixLine < cachedTextGenerator.lineCount - 1);
    if (notLastLine && (text2[charB] == shyChar || text2[charB] == zwnjChar)) {
    // Remove hyphen and space combo at end of line.
    string text3 = text2.Substring (0, charB) + text2.Substring (charB + 2);
    cachedTextGenerator.PopulateWithErrors (text3, settings, gameObject);

    int newLineIndex = cachedTextGenerator.lines[fixLine + 1].startCharIdx;
    int newCharB = GetLineEndCharacterIndex (text3, fixLine);
    // If we managed to add more to this line
    // AND the last character of the line without space and hyphens is smaller
    // than the actual last character (meaning we didn't cut a word improperly)
    // then accept this modification.
    if (newCharB >= charB && (newCharB < newLineIndex || text3[newCharB] == '-'))
    text2 = text3;

    // Otherwise just proceed to next line.
    // We need to first set the text generator back to using the unmodified string.
    else
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);
    }

    // There was no space after the last hyphen
    // so we need to insert a line break after the hyphen.
    text2 = text2.Substring (0, lastHyphen + 1) + "\n" + text2.Substring (lastHyphen + 1);
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);
    fixLine++;
    continue;
    }

    {
    // Remove the zero-width non-joiner characters we added
    // since Unity actually does render them.
    int lengthBefore = text2.Length;
    text2 = text2.Replace ("-" + zwnj, "-");
    if (text2.Length < lengthBefore)
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);
    }

    // The rest of the code is unmodified from the Text class.

    // Apply the offset to the vertices
    IList<UIVertex> verts = cachedTextGenerator.verts;
    float unitsPerPixel = 1 / pixelsPerUnit;
  3. runevision revised this gist Jun 11, 2021. 1 changed file with 9 additions and 0 deletions.
    9 changes: 9 additions & 0 deletions Text2.cs
    Original file line number Diff line number Diff line change
    @@ -54,6 +54,15 @@ protected override void OnPopulateMesh (VertexHelper toFill) {
    int space = LastIndexOfInSubString (text2, lastHyphen, charB - lastHyphen, " ");
    if (space >= 0) {
    if (softHyphen >= 0) {
    // First we need to test if more of this line could be put
    // on the previous line if we introduce a word break.
    string text3 = text2.Substring (0, softHyphen) + "- " + text2.Substring (softHyphen + 1);
    cachedTextGenerator.PopulateWithErrors (text3, settings, gameObject);
    if (cachedTextGenerator.lines[fixLine].startCharIdx > charA) {
    text2 = text3;
    continue;
    }

    // Remove the soft hyphen so it's not rendered.
    // Since this may make more room on the line,
    // we have to process the same line again.
  4. runevision revised this gist Jun 11, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Text2.cs
    Original file line number Diff line number Diff line change
    @@ -6,7 +6,7 @@
    // It makes hyphens and soft hyphens work.
    // Inserting soft hyphens in text can be tricky and confusing, given they are invisible,
    // so you can instead also insert Hyphenation Point characters, which will be replaced by soft hyphens:
    // https://www.fileformat.info/info/unicode/char/2027/index.htm
    // https://www.compart.com/en/unicode/U+2027
    public class Text2 : Text {

    int LastIndexOfInSubString (string input, int start, int length, string value) {
  5. runevision created this gist Jun 11, 2021.
    116 changes: 116 additions & 0 deletions Text2.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,116 @@
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;

    // Text2 extends the Text component in Unity UI.
    // It makes hyphens and soft hyphens work.
    // Inserting soft hyphens in text can be tricky and confusing, given they are invisible,
    // so you can instead also insert Hyphenation Point characters, which will be replaced by soft hyphens:
    // https://www.fileformat.info/info/unicode/char/2027/index.htm
    public class Text2 : Text {

    int LastIndexOfInSubString (string input, int start, int length, string value) {
    int pos = input.Substring (start, length).LastIndexOf (value);
    if (pos < 0)
    return pos;
    return pos + start;
    }

    readonly UIVertex[] m_TempVerts = new UIVertex[4];
    protected override void OnPopulateMesh (VertexHelper toFill) {
    if (font == null)
    return;

    string text2 = text;
    string shy = "\u00AD";
    // Replace Hyphenation Point characters with soft hyphens.
    text2 = text2.Replace ("\u2027", shy);

    // We don't care if we the font Texture changes while we are doing our Update.
    // The end result of cachedTextGenerator will be valid for this instance.
    // Otherwise we can get issues like Case 619238.
    m_DisableFontTextureRebuiltCallback = true;

    Vector2 extents = rectTransform.rect.size;

    var settings = GetGenerationSettings (extents);
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);

    int fixLine = 0;
    while (fixLine < cachedTextGenerator.lineCount) {
    var line = cachedTextGenerator.lines[fixLine];
    int charA = line.startCharIdx;
    int charB = fixLine == cachedTextGenerator.lineCount - 1 ?
    text2.Length :
    cachedTextGenerator.lines[fixLine + 1].startCharIdx;
    int regularHyphen = LastIndexOfInSubString (text2, charA, charB - charA, "-");
    int softHyphen = LastIndexOfInSubString (text2, charA, charB - charA, shy);
    int lastHyphen = Mathf.Max (softHyphen, regularHyphen);
    if (lastHyphen < 0) {
    // No hyphens so this line is ok.
    fixLine++;
    continue;
    }
    int space = LastIndexOfInSubString (text2, lastHyphen, charB - lastHyphen, " ");
    if (space >= 0) {
    if (softHyphen >= 0) {
    // Remove the soft hyphen so it's not rendered.
    // Since this may make more room on the line,
    // we have to process the same line again.
    text2 = text2.Substring (0, softHyphen) + text2.Substring (softHyphen + 1);
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);
    continue;
    }

    // There are no soft hyphens, and there is a space
    // after the last regular hyphen so this line is ok.
    fixLine++;
    continue;
    }

    // There was no space after the last hyphen
    // so we need to insert a line break after the hyphen.
    text2 = text2.Substring (0, lastHyphen + 1) + "\n" + text2.Substring (lastHyphen + 1);
    cachedTextGenerator.PopulateWithErrors (text2, settings, gameObject);
    fixLine++;
    continue;
    }

    // Apply the offset to the vertices
    IList<UIVertex> verts = cachedTextGenerator.verts;
    float unitsPerPixel = 1 / pixelsPerUnit;
    int vertCount = verts.Count;

    // We have no verts to process just return (case 1037923)
    if (vertCount <= 0) {
    toFill.Clear ();
    return;
    }

    Vector2 roundingOffset = new Vector2 (verts[0].position.x, verts[0].position.y) * unitsPerPixel;
    roundingOffset = PixelAdjustPoint (roundingOffset) - roundingOffset;
    toFill.Clear ();
    if (roundingOffset != Vector2.zero) {
    for (int i = 0; i < vertCount; ++i) {
    int tempVertsIndex = i & 3;
    m_TempVerts[tempVertsIndex] = verts[i];
    m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
    m_TempVerts[tempVertsIndex].position.x += roundingOffset.x;
    m_TempVerts[tempVertsIndex].position.y += roundingOffset.y;
    if (tempVertsIndex == 3)
    toFill.AddUIVertexQuad (m_TempVerts);
    }
    }
    else {
    for (int i = 0; i < vertCount; ++i) {
    int tempVertsIndex = i & 3;
    m_TempVerts[tempVertsIndex] = verts[i];
    m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
    if (tempVertsIndex == 3)
    toFill.AddUIVertexQuad (m_TempVerts);
    }
    }

    m_DisableFontTextureRebuiltCallback = false;
    }
    }