| /* |
| * Copyright (C) 2003, 2004, 2005, 2006, 2009 Apple Inc. All rights reserved. |
| * Copyright (C) 2013 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/platform/graphics/graphics_context.h" |
| |
| #include <memory> |
| |
| #include "base/logging.h" |
| #include "build/build_config.h" |
| #include "components/paint_preview/common/paint_preview_tracker.h" |
| #include "skia/ext/platform_canvas.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink.h" |
| #include "third_party/blink/renderer/platform/fonts/text_run_paint_info.h" |
| #include "third_party/blink/renderer/platform/geometry/float_rounded_rect.h" |
| #include "third_party/blink/renderer/platform/graphics/dark_mode_settings_builder.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h" |
| #include "third_party/blink/renderer/platform/graphics/interpolation_space.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_record.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_recorder.h" |
| #include "third_party/blink/renderer/platform/graphics/path.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/weborigin/kurl.h" |
| #include "third_party/blink/renderer/platform/wtf/math_extras.h" |
| #include "third_party/skia/include/core/SkAnnotation.h" |
| #include "third_party/skia/include/core/SkColorFilter.h" |
| #include "third_party/skia/include/core/SkData.h" |
| #include "third_party/skia/include/core/SkRRect.h" |
| #include "third_party/skia/include/core/SkRefCnt.h" |
| #include "third_party/skia/include/effects/SkHighContrastFilter.h" |
| #include "third_party/skia/include/effects/SkLumaColorFilter.h" |
| #include "third_party/skia/include/effects/SkTableColorFilter.h" |
| #include "third_party/skia/include/pathops/SkPathOps.h" |
| #include "third_party/skia/include/utils/SkNullCanvas.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/gfx/geometry/point_conversions.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/rect_f.h" |
| #include "ui/gfx/geometry/skia_conversions.h" |
| |
| // To avoid conflicts with the DrawText macro from the Windows SDK... |
| #undef DrawText |
| |
| namespace blink { |
| |
| namespace { |
| |
| float RoundDownThickness(float stroke_thickness) { |
| return std::max(floorf(stroke_thickness), 1.0f); |
| } |
| |
| gfx::RectF GetRectForTextLine(gfx::PointF pt, |
| float width, |
| float stroke_thickness) { |
| // Avoid anti-aliasing lines. Currently, these are always horizontal. |
| // Round to nearest pixel to match text and other content. |
| float y = floorf(pt.y() + 0.5f); |
| return gfx::RectF(pt.x(), y, width, stroke_thickness); |
| } |
| |
| std::pair<gfx::Point, gfx::Point> GetPointsForTextLine(gfx::PointF pt, |
| float width, |
| float stroke_thickness) { |
| int y = floorf(pt.y() + std::max<float>(stroke_thickness / 2.0f, 0.5f)); |
| return {gfx::Point(pt.x(), y), gfx::Point(pt.x() + width, y)}; |
| } |
| |
| Color DarkModeColor(GraphicsContext& context, |
| const Color& color, |
| const AutoDarkMode& auto_dark_mode) { |
| if (auto_dark_mode.enabled) { |
| return context.GetDarkModeFilter()->InvertColorIfNeeded( |
| color.Rgb(), auto_dark_mode.role); |
| } |
| return color; |
| } |
| |
| } // namespace |
| |
| // Helper class that copies |flags| only when dark mode is enabled. |
| // |
| // TODO(gilmanmh): Investigate removing const from |flags| in the calling |
| // methods and modifying the variable directly instead of copying it. |
| class GraphicsContext::DarkModeFlags final { |
| STACK_ALLOCATED(); |
| |
| public: |
| // This helper's lifetime should never exceed |flags|'. |
| DarkModeFlags(GraphicsContext* context, |
| const AutoDarkMode& auto_dark_mode, |
| const cc::PaintFlags& flags) { |
| if (auto_dark_mode.enabled) { |
| dark_mode_flags_ = context->GetDarkModeFilter()->ApplyToFlagsIfNeeded( |
| flags, auto_dark_mode.role); |
| if (dark_mode_flags_) { |
| flags_ = &dark_mode_flags_.value(); |
| return; |
| } |
| } |
| flags_ = &flags; |
| } |
| |
| bool applied_dark_mode() const { return !!dark_mode_flags_; } |
| |
| // NOLINTNEXTLINE(google-explicit-constructor) |
| operator const cc::PaintFlags&() const { return *flags_; } |
| |
| private: |
| const cc::PaintFlags* flags_; |
| absl::optional<cc::PaintFlags> dark_mode_flags_; |
| }; |
| |
| GraphicsContext::GraphicsContext(PaintController& paint_controller) |
| : paint_controller_(paint_controller) { |
| // FIXME: Do some tests to determine how many states are typically used, and |
| // allocate several here. |
| paint_state_stack_.push_back(std::make_unique<GraphicsContextState>()); |
| paint_state_ = paint_state_stack_.back().get(); |
| } |
| |
| GraphicsContext::~GraphicsContext() { |
| #if DCHECK_IS_ON() |
| if (!disable_destruction_checks_) { |
| DCHECK(!paint_state_index_); |
| DCHECK(!paint_state_->SaveCount()); |
| DCHECK(!layer_count_); |
| DCHECK(!SaveCount()); |
| } |
| #endif |
| } |
| |
| void GraphicsContext::CopyConfigFrom(GraphicsContext& other) { |
| SetPrintingMetafile(other.printing_metafile_); |
| SetPaintPreviewTracker(other.paint_preview_tracker_); |
| SetDeviceScaleFactor(other.device_scale_factor_); |
| SetPrinting(other.printing_); |
| } |
| |
| DarkModeFilter* GraphicsContext::GetDarkModeFilter() { |
| if (!dark_mode_filter_) { |
| dark_mode_filter_ = |
| std::make_unique<DarkModeFilter>(GetCurrentDarkModeSettings()); |
| } |
| return dark_mode_filter_.get(); |
| } |
| |
| void GraphicsContext::UpdateDarkModeSettingsForTest( |
| const DarkModeSettings& settings) { |
| dark_mode_filter_ = std::make_unique<DarkModeFilter>(settings); |
| } |
| |
| void GraphicsContext::Save() { |
| paint_state_->IncrementSaveCount(); |
| |
| DCHECK(canvas_); |
| canvas_->save(); |
| } |
| |
| void GraphicsContext::Restore() { |
| if (!paint_state_index_ && !paint_state_->SaveCount()) { |
| DLOG(ERROR) << "ERROR void GraphicsContext::restore() stack is empty"; |
| return; |
| } |
| |
| if (paint_state_->SaveCount()) { |
| paint_state_->DecrementSaveCount(); |
| } else { |
| paint_state_index_--; |
| paint_state_ = paint_state_stack_[paint_state_index_].get(); |
| } |
| |
| DCHECK(canvas_); |
| canvas_->restore(); |
| } |
| |
| #if DCHECK_IS_ON() |
| unsigned GraphicsContext::SaveCount() const { |
| // Each m_paintStateStack entry implies an additional save op |
| // (on top of its own saveCount), except for the first frame. |
| unsigned count = paint_state_index_; |
| DCHECK_GE(paint_state_stack_.size(), paint_state_index_); |
| for (unsigned i = 0; i <= paint_state_index_; ++i) |
| count += paint_state_stack_[i]->SaveCount(); |
| |
| return count; |
| } |
| #endif |
| |
| void GraphicsContext::SaveLayer(const SkRect* bounds, |
| const cc::PaintFlags* flags) { |
| DCHECK(canvas_); |
| canvas_->saveLayer(bounds, flags); |
| } |
| |
| void GraphicsContext::RestoreLayer() { |
| DCHECK(canvas_); |
| canvas_->restore(); |
| } |
| |
| void GraphicsContext::SetInDrawingRecorder(bool val) { |
| // Nested drawing recorers are not allowed. |
| DCHECK(!val || !in_drawing_recorder_); |
| in_drawing_recorder_ = val; |
| } |
| |
| void GraphicsContext::SetDOMNodeId(DOMNodeId new_node_id) { |
| DCHECK(NeedsDOMNodeId()); |
| if (canvas_) |
| canvas_->setNodeId(new_node_id); |
| |
| dom_node_id_ = new_node_id; |
| } |
| |
| DOMNodeId GraphicsContext::GetDOMNodeId() const { |
| DCHECK(NeedsDOMNodeId()); |
| return dom_node_id_; |
| } |
| |
| void GraphicsContext::SetDrawLooper(sk_sp<SkDrawLooper> draw_looper) { |
| MutableState()->SetDrawLooper(std::move(draw_looper)); |
| } |
| |
| SkColorFilter* GraphicsContext::GetColorFilter() const { |
| return ImmutableState()->GetColorFilter(); |
| } |
| |
| void GraphicsContext::SetColorFilter(ColorFilter color_filter) { |
| GraphicsContextState* state_to_set = MutableState(); |
| |
| // We only support one active color filter at the moment. If (when) this |
| // becomes a problem, we should switch to using color filter chains (Skia work |
| // in progress). |
| DCHECK(!state_to_set->GetColorFilter()); |
| state_to_set->SetColorFilter( |
| WebCoreColorFilterToSkiaColorFilter(color_filter)); |
| } |
| |
| void GraphicsContext::Concat(const SkMatrix& matrix) { |
| DCHECK(canvas_); |
| canvas_->concat(matrix); |
| } |
| |
| void GraphicsContext::BeginLayer(float opacity, |
| SkBlendMode xfermode, |
| const gfx::RectF* bounds, |
| ColorFilter color_filter, |
| sk_sp<PaintFilter> image_filter) { |
| cc::PaintFlags layer_flags; |
| layer_flags.setAlpha(static_cast<unsigned char>(opacity * 255)); |
| layer_flags.setBlendMode(xfermode); |
| layer_flags.setColorFilter(WebCoreColorFilterToSkiaColorFilter(color_filter)); |
| layer_flags.setImageFilter(std::move(image_filter)); |
| |
| if (bounds) { |
| SkRect sk_bounds = gfx::RectFToSkRect(*bounds); |
| SaveLayer(&sk_bounds, &layer_flags); |
| } else { |
| SaveLayer(nullptr, &layer_flags); |
| } |
| |
| #if DCHECK_IS_ON() |
| ++layer_count_; |
| #endif |
| } |
| |
| void GraphicsContext::EndLayer() { |
| RestoreLayer(); |
| |
| #if DCHECK_IS_ON() |
| DCHECK_GT(layer_count_--, 0); |
| #endif |
| } |
| |
| void GraphicsContext::BeginRecording(const gfx::RectF& bounds) { |
| DCHECK(!canvas_); |
| canvas_ = paint_recorder_.beginRecording(gfx::RectFToSkRect(bounds)); |
| if (printing_metafile_) |
| canvas_->SetPrintingMetafile(printing_metafile_); |
| if (paint_preview_tracker_) |
| canvas_->SetPaintPreviewTracker(paint_preview_tracker_); |
| } |
| |
| sk_sp<PaintRecord> GraphicsContext::EndRecording() { |
| sk_sp<PaintRecord> record = paint_recorder_.finishRecordingAsPicture(); |
| canvas_ = nullptr; |
| DCHECK(record); |
| return record; |
| } |
| |
| void GraphicsContext::DrawRecord(sk_sp<const PaintRecord> record) { |
| if (!record || !record->size()) |
| return; |
| |
| DCHECK(canvas_); |
| canvas_->drawPicture(std::move(record)); |
| } |
| |
| void GraphicsContext::CompositeRecord(sk_sp<PaintRecord> record, |
| const gfx::RectF& dest, |
| const gfx::RectF& src, |
| SkBlendMode op) { |
| if (!record) |
| return; |
| DCHECK(canvas_); |
| |
| cc::PaintFlags flags; |
| flags.setBlendMode(op); |
| |
| SkSamplingOptions sampling(cc::PaintFlags::FilterQualityToSkSamplingOptions( |
| static_cast<cc::PaintFlags::FilterQuality>(ImageInterpolationQuality()))); |
| canvas_->save(); |
| canvas_->concat( |
| SkMatrix::RectToRect(gfx::RectFToSkRect(src), gfx::RectFToSkRect(dest))); |
| canvas_->drawImage(PaintImageBuilder::WithDefault() |
| .set_paint_record(record, gfx::ToRoundedRect(src), |
| PaintImage::GetNextContentId()) |
| .set_id(PaintImage::GetNextId()) |
| .TakePaintImage(), |
| 0, 0, sampling, &flags); |
| canvas_->restore(); |
| } |
| |
| void GraphicsContext::DrawFocusRingPath(const SkPath& path, |
| const Color& color, |
| float width, |
| float corner_radius, |
| const AutoDarkMode& auto_dark_mode) { |
| DrawPlatformFocusRing(path, canvas_, |
| DarkModeColor(*this, color, auto_dark_mode).Rgb(), |
| width, corner_radius); |
| } |
| |
| void GraphicsContext::DrawFocusRingRect(const SkRRect& rrect, |
| const Color& color, |
| float width, |
| const AutoDarkMode& auto_dark_mode) { |
| DrawPlatformFocusRing( |
| rrect, canvas_, DarkModeColor(*this, color, auto_dark_mode).Rgb(), width); |
| } |
| |
| static void EnforceDotsAtEndpoints(GraphicsContext& context, |
| gfx::PointF& p1, |
| gfx::PointF& p2, |
| const int path_length, |
| const int width, |
| const cc::PaintFlags& flags, |
| const bool is_vertical_line, |
| const AutoDarkMode& auto_dark_mode) { |
| // For narrow lines, we always want integral dot and dash sizes, and start |
| // and end points, to prevent anti-aliasing from erasing the dot effect. |
| // For 1-pixel wide lines, we must make one end a dash. Otherwise we have |
| // a little more scope to distribute the error. But we never want to reduce |
| // the size of the end dots because doing so makes corners of all-dotted |
| // paths look odd. |
| // |
| // There is no way to give custom start and end dash sizes or gaps to Skia, |
| // so if we need non-uniform gaps we need to draw the start, and maybe the |
| // end dot ourselves, and move the line start (and end) to the start/end of |
| // the second dot. |
| DCHECK_LE(width, 3); // Width is max 3 according to StrokeIsDashed |
| int mod_4 = path_length % 4; |
| int mod_6 = path_length % 6; |
| // New start dot to be explicitly drawn, if needed, and the amount to grow the |
| // start dot and the offset for first gap. |
| bool use_start_dot = false; |
| int start_dot_growth = 0; |
| int start_line_offset = 0; |
| // New end dot to be explicitly drawn, if needed, and the amount to grow the |
| // second dot. |
| bool use_end_dot = false; |
| int end_dot_growth = 0; |
| if ((width == 1 && path_length % 2 == 0) || (width == 3 && mod_6 == 0)) { |
| // Cases where we add one pixel to the first dot. |
| use_start_dot = true; |
| start_dot_growth = 1; |
| start_line_offset = 1; |
| } |
| if ((width == 2 && (mod_4 == 0 || mod_4 == 1)) || |
| (width == 3 && (mod_6 == 1 || mod_6 == 2))) { |
| // Cases where we drop 1 pixel from the start gap |
| use_start_dot = true; |
| start_line_offset = -1; |
| } |
| if ((width == 2 && mod_4 == 0) || (width == 3 && mod_6 == 1)) { |
| // Cases where we drop 1 pixel from the end gap |
| use_end_dot = true; |
| } |
| if ((width == 2 && mod_4 == 3) || |
| (width == 3 && (mod_6 == 4 || mod_6 == 5))) { |
| // Cases where we add 1 pixel to the start gap |
| use_start_dot = true; |
| start_line_offset = 1; |
| } |
| if (width == 3 && mod_6 == 5) { |
| // Case where we add 1 pixel to the end gap and leave the end |
| // dot the same size. |
| use_end_dot = true; |
| } else if (width == 3 && mod_6 == 0) { |
| // Case where we add one pixel gap and one pixel to the dot at the end |
| use_end_dot = true; |
| end_dot_growth = 1; // Moves the larger end pt for this case |
| } |
| |
| if (use_start_dot || use_end_dot) { |
| cc::PaintFlags fill_flags; |
| fill_flags.setColor(flags.getColor()); |
| if (use_start_dot) { |
| SkRect start_dot; |
| if (is_vertical_line) { |
| start_dot.setLTRB(p1.x() - width / 2, p1.y(), |
| p1.x() + width - width / 2, |
| p1.y() + width + start_dot_growth); |
| p1.set_y(p1.y() + (2 * width + start_line_offset)); |
| } else { |
| start_dot.setLTRB(p1.x(), p1.y() - width / 2, |
| p1.x() + width + start_dot_growth, |
| p1.y() + width - width / 2); |
| p1.set_x(p1.x() + (2 * width + start_line_offset)); |
| } |
| context.DrawRect(start_dot, fill_flags, auto_dark_mode); |
| } |
| if (use_end_dot) { |
| SkRect end_dot; |
| if (is_vertical_line) { |
| end_dot.setLTRB(p2.x() - width / 2, p2.y() - width - end_dot_growth, |
| p2.x() + width - width / 2, p2.y()); |
| // Be sure to stop drawing before we get to the last dot |
| p2.set_y(p2.y() - (width + end_dot_growth + 1)); |
| } else { |
| end_dot.setLTRB(p2.x() - width - end_dot_growth, p2.y() - width / 2, |
| p2.x(), p2.y() + width - width / 2); |
| // Be sure to stop drawing before we get to the last dot |
| p2.set_x(p2.x() - (width + end_dot_growth + 1)); |
| } |
| context.DrawRect(end_dot, fill_flags, auto_dark_mode); |
| } |
| } |
| } |
| |
| void GraphicsContext::DrawLine(const gfx::Point& point1, |
| const gfx::Point& point2, |
| const AutoDarkMode& auto_dark_mode, |
| bool is_text_line, |
| const cc::PaintFlags* paint_flags) { |
| DCHECK(canvas_); |
| |
| StrokeStyle pen_style = GetStrokeStyle(); |
| if (pen_style == kNoStroke) |
| return; |
| |
| gfx::PointF p1 = gfx::PointF(point1); |
| gfx::PointF p2 = gfx::PointF(point2); |
| bool is_vertical_line = (p1.x() == p2.x()); |
| int width = roundf(StrokeThickness()); |
| |
| // We know these are vertical or horizontal lines, so the length will just |
| // be the sum of the displacement component vectors give or take 1 - |
| // probably worth the speed up of no square root, which also won't be exact. |
| gfx::Vector2dF disp = p2 - p1; |
| int length = SkScalarRoundToInt(disp.x() + disp.y()); |
| const DarkModeFlags flags(this, auto_dark_mode, |
| paint_flags |
| ? *paint_flags |
| : ImmutableState()->StrokeFlags(length, width)); |
| |
| if (pen_style == kDottedStroke) { |
| if (StrokeData::StrokeIsDashed(width, pen_style)) { |
| // When the length of the line is an odd multiple of the width, things |
| // work well because we get dots at each end of the line, but if the |
| // length is anything else, we get gaps or partial dots at the end of the |
| // line. Fix that by explicitly enforcing full dots at the ends of lines. |
| // Note that we don't enforce end points when it's text line as enforcing |
| // is to improve border line quality. |
| if (!is_text_line) { |
| EnforceDotsAtEndpoints(*this, p1, p2, length, width, flags, |
| is_vertical_line, auto_dark_mode); |
| } |
| } else { |
| // We draw thick dotted lines with 0 length dash strokes and round |
| // endcaps, producing circles. The endcaps extend beyond the line's |
| // endpoints, so move the start and end in. |
| if (is_vertical_line) { |
| p1.set_y(p1.y() + width / 2.f); |
| p2.set_y(p2.y() - width / 2.f); |
| } else { |
| p1.set_x(p1.x() + width / 2.f); |
| p2.set_x(p2.x() - width / 2.f); |
| } |
| } |
| } |
| |
| AdjustLineToPixelBoundaries(p1, p2, width); |
| canvas_->drawLine(p1.x(), p1.y(), p2.x(), p2.y(), flags); |
| } |
| |
| void GraphicsContext::DrawLineForText(const gfx::PointF& pt, |
| float width, |
| const AutoDarkMode& auto_dark_mode, |
| const cc::PaintFlags* paint_flags) { |
| if (width <= 0) |
| return; |
| |
| auto stroke_style = GetStrokeStyle(); |
| DCHECK_NE(stroke_style, kWavyStroke); |
| if (ShouldUseStrokeForTextLine(stroke_style)) { |
| auto [start, end] = GetPointsForTextLine(pt, width, StrokeThickness()); |
| DrawLine(start, end, auto_dark_mode, true, paint_flags); |
| } else { |
| if (paint_flags) { |
| // In SVG, we don't round down the thickness to an integer for better |
| // scaling behavior. See crbug.com/1270336. |
| SkRect r = |
| gfx::RectFToSkRect(GetRectForTextLine(pt, width, StrokeThickness())); |
| DrawRect(r, *paint_flags, auto_dark_mode); |
| } else { |
| cc::PaintFlags flags; |
| flags = ImmutableState()->FillFlags(); |
| // Text lines are drawn using the stroke color. |
| flags.setColor(StrokeColor().Rgb()); |
| SkRect r = gfx::RectFToSkRect( |
| GetRectForTextLine(pt, width, RoundDownThickness(StrokeThickness()))); |
| DrawRect(r, flags, auto_dark_mode); |
| } |
| } |
| } |
| |
| // Draws a filled rectangle with a stroked border. |
| void GraphicsContext::DrawRect(const gfx::Rect& rect, |
| const AutoDarkMode& auto_dark_mode) { |
| if (rect.IsEmpty()) |
| return; |
| |
| SkRect sk_rect = gfx::RectToSkRect(rect); |
| if (ImmutableState()->FillColor().Alpha()) |
| DrawRect(sk_rect, ImmutableState()->FillFlags(), auto_dark_mode); |
| |
| if (ImmutableState()->GetStrokeData().Style() != kNoStroke && |
| ImmutableState()->StrokeColor().Alpha()) { |
| // Stroke a width: 1 inset border |
| cc::PaintFlags flags(ImmutableState()->FillFlags()); |
| flags.setColor(StrokeColor().Rgb()); |
| flags.setStyle(cc::PaintFlags::kStroke_Style); |
| flags.setStrokeWidth(1); |
| |
| sk_rect.inset(0.5f, 0.5f); |
| DrawRect(sk_rect, flags, auto_dark_mode); |
| } |
| } |
| |
| void GraphicsContext::DrawText(const Font& font, |
| const TextRunPaintInfo& text_info, |
| const gfx::PointF& point, |
| const cc::PaintFlags& flags, |
| DOMNodeId node_id, |
| const AutoDarkMode& auto_dark_mode) { |
| font.DrawText(canvas_, text_info, point, device_scale_factor_, node_id, |
| DarkModeFlags(this, auto_dark_mode, flags), |
| printing_ ? Font::DrawType::kGlyphsAndClusters |
| : Font::DrawType::kGlyphsOnly); |
| } |
| |
| void GraphicsContext::DrawText(const Font& font, |
| const NGTextFragmentPaintInfo& text_info, |
| const gfx::PointF& point, |
| const cc::PaintFlags& flags, |
| DOMNodeId node_id, |
| const AutoDarkMode& auto_dark_mode) { |
| font.DrawText(canvas_, text_info, point, device_scale_factor_, node_id, |
| DarkModeFlags(this, auto_dark_mode, flags), |
| printing_ ? Font::DrawType::kGlyphsAndClusters |
| : Font::DrawType::kGlyphsOnly); |
| } |
| |
| template <typename DrawTextFunc> |
| void GraphicsContext::DrawTextPasses(const AutoDarkMode& auto_dark_mode, |
| const DrawTextFunc& draw_text) { |
| TextDrawingModeFlags mode_flags = TextDrawingMode(); |
| |
| if (mode_flags & kTextModeFill) { |
| const cc::PaintFlags& flags = ImmutableState()->FillFlags(); |
| DarkModeFlags dark_flags(this, auto_dark_mode, flags); |
| if (UNLIKELY(ShouldDrawDarkModeTextContrastOutline(flags, dark_flags))) { |
| cc::PaintFlags outline_flags(flags); |
| outline_flags.setStyle(cc::PaintFlags::kStroke_Style); |
| outline_flags.setStrokeWidth(1); |
| draw_text(outline_flags); |
| } |
| draw_text(dark_flags); |
| } |
| |
| if ((mode_flags & kTextModeStroke) && GetStrokeStyle() != kNoStroke && |
| StrokeThickness() > 0) { |
| cc::PaintFlags flags(ImmutableState()->StrokeFlags()); |
| if (mode_flags & kTextModeFill) { |
| // shadow was already applied during fill pass |
| flags.setLooper(nullptr); |
| } |
| draw_text(DarkModeFlags(this, auto_dark_mode, flags)); |
| } |
| } |
| |
| template <typename TextPaintInfo> |
| void GraphicsContext::DrawTextInternal(const Font& font, |
| const TextPaintInfo& text_info, |
| const gfx::PointF& point, |
| DOMNodeId node_id, |
| const AutoDarkMode& auto_dark_mode) { |
| DrawTextPasses(auto_dark_mode, [&](const cc::PaintFlags& flags) { |
| font.DrawText(canvas_, text_info, point, device_scale_factor_, node_id, |
| flags, |
| printing_ ? Font::DrawType::kGlyphsAndClusters |
| : Font::DrawType::kGlyphsOnly); |
| }); |
| } |
| |
| bool GraphicsContext::ShouldDrawDarkModeTextContrastOutline( |
| const cc::PaintFlags& original_flags, |
| const DarkModeFlags& dark_flags) const { |
| if (!dark_flags.applied_dark_mode()) |
| return false; |
| if (!GetCurrentDarkModeSettings().increase_text_contrast) |
| return false; |
| |
| // To avoid outlining all text, only draw an outline that improves contrast. |
| // 90000 represents a difference of roughly 175 in all three channels. |
| // TODO(pdr): Calculate a contrast ratio using luminance (see: |
| // https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html). |
| constexpr int kMinimumDifferenceSq = 90000; |
| Color dark_color(static_cast<const cc::PaintFlags&>(dark_flags).getColor()); |
| Color original_color(original_flags.getColor()); |
| return DifferenceSquared(dark_color, original_color) > kMinimumDifferenceSq; |
| } |
| |
| void GraphicsContext::DrawText(const Font& font, |
| const TextRunPaintInfo& text_info, |
| const gfx::PointF& point, |
| DOMNodeId node_id, |
| const AutoDarkMode& auto_dark_mode) { |
| DrawTextInternal(font, text_info, point, node_id, auto_dark_mode); |
| } |
| |
| void GraphicsContext::DrawText(const Font& font, |
| const NGTextFragmentPaintInfo& text_info, |
| const gfx::PointF& point, |
| DOMNodeId node_id, |
| const AutoDarkMode& auto_dark_mode) { |
| DrawTextInternal(font, text_info, point, node_id, auto_dark_mode); |
| } |
| |
| template <typename TextPaintInfo> |
| void GraphicsContext::DrawEmphasisMarksInternal( |
| const Font& font, |
| const TextPaintInfo& text_info, |
| const AtomicString& mark, |
| const gfx::PointF& point, |
| const AutoDarkMode& auto_dark_mode) { |
| DrawTextPasses(auto_dark_mode, [&font, &text_info, &mark, &point, |
| this](const cc::PaintFlags& flags) { |
| font.DrawEmphasisMarks(canvas_, text_info, mark, point, |
| device_scale_factor_, flags); |
| }); |
| } |
| |
| void GraphicsContext::DrawEmphasisMarks(const Font& font, |
| const TextRunPaintInfo& text_info, |
| const AtomicString& mark, |
| const gfx::PointF& point, |
| const AutoDarkMode& auto_dark_mode) { |
| DrawEmphasisMarksInternal(font, text_info, mark, point, auto_dark_mode); |
| } |
| |
| void GraphicsContext::DrawEmphasisMarks( |
| const Font& font, |
| const NGTextFragmentPaintInfo& text_info, |
| const AtomicString& mark, |
| const gfx::PointF& point, |
| const AutoDarkMode& auto_dark_mode) { |
| DrawEmphasisMarksInternal(font, text_info, mark, point, auto_dark_mode); |
| } |
| |
| void GraphicsContext::DrawBidiText( |
| const Font& font, |
| const TextRunPaintInfo& run_info, |
| const gfx::PointF& point, |
| const AutoDarkMode& auto_dark_mode, |
| Font::CustomFontNotReadyAction custom_font_not_ready_action) { |
| DrawTextPasses( |
| auto_dark_mode, [&font, &run_info, &point, custom_font_not_ready_action, |
| this](const cc::PaintFlags& flags) { |
| if (font.DrawBidiText(canvas_, run_info, point, |
| custom_font_not_ready_action, |
| device_scale_factor_, flags, |
| printing_ ? Font::DrawType::kGlyphsAndClusters |
| : Font::DrawType::kGlyphsOnly)) { |
| paint_controller_.SetTextPainted(); |
| } |
| }); |
| } |
| |
| void GraphicsContext::DrawHighlightForText(const Font& font, |
| const TextRun& run, |
| const gfx::PointF& point, |
| int h, |
| const Color& background_color, |
| const AutoDarkMode& auto_dark_mode, |
| int from, |
| int to) { |
| FillRect(font.SelectionRectForText(run, point, h, from, to), background_color, |
| auto_dark_mode); |
| } |
| |
| void GraphicsContext::DrawImage( |
| Image* image, |
| Image::ImageDecodingMode decode_mode, |
| const AutoDarkMode& auto_dark_mode, |
| const gfx::RectF& dest, |
| const gfx::RectF* src_ptr, |
| SkBlendMode op, |
| RespectImageOrientationEnum should_respect_image_orientation) { |
| if (!image) |
| return; |
| |
| const gfx::RectF src = src_ptr ? *src_ptr : gfx::RectF(image->Rect()); |
| |
| cc::PaintFlags image_flags = ImmutableState()->FillFlags(); |
| image_flags.setBlendMode(op); |
| image_flags.setColor(SK_ColorBLACK); |
| |
| SkSamplingOptions sampling = ComputeSamplingOptions(image, dest, src); |
| ImageDrawOptions draw_options( |
| auto_dark_mode.enabled ? GetDarkModeFilter() : nullptr, sampling, |
| should_respect_image_orientation, Image::kClampImageToSourceRect, |
| decode_mode, auto_dark_mode.enabled); |
| |
| image->Draw(canvas_, image_flags, dest, src, draw_options); |
| paint_controller_.SetImagePainted(); |
| } |
| |
| void GraphicsContext::DrawImageRRect( |
| Image* image, |
| Image::ImageDecodingMode decode_mode, |
| const AutoDarkMode& auto_dark_mode, |
| const FloatRoundedRect& dest, |
| const gfx::RectF& src_rect, |
| SkBlendMode op, |
| RespectImageOrientationEnum respect_orientation) { |
| if (!image) |
| return; |
| |
| if (!dest.IsRounded()) { |
| DrawImage(image, decode_mode, auto_dark_mode, dest.Rect(), &src_rect, op, |
| respect_orientation); |
| return; |
| } |
| |
| DCHECK(dest.IsRenderable()); |
| |
| const gfx::RectF visible_src = |
| IntersectRects(src_rect, gfx::RectF(image->Rect())); |
| if (dest.IsEmpty() || visible_src.IsEmpty()) |
| return; |
| |
| SkSamplingOptions sampling = |
| ComputeSamplingOptions(image, dest.Rect(), src_rect); |
| cc::PaintFlags image_flags = ImmutableState()->FillFlags(); |
| image_flags.setBlendMode(op); |
| image_flags.setColor(SK_ColorBLACK); |
| |
| ImageDrawOptions draw_options( |
| auto_dark_mode.enabled ? GetDarkModeFilter() : nullptr, sampling, |
| respect_orientation, Image::kClampImageToSourceRect, decode_mode, |
| auto_dark_mode.enabled); |
| |
| bool use_shader = (visible_src == src_rect) && |
| (respect_orientation == kDoNotRespectImageOrientation || |
| image->HasDefaultOrientation()); |
| if (use_shader) { |
| const SkMatrix local_matrix = SkMatrix::RectToRect( |
| gfx::RectFToSkRect(visible_src), gfx::RectFToSkRect(dest.Rect())); |
| use_shader = image->ApplyShader(image_flags, local_matrix, dest.Rect(), |
| src_rect, draw_options); |
| } |
| |
| if (use_shader) { |
| // Temporarily set filter-quality for the shader. <reed> |
| // Should be replaced with explicit sampling parameter passed to |
| // ApplyShader() |
| image_flags.setFilterQuality( |
| ComputeFilterQuality(image, dest.Rect(), src_rect)); |
| // Shader-based fast path. |
| canvas_->drawRRect(SkRRect(dest), image_flags); |
| } else { |
| // Clip-based fallback. |
| PaintCanvasAutoRestore auto_restore(canvas_, true); |
| canvas_->clipRRect(SkRRect(dest), image_flags.isAntiAlias()); |
| image->Draw(canvas_, image_flags, dest.Rect(), src_rect, draw_options); |
| } |
| |
| paint_controller_.SetImagePainted(); |
| } |
| |
| cc::PaintFlags::FilterQuality GraphicsContext::ComputeFilterQuality( |
| Image* image, |
| const gfx::RectF& dest, |
| const gfx::RectF& src) const { |
| InterpolationQuality resampling; |
| if (printing_) { |
| resampling = kInterpolationNone; |
| } else if (image->CurrentFrameIsLazyDecoded()) { |
| resampling = kInterpolationDefault; |
| } else { |
| resampling = ComputeInterpolationQuality( |
| SkScalarToFloat(src.width()), SkScalarToFloat(src.height()), |
| SkScalarToFloat(dest.width()), SkScalarToFloat(dest.height()), |
| image->CurrentFrameIsComplete()); |
| |
| if (resampling == kInterpolationNone) { |
| // FIXME: This is to not break tests (it results in the filter bitmap flag |
| // being set to true). We need to decide if we respect InterpolationNone |
| // being returned from computeInterpolationQuality. |
| resampling = kInterpolationLow; |
| } |
| } |
| return static_cast<cc::PaintFlags::FilterQuality>( |
| std::min(resampling, ImageInterpolationQuality())); |
| } |
| |
| void GraphicsContext::DrawImageTiled( |
| Image* image, |
| const gfx::RectF& dest_rect, |
| const ImageTilingInfo& tiling_info, |
| const AutoDarkMode& auto_dark_mode, |
| SkBlendMode op, |
| RespectImageOrientationEnum respect_orientation) { |
| if (!image) |
| return; |
| |
| cc::PaintFlags image_flags = ImmutableState()->FillFlags(); |
| image_flags.setBlendMode(op); |
| SkSamplingOptions sampling = ImageSamplingOptions(); |
| ImageDrawOptions draw_options( |
| auto_dark_mode.enabled ? GetDarkModeFilter() : nullptr, sampling, |
| respect_orientation, Image::kClampImageToSourceRect, Image::kSyncDecode, |
| auto_dark_mode.enabled); |
| |
| image->DrawPattern(*this, image_flags, dest_rect, tiling_info, draw_options); |
| paint_controller_.SetImagePainted(); |
| } |
| |
| void GraphicsContext::DrawOval(const SkRect& oval, |
| const cc::PaintFlags& flags, |
| const AutoDarkMode& auto_dark_mode) { |
| DCHECK(canvas_); |
| canvas_->drawOval(oval, DarkModeFlags(this, auto_dark_mode, flags)); |
| } |
| |
| void GraphicsContext::DrawPath(const SkPath& path, |
| const cc::PaintFlags& flags, |
| const AutoDarkMode& auto_dark_mode) { |
| DCHECK(canvas_); |
| canvas_->drawPath(path, DarkModeFlags(this, auto_dark_mode, flags)); |
| } |
| |
| void GraphicsContext::DrawRect(const SkRect& rect, |
| const cc::PaintFlags& flags, |
| const AutoDarkMode& auto_dark_mode) { |
| DCHECK(canvas_); |
| canvas_->drawRect(rect, DarkModeFlags(this, auto_dark_mode, flags)); |
| } |
| |
| void GraphicsContext::DrawRRect(const SkRRect& rrect, |
| const cc::PaintFlags& flags, |
| const AutoDarkMode& auto_dark_mode) { |
| DCHECK(canvas_); |
| canvas_->drawRRect(rrect, DarkModeFlags(this, auto_dark_mode, flags)); |
| } |
| |
| void GraphicsContext::FillPath(const Path& path_to_fill, |
| const AutoDarkMode& auto_dark_mode) { |
| if (path_to_fill.IsEmpty()) |
| return; |
| |
| DrawPath(path_to_fill.GetSkPath(), ImmutableState()->FillFlags(), |
| auto_dark_mode); |
| } |
| |
| void GraphicsContext::FillRect(const gfx::Rect& rect, |
| const AutoDarkMode& auto_dark_mode) { |
| FillRect(gfx::RectF(rect), auto_dark_mode); |
| } |
| |
| void GraphicsContext::FillRect(const gfx::Rect& rect, |
| const Color& color, |
| const AutoDarkMode& auto_dark_mode, |
| SkBlendMode xfer_mode) { |
| FillRect(gfx::RectF(rect), color, auto_dark_mode, xfer_mode); |
| } |
| |
| void GraphicsContext::FillRect(const gfx::RectF& rect, |
| const AutoDarkMode& auto_dark_mode) { |
| DrawRect(gfx::RectFToSkRect(rect), ImmutableState()->FillFlags(), |
| auto_dark_mode); |
| } |
| |
| void GraphicsContext::FillRect(const gfx::RectF& rect, |
| const Color& color, |
| const AutoDarkMode& auto_dark_mode, |
| SkBlendMode xfer_mode) { |
| cc::PaintFlags flags = ImmutableState()->FillFlags(); |
| flags.setColor(color.Rgb()); |
| flags.setBlendMode(xfer_mode); |
| |
| DrawRect(gfx::RectFToSkRect(rect), flags, auto_dark_mode); |
| } |
| |
| void GraphicsContext::FillRoundedRect(const FloatRoundedRect& rrect, |
| const Color& color, |
| const AutoDarkMode& auto_dark_mode) { |
| if (!rrect.IsRounded() || !rrect.IsRenderable()) { |
| FillRect(rrect.Rect(), color, auto_dark_mode); |
| return; |
| } |
| |
| if (color == FillColor()) { |
| DrawRRect(SkRRect(rrect), ImmutableState()->FillFlags(), auto_dark_mode); |
| return; |
| } |
| |
| cc::PaintFlags flags = ImmutableState()->FillFlags(); |
| flags.setColor(color.Rgb()); |
| |
| DrawRRect(SkRRect(rrect), flags, auto_dark_mode); |
| } |
| |
| namespace { |
| |
| bool IsSimpleDRRect(const FloatRoundedRect& outer, |
| const FloatRoundedRect& inner) { |
| // A DRRect is "simple" (i.e. can be drawn as a rrect stroke) if |
| // 1) all sides have the same width |
| const gfx::Vector2dF stroke_size = |
| inner.Rect().origin() - outer.Rect().origin(); |
| if (!WebCoreFloatNearlyEqual(stroke_size.AspectRatio(), 1) || |
| !WebCoreFloatNearlyEqual(stroke_size.x(), |
| outer.Rect().right() - inner.Rect().right()) || |
| !WebCoreFloatNearlyEqual(stroke_size.y(), |
| outer.Rect().bottom() - inner.Rect().bottom())) { |
| return false; |
| } |
| |
| const auto& is_simple_corner = [&stroke_size](const gfx::SizeF& outer, |
| const gfx::SizeF& inner) { |
| // trivial/zero-radius corner |
| if (outer.IsZero() && inner.IsZero()) |
| return true; |
| |
| // and |
| // 2) all corners are isotropic |
| // and |
| // 3) the inner radii are not constrained |
| return WebCoreFloatNearlyEqual(outer.width(), outer.height()) && |
| WebCoreFloatNearlyEqual(inner.width(), inner.height()) && |
| WebCoreFloatNearlyEqual(outer.width(), |
| inner.width() + stroke_size.x()); |
| }; |
| |
| const auto& o_radii = outer.GetRadii(); |
| const auto& i_radii = inner.GetRadii(); |
| |
| return is_simple_corner(o_radii.TopLeft(), i_radii.TopLeft()) && |
| is_simple_corner(o_radii.TopRight(), i_radii.TopRight()) && |
| is_simple_corner(o_radii.BottomRight(), i_radii.BottomRight()) && |
| is_simple_corner(o_radii.BottomLeft(), i_radii.BottomLeft()); |
| } |
| |
| } // anonymous namespace |
| |
| void GraphicsContext::FillDRRect(const FloatRoundedRect& outer, |
| const FloatRoundedRect& inner, |
| const Color& color, |
| const AutoDarkMode& auto_dark_mode) { |
| DCHECK(canvas_); |
| |
| if (!IsSimpleDRRect(outer, inner)) { |
| if (color == FillColor()) { |
| canvas_->drawDRRect( |
| SkRRect(outer), SkRRect(inner), |
| DarkModeFlags(this, auto_dark_mode, ImmutableState()->FillFlags())); |
| } else { |
| cc::PaintFlags flags(ImmutableState()->FillFlags()); |
| flags.setColor(color.Rgb()); |
| canvas_->drawDRRect(SkRRect(outer), SkRRect(inner), |
| DarkModeFlags(this, auto_dark_mode, flags)); |
| } |
| |
| return; |
| } |
| |
| // We can draw this as a stroked rrect. |
| float stroke_width = inner.Rect().x() - outer.Rect().x(); |
| SkRRect stroke_r_rect(outer); |
| stroke_r_rect.inset(stroke_width / 2, stroke_width / 2); |
| |
| cc::PaintFlags stroke_flags(ImmutableState()->FillFlags()); |
| stroke_flags.setColor(color.Rgb()); |
| stroke_flags.setStyle(cc::PaintFlags::kStroke_Style); |
| stroke_flags.setStrokeWidth(stroke_width); |
| |
| canvas_->drawRRect(stroke_r_rect, |
| DarkModeFlags(this, auto_dark_mode, stroke_flags)); |
| } |
| |
| void GraphicsContext::FillRectWithRoundedHole( |
| const gfx::RectF& rect, |
| const FloatRoundedRect& rounded_hole_rect, |
| const Color& color, |
| const AutoDarkMode& auto_dark_mode) { |
| cc::PaintFlags flags(ImmutableState()->FillFlags()); |
| flags.setColor(color.Rgb()); |
| canvas_->drawDRRect(SkRRect::MakeRect(gfx::RectFToSkRect(rect)), |
| SkRRect(rounded_hole_rect), |
| DarkModeFlags(this, auto_dark_mode, flags)); |
| } |
| |
| void GraphicsContext::FillEllipse(const gfx::RectF& ellipse, |
| const AutoDarkMode& auto_dark_mode) { |
| DrawOval(gfx::RectFToSkRect(ellipse), ImmutableState()->FillFlags(), |
| auto_dark_mode); |
| } |
| |
| void GraphicsContext::StrokePath(const Path& path_to_stroke, |
| const AutoDarkMode& auto_dark_mode, |
| const int length, |
| const int dash_thickness) { |
| if (path_to_stroke.IsEmpty()) |
| return; |
| |
| DrawPath(path_to_stroke.GetSkPath(), |
| ImmutableState()->StrokeFlags(length, dash_thickness), |
| auto_dark_mode); |
| } |
| |
| void GraphicsContext::StrokeRect(const gfx::RectF& rect, |
| float line_width, |
| const AutoDarkMode& auto_dark_mode) { |
| cc::PaintFlags flags(ImmutableState()->StrokeFlags()); |
| flags.setStrokeWidth(WebCoreFloatToSkScalar(line_width)); |
| // Reset the dash effect to account for the width |
| ImmutableState()->GetStrokeData().SetupPaintDashPathEffect(&flags); |
| // strokerect has special rules for CSS when the rect is degenerate: |
| // if width==0 && height==0, do nothing |
| // if width==0 || height==0, then just draw line for the other dimension |
| SkRect r = gfx::RectFToSkRect(rect); |
| bool valid_w = r.width() > 0; |
| bool valid_h = r.height() > 0; |
| if (valid_w && valid_h) { |
| DrawRect(r, flags, auto_dark_mode); |
| } else if (valid_w || valid_h) { |
| // we are expected to respect the lineJoin, so we can't just call |
| // drawLine -- we have to create a path that doubles back on itself. |
| SkPathBuilder path; |
| path.moveTo(r.fLeft, r.fTop); |
| path.lineTo(r.fRight, r.fBottom); |
| path.close(); |
| DrawPath(path.detach(), flags, auto_dark_mode); |
| } |
| } |
| |
| void GraphicsContext::StrokeEllipse(const gfx::RectF& ellipse, |
| const AutoDarkMode& auto_dark_mode) { |
| DrawOval(gfx::RectFToSkRect(ellipse), ImmutableState()->StrokeFlags(), |
| auto_dark_mode); |
| } |
| |
| void GraphicsContext::ClipRoundedRect(const FloatRoundedRect& rrect, |
| SkClipOp clip_op, |
| AntiAliasingMode should_antialias) { |
| if (!rrect.IsRounded()) { |
| ClipRect(gfx::RectFToSkRect(rrect.Rect()), should_antialias, clip_op); |
| return; |
| } |
| |
| ClipRRect(SkRRect(rrect), should_antialias, clip_op); |
| } |
| |
| void GraphicsContext::ClipOut(const Path& path_to_clip) { |
| // Use const_cast and temporarily toggle the inverse fill type instead of |
| // copying the path. |
| SkPath& path = const_cast<SkPath&>(path_to_clip.GetSkPath()); |
| path.toggleInverseFillType(); |
| ClipPath(path, kAntiAliased); |
| path.toggleInverseFillType(); |
| } |
| |
| void GraphicsContext::ClipOutRoundedRect(const FloatRoundedRect& rect) { |
| ClipRoundedRect(rect, SkClipOp::kDifference); |
| } |
| |
| void GraphicsContext::ClipRect(const SkRect& rect, |
| AntiAliasingMode aa, |
| SkClipOp op) { |
| DCHECK(canvas_); |
| canvas_->clipRect(rect, op, aa == kAntiAliased); |
| } |
| |
| void GraphicsContext::ClipPath(const SkPath& path, |
| AntiAliasingMode aa, |
| SkClipOp op) { |
| DCHECK(canvas_); |
| canvas_->clipPath(path, op, aa == kAntiAliased); |
| } |
| |
| void GraphicsContext::ClipRRect(const SkRRect& rect, |
| AntiAliasingMode aa, |
| SkClipOp op) { |
| DCHECK(canvas_); |
| canvas_->clipRRect(rect, op, aa == kAntiAliased); |
| } |
| |
| void GraphicsContext::Rotate(float angle_in_radians) { |
| DCHECK(canvas_); |
| canvas_->rotate( |
| WebCoreFloatToSkScalar(angle_in_radians * (180.0f / 3.14159265f))); |
| } |
| |
| void GraphicsContext::Translate(float x, float y) { |
| DCHECK(canvas_); |
| |
| if (!x && !y) |
| return; |
| |
| canvas_->translate(WebCoreFloatToSkScalar(x), WebCoreFloatToSkScalar(y)); |
| } |
| |
| void GraphicsContext::Scale(float x, float y) { |
| DCHECK(canvas_); |
| canvas_->scale(WebCoreFloatToSkScalar(x), WebCoreFloatToSkScalar(y)); |
| } |
| |
| void GraphicsContext::SetURLForRect(const KURL& link, |
| const gfx::Rect& dest_rect) { |
| DCHECK(canvas_); |
| |
| sk_sp<SkData> url(SkData::MakeWithCString(link.GetString().Utf8().c_str())); |
| canvas_->Annotate(cc::PaintCanvas::AnnotationType::URL, |
| gfx::RectToSkRect(dest_rect), std::move(url)); |
| } |
| |
| void GraphicsContext::SetURLFragmentForRect(const String& dest_name, |
| const gfx::Rect& rect) { |
| DCHECK(canvas_); |
| |
| sk_sp<SkData> sk_dest_name(SkData::MakeWithCString(dest_name.Utf8().c_str())); |
| canvas_->Annotate(cc::PaintCanvas::AnnotationType::LINK_TO_DESTINATION, |
| gfx::RectToSkRect(rect), std::move(sk_dest_name)); |
| } |
| |
| void GraphicsContext::SetURLDestinationLocation(const String& name, |
| const gfx::Point& location) { |
| DCHECK(canvas_); |
| |
| // Paint previews don't make use of linked destinations. |
| if (paint_preview_tracker_) |
| return; |
| |
| SkRect rect = SkRect::MakeXYWH(location.x(), location.y(), 0, 0); |
| sk_sp<SkData> sk_name(SkData::MakeWithCString(name.Utf8().c_str())); |
| canvas_->Annotate(cc::PaintCanvas::AnnotationType::NAMED_DESTINATION, rect, |
| std::move(sk_name)); |
| } |
| |
| void GraphicsContext::ConcatCTM(const AffineTransform& affine) { |
| Concat(AffineTransformToSkMatrix(affine)); |
| } |
| |
| void GraphicsContext::AdjustLineToPixelBoundaries(gfx::PointF& p1, |
| gfx::PointF& p2, |
| float stroke_width) { |
| // For odd widths, we add in 0.5 to the appropriate x/y so that the float |
| // arithmetic works out. For example, with a border width of 3, painting will |
| // pass us (y1+y2)/2, e.g., (50+53)/2 = 103/2 = 51 when we want 51.5. It is |
| // always true that an even width gave us a perfect position, but an odd width |
| // gave us a position that is off by exactly 0.5. |
| if (static_cast<int>(stroke_width) % 2) { // odd |
| if (p1.x() == p2.x()) { |
| // We're a vertical line. Adjust our x. |
| p1.set_x(p1.x() + 0.5f); |
| p2.set_x(p2.x() + 0.5f); |
| } else { |
| // We're a horizontal line. Adjust our y. |
| p1.set_y(p1.y() + 0.5f); |
| p2.set_y(p2.y() + 0.5f); |
| } |
| } |
| } |
| |
| Path GraphicsContext::GetPathForTextLine(const gfx::PointF& pt, |
| float width, |
| float stroke_thickness, |
| StrokeStyle stroke_style) { |
| Path path; |
| DCHECK_NE(stroke_style, kWavyStroke); |
| if (ShouldUseStrokeForTextLine(stroke_style)) { |
| auto [start, end] = GetPointsForTextLine(pt, width, stroke_thickness); |
| path.MoveTo(gfx::PointF(start)); |
| path.AddLineTo(gfx::PointF(end)); |
| } else { |
| path.AddRect( |
| GetRectForTextLine(pt, width, RoundDownThickness(stroke_thickness))); |
| } |
| return path; |
| } |
| |
| bool GraphicsContext::ShouldUseStrokeForTextLine(StrokeStyle stroke_style) { |
| switch (stroke_style) { |
| case kNoStroke: |
| case kSolidStroke: |
| case kDoubleStroke: |
| return false; |
| case kDottedStroke: |
| case kDashedStroke: |
| case kWavyStroke: |
| default: |
| return true; |
| } |
| } |
| |
| sk_sp<SkColorFilter> GraphicsContext::WebCoreColorFilterToSkiaColorFilter( |
| ColorFilter color_filter) { |
| switch (color_filter) { |
| case kColorFilterLuminanceToAlpha: |
| return SkLumaColorFilter::Make(); |
| case kColorFilterLinearRGBToSRGB: |
| return interpolation_space_utilities::CreateInterpolationSpaceFilter( |
| kInterpolationSpaceLinear, kInterpolationSpaceSRGB); |
| case kColorFilterSRGBToLinearRGB: |
| return interpolation_space_utilities::CreateInterpolationSpaceFilter( |
| kInterpolationSpaceSRGB, kInterpolationSpaceLinear); |
| case kColorFilterNone: |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| |
| return nullptr; |
| } |
| |
| } // namespace blink |