1 /*
2  * Copyright (C) 2007 Alp Toker <alp@atoker.com>
3  * Copyright (C) 2007 Apple Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public License
16  * along with this library; see the file COPYING.LIB.  If not, write to
17  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 #include "third_party/blink/renderer/core/page/print_context.h"
22 
23 #include <utility>
24 
25 #include "third_party/blink/public/web/web_print_page_description.h"
26 #include "third_party/blink/renderer/core/frame/local_frame.h"
27 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
28 #include "third_party/blink/renderer/core/frame/page_scale_constraints_set.h"
29 #include "third_party/blink/renderer/core/layout/layout_view.h"
30 #include "third_party/blink/renderer/core/page/page.h"
31 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
32 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
33 
34 namespace blink {
35 
36 namespace {
37 
EnclosingBoxModelObject(LayoutObject * object)38 LayoutBoxModelObject* EnclosingBoxModelObject(LayoutObject* object) {
39   while (object && !object->IsBoxModelObject())
40     object = object->Parent();
41   return To<LayoutBoxModelObject>(object);
42 }
43 
IsCoordinateInPage(int top,int left,const IntRect & page)44 bool IsCoordinateInPage(int top, int left, const IntRect& page) {
45   return page.X() <= left && left < page.MaxX() && page.Y() <= top &&
46          top < page.MaxY();
47 }
48 
49 }  // namespace
50 
PrintContext(LocalFrame * frame,bool use_printing_layout)51 PrintContext::PrintContext(LocalFrame* frame, bool use_printing_layout)
52     : frame_(frame),
53       is_printing_(false),
54       use_printing_layout_(use_printing_layout),
55       linked_destinations_valid_(false) {}
56 
~PrintContext()57 PrintContext::~PrintContext() {
58   DCHECK(!is_printing_);
59 }
60 
ComputePageRects(const FloatSize & print_size)61 void PrintContext::ComputePageRects(const FloatSize& print_size) {
62   page_rects_.clear();
63 
64   if (!IsFrameValid())
65     return;
66 
67   if (!use_printing_layout_) {
68     IntRect page_rect(0, 0, print_size.Width(), print_size.Height());
69     page_rects_.push_back(page_rect);
70     return;
71   }
72 
73   auto* view = frame_->GetDocument()->GetLayoutView();
74   const PhysicalRect& document_rect = view->DocumentRect();
75   FloatSize page_size = frame_->ResizePageRectsKeepingRatio(
76       print_size, FloatSize(document_rect.Width(), document_rect.Height()));
77   ComputePageRectsWithPageSizeInternal(page_size);
78 }
79 
ComputePageRectsWithPageSize(const FloatSize & page_size_in_pixels)80 void PrintContext::ComputePageRectsWithPageSize(
81     const FloatSize& page_size_in_pixels) {
82   page_rects_.clear();
83   ComputePageRectsWithPageSizeInternal(page_size_in_pixels);
84 }
85 
ComputePageRectsWithPageSizeInternal(const FloatSize & page_size_in_pixels)86 void PrintContext::ComputePageRectsWithPageSizeInternal(
87     const FloatSize& page_size_in_pixels) {
88   if (!IsFrameValid())
89     return;
90 
91   auto* view = frame_->GetDocument()->GetLayoutView();
92 
93   IntRect snapped_doc_rect = PixelSnappedIntRect(view->DocumentRect());
94 
95   int page_width = page_size_in_pixels.Width();
96   // We scaled with floating point arithmetic and need to ensure results like
97   // 13329.99 are treated as 13330 so that we don't mistakenly assign an extra
98   // page for the stray pixel.
99   int page_height = page_size_in_pixels.Height() + LayoutUnit::Epsilon();
100 
101   bool is_horizontal = view->StyleRef().IsHorizontalWritingMode();
102 
103   int doc_logical_height =
104       is_horizontal ? snapped_doc_rect.Height() : snapped_doc_rect.Width();
105   int page_logical_height = is_horizontal ? page_height : page_width;
106   int page_logical_width = is_horizontal ? page_width : page_height;
107 
108   int inline_direction_start = snapped_doc_rect.X();
109   int inline_direction_end = snapped_doc_rect.MaxX();
110   int block_direction_start = snapped_doc_rect.Y();
111   int block_direction_end = snapped_doc_rect.MaxY();
112   if (!is_horizontal) {
113     std::swap(block_direction_start, inline_direction_start);
114     std::swap(block_direction_end, inline_direction_end);
115   }
116   if (!view->StyleRef().IsLeftToRightDirection())
117     std::swap(inline_direction_start, inline_direction_end);
118   if (view->StyleRef().IsFlippedBlocksWritingMode())
119     std::swap(block_direction_start, block_direction_end);
120 
121   unsigned page_count =
122       ceilf(static_cast<float>(doc_logical_height) / page_logical_height);
123   for (unsigned i = 0; i < page_count; ++i) {
124     int page_logical_top =
125         block_direction_end > block_direction_start
126             ? block_direction_start + i * page_logical_height
127             : block_direction_start - (i + 1) * page_logical_height;
128 
129     int page_logical_left = inline_direction_end > inline_direction_start
130                                 ? inline_direction_start
131                                 : inline_direction_start - page_logical_width;
132 
133     auto* scrollable_area = GetFrame()->View()->LayoutViewport();
134     IntRect page_rect(page_logical_left, page_logical_top, page_logical_width,
135                       page_logical_height);
136     if (!is_horizontal)
137       page_rect = page_rect.TransposedRect();
138     IntSize frame_scroll = scrollable_area->ScrollOffsetInt();
139     page_rect.Move(-frame_scroll.Width(), -frame_scroll.Height());
140     page_rects_.push_back(page_rect);
141   }
142 }
143 
BeginPrintMode(float width,float height)144 void PrintContext::BeginPrintMode(float width, float height) {
145   DCHECK_GT(width, 0);
146   DCHECK_GT(height, 0);
147 
148   // This function can be called multiple times to adjust printing parameters
149   // without going back to screen mode.
150   is_printing_ = true;
151 
152   FloatSize original_page_size = FloatSize(width, height);
153   FloatSize min_layout_size = frame_->ResizePageRectsKeepingRatio(
154       original_page_size, FloatSize(width * kPrintingMinimumShrinkFactor,
155                                     height * kPrintingMinimumShrinkFactor));
156 
157   // This changes layout, so callers need to make sure that they don't paint to
158   // screen while in printing mode.
159   frame_->StartPrinting(
160       min_layout_size, original_page_size,
161       kPrintingMaximumShrinkFactor / kPrintingMinimumShrinkFactor);
162 }
163 
EndPrintMode()164 void PrintContext::EndPrintMode() {
165   DCHECK(is_printing_);
166   is_printing_ = false;
167   if (IsFrameValid()) {
168     frame_->EndPrinting();
169 
170     // Printing changes the viewport and content size which may result in
171     // changing the page scale factor. Call SetNeedsReset() so that we reset
172     // back to the initial page scale factor when we exit printing mode.
173     frame_->GetPage()->GetPageScaleConstraintsSet().SetNeedsReset(true);
174   }
175   linked_destinations_.clear();
176   linked_destinations_valid_ = false;
177 }
178 
179 // static
PageNumberForElement(Element * element,const FloatSize & page_size_in_pixels)180 int PrintContext::PageNumberForElement(Element* element,
181                                        const FloatSize& page_size_in_pixels) {
182   element->GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kPrinting);
183 
184   LocalFrame* frame = element->GetDocument().GetFrame();
185   FloatRect page_rect(FloatPoint(0, 0), page_size_in_pixels);
186   ScopedPrintContext print_context(frame);
187   print_context->BeginPrintMode(page_rect.Width(), page_rect.Height());
188 
189   LayoutBoxModelObject* box =
190       EnclosingBoxModelObject(element->GetLayoutObject());
191   if (!box)
192     return -1;
193 
194   FloatSize scaled_page_size = page_size_in_pixels;
195   scaled_page_size.Scale(
196       frame->View()->LayoutViewport()->ContentsSize().Width() /
197       page_rect.Width());
198   print_context->ComputePageRectsWithPageSize(scaled_page_size);
199 
200   int top = box->PixelSnappedOffsetTop(box->OffsetParent());
201   int left = box->PixelSnappedOffsetLeft(box->OffsetParent());
202   for (wtf_size_t page_number = 0; page_number < print_context->PageCount();
203        ++page_number) {
204     if (IsCoordinateInPage(top, left, print_context->PageRect(page_number)))
205       return static_cast<int>(page_number);
206   }
207   return -1;
208 }
209 
CollectLinkedDestinations(Node * node)210 void PrintContext::CollectLinkedDestinations(Node* node) {
211   for (Node* i = node->firstChild(); i; i = i->nextSibling())
212     CollectLinkedDestinations(i);
213 
214   auto* element = DynamicTo<Element>(node);
215   if (!node->IsLink() || !element)
216     return;
217   const AtomicString& href = element->getAttribute(html_names::kHrefAttr);
218   if (href.IsNull())
219     return;
220   KURL url = node->GetDocument().CompleteURL(href);
221   if (!url.IsValid())
222     return;
223 
224   if (url.HasFragmentIdentifier() &&
225       EqualIgnoringFragmentIdentifier(url, node->GetDocument().BaseURL())) {
226     String name = url.FragmentIdentifier();
227     if (Node* target = node->GetDocument().FindAnchor(name))
228       linked_destinations_.Set(name, target);
229   }
230 }
231 
OutputLinkedDestinations(GraphicsContext & context,const IntRect & page_rect)232 void PrintContext::OutputLinkedDestinations(GraphicsContext& context,
233                                             const IntRect& page_rect) {
234   if (!linked_destinations_valid_) {
235     // Collect anchors in the top-level frame only because our PrintContext
236     // supports only one namespace for the anchors.
237     CollectLinkedDestinations(GetFrame()->GetDocument());
238     linked_destinations_valid_ = true;
239   }
240 
241   for (const auto& entry : linked_destinations_) {
242     LayoutObject* layout_object = entry.value->GetLayoutObject();
243     if (!layout_object || !layout_object->GetFrameView())
244       continue;
245     IntPoint anchor_point = layout_object->AbsoluteBoundingBoxRect().Location();
246     if (page_rect.Contains(anchor_point))
247       context.SetURLDestinationLocation(entry.key, anchor_point);
248   }
249 }
250 
251 // static
PageProperty(LocalFrame * frame,const char * property_name,uint32_t page_number)252 String PrintContext::PageProperty(LocalFrame* frame,
253                                   const char* property_name,
254                                   uint32_t page_number) {
255   Document* document = frame->GetDocument();
256   ScopedPrintContext print_context(frame);
257   // Any non-zero size is OK here. We don't care about actual layout. We just
258   // want to collect @page rules and figure out what declarations apply on a
259   // given page (that may or may not exist).
260   print_context->BeginPrintMode(800, 1000);
261   scoped_refptr<const ComputedStyle> style =
262       document->StyleForPage(page_number);
263 
264   // Implement formatters for properties we care about.
265   if (!strcmp(property_name, "margin-left")) {
266     if (style->MarginLeft().IsAuto())
267       return String("auto");
268     return String::Number(style->MarginLeft().Value());
269   }
270   if (!strcmp(property_name, "line-height"))
271     return String::Number(style->LineHeight().Value());
272   if (!strcmp(property_name, "font-size"))
273     return String::Number(style->GetFontDescription().ComputedPixelSize());
274   if (!strcmp(property_name, "font-family"))
275     return style->GetFontDescription().Family().Family().GetString();
276   if (!strcmp(property_name, "size"))
277     return String::Number(style->PageSize().Width()) + ' ' +
278            String::Number(style->PageSize().Height());
279 
280   return String("pageProperty() unimplemented for: ") + property_name;
281 }
282 
IsPageBoxVisible(LocalFrame * frame,uint32_t page_number)283 bool PrintContext::IsPageBoxVisible(LocalFrame* frame, uint32_t page_number) {
284   return frame->GetDocument()->IsPageBoxVisible(page_number);
285 }
286 
PageSizeAndMarginsInPixels(LocalFrame * frame,uint32_t page_number,int width,int height,int margin_top,int margin_right,int margin_bottom,int margin_left)287 String PrintContext::PageSizeAndMarginsInPixels(LocalFrame* frame,
288                                                 uint32_t page_number,
289                                                 int width,
290                                                 int height,
291                                                 int margin_top,
292                                                 int margin_right,
293                                                 int margin_bottom,
294                                                 int margin_left) {
295   WebPrintPageDescription description;
296   description.size = WebDoubleSize(width, height);
297   description.margin_top = margin_top;
298   description.margin_right = margin_right;
299   description.margin_bottom = margin_bottom;
300   description.margin_left = margin_left;
301   frame->GetDocument()->GetPageDescription(page_number, &description);
302 
303   return "(" + String::Number(floor(description.size.Width())) + ", " +
304          String::Number(floor(description.size.Height())) + ") " +
305          String::Number(description.margin_top) + ' ' +
306          String::Number(description.margin_right) + ' ' +
307          String::Number(description.margin_bottom) + ' ' +
308          String::Number(description.margin_left);
309 }
310 
311 // static
NumberOfPages(LocalFrame * frame,const FloatSize & page_size_in_pixels)312 int PrintContext::NumberOfPages(LocalFrame* frame,
313                                 const FloatSize& page_size_in_pixels) {
314   frame->GetDocument()->UpdateStyleAndLayout(DocumentUpdateReason::kPrinting);
315 
316   FloatRect page_rect(FloatPoint(0, 0), page_size_in_pixels);
317   ScopedPrintContext print_context(frame);
318   print_context->BeginPrintMode(page_rect.Width(), page_rect.Height());
319   // Account for shrink-to-fit.
320   FloatSize scaled_page_size = page_size_in_pixels;
321   scaled_page_size.Scale(
322       frame->View()->LayoutViewport()->ContentsSize().Width() /
323       page_rect.Width());
324   print_context->ComputePageRectsWithPageSize(scaled_page_size);
325   return print_context->PageCount();
326 }
327 
IsFrameValid() const328 bool PrintContext::IsFrameValid() const {
329   return frame_->View() && frame_->GetDocument() &&
330          frame_->GetDocument()->GetLayoutView();
331 }
332 
Trace(Visitor * visitor) const333 void PrintContext::Trace(Visitor* visitor) const {
334   visitor->Trace(frame_);
335   visitor->Trace(linked_destinations_);
336 }
337 
use_printing_layout() const338 bool PrintContext::use_printing_layout() const {
339   return use_printing_layout_;
340 }
341 
ScopedPrintContext(LocalFrame * frame)342 ScopedPrintContext::ScopedPrintContext(LocalFrame* frame)
343     : context_(
344           MakeGarbageCollected<PrintContext>(frame,
345                                              /*use_printing_layout=*/true)) {}
346 
~ScopedPrintContext()347 ScopedPrintContext::~ScopedPrintContext() {
348   context_->EndPrintMode();
349 }
350 
351 }  // namespace blink
352