From 1c108c04dddfe68539366b76fc3c7aca2f187f7b Mon Sep 17 00:00:00 2001 From: Gary Qian Date: Wed, 9 Aug 2017 11:02:03 -0700 Subject: [PATCH] Use SkPath for wavy decorations for x5 improvements in performance. Change-Id: I371f525bb91c2d415d6af4f8ff047f43678e5ac3 --- engine/src/flutter/src/paragraph.cc | 293 ++++++++++++++-------------- engine/src/flutter/src/paragraph.h | 9 - 2 files changed, 144 insertions(+), 158 deletions(-) diff --git a/engine/src/flutter/src/paragraph.cc b/engine/src/flutter/src/paragraph.cc index 9a0692ea0c..34bb122b77 100644 --- a/engine/src/flutter/src/paragraph.cc +++ b/engine/src/flutter/src/paragraph.cc @@ -630,162 +630,157 @@ void Paragraph::PaintDecorations(SkCanvas* canvas, double y, size_t record_index) { PaintRecord& record = records_[record_index]; - if (record.style().decoration != TextDecoration::kNone) { - const SkPaint::FontMetrics& metrics = record.metrics(); - SkPaint paint; - paint.setStyle(SkPaint::kStroke_Style); - if (record.style().decoration_color == SK_ColorTRANSPARENT) { - paint.setColor(record.style().color); - } else { - paint.setColor(record.style().decoration_color); + if (record.style().decoration == TextDecoration::kNone) + return; + + const SkPaint::FontMetrics& metrics = record.metrics(); + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + if (record.style().decoration_color == SK_ColorTRANSPARENT) { + paint.setColor(record.style().color); + } else { + paint.setColor(record.style().decoration_color); + } + paint.setAntiAlias(true); + + // This is set to 2 for the double line style + int decoration_count = 1; + + // Filled when drawing wavy decorations. + SkPath path; + + double width = 0; + if (paragraph_style_.text_align == TextAlign::justify && + record.line() != lines_ - 1) { + width = width_; + } else { + width = record.GetRunWidth(); + } + + paint.setStrokeWidth( + (SkToBool(metrics.fFlags & SkPaint::FontMetrics::FontMetricsFlags:: + kUnderlineThicknessIsValid_Flag)) + ? metrics.fUnderlineThickness * + record.style().decoration_thickness_multiplier + // Backup value if the fUnderlineThickness metric is not available: + // Divide by 14pt as it is the default size. + : record.style().font_size / 14.0f * + record.style().decoration_thickness_multiplier); + + // Setup the decorations. + switch (record.style().decoration_style) { + case TextDecorationStyle::kSolid: { + break; } - paint.setAntiAlias(true); - - // This is set to 2 for the double line style - int decoration_count = 1; - - // Filled when drawing wavy decorations. - std::vector wave_coords; - - double width = 0; - if (paragraph_style_.text_align == TextAlign::justify && - record.line() != lines_ - 1) { - width = width_; - } else { - width = record.GetRunWidth(); + case TextDecorationStyle::kDouble: { + decoration_count = 2; + break; } - - paint.setStrokeWidth( - (SkToBool(metrics.fFlags & SkPaint::FontMetrics::FontMetricsFlags:: - kUnderlineThicknessIsValid_Flag)) - ? metrics.fUnderlineThickness - // Backup value if the fUnderlineThickness metric is not available: - // Divide by 14pt as it is the default size. - : record.style().font_size / 14.0f * - record.style().decoration_thickness_multiplier); - - // Setup the decorations. - switch (record.style().decoration_style) { - case TextDecorationStyle::kSolid: { - break; - } - case TextDecorationStyle::kDouble: { - decoration_count = 2; - break; - } - // Note: the intervals are scaled by the thickness of the line, so it is - // possible to change spacing by changing the decoration_thickness - // property of TextStyle. - case TextDecorationStyle::kDotted: { - // Divide by 14pt as it is the default size. - const float scale = record.style().font_size / 14.0f; - const SkScalar intervals[] = {1.0f * scale, 1.5f * scale, 1.0f * scale, - 1.5f * scale}; - size_t count = sizeof(intervals) / sizeof(intervals[0]); - paint.setPathEffect(SkPathEffect::MakeCompose( - SkDashPathEffect::Make(intervals, count, 0.0f), - SkDiscretePathEffect::Make(0, 0))); - break; - } - // Note: the intervals are scaled by the thickness of the line, so it is - // possible to change spacing by changing the decoration_thickness - // property of TextStyle. - case TextDecorationStyle::kDashed: { - // Divide by 14pt as it is the default size. - const float scale = record.style().font_size / 14.0f; - const SkScalar intervals[] = {4.0f * scale, 2.0f * scale, 4.0f * scale, - 2.0f * scale}; - size_t count = sizeof(intervals) / sizeof(intervals[0]); - paint.setPathEffect(SkPathEffect::MakeCompose( - SkDashPathEffect::Make(intervals, count, 0.0f), - SkDiscretePathEffect::Make(0, 0))); - break; - } - case TextDecorationStyle::kWavy: { - int wave_count = 0; - double x_start = 0; - double y_top = -metrics.fUnderlineThickness; - double y_bottom = metrics.fUnderlineThickness; - while (x_start + metrics.fUnderlineThickness * 2 < width) { - wave_coords.push_back( - WaveCoordinates(x_start, wave_count % 2 == 0 ? y_bottom : y_top, - x_start + metrics.fUnderlineThickness * 2, - wave_count % 2 == 0 ? y_top : y_bottom)); - x_start += metrics.fUnderlineThickness * 2; - ++wave_count; - } - break; - } + // Note: the intervals are scaled by the thickness of the line, so it is + // possible to change spacing by changing the decoration_thickness + // property of TextStyle. + case TextDecorationStyle::kDotted: { + // Divide by 14pt as it is the default size. + const float scale = record.style().font_size / 14.0f; + const SkScalar intervals[] = {1.0f * scale, 1.5f * scale, 1.0f * scale, + 1.5f * scale}; + size_t count = sizeof(intervals) / sizeof(intervals[0]); + paint.setPathEffect(SkPathEffect::MakeCompose( + SkDashPathEffect::Make(intervals, count, 0.0f), + SkDiscretePathEffect::Make(0, 0))); + break; } - - // Draw the decorations. - // Use a for loop for "kDouble" decoration style - for (int i = 0; i < decoration_count; i++) { - double y_offset = - i * metrics.fUnderlineThickness * kDoubleDecorationSpacing; - double y_offset_original = y_offset; - // Underline - if (record.style().decoration & 0x1) { - y_offset += - (SkToBool(metrics.fFlags & SkPaint::FontMetrics::FontMetricsFlags:: - kUnderlinePositionIsValid_Flag)) - ? metrics.fUnderlinePosition - : metrics.fUnderlineThickness; - if (record.style().decoration_style != TextDecorationStyle::kWavy) - canvas->drawLine(x, y + y_offset, x + width, y + y_offset, paint); - else - PaintWavyDecoration(canvas, wave_coords, paint, x, y, y_offset, - width); - y_offset = y_offset_original; - } - // Overline - if (record.style().decoration & 0x2) { - y_offset -= metrics.fAscent; - if (record.style().decoration_style != TextDecorationStyle::kWavy) - canvas->drawLine(x, y - y_offset, x + width, y - y_offset, paint); - else - PaintWavyDecoration(canvas, wave_coords, paint, x, y, -y_offset, - width); - y_offset = y_offset_original; - } - // Strikethrough - if (record.style().decoration & 0x4) { - if (SkToBool(metrics.fFlags & SkPaint::FontMetrics::FontMetricsFlags:: - kStrikeoutThicknessIsValid_Flag)) - paint.setStrokeWidth(metrics.fStrikeoutThickness * - record.style().decoration_thickness_multiplier); - // Make sure the double line is "centered" vertically. - y_offset += (decoration_count - 1.0) * metrics.fUnderlineThickness * - kDoubleDecorationSpacing / -2.0; - y_offset += - (SkToBool(metrics.fFlags & SkPaint::FontMetrics::FontMetricsFlags:: - kStrikeoutThicknessIsValid_Flag)) - ? metrics.fStrikeoutPosition - // Backup value if the strikeoutposition metric is not - // available: - : metrics.fXHeight / -2.0; - if (record.style().decoration_style != TextDecorationStyle::kWavy) - canvas->drawLine(x, y + y_offset, x + width, y + y_offset, paint); - else - PaintWavyDecoration(canvas, wave_coords, paint, x, y, y_offset, - width); - y_offset = y_offset_original; + // Note: the intervals are scaled by the thickness of the line, so it is + // possible to change spacing by changing the decoration_thickness + // property of TextStyle. + case TextDecorationStyle::kDashed: { + // Divide by 14pt as it is the default size. + const float scale = record.style().font_size / 14.0f; + const SkScalar intervals[] = {4.0f * scale, 2.0f * scale, 4.0f * scale, + 2.0f * scale}; + size_t count = sizeof(intervals) / sizeof(intervals[0]); + paint.setPathEffect(SkPathEffect::MakeCompose( + SkDashPathEffect::Make(intervals, count, 0.0f), + SkDiscretePathEffect::Make(0, 0))); + break; + } + case TextDecorationStyle::kWavy: { + int wave_count = 0; + double x_start = 0; + double wavelength = metrics.fUnderlineThickness * + record.style().decoration_thickness_multiplier; + path.moveTo(x, y); + while (x_start + wavelength * 2 < width) { + path.rQuadTo(wavelength, wave_count % 2 != 0 ? wavelength : -wavelength, + wavelength * 2, 0); + x_start += wavelength * 2; + ++wave_count; } + break; } } -} -void Paragraph::PaintWavyDecoration(SkCanvas* canvas, - std::vector wave_coords, - SkPaint paint, - double x, - double y, - double y_offset, - double width) { - for (size_t i = 0; i < wave_coords.size(); ++i) { - WaveCoordinates coords = wave_coords[i]; - canvas->drawLine(x + coords.x_start, y + y_offset + coords.y_start, - x + coords.x_end, y + y_offset + coords.y_end, paint); + // Draw the decorations. + // Use a for loop for "kDouble" decoration style + for (int i = 0; i < decoration_count; i++) { + double y_offset = + i * metrics.fUnderlineThickness * kDoubleDecorationSpacing; + double y_offset_original = y_offset; + // Underline + if (record.style().decoration & 0x1) { + y_offset += + (SkToBool(metrics.fFlags & SkPaint::FontMetrics::FontMetricsFlags:: + kUnderlinePositionIsValid_Flag)) + ? metrics.fUnderlinePosition + : metrics.fUnderlineThickness; + if (record.style().decoration_style != TextDecorationStyle::kWavy) { + canvas->drawLine(x, y + y_offset, x + width, y + y_offset, paint); + } else { + SkPath offsetPath = path; + offsetPath.offset(0, y_offset); + canvas->drawPath(offsetPath, paint); + } + y_offset = y_offset_original; + } + // Overline + if (record.style().decoration & 0x2) { + // We subtract fAscent here because for double overlines, we want the + // second line to be above, not below the first. + y_offset -= metrics.fAscent; + if (record.style().decoration_style != TextDecorationStyle::kWavy) { + canvas->drawLine(x, y - y_offset, x + width, y - y_offset, paint); + } else { + SkPath offsetPath = path; + offsetPath.offset(0, -y_offset); + canvas->drawPath(offsetPath, paint); + } + y_offset = y_offset_original; + } + // Strikethrough + if (record.style().decoration & 0x4) { + if (SkToBool(metrics.fFlags & SkPaint::FontMetrics::FontMetricsFlags:: + kStrikeoutThicknessIsValid_Flag)) + paint.setStrokeWidth(metrics.fStrikeoutThickness * + record.style().decoration_thickness_multiplier); + // Make sure the double line is "centered" vertically. + y_offset += (decoration_count - 1.0) * metrics.fUnderlineThickness * + kDoubleDecorationSpacing / -2.0; + y_offset += + (SkToBool(metrics.fFlags & SkPaint::FontMetrics::FontMetricsFlags:: + kStrikeoutThicknessIsValid_Flag)) + ? metrics.fStrikeoutPosition + // Backup value if the strikeoutposition metric is not + // available: + : metrics.fXHeight / -2.0; + if (record.style().decoration_style != TextDecorationStyle::kWavy) { + canvas->drawLine(x, y + y_offset, x + width, y + y_offset, paint); + } else { + SkPath offsetPath = path; + offsetPath.offset(0, y_offset); + canvas->drawPath(offsetPath, paint); + } + y_offset = y_offset_original; + } } } diff --git a/engine/src/flutter/src/paragraph.h b/engine/src/flutter/src/paragraph.h index 614e50197b..e62c4a1d29 100644 --- a/engine/src/flutter/src/paragraph.h +++ b/engine/src/flutter/src/paragraph.h @@ -242,15 +242,6 @@ class Paragraph { double y, size_t record_index); - // Calculates wavy decorations and Draws them onto the canvas. - void PaintWavyDecoration(SkCanvas* canvas, - std::vector wave_coords, - SkPaint paint, - double x, - double y, - double y_offset, - double width); - void CalculateIntrinsicWidths(); FTL_DISALLOW_COPY_AND_ASSIGN(Paragraph);