text: /*
   * Copyright 2016 Google Inc.
   *
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  
  #include "SkShaper.h"
  #include "SkFontMetrics.h"
  #include "SkStream.h"
  #include "SkTo.h"
  #include "SkTypeface.h"
  #include "SkUTF.h"
  
  struct SkShaper::Impl {};
  SkShaper::SkShaper(sk_sp<SkTypeface> tf) : fImpl(new Impl()) {}
  
  SkShaper::~SkShaper() {}
  
  bool SkShaper::good() const { return true; }
  
  static inline bool is_breaking_whitespace(SkUnichar c) {
      switch (c) {
          case 0x0020: // SPACE
          //case 0x00A0: // NO-BREAK SPACE
          case 0x1680: // OGHAM SPACE MARK
          case 0x180E: // MONGOLIAN VOWEL SEPARATOR
          case 0x2000: // EN QUAD
          case 0x2001: // EM QUAD
          case 0x2002: // EN SPACE (nut)
          case 0x2003: // EM SPACE (mutton)
          case 0x2004: // THREE-PER-EM SPACE (thick space)
          case 0x2005: // FOUR-PER-EM SPACE (mid space)
          case 0x2006: // SIX-PER-EM SPACE
          case 0x2007: // FIGURE SPACE
          case 0x2008: // PUNCTUATION SPACE
          case 0x2009: // THIN SPACE
          case 0x200A: // HAIR SPACE
          case 0x200B: // ZERO WIDTH SPACE
          case 0x202F: // NARROW NO-BREAK SPACE
          case 0x205F: // MEDIUM MATHEMATICAL SPACE
          case 0x3000: // IDEOGRAPHIC SPACE
          //case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE
              return true;
          default:
              return false;
      }
  }
  
  static size_t linebreak(const char text[], const char stop[],
                          const SkFont& font, SkScalar width,
                          SkScalar* advance,
                          size_t* trailing)
  {
      SkScalar accumulatedWidth = 0;
      int glyphIndex = 0;
      const char* start = text;
      const char* word_start = text;
      bool prevWS = true;
      *trailing = 0;
  
      while (text < stop) {
          const char* prevText = text;
          SkUnichar uni = SkUTF::NextUTF8(&text, stop);
          accumulatedWidth += advance[glyphIndex++];
          bool currWS = is_breaking_whitespace(uni);
  
          if (!currWS && prevWS) {
              word_start = prevText;
          }
          prevWS = currWS;
  
          if (width < accumulatedWidth) {
              if (currWS) {
                  // eat the rest of the whitespace
                  const char* next = text;
                  while (next < stop && is_breaking_whitespace(SkUTF::NextUTF8(&next, stop))) {
                      text = next;
                  }
                  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 = SkUTF::NextUTF8(&text, stop);
                  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 = SkUTF::NextUTF8(&text, stop);
                  if ('\n' == uni) {
                      ret = text - start;
                      ++lineBreakSize;
                  }
              }
              if (trailing) {
                  *trailing = lineBreakSize;
              }
              return ret;
          }
      }
  
      return text - start;
  }
  
  SkPoint SkShaper::shape(RunHandler* handler,
                          const SkFont& font,
                          const char* utf8text,
                          size_t textBytes,
                          bool leftToRight,
                          SkPoint point,
                          SkScalar width) const {
      sk_ignore_unused_variable(leftToRight);
  
      int glyphCount = font.countText(utf8text, textBytes, SkTextEncoding::kUTF8);
      if (glyphCount <= 0) {
          return point;
      }
  
      std::unique_ptr<SkGlyphID[]> glyphs(new SkGlyphID[glyphCount]);
      font.textToGlyphs(utf8text, textBytes, SkTextEncoding::kUTF8, glyphs.get(), glyphCount);
  
      std::unique_ptr<SkScalar[]> advances(new SkScalar[glyphCount]);
      font.getWidthsBounds(glyphs.get(), glyphCount, advances.get(), nullptr, nullptr);
  
      SkFontMetrics metrics;
      font.getMetrics(&metrics);
  
      size_t glyphOffset = 0;
      size_t utf8Offset = 0;
      while (0 < textBytes) {
          point.fY -= metrics.fAscent;
  
          size_t bytesCollapsed;
          size_t bytesConsumed = linebreak(utf8text, utf8text + textBytes, font, width,
                                           advances.get() + glyphOffset, &bytesCollapsed);
          size_t bytesVisible = bytesConsumed - bytesCollapsed;
  
          int numGlyphs = SkUTF::CountUTF8(utf8text, bytesVisible);
          const RunHandler::RunInfo info = {
              { font.measureText(utf8text, bytesVisible, SkTextEncoding::kUTF8), 0 },
              metrics.fAscent,
              metrics.fDescent,
              metrics.fLeading,
          };
          const auto buffer = handler->newRunBuffer(info, font, numGlyphs,
                                                    SkSpan<const char>(utf8text, bytesVisible));
  
          memcpy(buffer.glyphs, glyphs.get() + glyphOffset, numGlyphs * sizeof(SkGlyphID));
          SkScalar position = point.fX;
          for (int i = 0; i < numGlyphs; ++i) {
              buffer.positions[i] = { position, point.fY };
              position += advances[i + glyphOffset];
          }
          if (buffer.clusters) {
              const char* txtPtr = utf8text;
              for (int i = 0; i < numGlyphs; ++i) {
                  // Each character maps to exactly one glyph.
                  buffer.clusters[i] = SkToU32(txtPtr - utf8text + utf8Offset);
                  SkUTF::NextUTF8(&txtPtr, utf8text + textBytes);
              }
          }
          handler->commitRun();
          handler->commitLine();
  
          glyphOffset += SkUTF::CountUTF8(utf8text, bytesConsumed);
          utf8Offset += bytesConsumed;
          utf8text += bytesConsumed;
          textBytes -= bytesConsumed;
          point.fY += metrics.fDescent + metrics.fLeading;
      }
  
      return point;
  }
  