| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/paint/list_marker_painter.h" |
| |
| #include "third_party/blink/renderer/core/layout/layout_list_item.h" |
| #include "third_party/blink/renderer/core/layout/layout_list_marker.h" |
| #include "third_party/blink/renderer/core/layout/list_marker_text.h" |
| #include "third_party/blink/renderer/core/paint/box_model_object_painter.h" |
| #include "third_party/blink/renderer/core/paint/paint_info.h" |
| #include "third_party/blink/renderer/core/paint/scoped_paint_state.h" |
| #include "third_party/blink/renderer/core/paint/selection_painting_utils.h" |
| #include "third_party/blink/renderer/core/paint/text_painter.h" |
| #include "third_party/blink/renderer/platform/fonts/text_run_paint_info.h" |
| #include "third_party/blink/renderer/platform/geometry/layout_point.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h" |
| |
| namespace blink { |
| |
| void ListMarkerPainter::PaintSymbol(const PaintInfo& paint_info, |
| const LayoutObject* object, |
| const ComputedStyle& style, |
| const IntRect& marker) { |
| DCHECK(object); |
| GraphicsContext& context = paint_info.context; |
| Color color(object->ResolveColor(GetCSSPropertyColor())); |
| if (BoxModelObjectPainter::ShouldForceWhiteBackgroundForPrintEconomy( |
| object->GetDocument(), style)) |
| color = TextPainter::TextColorForWhiteBackground(color); |
| // Apply the color to the list marker text. |
| context.SetFillColor(color); |
| context.SetStrokeColor(color); |
| context.SetStrokeStyle(kSolidStroke); |
| context.SetStrokeThickness(1.0f); |
| switch (style.ListStyleType()) { |
| case EListStyleType::kDisc: |
| context.FillEllipse(FloatRect(marker)); |
| break; |
| case EListStyleType::kCircle: |
| context.StrokeEllipse(FloatRect(marker)); |
| break; |
| case EListStyleType::kSquare: |
| context.FillRect(marker); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| void ListMarkerPainter::Paint(const PaintInfo& paint_info) { |
| if (paint_info.phase != PaintPhase::kForeground) |
| return; |
| |
| if (layout_list_marker_.StyleRef().Visibility() != EVisibility::kVisible) |
| return; |
| |
| if (DrawingRecorder::UseCachedDrawingIfPossible( |
| paint_info.context, layout_list_marker_, paint_info.phase)) |
| return; |
| |
| ScopedPaintState paint_state(layout_list_marker_, paint_info); |
| if (!paint_state.LocalRectIntersectsCullRect( |
| layout_list_marker_.PhysicalVisualOverflowRect())) |
| return; |
| |
| const auto& local_paint_info = paint_state.GetPaintInfo(); |
| auto box_origin = paint_state.PaintOffset(); |
| |
| DrawingRecorder recorder(local_paint_info.context, layout_list_marker_, |
| local_paint_info.phase); |
| |
| LayoutRect box(box_origin, layout_list_marker_.Size()); |
| |
| LayoutRect marker = layout_list_marker_.GetRelativeMarkerRect(); |
| marker.MoveBy(box_origin); |
| |
| GraphicsContext& context = local_paint_info.context; |
| |
| if (layout_list_marker_.IsImage()) { |
| // Since there is no way for the developer to specify decode behavior, use |
| // kSync by default. |
| context.DrawImage( |
| layout_list_marker_.GetImage() |
| ->GetImage(layout_list_marker_, layout_list_marker_.GetDocument(), |
| layout_list_marker_.StyleRef(), FloatSize(marker.Size())) |
| .get(), |
| Image::kSyncDecode, FloatRect(marker)); |
| return; |
| } |
| |
| LayoutListMarker::ListStyleCategory style_category = |
| layout_list_marker_.GetListStyleCategory(); |
| if (style_category == LayoutListMarker::ListStyleCategory::kNone) |
| return; |
| |
| if (style_category == LayoutListMarker::ListStyleCategory::kSymbol) { |
| PaintSymbol(paint_info, &layout_list_marker_, |
| layout_list_marker_.StyleRef(), PixelSnappedIntRect(marker)); |
| return; |
| } |
| |
| if (layout_list_marker_.GetText().IsEmpty()) |
| return; |
| |
| Color color(layout_list_marker_.ResolveColor(GetCSSPropertyColor())); |
| |
| if (BoxModelObjectPainter::ShouldForceWhiteBackgroundForPrintEconomy( |
| layout_list_marker_.ListItem()->GetDocument(), |
| layout_list_marker_.StyleRef())) |
| color = TextPainter::TextColorForWhiteBackground(color); |
| |
| // Apply the color to the list marker text. |
| context.SetFillColor(color); |
| |
| const Font& font = layout_list_marker_.StyleRef().GetFont(); |
| TextRun text_run = ConstructTextRun(font, layout_list_marker_.GetText(), |
| layout_list_marker_.StyleRef()); |
| |
| GraphicsContextStateSaver state_saver(context, false); |
| if (!layout_list_marker_.StyleRef().IsHorizontalWritingMode()) { |
| marker.MoveBy(-box_origin); |
| marker = marker.TransposedRect(); |
| marker.MoveBy( |
| IntPoint(RoundToInt(box.X()), |
| RoundToInt(box.Y() - layout_list_marker_.LogicalHeight()))); |
| state_saver.Save(); |
| context.Translate(marker.X(), marker.MaxY()); |
| context.Rotate(static_cast<float>(deg2rad(90.))); |
| context.Translate(-marker.X(), -marker.MaxY()); |
| } |
| |
| TextRunPaintInfo text_run_paint_info(text_run); |
| text_run_paint_info.bounds = FloatRect(EnclosingIntRect(marker)); |
| const SimpleFontData* font_data = |
| layout_list_marker_.StyleRef().GetFont().PrimaryFont(); |
| FloatPoint text_origin = |
| FloatPoint(marker.X().Round(), |
| marker.Y().Round() + |
| (font_data ? font_data->GetFontMetrics().Ascent() : 0)); |
| |
| // Text is not arbitrary. We can judge whether it's RTL from the first |
| // character, and we only need to handle the direction RightToLeft for now. |
| bool text_needs_reversing = |
| WTF::unicode::Direction(layout_list_marker_.GetText()[0]) == |
| WTF::unicode::kRightToLeft; |
| StringBuilder reversed_text; |
| if (text_needs_reversing) { |
| unsigned length = layout_list_marker_.GetText().length(); |
| reversed_text.ReserveCapacity(length); |
| for (int i = length - 1; i >= 0; --i) |
| reversed_text.Append(layout_list_marker_.GetText()[i]); |
| DCHECK(reversed_text.length() == length); |
| text_run.SetText(reversed_text.ToString()); |
| } |
| |
| const UChar suffix = |
| list_marker_text::Suffix(layout_list_marker_.StyleRef().ListStyleType(), |
| layout_list_marker_.ListItem()->Value()); |
| UChar suffix_str[2] = {suffix, static_cast<UChar>(' ')}; |
| TextRun suffix_run = |
| ConstructTextRun(font, suffix_str, 2, layout_list_marker_.StyleRef(), |
| layout_list_marker_.StyleRef().Direction()); |
| TextRunPaintInfo suffix_run_info(suffix_run); |
| suffix_run_info.bounds = FloatRect(EnclosingIntRect(marker)); |
| |
| if (layout_list_marker_.StyleRef().IsLeftToRightDirection()) { |
| context.DrawText(font, text_run_paint_info, text_origin); |
| context.DrawText(font, suffix_run_info, |
| text_origin + FloatSize(IntSize(font.Width(text_run), 0))); |
| } else { |
| context.DrawText(font, suffix_run_info, text_origin); |
| // Is the truncation to IntSize below meaningful or a bug? |
| context.DrawText( |
| font, text_run_paint_info, |
| text_origin + FloatSize(IntSize(font.Width(suffix_run), 0))); |
| } |
| // TODO(npm): Check that there are non-whitespace characters. See |
| // crbug.com/788444. |
| context.GetPaintController().SetTextPainted(); |
| } |
| |
| } // namespace blink |