| /* |
| * Copyright 2019 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. |
| */ |
| |
| #include "paragraph_skia.h" |
| |
| #include <algorithm> |
| #include <numeric> |
| |
| namespace txt { |
| |
| namespace skt = skia::textlayout; |
| using PaintID = skt::ParagraphPainter::PaintID; |
| |
| using namespace flutter; |
| |
| namespace { |
| |
| // Convert SkFontStyle::Weight values (ranging from 100-900) to txt::FontWeight |
| // values (ranging from 0-8). |
| txt::FontWeight GetTxtFontWeight(int font_weight) { |
| int txt_weight = (font_weight - 100) / 100; |
| txt_weight = std::clamp(txt_weight, static_cast<int>(txt::FontWeight::w100), |
| static_cast<int>(txt::FontWeight::w900)); |
| return static_cast<txt::FontWeight>(txt_weight); |
| } |
| |
| txt::FontStyle GetTxtFontStyle(SkFontStyle::Slant font_slant) { |
| return font_slant == SkFontStyle::Slant::kUpright_Slant |
| ? txt::FontStyle::normal |
| : txt::FontStyle::italic; |
| } |
| |
| class DisplayListParagraphPainter : public skt::ParagraphPainter { |
| public: |
| DisplayListParagraphPainter(DisplayListBuilder* builder, |
| const std::vector<DlPaint>& dl_paints) |
| : builder_(builder), dl_paints_(dl_paints) {} |
| |
| DlPaint toDlPaint(const DecorationStyle& decor_style, |
| DlDrawStyle draw_style = DlDrawStyle::kStroke) { |
| DlPaint paint; |
| paint.setDrawStyle(draw_style); |
| paint.setAntiAlias(true); |
| paint.setColor(decor_style.getColor()); |
| paint.setStrokeWidth(decor_style.getStrokeWidth()); |
| std::optional<DashPathEffect> dash_path_effect = |
| decor_style.getDashPathEffect(); |
| if (dash_path_effect) { |
| std::array<SkScalar, 2> intervals{dash_path_effect->fOnLength, |
| dash_path_effect->fOffLength}; |
| paint.setPathEffect( |
| DlDashPathEffect::Make(intervals.data(), intervals.size(), 0)); |
| } |
| return paint; |
| } |
| |
| void drawTextBlob(const sk_sp<SkTextBlob>& blob, |
| SkScalar x, |
| SkScalar y, |
| const SkPaintOrID& paint) override { |
| if (!blob) { |
| return; |
| } |
| size_t paint_id = std::get<PaintID>(paint); |
| FML_DCHECK(paint_id < dl_paints_.size()); |
| builder_->drawTextBlob(blob, x, y, dl_paints_[paint_id]); |
| } |
| |
| void drawTextShadow(const sk_sp<SkTextBlob>& blob, |
| SkScalar x, |
| SkScalar y, |
| SkColor color, |
| SkScalar blur_sigma) override { |
| if (!blob) { |
| return; |
| } |
| DlPaint paint; |
| paint.setColor(color); |
| if (blur_sigma > 0.0) { |
| DlBlurMaskFilter filter(SkBlurStyle::kNormal_SkBlurStyle, blur_sigma, |
| false); |
| paint.setMaskFilter(&filter); |
| } |
| builder_->drawTextBlob(blob, x, y, paint); |
| } |
| |
| void drawRect(const SkRect& rect, const SkPaintOrID& paint) override { |
| size_t paint_id = std::get<PaintID>(paint); |
| FML_DCHECK(paint_id < dl_paints_.size()); |
| builder_->drawRect(rect, dl_paints_[paint_id]); |
| } |
| |
| void drawFilledRect(const SkRect& rect, |
| const DecorationStyle& decor_style) override { |
| DlPaint paint = toDlPaint(decor_style, DlDrawStyle::kFill); |
| builder_->drawRect(rect, paint); |
| } |
| |
| void drawPath(const SkPath& path, |
| const DecorationStyle& decor_style) override { |
| builder_->drawPath(path, toDlPaint(decor_style)); |
| } |
| |
| void drawLine(SkScalar x0, |
| SkScalar y0, |
| SkScalar x1, |
| SkScalar y1, |
| const DecorationStyle& decor_style) override { |
| builder_->drawLine(SkPoint::Make(x0, y0), SkPoint::Make(x1, y1), |
| toDlPaint(decor_style)); |
| } |
| |
| void clipRect(const SkRect& rect) override { |
| builder_->clipRect(rect, SkClipOp::kIntersect, false); |
| } |
| |
| void translate(SkScalar dx, SkScalar dy) override { |
| builder_->translate(dx, dy); |
| } |
| |
| void save() override { builder_->save(); } |
| |
| void restore() override { builder_->restore(); } |
| |
| private: |
| DisplayListBuilder* builder_; |
| const std::vector<DlPaint>& dl_paints_; |
| }; |
| |
| } // anonymous namespace |
| |
| ParagraphSkia::ParagraphSkia(std::unique_ptr<skt::Paragraph> paragraph, |
| std::vector<flutter::DlPaint>&& dl_paints) |
| : paragraph_(std::move(paragraph)), dl_paints_(dl_paints) {} |
| |
| double ParagraphSkia::GetMaxWidth() { |
| return SkScalarToDouble(paragraph_->getMaxWidth()); |
| } |
| |
| double ParagraphSkia::GetHeight() { |
| return SkScalarToDouble(paragraph_->getHeight()); |
| } |
| |
| double ParagraphSkia::GetLongestLine() { |
| return SkScalarToDouble(paragraph_->getLongestLine()); |
| } |
| |
| std::vector<LineMetrics>& ParagraphSkia::GetLineMetrics() { |
| if (!line_metrics_) { |
| std::vector<skt::LineMetrics> metrics; |
| paragraph_->getLineMetrics(metrics); |
| |
| line_metrics_.emplace(); |
| line_metrics_styles_.reserve( |
| std::accumulate(metrics.begin(), metrics.end(), 0, |
| [](const int a, const skt::LineMetrics& b) { |
| return a + b.fLineMetrics.size(); |
| })); |
| |
| for (const skt::LineMetrics& skm : metrics) { |
| LineMetrics& txtm = line_metrics_->emplace_back( |
| skm.fStartIndex, skm.fEndIndex, skm.fEndExcludingWhitespaces, |
| skm.fEndIncludingNewline, skm.fHardBreak); |
| txtm.ascent = skm.fAscent; |
| txtm.descent = skm.fDescent; |
| txtm.unscaled_ascent = skm.fUnscaledAscent; |
| txtm.height = skm.fHeight; |
| txtm.width = skm.fWidth; |
| txtm.left = skm.fLeft; |
| txtm.baseline = skm.fBaseline; |
| txtm.line_number = skm.fLineNumber; |
| |
| for (const auto& sk_iter : skm.fLineMetrics) { |
| const skt::StyleMetrics& sk_style_metrics = sk_iter.second; |
| line_metrics_styles_.push_back(SkiaToTxt(*sk_style_metrics.text_style)); |
| txtm.run_metrics.emplace( |
| std::piecewise_construct, std::forward_as_tuple(sk_iter.first), |
| std::forward_as_tuple(&line_metrics_styles_.back(), |
| sk_style_metrics.font_metrics)); |
| } |
| } |
| } |
| |
| return line_metrics_.value(); |
| } |
| |
| double ParagraphSkia::GetMinIntrinsicWidth() { |
| return SkScalarToDouble(paragraph_->getMinIntrinsicWidth()); |
| } |
| |
| double ParagraphSkia::GetMaxIntrinsicWidth() { |
| return SkScalarToDouble(paragraph_->getMaxIntrinsicWidth()); |
| } |
| |
| double ParagraphSkia::GetAlphabeticBaseline() { |
| return SkScalarToDouble(paragraph_->getAlphabeticBaseline()); |
| } |
| |
| double ParagraphSkia::GetIdeographicBaseline() { |
| return SkScalarToDouble(paragraph_->getIdeographicBaseline()); |
| } |
| |
| bool ParagraphSkia::DidExceedMaxLines() { |
| return paragraph_->didExceedMaxLines(); |
| } |
| |
| void ParagraphSkia::Layout(double width) { |
| line_metrics_.reset(); |
| line_metrics_styles_.clear(); |
| paragraph_->layout(width); |
| } |
| |
| void ParagraphSkia::Paint(SkCanvas* canvas, double x, double y) { |
| paragraph_->paint(canvas, x, y); |
| } |
| |
| bool ParagraphSkia::Paint(DisplayListBuilder* builder, double x, double y) { |
| DisplayListParagraphPainter painter(builder, dl_paints_); |
| paragraph_->paint(&painter, x, y); |
| return true; |
| } |
| |
| std::vector<Paragraph::TextBox> ParagraphSkia::GetRectsForRange( |
| size_t start, |
| size_t end, |
| RectHeightStyle rect_height_style, |
| RectWidthStyle rect_width_style) { |
| std::vector<skt::TextBox> skia_boxes = paragraph_->getRectsForRange( |
| start, end, static_cast<skt::RectHeightStyle>(rect_height_style), |
| static_cast<skt::RectWidthStyle>(rect_width_style)); |
| |
| std::vector<Paragraph::TextBox> boxes; |
| for (const skt::TextBox& skia_box : skia_boxes) { |
| boxes.emplace_back(skia_box.rect, |
| static_cast<TextDirection>(skia_box.direction)); |
| } |
| |
| return boxes; |
| } |
| |
| std::vector<Paragraph::TextBox> ParagraphSkia::GetRectsForPlaceholders() { |
| std::vector<skt::TextBox> skia_boxes = paragraph_->getRectsForPlaceholders(); |
| |
| std::vector<Paragraph::TextBox> boxes; |
| for (const skt::TextBox& skia_box : skia_boxes) { |
| boxes.emplace_back(skia_box.rect, |
| static_cast<TextDirection>(skia_box.direction)); |
| } |
| |
| return boxes; |
| } |
| |
| Paragraph::PositionWithAffinity ParagraphSkia::GetGlyphPositionAtCoordinate( |
| double dx, |
| double dy) { |
| skt::PositionWithAffinity skia_pos = |
| paragraph_->getGlyphPositionAtCoordinate(dx, dy); |
| |
| return ParagraphSkia::PositionWithAffinity( |
| skia_pos.position, static_cast<Affinity>(skia_pos.affinity)); |
| } |
| |
| Paragraph::Range<size_t> ParagraphSkia::GetWordBoundary(size_t offset) { |
| skt::SkRange<size_t> range = paragraph_->getWordBoundary(offset); |
| return Paragraph::Range<size_t>(range.start, range.end); |
| } |
| |
| TextStyle ParagraphSkia::SkiaToTxt(const skt::TextStyle& skia) { |
| TextStyle txt; |
| |
| txt.color = skia.getColor(); |
| txt.decoration = static_cast<TextDecoration>(skia.getDecorationType()); |
| txt.decoration_color = skia.getDecorationColor(); |
| txt.decoration_style = |
| static_cast<TextDecorationStyle>(skia.getDecorationStyle()); |
| txt.decoration_thickness_multiplier = |
| SkScalarToDouble(skia.getDecorationThicknessMultiplier()); |
| txt.font_weight = GetTxtFontWeight(skia.getFontStyle().weight()); |
| txt.font_style = GetTxtFontStyle(skia.getFontStyle().slant()); |
| |
| txt.text_baseline = static_cast<TextBaseline>(skia.getTextBaseline()); |
| |
| for (const SkString& font_family : skia.getFontFamilies()) { |
| txt.font_families.emplace_back(font_family.c_str()); |
| } |
| |
| txt.font_size = SkScalarToDouble(skia.getFontSize()); |
| txt.letter_spacing = SkScalarToDouble(skia.getLetterSpacing()); |
| txt.word_spacing = SkScalarToDouble(skia.getWordSpacing()); |
| txt.height = SkScalarToDouble(skia.getHeight()); |
| |
| txt.locale = skia.getLocale().c_str(); |
| if (skia.hasBackground()) { |
| skt::ParagraphPainter::SkPaintOrID background = |
| skia.getBackgroundPaintOrID(); |
| if (std::holds_alternative<SkPaint>(background)) { |
| txt.background = std::get<SkPaint>(background); |
| } else if (std::holds_alternative<PaintID>(background)) { |
| txt.background_dl = dl_paints_[std::get<PaintID>(background)]; |
| } |
| } |
| if (skia.hasForeground()) { |
| skt::ParagraphPainter::SkPaintOrID foreground = |
| skia.getForegroundPaintOrID(); |
| if (std::holds_alternative<SkPaint>(foreground)) { |
| txt.foreground = std::get<SkPaint>(foreground); |
| } else if (std::holds_alternative<PaintID>(foreground)) { |
| txt.foreground_dl = dl_paints_[std::get<PaintID>(foreground)]; |
| } |
| } |
| |
| txt.text_shadows.clear(); |
| for (const skt::TextShadow& skia_shadow : skia.getShadows()) { |
| txt::TextShadow shadow; |
| shadow.offset = skia_shadow.fOffset; |
| shadow.blur_sigma = skia_shadow.fBlurSigma; |
| shadow.color = skia_shadow.fColor; |
| txt.text_shadows.emplace_back(shadow); |
| } |
| |
| return txt; |
| } |
| |
| } // namespace txt |