Commit fbeec0d2 authored by Fernando Serboncini's avatar Fernando Serboncini Committed by Commit Bot

Canvas PDF Printing

- this adds a new beforePrinting state to Document::Printing
- if we are in beforePrinting, keep the Canvas recording and
  use it for printing
- We only do this if rendering-mode is not pixelated

Bug: 959357
Change-Id: If29b90b344b5af42cc3cbbed2f867d82841f2def
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1637656
Commit-Queue: Fernando Serboncini <fserb@chromium.org>
Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Reviewed-by: default avatarKent Tamura <tkent@chromium.org>
Cr-Commit-Position: refs/heads/master@{#676443}
parent 9e9e557b
...@@ -729,8 +729,16 @@ class CORE_EXPORT Document : public ContainerNode, ...@@ -729,8 +729,16 @@ class CORE_EXPORT Document : public ContainerNode,
// FinishingPrinting denotes that the non-printing layout state is being // FinishingPrinting denotes that the non-printing layout state is being
// restored. // restored.
enum PrintingState { kNotPrinting, kPrinting, kFinishingPrinting }; enum PrintingState {
kNotPrinting,
kBeforePrinting,
kPrinting,
kFinishingPrinting
};
bool Printing() const { return printing_ == kPrinting; } bool Printing() const { return printing_ == kPrinting; }
bool BeforePrintingOrPrinting() const {
return printing_ == kPrinting || printing_ == kBeforePrinting;
}
bool FinishingOrIsPrinting() { bool FinishingOrIsPrinting() {
return printing_ == kPrinting || printing_ == kFinishingPrinting; return printing_ == kPrinting || printing_ == kFinishingPrinting;
} }
......
...@@ -1432,6 +1432,7 @@ void WebLocalFrameImpl::DispatchBeforePrintEvent() { ...@@ -1432,6 +1432,7 @@ void WebLocalFrameImpl::DispatchBeforePrintEvent() {
is_in_printing_ = true; is_in_printing_ = true;
#endif #endif
GetFrame()->GetDocument()->SetPrinting(Document::kBeforePrinting);
DispatchPrintEventRecursively(event_type_names::kBeforeprint); DispatchPrintEventRecursively(event_type_names::kBeforeprint);
} }
......
...@@ -773,6 +773,27 @@ void HTMLCanvasElement::PaintInternal(GraphicsContext& context, ...@@ -773,6 +773,27 @@ void HTMLCanvasElement::PaintInternal(GraphicsContext& context,
context_->PaintRenderingResultsToCanvas(kFrontBuffer); context_->PaintRenderingResultsToCanvas(kFrontBuffer);
if (HasResourceProvider()) { if (HasResourceProvider()) {
if (!context.ContextDisabled()) { if (!context.ContextDisabled()) {
// For 2D Canvas, there are two ways of render Canvas for printing:
// display list or image snapshot. Display list allows better PDF printing
// and we prefer this method.
// Here are the requirements for display list to be used:
// 1. We must have had a full repaint of the Canvas after beginprint
// event has been fired. Otherwise, we don't have a PaintRecord.
// 2. CSS property 'image-rendering' must not be 'pixelated'.
// display list rendering: we replay the last full PaintRecord, if Canvas
// has been redraw since beginprint happened.
if (IsPrinting() && !Is3d() && canvas2d_bridge_) {
canvas2d_bridge_->FlushRecording();
if (canvas2d_bridge_->getLastRecord()) {
const ComputedStyle* style = GetComputedStyle();
if (style && style->ImageRendering() != EImageRendering::kPixelated) {
context.Canvas()->drawPicture(canvas2d_bridge_->getLastRecord());
return;
}
}
}
// or image snapshot rendering: grab a snapshot and raster it.
SkBlendMode composite_operator = SkBlendMode composite_operator =
!context_ || context_->CreationAttributes().alpha !context_ || context_->CreationAttributes().alpha
? SkBlendMode::kSrcOver ? SkBlendMode::kSrcOver
...@@ -801,6 +822,10 @@ void HTMLCanvasElement::PaintInternal(GraphicsContext& context, ...@@ -801,6 +822,10 @@ void HTMLCanvasElement::PaintInternal(GraphicsContext& context,
context_->MarkLayerComposited(); context_->MarkLayerComposited();
} }
bool HTMLCanvasElement::IsPrinting() const {
return GetDocument().BeforePrintingOrPrinting();
}
void HTMLCanvasElement::SetSurfaceSize(const IntSize& size) { void HTMLCanvasElement::SetSurfaceSize(const IntSize& size) {
size_ = size; size_ = size;
did_fail_to_create_resource_provider_ = false; did_fail_to_create_resource_provider_ = false;
......
...@@ -223,6 +223,7 @@ class CORE_EXPORT HTMLCanvasElement final ...@@ -223,6 +223,7 @@ class CORE_EXPORT HTMLCanvasElement final
bool LowLatencyEnabled() const override { return !!frame_dispatcher_; } bool LowLatencyEnabled() const override { return !!frame_dispatcher_; }
CanvasResourceProvider* GetOrCreateCanvasResourceProvider( CanvasResourceProvider* GetOrCreateCanvasResourceProvider(
AccelerationHint hint) override; AccelerationHint hint) override;
bool IsPrinting() const override;
void DisableAcceleration(std::unique_ptr<Canvas2DLayerBridge> void DisableAcceleration(std::unique_ptr<Canvas2DLayerBridge>
unaccelerated_bridge_used_for_testing = nullptr); unaccelerated_bridge_used_for_testing = nullptr);
......
...@@ -6,8 +6,10 @@ ...@@ -6,8 +6,10 @@
#include <memory> #include <memory>
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/events/before_print_event.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/html/html_element.h" #include "third_party/blink/renderer/core/html/html_element.h"
...@@ -24,6 +26,8 @@ ...@@ -24,6 +26,8 @@
#include "third_party/blink/renderer/platform/wtf/text/text_stream.h" #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
#include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkCanvas.h"
using testing::_;
namespace blink { namespace blink {
const int kPageWidth = 800; const int kPageWidth = 800;
...@@ -60,6 +64,22 @@ class MockPageContextCanvas : public SkCanvas { ...@@ -60,6 +64,22 @@ class MockPageContextCanvas : public SkCanvas {
return recorded_operations_; return recorded_operations_;
} }
MOCK_METHOD2(onDrawRect, void(const SkRect&, const SkPaint&));
MOCK_METHOD1(DrawPicture, void(const SkPicture*));
MOCK_METHOD1(OnDrawPicture, void(const SkPicture*));
MOCK_METHOD3(OnDrawPicture,
void(const SkPicture*, const SkMatrix*, const SkPaint*));
MOCK_METHOD3(DrawPicture,
void(const SkPicture*, const SkMatrix*, const SkPaint*));
MOCK_METHOD4(onDrawImage,
void(const SkImage*, SkScalar, SkScalar, const SkPaint*));
MOCK_METHOD5(onDrawImageRect,
void(const SkImage*,
const SkRect*,
const SkRect&,
const SkPaint*,
SrcRectConstraint));
private: private:
Vector<Operation> recorded_operations_; Vector<Operation> recorded_operations_;
}; };
...@@ -84,8 +104,11 @@ class PrintContextTest : public PaintTestConfigurations, public RenderingTest { ...@@ -84,8 +104,11 @@ class PrintContextTest : public PaintTestConfigurations, public RenderingTest {
GetDocument().body()->SetInnerHTMLFromString(body_content); GetDocument().body()->SetInnerHTMLFromString(body_content);
} }
void PrintSinglePage(MockPageContextCanvas& canvas) { void PrintSinglePage(SkCanvas& canvas) {
IntRect page_rect(0, 0, kPageWidth, kPageHeight); IntRect page_rect(0, 0, kPageWidth, kPageHeight);
GetDocument().SetPrinting(Document::kBeforePrinting);
Event* event = MakeGarbageCollected<BeforePrintEvent>();
GetPrintContext().GetFrame()->DomWindow()->DispatchEvent(*event);
GetPrintContext().BeginPrintMode(page_rect.Width(), page_rect.Height()); GetPrintContext().BeginPrintMode(page_rect.Width(), page_rect.Height());
UpdateAllLifecyclePhasesForTest(); UpdateAllLifecyclePhasesForTest();
PaintRecordBuilder builder; PaintRecordBuilder builder;
...@@ -396,6 +419,46 @@ TEST_P(PrintContextFrameTest, BasicPrintPageLayout) { ...@@ -396,6 +419,46 @@ TEST_P(PrintContextFrameTest, BasicPrintPageLayout) {
EXPECT_EQ(node->OffsetWidth(), 800); EXPECT_EQ(node->OffsetWidth(), 800);
} }
TEST_P(PrintContextTest, Canvas2DBeforePrint) {
MockPageContextCanvas canvas;
SetBodyInnerHTML("<canvas id='c' width=100 height=100></canvas>");
GetDocument().GetSettings()->SetScriptEnabled(true);
Element* const script_element =
GetDocument().CreateRawElement(html_names::kScriptTag);
script_element->setTextContent(
"window.addEventListener('beforeprint', (ev) => {"
"const ctx = document.getElementById('c').getContext('2d');"
"ctx.fillRect(0, 0, 10, 10);"
"ctx.fillRect(50, 50, 10, 10);"
"});");
GetDocument().body()->AppendChild(script_element);
EXPECT_CALL(canvas, onDrawRect(_, _)).Times(testing::AtLeast(2));
PrintSinglePage(canvas);
}
TEST_P(PrintContextTest, Canvas2DPixelated) {
MockPageContextCanvas canvas;
SetBodyInnerHTML(
"<canvas id='c' style='image-rendering: pixelated' "
"width=100 height=100></canvas>");
GetDocument().GetSettings()->SetScriptEnabled(true);
Element* const script_element =
GetDocument().CreateRawElement(html_names::kScriptTag);
script_element->setTextContent(
"window.addEventListener('beforeprint', (ev) => {"
"const ctx = document.getElementById('c').getContext('2d');"
"ctx.fillRect(0, 0, 10, 10);"
"ctx.fillRect(50, 50, 10, 10);"
"});");
GetDocument().body()->AppendChild(script_element);
EXPECT_CALL(canvas, onDrawImageRect(_, _, _, _, _));
PrintSinglePage(canvas);
}
// This tests that we don't resize or re-layout subframes in printed content. // This tests that we don't resize or re-layout subframes in printed content.
// TODO(weili): This test fails when the iframe isn't the root scroller - e.g. // TODO(weili): This test fails when the iframe isn't the root scroller - e.g.
// Adding ScopedImplicitRootScrollerForTest disabler(false); // Adding ScopedImplicitRootScrollerForTest disabler(false);
......
...@@ -69,7 +69,8 @@ Canvas2DLayerBridge::Canvas2DLayerBridge(const IntSize& size, ...@@ -69,7 +69,8 @@ Canvas2DLayerBridge::Canvas2DLayerBridge(const IntSize& size,
snapshot_state_(kInitialSnapshotState), snapshot_state_(kInitialSnapshotState),
resource_host_(nullptr), resource_host_(nullptr),
random_generator_((uint32_t)base::RandUint64()), random_generator_((uint32_t)base::RandUint64()),
bernoulli_distribution_(kRasterMetricProbability) { bernoulli_distribution_(kRasterMetricProbability),
last_recording_(nullptr) {
// Used by browser tests to detect the use of a Canvas2DLayerBridge. // Used by browser tests to detect the use of a Canvas2DLayerBridge.
TRACE_EVENT_INSTANT0("test_gpu", "Canvas2DLayerBridgeCreation", TRACE_EVENT_INSTANT0("test_gpu", "Canvas2DLayerBridgeCreation",
TRACE_EVENT_SCOPE_GLOBAL); TRACE_EVENT_SCOPE_GLOBAL);
...@@ -548,8 +549,11 @@ void Canvas2DLayerBridge::FlushRecording() { ...@@ -548,8 +549,11 @@ void Canvas2DLayerBridge::FlushRecording() {
{ // Make a new scope so that PaintRecord gets deleted and that gets timed { // Make a new scope so that PaintRecord gets deleted and that gets timed
cc::PaintCanvas* canvas = ResourceProvider()->Canvas(); cc::PaintCanvas* canvas = ResourceProvider()->Canvas();
sk_sp<PaintRecord> recording = recorder_->finishRecordingAsPicture(); last_recording_ = recorder_->finishRecordingAsPicture();
canvas->drawPicture(recording); canvas->drawPicture(last_recording_);
if (!resource_host_ || !resource_host_->IsPrinting()) {
last_recording_ = nullptr;
}
ResourceProvider()->FlushSkia(); ResourceProvider()->FlushSkia();
} }
......
...@@ -169,6 +169,9 @@ class PLATFORM_EXPORT Canvas2DLayerBridge : public cc::TextureLayerClient { ...@@ -169,6 +169,9 @@ class PLATFORM_EXPORT Canvas2DLayerBridge : public cc::TextureLayerClient {
CanvasResourceProvider* ResourceProvider() const; CanvasResourceProvider* ResourceProvider() const;
void FlushRecording(); void FlushRecording();
PaintRecorder* getRecorder() { return recorder_.get(); }
sk_sp<cc::PaintRecord> getLastRecord() { return last_recording_; }
private: private:
friend class Canvas2DLayerBridgeTest; friend class Canvas2DLayerBridgeTest;
friend class CanvasRenderingContext2DTest; friend class CanvasRenderingContext2DTest;
...@@ -230,6 +233,8 @@ class PLATFORM_EXPORT Canvas2DLayerBridge : public cc::TextureLayerClient { ...@@ -230,6 +233,8 @@ class PLATFORM_EXPORT Canvas2DLayerBridge : public cc::TextureLayerClient {
std::bernoulli_distribution bernoulli_distribution_; std::bernoulli_distribution bernoulli_distribution_;
Deque<RasterTimer> pending_raster_timers_; Deque<RasterTimer> pending_raster_timers_;
sk_sp<cc::PaintRecord> last_recording_;
base::WeakPtrFactory<Canvas2DLayerBridge> weak_ptr_factory_{this}; base::WeakPtrFactory<Canvas2DLayerBridge> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(Canvas2DLayerBridge); DISALLOW_COPY_AND_ASSIGN(Canvas2DLayerBridge);
......
...@@ -40,6 +40,8 @@ class PLATFORM_EXPORT CanvasResourceHost { ...@@ -40,6 +40,8 @@ class PLATFORM_EXPORT CanvasResourceHost {
virtual void DiscardResourceProvider(); virtual void DiscardResourceProvider();
virtual bool IsPrinting() const { return false; }
private: private:
std::unique_ptr<CanvasResourceProvider> resource_provider_; std::unique_ptr<CanvasResourceProvider> resource_provider_;
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment