layout method

  1. @override
void layout(
  1. Context context,
  2. BoxConstraints constraints, {
  3. bool parentUsesSize = false,
})
override

First widget pass to calculate the children layout and bounding box

Implementation

@override
void layout(Context context, BoxConstraints constraints,
    {bool parentUsesSize = false}) {
  _spans.clear();
  _decorations.clear();

  final theme = Theme.of(context);
  final _softWrap = softWrap ?? theme.softWrap;
  final _maxLines = maxLines ?? theme.maxLines;
  final _textDirection = textDirection ?? Directionality.of(context);
  _textAlign = textAlign ?? theme.textAlign ?? TextAlign.start;

  final _overflow = this.overflow ?? theme.overflow;

  final constraintWidth = constraints.hasBoundedWidth
      ? constraints.maxWidth
      : constraints.constrainWidth();
  final constraintHeight = constraints.hasBoundedHeight
      ? constraints.maxHeight
      : constraints.constrainHeight();

  var offsetX = 0.0;
  var offsetY = _context.startOffset;

  var top = 0.0;
  var bottom = 0.0;

  final lines = <_Line>[];
  var spanCount = 0;
  var spanStart = 0;
  var overflow = false;

  _preprocessed ??= _preProcessSpans(context);

  void _buildLines() {
    for (final span in _preprocessed!) {
      final style = span.style;
      final annotation = span.annotation;

      if (span is TextSpan) {
        if (span.text == null) {
          continue;
        }

        final font = style!.font!.getFont(context);

        final space =
            font.stringMetrics(' ') * (style.fontSize! * textScaleFactor);

        final spanLines = (useArabic && _textDirection == TextDirection.rtl
                ? arabic.convert(span.text!)
                : useBidi && _textDirection == TextDirection.rtl
                    ? bidi.logicalToVisual(span.text!)
                    : span.text)!
            .split('\n');

        for (var line = 0; line < spanLines.length; line++) {
          final words = spanLines[line].split(RegExp(r'\s'));
          for (var index = 0; index < words.length; index++) {
            final word = words[index];

            if (word.isEmpty) {
              offsetX += space.advanceWidth * style.wordSpacing! +
                  style.letterSpacing!;
              continue;
            }

            final metrics = font.stringMetrics(word,
                    letterSpacing: style.letterSpacing! /
                        (style.fontSize! * textScaleFactor)) *
                (style.fontSize! * textScaleFactor);

            if (_softWrap &&
                offsetX + metrics.width > constraintWidth + 0.00001) {
              if (hyphenation != null) {
                final syllables = hyphenation!(word);
                if (syllables.length > 1) {
                  var fits = '';
                  for (var syllable in syllables) {
                    if (offsetX +
                            ((font.stringMetrics('$fits$syllable-',
                                        letterSpacing: style.letterSpacing! /
                                            (style.fontSize! *
                                                textScaleFactor)) *
                                    (style.fontSize! * textScaleFactor))
                                .width) >
                        constraintWidth + 0.00001) {
                      break;
                    }
                    fits += syllable;
                  }
                  if (fits.isNotEmpty) {
                    words[index] = '$fits-';
                    words.insert(index + 1, word.substring(fits.length));
                    index--;
                    continue;
                  }
                }
              }

              if (spanCount > 0 && metrics.width <= constraintWidth) {
                overflow = true;
                lines.add(_Line(
                  this,
                  spanStart,
                  spanCount,
                  bottom,
                  offsetX -
                      space.advanceWidth * style.wordSpacing! -
                      style.letterSpacing!,
                  _textDirection,
                  true,
                ));

                spanStart += spanCount;
                spanCount = 0;

                offsetX = 0.0;
                offsetY += bottom - top;
                top = 0;
                bottom = 0;

                if (_maxLines != null && lines.length >= _maxLines) {
                  return;
                }

                if (offsetY > constraintHeight) {
                  return;
                }

                offsetY += style.lineSpacing! * textScaleFactor;
              } else {
                // One word Overflow, try to split it.
                final pos = _splitWord(word, font, style, constraintWidth);

                if (pos < word.length) {
                  words[index] = word.substring(0, pos);
                  words.insert(index + 1, word.substring(pos));

                  // Try again
                  index--;
                  continue;
                }
              }
            }

            final baseline = span.baseline * textScaleFactor;
            final mt = tightBounds ? metrics.top : metrics.descent;
            final mb = tightBounds ? metrics.bottom : metrics.ascent;
            top = math.min(top, mt + baseline);
            bottom = math.max(bottom, mb + baseline);

            final wd = _Word(
              word,
              style,
              metrics,
            );
            wd.offset = PdfPoint(offsetX, -offsetY + baseline);
            _spans.add(wd);
            spanCount++;

            _appendDecoration(
              spanCount > 1,
              _TextDecoration(
                style,
                annotation,
                _spans.length - 1,
                _spans.length - 1,
              ),
            );

            offsetX += metrics.advanceWidth +
                space.advanceWidth * style.wordSpacing! +
                style.letterSpacing!;
          }

          if (line < spanLines.length - 1) {
            lines.add(_Line(
              this,
              spanStart,
              spanCount,
              bottom,
              offsetX -
                  space.advanceWidth * style.wordSpacing! -
                  style.letterSpacing!,
              _textDirection,
              false,
            ));

            spanStart += spanCount;

            offsetX = 0.0;
            if (spanCount > 0) {
              offsetY += bottom - top;
            } else {
              offsetY +=
                  font.emptyLineHeight * style.fontSize! * textScaleFactor;
            }
            top = 0;
            bottom = 0;
            spanCount = 0;

            if (_maxLines != null && lines.length >= _maxLines) {
              return;
            }

            if (offsetY > constraintHeight) {
              return;
            }

            offsetY += style.lineSpacing! * textScaleFactor;
          }
        }

        offsetX -=
            space.advanceWidth * style.wordSpacing! - style.letterSpacing!;
      } else if (span is WidgetSpan) {
        span.child.layout(
            context,
            BoxConstraints(
              maxWidth: constraintWidth,
              maxHeight: constraintHeight,
            ));
        final ws = _WidgetSpan(
          span.child,
          style!,
          span.baseline,
        );

        if (offsetX + ws.width > constraintWidth && spanCount > 0) {
          overflow = true;
          lines.add(_Line(
            this,
            spanStart,
            spanCount,
            bottom,
            offsetX,
            _textDirection,
            true,
          ));

          spanStart += spanCount;
          spanCount = 0;

          if (_maxLines != null && lines.length > _maxLines) {
            return;
          }

          offsetX = 0.0;
          offsetY += bottom - top;
          top = 0;
          bottom = 0;

          if (offsetY > constraintHeight) {
            return;
          }

          offsetY += style.lineSpacing! * textScaleFactor;
        }

        final baseline = span.baseline * textScaleFactor;
        top = math.min(top, baseline);
        bottom = math.max(
          bottom,
          ws.height + baseline,
        );

        ws.offset = PdfPoint(offsetX, -offsetY + baseline);
        _spans.add(ws);
        spanCount++;

        _appendDecoration(
          spanCount > 1,
          _TextDecoration(
            style,
            annotation,
            _spans.length - 1,
            _spans.length - 1,
          ),
        );

        offsetX += ws.left + ws.width;
      }
    }
  }

  _buildLines();

  if (spanCount > 0) {
    lines.add(_Line(
      this,
      spanStart,
      spanCount,
      bottom,
      offsetX,
      _textDirection,
      false,
    ));
    offsetY += bottom - top;
  }

  assert(!overflow || constraintWidth.isFinite);
  var width = overflow ? constraintWidth : constraints.minWidth;

  if (lines.isNotEmpty) {
    if (!overflow) {
      // Calculate the final width
      for (final line in lines) {
        width = math.max(width, line.wordsWidth);
      }
    }

    // Realign all the lines
    for (final line in lines) {
      line.realign(width);
    }
  }

  box = PdfRect(0, 0, constraints.constrainWidth(width),
      constraints.constrainHeight(offsetY));

  _context
    ..endOffset = offsetY - _context.startOffset
    ..spanEnd = _spans.length;

  if (_overflow != TextOverflow.span) {
    if (_overflow != TextOverflow.visible) {
      _mustClip = true;
    }
    return;
  }

  if (offsetY > constraintHeight + 0.0001) {
    _context.spanEnd -= lines.last.countSpan;
    _context.endOffset -= lines.last.height;
  }

  for (var index = 0; index < _decorations.length; index++) {
    final decoration = _decorations[index];
    if (decoration.startSpan >= _context.spanEnd ||
        decoration.endSpan < _context.spanStart) {
      _decorations.removeAt(index);
      index--;
    }
  }
}