blob: 09191308682116c8f6107112df5399d24032e0d3 [file] [log] [blame]
/*
* 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/LinuxUtils.h"
#include "utils/MacUtils.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, SimpleParagraphSmall);
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, InlinePlaceholderLongestLine);
FRIEND_TEST_LINUX_ONLY(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_LINUX_ONLY(ParagraphTest, KernScaleParagraph);
FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, NewlineParagraph);
FRIEND_TEST_LINUX_ONLY(ParagraphTest, EmojiParagraph);
FRIEND_TEST_LINUX_ONLY(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);
FRIEND_TEST(ParagraphTest, KhmerLineBreaker);
FRIEND_TEST(ParagraphTest, TextHeightBehaviorRectsParagraph);
// 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),
is_ghost_(false),
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;
GlyphPosition(double x_start,
double x_advance,
size_t code_unit_index,
size_t code_unit_width);
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;
void UpdateLineMetrics(const SkFontMetrics& metrics,
const TextStyle& style,
double& max_ascent,
double& max_descent,
double& max_unscaled_ascent,
PlaceholderRun* placeholder_run,
size_t line_number,
size_t line_limit);
// Calculate the starting X offset of a line based on the line's width and
// alignment.
double GetLineXOffset(double line_total_advance, 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_