wordWrap method

  1. @useResult
List<String> wordWrap(
  1. int columnWidth
)

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);
}