/// <summary> /// 文字列の折り返しを行います。 /// このメソッドの呼び出しにより、WrappingValid プロパティが true に設定されます。 /// このメソッドでは、LF (\n) を見つけた場合、LF を基準とした分割をまず行います。 /// なお、このメソッドは CR (\r) には対応しておらず、 /// Text プロパティへの設定において CR を除去した文字列を内部で管理しています。 /// 続いて、このメソッドでは、空白文字を基準とした分割を行います。 /// ここで、このメソッドが認識する空白文字は半角空白文字のみであり、 /// 全角空白文字には対応しません。 /// 最後に、基準にできる空白文字が見つからない場合は、 /// ClientWidth プロパティが示す幅に収まるように強制的に分割します。 /// </summary> public void Wrap() { if (WrappingValid) { return; } measuredSize = Vector2.Zero; MaxMeasuredHeight = 0; lines.Clear(); // Font 不明、Text が空ならばサイズ 0 とします。 if (font == null || charArray == null || charArray.Length == 0) { measuredSize = Vector2.Zero; WrappingValid = true; return; } // 文字数 1 ならば折り返せないため、そのまま測定して返します。 // コンテナ幅が不明な場合も折り返せないため、そのまま測定して返します。 if (text.Length == 1 || clientWidth <= 0 || float.IsNaN(clientWidth)) { lines.Add(new WrappedTextLine(0, text.Length)); measuredSize = font.MeasureString(text) * fontStretch; WrappingValid = true; return; } if (builder == null) { builder = new StringBuilder(); } builder.Length = 0; int charIndex = 0; int startIndex = 0; int lastSpaceIndex = -1; Vector2 size = new Vector2(); while (charIndex < charArray.Length) { var c = charArray[charIndex]; // LF を見つけたら強制折り返しさせます。 // LF ではない場合は文字を追加して測定し、改行するかどうかを判定します。 if (c == '\n') { lastSpaceIndex = -1; } else { builder.Append(c); size = font.MeasureString(builder) * fontStretch; // clientWidth 未満ならばその行を継続します。 if (size.X <= clientWidth) { if (c == ' ') { lastSpaceIndex = charIndex; } charIndex++; continue; } if (0 <= lastSpaceIndex) { // スペースを見つけていたならば、その最後のスペースの位置まで戻します。 charIndex = lastSpaceIndex + 1; builder.Length = charIndex - startIndex; lastSpaceIndex = -1; } else { // スペースを見つけていないならば、1 文字前に戻します。 charIndex--; if (charIndex == 0) { // 改行できないほどに ClientWidth が小さいならば、 // 改行処理を中断します。 measuredSize = Vector2.Zero; WrappingValid = true; return; } // 対象文字についても 1 文字前に戻します。 builder.Length = builder.Length - 1; } } // 行を測定します。 // 改行のみが行われたなどの空行の場合は測定をスキップします。 if (0 < builder.Length) { size = font.MeasureString(builder) * fontStretch; measuredSize.X = Math.Max(measuredSize.X, size.X); MaxMeasuredHeight = Math.Max(MaxMeasuredHeight, size.Y); } // 折り返し範囲を記録します。 lines.Add(new WrappedTextLine(startIndex, charIndex - startIndex)); // LF で折り返した場合はインデックスを進めます。 if (c == '\n') { charIndex++; } builder.Length = 0; startIndex = charIndex; } // 最後の 1 行を測定します。 size = font.MeasureString(builder) * fontStretch; measuredSize.X = Math.Max(measuredSize.X, size.X); MaxMeasuredHeight = Math.Max(MaxMeasuredHeight, size.Y); // 折り返し範囲を記録します。 var lastElement = new WrappedTextLine(startIndex, charIndex - startIndex); lines.Add(lastElement); // 最大の行の高さから行全体の高さを決定します。 measuredSize.Y = lines.Count * MaxMeasuredHeight; WrappingValid = true; }
/// <summary> /// 文字列の折り返しを行います。 /// このメソッドの呼び出しにより、WrappingValid プロパティが true に設定されます。 /// このメソッドでは、LF (\n) を見つけた場合、LF を基準とした分割をまず行います。 /// なお、このメソッドは CR (\r) には対応しておらず、 /// Text プロパティへの設定において CR を除去した文字列を内部で管理しています。 /// 続いて、このメソッドでは、空白文字を基準とした分割を行います。 /// ここで、このメソッドが認識する空白文字は半角空白文字のみであり、 /// 全角空白文字には対応しません。 /// 最後に、基準にできる空白文字が見つからない場合は、 /// ClientWidth プロパティが示す幅に収まるように強制的に分割します。 /// </summary> public void Wrap() { if (WrappingValid) return; measuredSize = Vector2.Zero; MaxMeasuredHeight = 0; lines.Clear(); // Font 不明、Text が空ならばサイズ 0 とします。 if (font == null || charArray == null || charArray.Length == 0) { measuredSize = Vector2.Zero; WrappingValid = true; return; } // 文字数 1 ならば折り返せないため、そのまま測定して返します。 // コンテナ幅が不明な場合も折り返せないため、そのまま測定して返します。 if (text.Length == 1 || clientWidth <= 0 || float.IsNaN(clientWidth)) { lines.Add(new WrappedTextLine(0, text.Length)); measuredSize = font.MeasureString(text) * fontStretch; WrappingValid = true; return; } if (builder == null) builder = new StringBuilder(); builder.Length = 0; int charIndex = 0; int startIndex = 0; int lastSpaceIndex = -1; Vector2 size = new Vector2(); while (charIndex < charArray.Length) { var c = charArray[charIndex]; // LF を見つけたら強制折り返しさせます。 // LF ではない場合は文字を追加して測定し、改行するかどうかを判定します。 if (c == '\n') { lastSpaceIndex = -1; } else { builder.Append(c); size = font.MeasureString(builder) * fontStretch; // clientWidth 未満ならばその行を継続します。 if (size.X <= clientWidth) { if (c == ' ') lastSpaceIndex = charIndex; charIndex++; continue; } if (0 <= lastSpaceIndex) { // スペースを見つけていたならば、その最後のスペースの位置まで戻します。 charIndex = lastSpaceIndex + 1; builder.Length = charIndex - startIndex; lastSpaceIndex = -1; } else { // スペースを見つけていないならば、1 文字前に戻します。 charIndex--; if (charIndex == 0) { // 改行できないほどに ClientWidth が小さいならば、 // 改行処理を中断します。 measuredSize = Vector2.Zero; WrappingValid = true; return; } // 対象文字についても 1 文字前に戻します。 builder.Length = builder.Length - 1; } } // 行を測定します。 // 改行のみが行われたなどの空行の場合は測定をスキップします。 if (0 < builder.Length) { size = font.MeasureString(builder) * fontStretch; measuredSize.X = Math.Max(measuredSize.X, size.X); MaxMeasuredHeight = Math.Max(MaxMeasuredHeight, size.Y); } // 折り返し範囲を記録します。 lines.Add(new WrappedTextLine(startIndex, charIndex - startIndex)); // LF で折り返した場合はインデックスを進めます。 if (c == '\n') charIndex++; builder.Length = 0; startIndex = charIndex; } // 最後の 1 行を測定します。 size = font.MeasureString(builder) * fontStretch; measuredSize.X = Math.Max(measuredSize.X, size.X); MaxMeasuredHeight = Math.Max(MaxMeasuredHeight, size.Y); // 折り返し範囲を記録します。 var lastElement = new WrappedTextLine(startIndex, charIndex - startIndex); lines.Add(lastElement); // 最大の行の高さから行全体の高さを決定します。 measuredSize.Y = lines.Count * MaxMeasuredHeight; WrappingValid = true; }