text: /*
   * Copyright 2017 Google Inc.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  #ifndef LIB_TXT_SRC_PARAGRAPH_TXT_H_
  #define LIB_TXT_SRC_PARAGRAPH_TXT_H_
  
  #include <set>
  #include <utility>
  #include <vector>
  
  #include "flutter/fml/compiler_specific.h"
  #include "flutter/fml/macros.h"
  #include "font_collection.h"
  #include "line_metrics.h"
  #include "minikin/LineBreaker.h"
  #include "paint_record.h"
  #include "paragraph.h"
  #include "paragraph_style.h"
  #include "placeholder_run.h"
  #include "run_metrics.h"
  #include "styled_runs.h"
  #include "third_party/googletest/googletest/include/gtest/gtest_prod.h"  // nogncheck
  #include "third_party/skia/include/core/SkFontMetrics.h"
  #include "third_party/skia/include/core/SkRect.h"
  #include "utils/WindowsUtils.h"
  
  namespace txt {
  
  using GlyphID = uint32_t;
  
  // Constant with the unicode codepoint for the "Object replacement character".
  // Used as a stand-in character for Placeholder boxes.
  const int objReplacementChar = 0xFFFC;
  // Constant with the unicode codepoint for the "Replacement character". This is
  // the character that commonly renders as a black diamond with a white question
  // mark. Used to replace non-placeholder instances of 0xFFFC in the text buffer.
  const int replacementChar = 0xFFFD;
  
  // Paragraph provides Layout, metrics, and painting capabilities for text. Once
  // a Paragraph is constructed with ParagraphBuilder::Build(), an example basic
  // workflow can be this:
  //
  //   std::unique_ptr<Paragraph> paragraph = paragraph_builder.Build();
  //   paragraph->Layout(<somewidthgoeshere>);
  //   paragraph->Paint(<someSkCanvas>, <xpos>, <ypos>);
  class ParagraphTxt : public Paragraph {
   public:
    // Constructor. It is highly recommended to construct a paragraph with a
    // ParagraphBuilder.
    ParagraphTxt();
  
    virtual ~ParagraphTxt();
  
    // Minikin Layout doLayout() and LineBreaker addStyleRun() has an
    // O(N^2) (according to benchmarks) time complexity where N is the total
    // number of characters. However, this is not significant for reasonably sized
    // paragraphs. It is currently recommended to break up very long paragraphs
    // (10k+ characters) to ensure speedy layout.
    virtual void Layout(double width) override;
  
    virtual void Paint(SkCanvas* canvas, double x, double y) override;
  
    // Getter for paragraph_style_.
    const ParagraphStyle& GetParagraphStyle() const;
  
    // Returns the number of characters/unicode characters. AKA text_.size()
    size_t TextSize() const;
  
    double GetHeight() override;
  
    double GetMaxWidth() override;
  
    double GetLongestLine() override;
  
    double GetAlphabeticBaseline() override;
  
    double GetIdeographicBaseline() override;
  
    double GetMaxIntrinsicWidth() override;
  
    // Currently, calculated similarly to as GetLayoutWidth(), however this is not
    // necessarily 100% correct in all cases.
    double GetMinIntrinsicWidth() override;
  
    std::vector<TextBox> GetRectsForRange(
        size_t start,
        size_t end,
        RectHeightStyle rect_height_style,
        RectWidthStyle rect_width_style) override;
  
    PositionWithAffinity GetGlyphPositionAtCoordinate(double dx,
                                                      double dy) override;
  
    std::vector<Paragraph::TextBox> GetRectsForPlaceholders() override;
  
    Range<size_t> GetWordBoundary(size_t offset) override;
  
    // Returns the number of lines the paragraph takes up. If the text exceeds the
    // amount width and maxlines provides, Layout() truncates the extra text from
    // the layout and this will return the max lines allowed.
    size_t GetLineCount();
  
    bool DidExceedMaxLines() override;
  
    // Gets the full vector of LineMetrics which includes detailed data on each
    // line in the final layout.
    std::vector<LineMetrics>& GetLineMetrics() override;
  
    // Sets the needs_layout_ to dirty. When Layout() is called, a new Layout will
    // be performed when this is set to true. Can also be used to prevent a new
    // Layout from being calculated by setting to false.
    void SetDirty(bool dirty = true);
  
   private:
    friend class ParagraphBuilderTxt;
    FRIEND_TEST(ParagraphTest, SimpleParagraph);
    FRIEND_TEST(ParagraphTest, SimpleRedParagraph);
    FRIEND_TEST(ParagraphTest, RainbowParagraph);
    FRIEND_TEST(ParagraphTest, DefaultStyleParagraph);
    FRIEND_TEST(ParagraphTest, BoldParagraph);
    FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, LeftAlignParagraph);
    FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, RightAlignParagraph);
    FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, CenterAlignParagraph);
    FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, JustifyAlignParagraph);
    FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, JustifyRTL);
    FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, JustifyRTLNewLine);
    FRIEND_TEST(ParagraphTest, DecorationsParagraph);
    FRIEND_TEST(ParagraphTest, ItalicsParagraph);
    FRIEND_TEST(ParagraphTest, ChineseParagraph);
    FRIEND_TEST(ParagraphTest, DISABLED_ArabicParagraph);
    FRIEND_TEST(ParagraphTest, SpacingParagraph);
    FRIEND_TEST(ParagraphTest, LongWordParagraph);
    FRIEND_TEST(ParagraphTest, KernScaleParagraph);
    FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, NewlineParagraph);
    FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, EmojiParagraph);
    FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, EmojiMultiLineRectsParagraph);
    FRIEND_TEST(ParagraphTest, HyphenBreakParagraph);
    FRIEND_TEST(ParagraphTest, RepeatLayoutParagraph);
    FRIEND_TEST(ParagraphTest, Ellipsize);
    FRIEND_TEST(ParagraphTest, UnderlineShiftParagraph);
    FRIEND_TEST(ParagraphTest, WavyDecorationParagraph);
    FRIEND_TEST(ParagraphTest, SimpleShadow);
    FRIEND_TEST(ParagraphTest, ComplexShadow);
    FRIEND_TEST(ParagraphTest, FontFallbackParagraph);
    FRIEND_TEST(ParagraphTest, InlinePlaceholder0xFFFCParagraph);
    FRIEND_TEST(ParagraphTest, FontFeaturesParagraph);
    FRIEND_TEST(ParagraphTest, GetGlyphPositionAtCoordinateSegfault);
  
    // Starting data to layout.
    std::vector<uint16_t> text_;
    // A vector of PlaceholderRuns, which detail the sizes, positioning and break
    // behavior of the empty spaces to leave. Each placeholder span corresponds to
    // a 0xFFFC (object replacement character) in text_, which indicates the
    // position in the text where the placeholder will occur. There should be an
    // equal number of 0xFFFC characters and elements in this vector.
    std::vector<PlaceholderRun> inline_placeholders_;
    // The indexes of the boxes that correspond to an inline placeholder.
    std::vector<size_t> inline_placeholder_boxes_;
    // The indexes of instances of 0xFFFC that correspond to placeholders. This is
    // necessary since the user may pass in manually entered 0xFFFC values using
    // AddText().
    std::unordered_set<size_t> obj_replacement_char_indexes_;
    StyledRuns runs_;
    ParagraphStyle paragraph_style_;
    std::shared_ptr<FontCollection> font_collection_;
  
    minikin::LineBreaker breaker_;
    mutable std::unique_ptr<icu::BreakIterator> word_breaker_;
  
    std::vector<LineMetrics> line_metrics_;
    size_t final_line_count_;
    std::vector<double> line_widths_;
  
    // Stores the result of Layout().
    std::vector<PaintRecord> records_;
  
    bool did_exceed_max_lines_;
  
    // Strut metrics of zero will have no effect on the layout.
    struct StrutMetrics {
      double ascent = 0;  // Positive value to keep signs clear.
      double descent = 0;
      double leading = 0;
      double half_leading = 0;
      double line_height = 0;
      bool force_strut = false;
    };
  
    StrutMetrics strut_;
  
    // Overall left and right extremes over all lines.
    double max_right_;
    double min_left_;
  
    class BidiRun {
     public:
      // Constructs a BidiRun with is_ghost defaulted to false.
      BidiRun(size_t s, size_t e, TextDirection d, const TextStyle& st)
          : start_(s), end_(e), direction_(d), style_(&st), is_ghost_(false) {}
  
      // Constructs a BidiRun with a custom is_ghost flag.
      BidiRun(size_t s,
              size_t e,
              TextDirection d,
              const TextStyle& st,
              bool is_ghost)
          : start_(s), end_(e), direction_(d), style_(&st), is_ghost_(is_ghost) {}
  
      // Constructs a placeholder bidi run.
      BidiRun(size_t s,
              size_t e,
              TextDirection d,
              const TextStyle& st,
              PlaceholderRun& placeholder)
          : start_(s),
            end_(e),
            direction_(d),
            style_(&st),
            placeholder_run_(&placeholder) {}
  
      size_t start() const { return start_; }
      size_t end() const { return end_; }
      size_t size() const { return end_ - start_; }
      TextDirection direction() const { return direction_; }
      const TextStyle& style() const { return *style_; }
      PlaceholderRun* placeholder_run() const { return placeholder_run_; }
      bool is_rtl() const { return direction_ == TextDirection::rtl; }
      // Tracks if the run represents trailing whitespace.
      bool is_ghost() const { return is_ghost_; }
      bool is_placeholder_run() const { return placeholder_run_ != nullptr; }
  
     private:
      size_t start_, end_;
      TextDirection direction_;
      const TextStyle* style_;
      bool is_ghost_;
      PlaceholderRun* placeholder_run_ = nullptr;
    };
  
    struct GlyphPosition {
      Range<size_t> code_units;
      Range<double> x_pos;
      // Tracks the cluster that this glyph position belongs to. For example, in
      // extended emojis, multiple glyph positions will have the same cluster. The
      // cluster can be used as a key to distinguish between codepoints that
      // contribute to the drawing of a single glyph.
      size_t cluster;
  
      GlyphPosition(double x_start,
                    double x_advance,
                    size_t code_unit_index,
                    size_t code_unit_width,
                    size_t cluster);
  
      void Shift(double delta);
    };
  
    struct GlyphLine {
      // Glyph positions sorted by x coordinate.
      const std::vector<GlyphPosition> positions;
      const size_t total_code_units;
  
      GlyphLine(std::vector<GlyphPosition>&& p, size_t tcu);
    };
  
    struct CodeUnitRun {
      // Glyph positions sorted by code unit index.
      std::vector<GlyphPosition> positions;
      Range<size_t> code_units;
      Range<double> x_pos;
      size_t line_number;
      SkFontMetrics font_metrics;
      const TextStyle* style;
      TextDirection direction;
      const PlaceholderRun* placeholder_run;
  
      CodeUnitRun(std::vector<GlyphPosition>&& p,
                  Range<size_t> cu,
                  Range<double> x,
                  size_t line,
                  const SkFontMetrics& metrics,
                  const TextStyle& st,
                  TextDirection dir,
                  const PlaceholderRun* placeholder);
  
      void Shift(double delta);
    };
  
    // Holds the laid out x positions of each glyph.
    std::vector<GlyphLine> glyph_lines_;
  
    // Holds the positions of each range of code units in the text.
    // Sorted in code unit index order.
    std::vector<CodeUnitRun> code_unit_runs_;
    // Holds the positions of the inline placeholders.
    std::vector<CodeUnitRun> inline_placeholder_code_unit_runs_;
  
    // The max width of the paragraph as provided in the most recent Layout()
    // call.
    double width_ = -1.0f;
    double longest_line_ = -1.0f;
    double max_intrinsic_width_ = 0;
    double min_intrinsic_width_ = 0;
    double alphabetic_baseline_ = FLT_MAX;
    double ideographic_baseline_ = FLT_MAX;
  
    bool needs_layout_ = true;
  
    struct WaveCoordinates {
      double x_start;
      double y_start;
      double x_end;
      double y_end;
  
      WaveCoordinates(double x_s, double y_s, double x_e, double y_e)
          : x_start(x_s), y_start(y_s), x_end(x_e), y_end(y_e) {}
    };
  
    // Passes in the text and Styled Runs. text_ and runs_ will later be passed
    // into breaker_ in InitBreaker(), which is called in Layout().
    void SetText(std::vector<uint16_t> text, StyledRuns runs);
  
    void SetParagraphStyle(const ParagraphStyle& style);
  
    void SetFontCollection(std::shared_ptr<FontCollection> font_collection);
  
    void SetInlinePlaceholders(
        std::vector<PlaceholderRun> inline_placeholders,
        std::unordered_set<size_t> obj_replacement_char_indexes);
  
    // Break the text into lines.
    bool ComputeLineBreaks();
  
    // Break the text into runs based on LTR/RTL text direction.
    bool ComputeBidiRuns(std::vector<BidiRun>* result);
  
    // Calculates and populates strut based on paragraph_style_ strut info.
    void ComputeStrut(StrutMetrics* strut, SkFont& font);
  
    // Adjusts the ascent and descent based on the existence and type of
    // placeholder. This method sets the proper metrics to achieve the different
    // PlaceholderAlignment options.
    void ComputePlaceholder(PlaceholderRun* placeholder_run,
                            double& ascent,
                            double& descent);
  
    bool IsStrutValid() const;
  
    // Calculate the starting X offset of a line based on the line's width and
    // alignment.
    double GetLineXOffset(double line_total_advance,
                          size_t line_number,
                          bool justify_line);
  
    // Creates and draws the decorations onto the canvas.
    void PaintDecorations(SkCanvas* canvas,
                          const PaintRecord& record,
                          SkPoint base_offset);
  
    // Computes the beziers for a wavy decoration. The results will be
    // applied to path.
    void ComputeWavyDecoration(SkPath& path,
                               double x,
                               double y,
                               double width,
                               double thickness);
  
    // Draws the background onto the canvas.
    void PaintBackground(SkCanvas* canvas,
                         const PaintRecord& record,
                         SkPoint base_offset);
  
    // Draws the shadows onto the canvas.
    void PaintShadow(SkCanvas* canvas, const PaintRecord& record, SkPoint offset);
  
    // Obtain a Minikin font collection matching this text style.
    std::shared_ptr<minikin::FontCollection> GetMinikinFontCollectionForStyle(
        const TextStyle& style);
  
    // Get a default SkTypeface for a text style.
    sk_sp<SkTypeface> GetDefaultSkiaTypeface(const TextStyle& style);
  
    FML_DISALLOW_COPY_AND_ASSIGN(ParagraphTxt);
  };
  
  }  // namespace txt
  
  #endif  // LIB_TXT_SRC_PARAGRAPH_TXT_H_
  