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