• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef LIB_TXT_SRC_PARAGRAPH_TXT_H_
18 #define LIB_TXT_SRC_PARAGRAPH_TXT_H_
19 
20 #include <set>
21 #include <utility>
22 #include <vector>
23 
24 #include "flutter/fml/compiler_specific.h"
25 #include "flutter/fml/macros.h"
26 #include "font_collection.h"
27 #include "minikin/LineBreaker.h"
28 #include "paint_record.h"
29 #include "paragraph.h"
30 #include "paragraph_style.h"
31 #include "placeholder_run.h"
32 #include "styled_runs.h"
33 #include "third_party/googletest/googletest/include/gtest/gtest_prod.h"  // nogncheck
34 #include "third_party/skia/include/core/SkFontMetrics.h"
35 #include "third_party/skia/include/core/SkRect.h"
36 #include "utils/WindowsUtils.h"
37 
38 namespace txt {
39 
40 using GlyphID = uint32_t;
41 
42 // Constant with the unicode codepoint for the "Object replacement character".
43 // Used as a stand-in character for Placeholder boxes.
44 const int objReplacementChar = 0xFFFC;
45 // Constant with the unicode codepoint for the "Replacement character". This is
46 // the character that commonly renders as a black diamond with a white question
47 // mark. Used to replace non-placeholder instances of 0xFFFC in the text buffer.
48 const int replacementChar = 0xFFFD;
49 
50 // Paragraph provides Layout, metrics, and painting capabilities for text. Once
51 // a Paragraph is constructed with ParagraphBuilder::Build(), an example basic
52 // workflow can be this:
53 //
54 //   std::unique_ptr<Paragraph> paragraph = paragraph_builder.Build();
55 //   paragraph->Layout(<somewidthgoeshere>);
56 //   paragraph->Paint(<someSkCanvas>, <xpos>, <ypos>);
57 class ParagraphTxt : public Paragraph {
58  public:
59   // Constructor. It is highly recommended to construct a paragraph with a
60   // ParagraphBuilder.
61   ParagraphTxt();
62 
63   virtual ~ParagraphTxt();
64 
65   // Minikin Layout doLayout() and LineBreaker addStyleRun() has an
66   // O(N^2) (according to benchmarks) time complexity where N is the total
67   // number of characters. However, this is not significant for reasonably sized
68   // paragraphs. It is currently recommended to break up very long paragraphs
69   // (10k+ characters) to ensure speedy layout.
70   virtual void Layout(double width) override;
71 
72   virtual void Paint(SkCanvas* canvas, double x, double y) override;
73 
74   // Getter for paragraph_style_.
75   const ParagraphStyle& GetParagraphStyle() const;
76 
77   // Returns the number of characters/unicode characters. AKA text_.size()
78   size_t TextSize() const;
79 
80   double GetHeight() override;
81 
82   double GetMaxWidth() override;
83 
84   double GetLongestLine() override;
85 
86   double GetAlphabeticBaseline() override;
87 
88   double GetIdeographicBaseline() override;
89 
90   double GetMaxIntrinsicWidth() override;
91 
92   // Currently, calculated similarly to as GetLayoutWidth(), however this is not
93   // necessarily 100% correct in all cases.
94   double GetMinIntrinsicWidth() override;
95   void SetIndents(const std::vector<float>& indents);
96   std::vector<TextBox> GetRectsForRange(
97       size_t start,
98       size_t end,
99       RectHeightStyle rect_height_style,
100       RectWidthStyle rect_width_style) override;
101 
102   PositionWithAffinity GetGlyphPositionAtCoordinate(double dx,
103                                                     double dy) override;
104 
105   PositionWithAffinity GetGlyphPositionAtCoordinateWithCluster(double dx,
106                                                                double dy) override;
107 
108   std::vector<Paragraph::TextBox> GetRectsForPlaceholders() override;
109 
110   Range<size_t> GetWordBoundary(size_t offset) override;
111 
112   // Returns the number of lines the paragraph takes up. If the text exceeds the
113   // amount width and maxlines provides, Layout() truncates the extra text from
114   // the layout and this will return the max lines allowed.
115   size_t GetLineCount();
116 
117   bool DidExceedMaxLines() override;
118 
119   // Sets the needs_layout_ to dirty. When Layout() is called, a new Layout will
120   // be performed when this is set to true. Can also be used to prevent a new
121   // Layout from being calculated by setting to false.
122   void SetDirty(bool dirty = true);
123 
124  private:
125   friend class ParagraphBuilderTxt;
126   FRIEND_TEST(ParagraphTest, SimpleParagraph);
127   FRIEND_TEST(ParagraphTest, SimpleRedParagraph);
128   FRIEND_TEST(ParagraphTest, RainbowParagraph);
129   FRIEND_TEST(ParagraphTest, DefaultStyleParagraph);
130   FRIEND_TEST(ParagraphTest, BoldParagraph);
131   FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, LeftAlignParagraph);
132   FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, RightAlignParagraph);
133   FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, CenterAlignParagraph);
134   FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, JustifyAlignParagraph);
135   FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, JustifyRTL);
136   FRIEND_TEST(ParagraphTest, DecorationsParagraph);
137   FRIEND_TEST(ParagraphTest, ItalicsParagraph);
138   FRIEND_TEST(ParagraphTest, ChineseParagraph);
139   FRIEND_TEST(ParagraphTest, DISABLED_ArabicParagraph);
140   FRIEND_TEST(ParagraphTest, SpacingParagraph);
141   FRIEND_TEST(ParagraphTest, LongWordParagraph);
142   FRIEND_TEST(ParagraphTest, KernScaleParagraph);
143   FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, NewlineParagraph);
144   FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, EmojiParagraph);
145   FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, EmojiMultiLineRectsParagraph);
146   FRIEND_TEST(ParagraphTest, HyphenBreakParagraph);
147   FRIEND_TEST(ParagraphTest, RepeatLayoutParagraph);
148   FRIEND_TEST(ParagraphTest, Ellipsize);
149   FRIEND_TEST(ParagraphTest, UnderlineShiftParagraph);
150   FRIEND_TEST(ParagraphTest, WavyDecorationParagraph);
151   FRIEND_TEST(ParagraphTest, SimpleShadow);
152   FRIEND_TEST(ParagraphTest, ComplexShadow);
153   FRIEND_TEST(ParagraphTest, FontFallbackParagraph);
154   FRIEND_TEST(ParagraphTest, InlinePlaceholder0xFFFCParagraph);
155   FRIEND_TEST(ParagraphTest, FontFeaturesParagraph);
156 
157   // Starting data to layout.
158   std::vector<uint16_t> text_;
159   std::vector<float> indents_;
160   // A vector of PlaceholderRuns, which detail the sizes, positioning and break
161   // behavior of the empty spaces to leave. Each placeholder span corresponds to
162   // a 0xFFFC (object replacement character) in text_, which indicates the
163   // position in the text where the placeholder will occur. There should be an
164   // equal number of 0xFFFC characters and elements in this vector.
165   std::vector<PlaceholderRun> inline_placeholders_;
166   // The indexes of the boxes that correspond to an inline placeholder.
167   std::vector<size_t> inline_placeholder_boxes_;
168   // The indexes of instances of 0xFFFC that correspond to placeholders. This is
169   // necessary since the user may pass in manually entered 0xFFFC values using
170   // AddText().
171   std::unordered_set<size_t> obj_replacement_char_indexes_;
172   StyledRuns runs_;
173   ParagraphStyle paragraph_style_;
174   std::shared_ptr<FontCollection> font_collection_;
175 
176   minikin::LineBreaker breaker_;
177   mutable std::unique_ptr<icu::BreakIterator> word_breaker_;
178 
179   struct LineRange {
LineRangeLineRange180     LineRange(size_t s, size_t e, size_t eew, size_t ein, bool h)
181         : start(s),
182           end(e),
183           end_excluding_whitespace(eew),
184           end_including_newline(ein),
185           hard_break(h) {}
186     size_t start, end;
187     size_t end_excluding_whitespace;
188     size_t end_including_newline;
189     bool hard_break;
190   };
191   std::vector<LineRange> line_ranges_;
192   std::vector<double> line_widths_;
193 
194   // Stores the result of Layout().
195   std::vector<PaintRecord> records_;
196 
197   std::vector<double> line_heights_;
198   std::vector<double> line_baselines_;
199   bool did_exceed_max_lines_;
200 
201   // Strut metrics of zero will have no effect on the layout.
202   struct StrutMetrics {
203     double ascent = 0;  // Positive value to keep signs clear.
204     double descent = 0;
205     double leading = 0;
206     double half_leading = 0;
207     double line_height = 0;
208     bool force_strut = false;
209   };
210 
211   StrutMetrics strut_;
212 
213   // Metrics for use in GetRectsForRange(...);
214   // Per-line max metrics over all runs in a given line.
215   std::vector<SkScalar> line_max_spacings_;
216   std::vector<SkScalar> line_max_descent_;
217   std::vector<SkScalar> line_max_ascent_;
218   // Overall left and right extremes over all lines.
219   double max_right_;
220   double min_left_;
221 
222   class BidiRun {
223    public:
224     // Constructs a BidiRun with is_ghost defaulted to false.
BidiRun(size_t s,size_t e,TextDirection d,const TextStyle & st)225     BidiRun(size_t s, size_t e, TextDirection d, const TextStyle& st)
226         : start_(s), end_(e), direction_(d), style_(&st), is_ghost_(false) {}
227 
228     // Constructs a BidiRun with a custom is_ghost flag.
BidiRun(size_t s,size_t e,TextDirection d,const TextStyle & st,bool is_ghost)229     BidiRun(size_t s,
230             size_t e,
231             TextDirection d,
232             const TextStyle& st,
233             bool is_ghost)
234         : start_(s), end_(e), direction_(d), style_(&st), is_ghost_(is_ghost) {}
235 
236     // Constructs a placeholder bidi run.
BidiRun(size_t s,size_t e,TextDirection d,const TextStyle & st,PlaceholderRun & placeholder)237     BidiRun(size_t s,
238             size_t e,
239             TextDirection d,
240             const TextStyle& st,
241             PlaceholderRun& placeholder)
242         : start_(s),
243           end_(e),
244           direction_(d),
245           style_(&st),
246           placeholder_run_(&placeholder) {}
247 
start()248     size_t start() const { return start_; }
end()249     size_t end() const { return end_; }
size()250     size_t size() const { return end_ - start_; }
direction()251     TextDirection direction() const { return direction_; }
style()252     const TextStyle& style() const { return *style_; }
placeholder_run()253     PlaceholderRun* placeholder_run() const { return placeholder_run_; }
is_rtl()254     bool is_rtl() const { return direction_ == TextDirection::rtl; }
255     // Tracks if the run represents trailing whitespace.
is_ghost()256     bool is_ghost() const { return is_ghost_; }
is_placeholder_run()257     bool is_placeholder_run() const { return placeholder_run_ != nullptr; }
258 
259    private:
260     size_t start_, end_;
261     TextDirection direction_;
262     const TextStyle* style_;
263     bool is_ghost_;
264     PlaceholderRun* placeholder_run_ = nullptr;
265   };
266 
267   struct GlyphPosition {
268     Range<size_t> code_units;
269     Range<double> x_pos;
270     // Tracks the cluster that this glyph position belongs to. For example, in
271     // extended emojis, multiple glyph positions will have the same cluster. The
272     // cluster can be used as a key to distinguish between codepoints that
273     // contribute to the drawing of a single glyph.
274     size_t cluster;
275 
276     GlyphPosition(double x_start,
277                   double x_advance,
278                   size_t code_unit_index,
279                   size_t code_unit_width,
280                   size_t cluster);
281 
282     void Shift(double delta);
283   };
284 
285   struct GlyphLine {
286     // Glyph positions sorted by x coordinate.
287     const std::vector<GlyphPosition> positions;
288     const size_t total_code_units;
289 
290     GlyphLine(std::vector<GlyphPosition>&& p, size_t tcu);
291   };
292 
293   struct CodeUnitRun {
294     // Glyph positions sorted by code unit index.
295     std::vector<GlyphPosition> positions;
296     Range<size_t> code_units;
297     Range<double> x_pos;
298     size_t line_number;
299     SkFontMetrics font_metrics;
300     TextDirection direction;
301     const PlaceholderRun* placeholder_run;
302 
303     CodeUnitRun(std::vector<GlyphPosition>&& p,
304                 Range<size_t> cu,
305                 Range<double> x,
306                 size_t line,
307                 const SkFontMetrics& metrics,
308                 TextDirection dir,
309                 const PlaceholderRun* placeholder);
310 
311     void Shift(double delta);
312   };
313 
314   // Holds the laid out x positions of each glyph.
315   std::vector<GlyphLine> glyph_lines_;
316 
317   // Holds the positions of each range of code units in the text.
318   // Sorted in code unit index order.
319   std::vector<CodeUnitRun> code_unit_runs_;
320   // Holds the positions of the inline placeholders.
321   std::vector<CodeUnitRun> inline_placeholder_code_unit_runs_;
322 
323   // The max width of the paragraph as provided in the most recent Layout()
324   // call.
325   double width_ = -1.0f;
326   double longest_line_ = -1.0f;
327   double max_intrinsic_width_ = 0;
328   double min_intrinsic_width_ = 0;
329   double alphabetic_baseline_ = FLT_MAX;
330   double ideographic_baseline_ = FLT_MAX;
331 
332   bool needs_layout_ = true;
333 
334   struct WaveCoordinates {
335     double x_start;
336     double y_start;
337     double x_end;
338     double y_end;
339 
WaveCoordinatesWaveCoordinates340     WaveCoordinates(double x_s, double y_s, double x_e, double y_e)
341         : x_start(x_s), y_start(y_s), x_end(x_e), y_end(y_e) {}
342   };
343 
344   // Passes in the text and Styled Runs. text_ and runs_ will later be passed
345   // into breaker_ in InitBreaker(), which is called in Layout().
346   void SetText(std::vector<uint16_t> text, StyledRuns runs);
347 
348   void SetParagraphStyle(const ParagraphStyle& style);
349 
350   void SetFontCollection(std::shared_ptr<FontCollection> font_collection);
351 
352   void SetInlinePlaceholders(
353       std::vector<PlaceholderRun> inline_placeholders,
354       std::unordered_set<size_t> obj_replacement_char_indexes);
355 
356   // Break the text into lines.
357   bool ComputeLineBreaks();
358 
359   // Break the text into runs based on LTR/RTL text direction.
360   bool ComputeBidiRuns(std::vector<BidiRun>* result);
361 
362   // Calculates and populates strut based on paragraph_style_ strut info.
363   void ComputeStrut(StrutMetrics* strut, SkFont& font);
364 
365   // Adjusts the ascent and descent based on the existence and type of
366   // placeholder. This method sets the proper metrics to achieve the different
367   // PlaceholderAlignment options.
368   void ComputePlaceholder(PlaceholderRun* placeholder_run,
369                           double& ascent,
370                           double& descent);
371 
372   bool IsStrutValid() const;
373 
374   // Calculate the starting X offset of a line based on the line's width and
375   // alignment.
376   double GetLineXOffset(double line_total_advance,
377                         size_t line_number,
378                         size_t line_limit);
379 
380   // Creates and draws the decorations onto the canvas.
381   void PaintDecorations(SkCanvas* canvas,
382                         const PaintRecord& record,
383                         SkPoint base_offset);
384 
385   // Computes the beziers for a wavy decoration. The results will be
386   // applied to path.
387   void ComputeWavyDecoration(SkPath& path,
388                              double x,
389                              double y,
390                              double width,
391                              double thickness);
392 
393   // Draws the background onto the canvas.
394   void PaintBackground(SkCanvas* canvas,
395                        const PaintRecord& record,
396                        SkPoint base_offset);
397 
398   // Draws the shadows onto the canvas.
399   void PaintShadow(SkCanvas* canvas, const PaintRecord& record, SkPoint offset);
400 
401   // Obtain a Minikin font collection matching this text style.
402   std::shared_ptr<minikin::FontCollection> GetMinikinFontCollectionForStyle(
403       const TextStyle& style);
404 
405   // Get a default SkTypeface for a text style.
406   sk_sp<SkTypeface> GetDefaultSkiaTypeface(const TextStyle& style);
407 
408   FML_DISALLOW_COPY_AND_ASSIGN(ParagraphTxt);
409 };
410 
411 }  // namespace txt
412 
413 #endif  // LIB_TXT_SRC_PARAGRAPH_TXT_H_
414