text: // Copyright 2019 Google LLC.
  #include "modules/skparagraph/src/ParagraphImpl.h"
  #include "modules/skparagraph/src/TextWrapper.h"
  
  namespace skia {
  namespace textlayout {
  
  namespace {
  SkScalar littleRound(SkScalar a) {
      // This rounding is done to match Flutter tests. Must be removed..
      auto val = std::fabs(a);
      if (val < 10000) {
          return SkScalarRoundToScalar(a * 100) * (1.0f/100);
      } else if (val < 100000) {
          return SkScalarRoundToScalar(a *  10) * (1.0f/10);
      } else {
          return SkScalarFloorToScalar(a);
      }
  }
  }  // namespace
  
  // Since we allow cluster clipping when they don't fit
  // we have to work with stretches - parts of clusters
  void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters) {
  
      reset();
      fEndLine.metrics().clean();
      fWords.startFrom(fEndLine.startCluster(), fEndLine.startPos());
      fClusters.startFrom(fEndLine.startCluster(), fEndLine.startPos());
      fClip.startFrom(fEndLine.startCluster(), fEndLine.startPos());
  
      for (auto cluster = fEndLine.endCluster(); cluster < endOfClusters; ++cluster) {
          // TODO: Trying to deal with flutter rounding problem. Must be removed...
          auto width = fWords.width() + fClusters.width() + cluster->width();
          auto roundedWidth = littleRound(width);
          if (cluster->isHardBreak()) {
          } else if (roundedWidth > maxWidth) {
              if (cluster->isWhitespaces()) {
                  // It's the end of the word
                  fClusters.extend(cluster);
                  fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
                  fWords.extend(fClusters);
                  break;
              } else if (cluster->run()->isPlaceholder()) {
                  if (!fClusters.empty()) {
                      // Placeholder ends the previous word
                      fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
                      fWords.extend(fClusters);
                  }
  
                  if (cluster->width() > maxWidth) {
                      // Placeholder is longer than the line; it does not count in fMinIntrinsicWidth
                      fClusters.extend(cluster);
                      fTooLongCluster = true;
                      fTooLongWord = true;
                  } else {
                      // Placeholder does not fit the line; it will be considered again on the next line
                  }
                  break;
              }
  
              // Walk further to see if there is a too long word, cluster or glyph
              SkScalar nextWordLength = fClusters.width();
              for (auto further = cluster; further != endOfClusters; ++further) {
                  if (further->isSoftBreak() || further->isHardBreak() || further->isWhitespaces()) {
                      break;
                  }
                  if (further->run()->isPlaceholder()) {
                    // Placeholder ends the word
                    break;
                  }
                  if (maxWidth == 0) {
                      // This is a tricky flutter case: layout(width:0) places 1 cluster on each line
                      nextWordLength = std::max(nextWordLength, further->width());
                  } else {
                      nextWordLength += further->width();
                  }
              }
              if (nextWordLength > maxWidth) {
                  // If the word is too long we can break it right now and hope it's enough
                  fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextWordLength);
                  if (fClusters.endPos() - fClusters.startPos() > 1 ||
                      fWords.empty()) {
                      fTooLongWord = true;
                  } else {
                      // Even if the word is too long there is a very little space on this line.
                      // let's deal with it on the next line.
                  }
              }
  
              if (cluster->width() > maxWidth) {
                  fClusters.extend(cluster);
                  fTooLongCluster = true;
                  fTooLongWord = true;
              }
              break;
          }
  
          if (cluster->run()->isPlaceholder()) {
              if (!fClusters.empty()) {
                  // Placeholder ends the previous word (placeholders are ignored in trimming)
                  fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
                  fWords.extend(fClusters);
              }
  
              // Placeholder is separate word and its width now is counted in minIntrinsicWidth
              fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
              fWords.extend(cluster);
          } else {
              fClusters.extend(cluster);
  
              // Keep adding clusters/words
              if (fClusters.endOfWord()) {
                  fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
                  fWords.extend(fClusters);
              }
          }
  
          if ((fHardLineBreak = cluster->isHardBreak())) {
              // Stop at the hard line break
              break;
          }
      }
  }
  
  void TextWrapper::moveForward(bool hasEllipsis) {
  
      // We normally break lines by words.
      // The only way we may go to clusters is if the word is too long or
      // it's the first word and it has an ellipsis attached to it.
      // If nothing fits we show the clipping.
      if (!fWords.empty()) {
          fEndLine.extend(fWords);
          if (!fTooLongWord || hasEllipsis) {
              return;
          }
      }
      if (!fClusters.empty()) {
          fEndLine.extend(fClusters);
          if (!fTooLongCluster) {
              return;
          }
      }
  
      if (!fClip.empty()) {
          // Flutter: forget the clipped cluster but keep the metrics
          fEndLine.metrics().add(fClip.metrics());
      }
  }
  
  // Special case for start/end cluster since they can be clipped
  void TextWrapper::trimEndSpaces(TextAlign align) {
      // Remember the breaking position
      fEndLine.saveBreak();
      // Skip all space cluster at the end
      for (auto cluster = fEndLine.endCluster();
           cluster >= fEndLine.startCluster() && cluster->isWhitespaces();
           --cluster) {
          fEndLine.trim(cluster);
      }
      fEndLine.trim();
  }
  
  SkScalar TextWrapper::getClustersTrimmedWidth() {
      // Move the end of the line to the left
      SkScalar width = 0;
      bool trailingSpaces = true;
      for (auto cluster = fClusters.endCluster(); cluster >= fClusters.startCluster(); --cluster) {
          if (cluster->run()->isPlaceholder()) {
              continue;
          }
          if (trailingSpaces) {
              if (!cluster->isWhitespaces()) {
                  width += cluster->trimmedWidth(cluster->endPos());
                  trailingSpaces = false;
              }
              continue;
          }
          width += cluster->width();
      }
      return width;
  }
  
  // Trim the beginning spaces in case of soft line break
  std::tuple<Cluster*, size_t, SkScalar> TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
  
      if (fHardLineBreak) {
          // End of line is always end of cluster, but need to skip \n
          auto width = fEndLine.width();
          auto cluster = fEndLine.endCluster() + 1;
          while (cluster < fEndLine.breakCluster() && cluster->isWhitespaces()) {
              width += cluster->width();
              ++cluster;
          }
          return std::make_tuple(fEndLine.breakCluster() + 1, 0, width);
      }
  
      auto width = fEndLine.widthWithGhostSpaces();
      auto cluster = fEndLine.breakCluster();
      if (fEndLine.endCluster() != fEndLine.startCluster() ||
          fEndLine.endPos() != fEndLine.startPos()) {
          ++cluster;
          while (cluster < endOfClusters && cluster->isWhitespaces()) {
              width += cluster->width();
              ++cluster;
          }
      } else {
          // Nothing fits the line - no need to check for spaces
      }
  
      return std::make_tuple(cluster, 0, width);
  }
  
  // TODO: refactor the code for line ending (with/without ellipsis)
  void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
                                       SkScalar maxWidth,
                                       const AddLineToParagraph& addLine) {
      fHeight = 0;
      fMinIntrinsicWidth = std::numeric_limits<SkScalar>::min();
      fMaxIntrinsicWidth = std::numeric_limits<SkScalar>::min();
  
      auto span = parent->clusters();
      if (span.size() == 0) {
          return;
      }
      auto maxLines = parent->paragraphStyle().getMaxLines();
      auto align = parent->paragraphStyle().effective_align();
      auto unlimitedLines = maxLines == std::numeric_limits<size_t>::max();
      auto endlessLine = !SkScalarIsFinite(maxWidth);
      auto hasEllipsis = parent->paragraphStyle().ellipsized();
  
      auto disableFirstAscent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent;
      auto disableLastDescent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent;
      bool firstLine = true; // We only interested in fist line if we have to disable the first ascent
  
      SkScalar softLineMaxIntrinsicWidth = 0;
      fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight());
      auto end = span.end() - 1;
      auto start = span.begin();
      InternalLineMetrics maxRunMetrics;
      bool needEllipsis = false;
      while (fEndLine.endCluster() != end) {
  
          lookAhead(maxWidth, end);
  
          auto lastLine = (hasEllipsis && unlimitedLines) || fLineNumber >= maxLines;
          needEllipsis = hasEllipsis && !endlessLine && lastLine;
  
          moveForward(needEllipsis);
          needEllipsis &= fEndLine.endCluster() < end - 1; // Only if we have some text to ellipsize
  
          // Do not trim end spaces on the naturally last line of the left aligned text
          trimEndSpaces(align);
  
          // For soft line breaks add to the line all the spaces next to it
          Cluster* startLine;
          size_t pos;
          SkScalar widthWithSpaces;
          std::tie(startLine, pos, widthWithSpaces) = trimStartSpaces(end);
  
          if (needEllipsis && !fHardLineBreak) {
              // This is what we need to do to preserve a space before the ellipsis
              fEndLine.restoreBreak();
              widthWithSpaces = fEndLine.widthWithGhostSpaces();
          }
  
          // If the line is empty with the hard line break, let's take the paragraph font (flutter???)
          if (fHardLineBreak && fEndLine.width() == 0) {
              fEndLine.setMetrics(parent->getEmptyMetrics());
          }
  
          // Deal with placeholder clusters == runs[@size==1]
          Run* lastRun = nullptr;
          for (auto cluster = fEndLine.startCluster(); cluster <= fEndLine.endCluster(); ++cluster) {
              if (cluster->run() == lastRun) {
                  continue;
              }
              lastRun = cluster->run();
              if (lastRun->placeholderStyle() != nullptr) {
                  SkASSERT(lastRun->size() == 1);
                  // Update the placeholder metrics so we can get the placeholder positions later
                  // and the line metrics (to make sure the placeholder fits)
                  lastRun->updateMetrics(&fEndLine.metrics());
              }
          }
  
          // Before we update the line metrics with struts,
          // let's save it for GetRectsForRange(RectHeightStyle::kMax)
          maxRunMetrics = fEndLine.metrics();
          maxRunMetrics.fForceStrut = false;
  
          if (parent->strutEnabled()) {
              // Make sure font metrics are not less than the strut
              parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
          }
  
          // TODO: keep start/end/break info for text and runs but in a better way that below
          TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
          TextRange textWithSpaces(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
          if (startLine == end) {
              textWithSpaces.end = parent->text().size();
          }
          ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start + 1);
          ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
  
          if (disableFirstAscent && firstLine) {
              fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
          }
          if (disableLastDescent && (lastLine || (startLine == end && !fHardLineBreak ))) {
              fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
          }
  
          SkScalar lineHeight = fEndLine.metrics().height();
          firstLine = false;
  
          addLine(text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces,
                  fEndLine.startPos(),
                  fEndLine.endPos(),
                  SkVector::Make(0, fHeight),
                  SkVector::Make(fEndLine.width(), lineHeight),
                  fEndLine.metrics(),
                  needEllipsis && !fHardLineBreak);
  
          softLineMaxIntrinsicWidth += widthWithSpaces;
  
          fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
          if (fHardLineBreak) {
              softLineMaxIntrinsicWidth = 0;
          }
          // Start a new line
          fHeight += lineHeight;
          if (!fHardLineBreak || startLine != end) {
              fEndLine.clean();
          }
          fEndLine.startFrom(startLine, pos);
          parent->fMaxWidthWithTrailingSpaces = std::max(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
  
          if (hasEllipsis && unlimitedLines) {
              // There is one case when we need an ellipsis on a separate line
              // after a line break when width is infinite
              if (!fHardLineBreak) {
                  break;
              }
          } else if (lastLine) {
              // There is nothing more to draw
              fHardLineBreak = false;
              break;
          }
  
          ++fLineNumber;
      }
  
      // We finished formatting the text but we need to scan the rest for some numbers
      // TODO: make it a case of a normal flow
      if (fEndLine.endCluster() != nullptr) {
          auto lastWordLength = 0.0f;
          auto cluster = fEndLine.endCluster();
          while (cluster != end || cluster->endPos() < end->endPos()) {
              fExceededMaxLines = true;
              if (cluster->isHardBreak()) {
                  // Hard line break ends the word and the line
                  fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
                  softLineMaxIntrinsicWidth = 0;
                  fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
                  lastWordLength = 0;
              } else if (cluster->isWhitespaces()) {
                  // Whitespaces end the word
                  softLineMaxIntrinsicWidth += cluster->width();
                  fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
                  lastWordLength = 0;
              } else if (cluster->run()->isPlaceholder()) {
                  // Placeholder ends the previous word and creates a separate one
                  fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
                  // Placeholder width now counts in fMinIntrinsicWidth
                  softLineMaxIntrinsicWidth += cluster->width();
                  fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
                  lastWordLength = 0;
              } else {
                  // Nothing out of ordinary - just add this cluster to the word and to the line
                  softLineMaxIntrinsicWidth += cluster->width();
                  lastWordLength += cluster->width();
              }
              ++cluster;
          }
          fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
          fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
  
          if (parent->lines().empty()) {
              // In case we could not place even a single cluster on the line
              if (disableFirstAscent) {
                  fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
              }
              if (disableLastDescent && !fHardLineBreak) {
                  fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
              }
              fHeight = std::max(fHeight, fEndLine.metrics().height());
          }
      }
  
      if (fHardLineBreak) {
          // Last character is a line break
          if (parent->strutEnabled()) {
              // Make sure font metrics are not less than the strut
              parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
          }
          TextRange empty(fEndLine.breakCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
          TextRange hardBreak(fEndLine.breakCluster()->textRange().end, fEndLine.breakCluster()->textRange().end);
          ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.endCluster() - start);
          addLine(empty, hardBreak, clusters, clusters,
                  0,
                  0,
                  0,
                  SkVector::Make(0, fHeight),
                  SkVector::Make(0, fEndLine.metrics().height()),
                  fEndLine.metrics(),
                  needEllipsis);
          fHeight += fEndLine.metrics().height();
          parent->lines().back().setMaxRunMetrics(maxRunMetrics);
      }
  }
  
  }  // namespace textlayout
  }  // namespace skia
  