| // Copyright 2020 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/modules/canvas/canvas2d/canvas_formatted_text.h" |
| #include "third_party/blink/renderer/core/inspector/inspector_trace_events.h" |
| #include "third_party/blink/renderer/core/layout/layout_block_flow.h" |
| #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_child_layout_context.h" |
| #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" |
| #include "third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.h" |
| #include "third_party/blink/renderer/core/paint/paint_info.h" |
| #include "third_party/blink/renderer/core/style/computed_style.h" |
| #include "third_party/blink/renderer/platform/fonts/font_description.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_context.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h" |
| |
| namespace blink { |
| |
| void CanvasFormattedText::Trace(Visitor* visitor) const { |
| visitor->Trace(text_runs_); |
| ScriptWrappable::Trace(visitor); |
| } |
| |
| CanvasFormattedText* CanvasFormattedText::Create( |
| ExecutionContext* execution_context, |
| const String text) { |
| CanvasFormattedText* canvas_formatted_text = |
| MakeGarbageCollected<CanvasFormattedText>(execution_context); |
| CanvasFormattedTextRun* run = |
| MakeGarbageCollected<CanvasFormattedTextRun>(execution_context, text); |
| canvas_formatted_text->text_runs_.push_back(run); |
| canvas_formatted_text->block_->AddChild(run->GetLayoutObject()); |
| return canvas_formatted_text; |
| } |
| |
| CanvasFormattedText::CanvasFormattedText(ExecutionContext* execution_context) { |
| scoped_refptr<ComputedStyle> style = ComputedStyle::Create(); |
| style->SetDisplay(EDisplay::kBlock); |
| // Refrain from extending the use of document, apart from creating layout |
| // block flow. In the future we should handle execution_context's from worker |
| // threads that do not have a document. |
| auto* window = To<LocalDOMWindow>(execution_context); |
| block_ = LayoutBlockFlow::CreateAnonymous(window->document(), style, |
| LegacyLayout::kAuto); |
| block_->SetIsLayoutNGObjectForCanvasFormattedText(true); |
| } |
| |
| void CanvasFormattedText::Dispose() { |
| // Detach all the anonymous children we added, since block_->Destroy will |
| // destroy them. We want the lifetime of the children to be managed by their |
| // corresponding CanvasFormattedTextRun and not destroyed at this point. |
| while (block_->FirstChild()) { |
| block_->RemoveChild(block_->FirstChild()); |
| } |
| AllowDestroyingLayoutObjectInFinalizerScope scope; |
| if (block_) |
| block_->Destroy(); |
| } |
| |
| LayoutBlockFlow* CanvasFormattedText::GetLayoutBlock( |
| Document& document, |
| const FontDescription& defaultFont) { |
| scoped_refptr<ComputedStyle> style = ComputedStyle::Create(); |
| style->SetDisplay(EDisplay::kBlock); |
| style->SetFontDescription(defaultFont); |
| block_->SetStyle(style); |
| return block_; |
| } |
| |
| CanvasFormattedTextRun* CanvasFormattedText::appendRun( |
| CanvasFormattedTextRun* run, |
| ExceptionState& exception_state) { |
| if (!CheckRunIsNotParented(run, &exception_state)) |
| return nullptr; |
| text_runs_.push_back(run); |
| block_->AddChild(run->GetLayoutObject()); |
| return run; |
| } |
| |
| CanvasFormattedTextRun* CanvasFormattedText::setRun( |
| unsigned index, |
| CanvasFormattedTextRun* run, |
| ExceptionState& exception_state) { |
| if (!CheckRunsIndexBound(index, &exception_state) || |
| !CheckRunIsNotParented(run, &exception_state)) |
| return nullptr; |
| block_->AddChild(run->GetLayoutObject(), |
| text_runs_[index]->GetLayoutObject()); |
| block_->RemoveChild(text_runs_[index]->GetLayoutObject()); |
| text_runs_[index] = run; |
| return text_runs_[index]; |
| } |
| |
| CanvasFormattedTextRun* CanvasFormattedText::insertRun( |
| unsigned index, |
| CanvasFormattedTextRun* run, |
| ExceptionState& exception_state) { |
| if (!CheckRunIsNotParented(run, &exception_state)) |
| return nullptr; |
| if (index == text_runs_.size()) |
| return appendRun(run, exception_state); |
| if (!CheckRunsIndexBound(index, &exception_state)) |
| return nullptr; |
| block_->AddChild(run->GetLayoutObject(), |
| text_runs_[index]->GetLayoutObject()); |
| text_runs_.insert(index, run); |
| return text_runs_[index]; |
| } |
| |
| void CanvasFormattedText::deleteRun(unsigned index, |
| unsigned length, |
| ExceptionState& exception_state) { |
| if (!CheckRunsIndexBound(index, &exception_state)) |
| return; |
| // Protect against overflow, do not perform math like index + length < |
| // text_runs_.size(). The length passed in can be close to INT_MAX. |
| if (text_runs_.size() - index < length) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kIndexSizeError, |
| ExceptionMessages::IndexExceedsMaximumBound("length", length, |
| text_runs_.size() - index)); |
| return; |
| } |
| |
| for (wtf_size_t i = index; i < index + length; i++) { |
| block_->RemoveChild(text_runs_[i]->GetLayoutObject()); |
| } |
| block_->SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation( |
| layout_invalidation_reason::kCanvasFormattedTextRunChange); |
| text_runs_.EraseAt(static_cast<wtf_size_t>(index), |
| static_cast<wtf_size_t>(length)); |
| } |
| |
| sk_sp<PaintRecord> CanvasFormattedText::PaintFormattedText( |
| Document& document, |
| const FontDescription& font, |
| double x, |
| double y, |
| double wrap_width, |
| FloatRect& bounds) { |
| LayoutBlockFlow* block = GetLayoutBlock(document, font); |
| NGBlockNode block_node(block); |
| NGInlineNode node(block); |
| // Call IsEmptyInline to force prepare layout. |
| if (node.IsEmptyInline()) |
| return nullptr; |
| |
| // TODO(sushraja) Once we add support for writing mode on the canvas formatted |
| // text, fix this to be not hardcoded horizontal top to bottom. |
| NGConstraintSpaceBuilder builder( |
| WritingMode::kHorizontalTb, |
| {WritingMode::kHorizontalTb, TextDirection::kLtr}, |
| /* is_new_fc */ true); |
| LayoutUnit available_logical_width(wrap_width); |
| LogicalSize available_size = {available_logical_width, kIndefiniteSize}; |
| builder.SetAvailableSize(available_size); |
| NGConstraintSpace space = builder.ToConstraintSpace(); |
| scoped_refptr<const NGLayoutResult> block_results = |
| block_node.Layout(space, nullptr); |
| const auto& fragment = |
| To<NGPhysicalBoxFragment>(block_results->PhysicalFragment()); |
| block->RecalcInlineChildrenVisualOverflow(); |
| bounds = FloatRect(block->PhysicalVisualOverflowRect()); |
| |
| PaintController paint_controller(PaintController::Usage::kTransient); |
| paint_controller.UpdateCurrentPaintChunkProperties(nullptr, |
| PropertyTreeState::Root()); |
| GraphicsContext graphics_context(paint_controller); |
| PhysicalOffset physical_offset((LayoutUnit(x)), (LayoutUnit(y))); |
| NGBoxFragmentPainter box_fragment_painter(fragment); |
| PaintInfo paint_info(graphics_context, CullRect::Infinite(), |
| PaintPhase::kForeground, kGlobalPaintNormalPhase, |
| kPaintLayerPaintingRenderingClipPathAsMask | |
| kPaintLayerPaintingRenderingResourceSubtree); |
| box_fragment_painter.PaintObject(paint_info, physical_offset); |
| paint_controller.CommitNewDisplayItems(); |
| paint_controller.FinishCycle(); |
| sk_sp<PaintRecord> recording = |
| paint_controller.GetPaintArtifact().GetPaintRecord( |
| PropertyTreeState::Root()); |
| return recording; |
| } |
| |
| } // namespace blink |