wordWrap method
Wraps this string at columnWidth, breaking at word boundaries when possible.
Uses grapheme length for column count. columnWidth must be positive.
Returns a list of lines (no trailing newline in each).
Throws ArgumentError if columnWidth is not positive.
Example:
'hello world'.wordWrap(5); // ['hello', 'world']
'hi'.wordWrap(10); // ['hi']
Implementation
@useResult
List<String> wordWrap(int columnWidth) {
if (columnWidth < 1) {
throw ArgumentError(_kErrColumnWidthPositive, _kParamColumnWidth);
}
if (isEmpty) return <String>[];
final Characters chars = characters;
final int estimatedLines = (chars.length / columnWidth).ceil() + 1;
final List<String> lines = List<String>.filled(estimatedLines, '');
int lineIndex = 0;
int i = 0;
while (i < chars.length) {
int lineEnd = i + columnWidth;
if (lineEnd > chars.length) lineEnd = chars.length;
final String segment = chars.getRange(i, lineEnd).string;
int lineGraphemeCount = segment.characters.length;
// Only try a word-boundary break when this segment isn't the final tail;
// the last segment is taken whole so no trailing content is dropped.
if (lineEnd < chars.length) {
final int lastSpace = segment.lastIndexOf(_kSpace);
// Back the line up to the last space so words aren't split mid-word.
// Re-measure in graphemes because lastIndexOf returns a code-unit offset,
// which can't be used directly to slice the grapheme-indexed source.
// No space at all means one unbreakable run, so fall through to a hard
// cut at columnWidth.
if (lastSpace >= 0) {
lineGraphemeCount = segment.replaceRange(lastSpace, segment.length, '').characters.length;
}
}
final String lineContent = lineGraphemeCount <= 0
? ''
: chars.getRange(i, i + lineGraphemeCount).string;
lines[lineIndex++] = lineContent;
i += lineContent.characters.length;
// Consume the single break space so it doesn't lead the next line.
if (i < chars.length && chars.elementAt(i) == _kSpace) i++;
}
return lines.sublist(0, lineIndex);
}