text: /*
   * Copyright 2006 The Android Open Source Project
   *
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  
  #include "SkTextBox.h"
  #include "SkUtils.h"
  
  static inline int is_ws(int c)
  {
      return !((c - 1) >> 5);
  }
  
  static size_t linebreak(const char text[], const char stop[],
                          const SkPaint& paint, SkScalar margin,
                          size_t* trailing = nullptr)
  {
      size_t lengthBreak = paint.breakText(text, stop - text, margin);
  
      //Check for white space or line breakers before the lengthBreak
      const char* start = text;
      const char* word_start = text;
      int prevWS = true;
      if (trailing) {
          *trailing = 0;
      }
  
      while (text < stop) {
          const char* prevText = text;
          SkUnichar uni = SkUTF8_NextUnichar(&text);
          int currWS = is_ws(uni);
  
          if (!currWS && prevWS) {
              word_start = prevText;
          }
          prevWS = currWS;
  
          if (text > start + lengthBreak) {
              if (currWS) {
                  // eat the rest of the whitespace
                  while (text < stop && is_ws(SkUTF8_ToUnichar(text))) {
                      text += SkUTF8_CountUTF8Bytes(text);
                  }
                  if (trailing) {
                      *trailing = text - prevText;
                  }
              } else {
                  // backup until a whitespace (or 1 char)
                  if (word_start == start) {
                      if (prevText > start) {
                          text = prevText;
                      }
                  } else {
                      text = word_start;
                  }
              }
              break;
          }
  
          if ('\n' == uni) {
              size_t ret = text - start;
              size_t lineBreakSize = 1;
              if (text < stop) {
                  uni = SkUTF8_NextUnichar(&text);
                  if ('\r' == uni) {
                      ret = text - start;
                      ++lineBreakSize;
                  }
              }
              if (trailing) {
                  *trailing = lineBreakSize;
              }
              return ret;
          }
  
          if ('\r' == uni) {
              size_t ret = text - start;
              size_t lineBreakSize = 1;
              if (text < stop) {
                  uni = SkUTF8_NextUnichar(&text);
                  if ('\n' == uni) {
                      ret = text - start;
                      ++lineBreakSize;
                  }
              }
              if (trailing) {
                  *trailing = lineBreakSize;
              }
              return ret;
          }
      }
  
      return text - start;
  }
  
  int SkTextLineBreaker::CountLines(const char text[], size_t len, const SkPaint& paint, SkScalar width)
  {
      const char* stop = text + len;
      int         count = 0;
  
      if (width > 0)
      {
          do {
              count += 1;
              text += linebreak(text, stop, paint, width);
          } while (text < stop);
      }
      return count;
  }
  
  //////////////////////////////////////////////////////////////////////////////
  
  SkTextBox::SkTextBox()
  {
      fBox.setEmpty();
      fSpacingMul = SK_Scalar1;
      fSpacingAdd = 0;
      fMode = kLineBreak_Mode;
      fSpacingAlign = kStart_SpacingAlign;
  }
  
  void SkTextBox::setMode(Mode mode)
  {
      SkASSERT((unsigned)mode < kModeCount);
      fMode = SkToU8(mode);
  }
  
  void SkTextBox::setSpacingAlign(SpacingAlign align)
  {
      SkASSERT((unsigned)align < kSpacingAlignCount);
      fSpacingAlign = SkToU8(align);
  }
  
  void SkTextBox::getBox(SkRect* box) const
  {
      if (box)
          *box = fBox;
  }
  
  void SkTextBox::setBox(const SkRect& box)
  {
      fBox = box;
  }
  
  void SkTextBox::setBox(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom)
  {
      fBox.set(left, top, right, bottom);
  }
  
  void SkTextBox::getSpacing(SkScalar* mul, SkScalar* add) const
  {
      if (mul)
          *mul = fSpacingMul;
      if (add)
          *add = fSpacingAdd;
  }
  
  void SkTextBox::setSpacing(SkScalar mul, SkScalar add)
  {
      fSpacingMul = mul;
      fSpacingAdd = add;
  }
  
  /////////////////////////////////////////////////////////////////////////////////////////////
  
  SkScalar SkTextBox::visit(Visitor& visitor, const char text[], size_t len,
                            const SkPaint& paint) const {
      SkScalar marginWidth = fBox.width();
  
      if (marginWidth <= 0 || len == 0) {
          return fBox.top();
      }
  
      const char* textStop = text + len;
  
      SkScalar                x, y, scaledSpacing, height, fontHeight;
      SkPaint::FontMetrics    metrics;
  
      switch (paint.getTextAlign()) {
      case SkPaint::kLeft_Align:
          x = 0;
          break;
      case SkPaint::kCenter_Align:
          x = SkScalarHalf(marginWidth);
          break;
      default:
          x = marginWidth;
          break;
      }
      x += fBox.fLeft;
  
      fontHeight = paint.getFontMetrics(&metrics);
      scaledSpacing = SkScalarMul(fontHeight, fSpacingMul) + fSpacingAdd;
      height = fBox.height();
  
      //  compute Y position for first line
      {
          SkScalar textHeight = fontHeight;
  
          if (fMode == kLineBreak_Mode && fSpacingAlign != kStart_SpacingAlign) {
              int count = SkTextLineBreaker::CountLines(text, textStop - text, paint, marginWidth);
              SkASSERT(count > 0);
              textHeight += scaledSpacing * (count - 1);
          }
  
          switch (fSpacingAlign) {
          case kStart_SpacingAlign:
              y = 0;
              break;
          case kCenter_SpacingAlign:
              y = SkScalarHalf(height - textHeight);
              break;
          default:
              SkASSERT(fSpacingAlign == kEnd_SpacingAlign);
              y = height - textHeight;
              break;
          }
          y += fBox.fTop - metrics.fAscent;
      }
  
      for (;;) {
          size_t trailing;
          len = linebreak(text, textStop, paint, marginWidth, &trailing);
          if (y + metrics.fDescent + metrics.fLeading > 0) {
              visitor(text, len - trailing, x, y, paint);
          }
          text += len;
          if (text >= textStop) {
              break;
          }
          y += scaledSpacing;
          if (y + metrics.fAscent >= fBox.fBottom) {
              break;
          }
      }
      return y + metrics.fDescent + metrics.fLeading;
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  
  class CanvasVisitor : public SkTextBox::Visitor {
      SkCanvas* fCanvas;
  public:
      CanvasVisitor(SkCanvas* canvas) : fCanvas(canvas) {}
  
      void operator()(const char text[], size_t length, SkScalar x, SkScalar y,
                      const SkPaint& paint) override {
          fCanvas->drawText(text, length, x, y, paint);
      }
  };
  
  void SkTextBox::setText(const char text[], size_t len, const SkPaint& paint) {
      fText = text;
      fLen = len;
      fPaint = &paint;
  }
  
  void SkTextBox::draw(SkCanvas* canvas, const char text[], size_t len, const SkPaint& paint) {
      CanvasVisitor sink(canvas);
      this->visit(sink, text, len, paint);
  }
  
  void SkTextBox::draw(SkCanvas* canvas) {
      this->draw(canvas, fText, fLen, *fPaint);
  }
  
  int SkTextBox::countLines() const {
      return SkTextLineBreaker::CountLines(fText, fLen, *fPaint, fBox.width());
  }
  
  SkScalar SkTextBox::getTextHeight() const {
      SkScalar spacing = SkScalarMul(fPaint->getTextSize(), fSpacingMul) + fSpacingAdd;
      return this->countLines() * spacing;
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  
  #include "SkTextBlob.h"
  
  class TextBlobVisitor : public SkTextBox::Visitor {
  public:
      SkTextBlobBuilder fBuilder;
  
      void operator()(const char text[], size_t length, SkScalar x, SkScalar y,
                      const SkPaint& paint) override {
          SkPaint p(paint);
          p.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
          const int count = paint.countText(text, length);
          paint.textToGlyphs(text, length, fBuilder.allocRun(p, count, x, y).glyphs);
      }
  };
  
  SkTextBlob* SkTextBox::snapshotTextBlob(SkScalar* computedBottom) const {
      TextBlobVisitor visitor;
      SkScalar newB = this->visit(visitor, fText, fLen, *fPaint);
      if (computedBottom) {
          *computedBottom = newB;
      }
      return (SkTextBlob*)visitor.fBuilder.build();
  }
  