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/renderer/core/frame/local_frame.h"
26 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
27 #include "third_party/blink/renderer/core/layout/layout_view.h"
28 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
29 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
30
31 namespace blink {
32
33 namespace {
34
EnclosingBoxModelObject(LayoutObject * object)35 LayoutBoxModelObject* EnclosingBoxModelObject(LayoutObject* object) {
36 while (object && !object->IsBoxModelObject())
37 object = object->Parent();
38 if (!object)
39 return nullptr;
40 return ToLayoutBoxModelObject(object);
41 }
42
IsCoordinateInPage(int top,int left,const IntRect & page)43 bool IsCoordinateInPage(int top, int left, const IntRect& page) {
44 return page.X() <= left && left < page.MaxX() && page.Y() <= top &&
45 top < page.MaxY();
46 }
47
48 } // namespace
49
PrintContext(LocalFrame * frame,bool use_printing_layout)50 PrintContext::PrintContext(LocalFrame* frame, bool use_printing_layout)
51 : frame_(frame),
52 is_printing_(false),
53 use_printing_layout_(use_printing_layout),
54 linked_destinations_valid_(false) {}
55
~PrintContext()56 PrintContext::~PrintContext() {
57 DCHECK(!is_printing_);
58 }
59
ComputePageRects(const FloatSize & print_size)60 void PrintContext::ComputePageRects(const FloatSize& print_size) {
61 page_rects_.clear();
62
63 if (!IsFrameValid())
64 return;
65
66 if (!use_printing_layout_) {
67 IntRect page_rect(0, 0, print_size.Width(), print_size.Height());
68 page_rects_.push_back(page_rect);
69 return;
70 }
71
72 auto* view = frame_->GetDocument()->GetLayoutView();
73 const PhysicalRect& document_rect = view->DocumentRect();
74 FloatSize page_size = frame_->ResizePageRectsKeepingRatio(
75 print_size, FloatSize(document_rect.Width(), document_rect.Height()));
76 ComputePageRectsWithPageSizeInternal(page_size);
77 }
78
ComputePageRectsWithPageSize(const FloatSize & page_size_in_pixels)79 void PrintContext::ComputePageRectsWithPageSize(
80 const FloatSize& page_size_in_pixels) {
81 page_rects_.clear();
82 ComputePageRectsWithPageSizeInternal(page_size_in_pixels);
83 }
84
ComputePageRectsWithPageSizeInternal(const FloatSize & page_size_in_pixels)85 void PrintContext::ComputePageRectsWithPageSizeInternal(
86 const FloatSize& page_size_in_pixels) {
87 if (!IsFrameValid())
88 return;
89
90 auto* view = frame_->GetDocument()->GetLayoutView();
91
92 IntRect snapped_doc_rect = PixelSnappedIntRect(view->DocumentRect());
93
94 int page_width = page_size_in_pixels.Width();
95 // We scaled with floating point arithmetic and need to ensure results like
96 // 13329.99 are treated as 13330 so that we don't mistakenly assign an extra
97 // page for the stray pixel.
98 int page_height = page_size_in_pixels.Height() + LayoutUnit::Epsilon();
99
100 bool is_horizontal = view->StyleRef().IsHorizontalWritingMode();
101
102 int doc_logical_height =
103 is_horizontal ? snapped_doc_rect.Height() : snapped_doc_rect.Width();
104 int page_logical_height = is_horizontal ? page_height : page_width;
105 int page_logical_width = is_horizontal ? page_width : page_height;
106
107 int inline_direction_start = snapped_doc_rect.X();
108 int inline_direction_end = snapped_doc_rect.MaxX();
109 int block_direction_start = snapped_doc_rect.Y();
110 int block_direction_end = snapped_doc_rect.MaxY();
111 if (!is_horizontal) {
112 std::swap(block_direction_start, inline_direction_start);
113 std::swap(block_direction_end, inline_direction_end);
114 }
115 if (!view->StyleRef().IsLeftToRightDirection())
116 std::swap(inline_direction_start, inline_direction_end);
117 if (view->StyleRef().IsFlippedBlocksWritingMode())
118 std::swap(block_direction_start, block_direction_end);
119
120 unsigned page_count =
121 ceilf(static_cast<float>(doc_logical_height) / page_logical_height);
122 for (unsigned i = 0; i < page_count; ++i) {
123 int page_logical_top =
124 block_direction_end > block_direction_start
125 ? block_direction_start + i * page_logical_height
126 : block_direction_start - (i + 1) * page_logical_height;
127
128 int page_logical_left = inline_direction_end > inline_direction_start
129 ? inline_direction_start
130 : inline_direction_start - page_logical_width;
131
132 auto* scrollable_area = GetFrame()->View()->LayoutViewport();
133 IntSize frame_scroll = scrollable_area->ScrollOffsetInt();
134 page_logical_left -= frame_scroll.Width();
135 page_logical_top -= frame_scroll.Height();
136 IntRect page_rect(page_logical_left, page_logical_top, page_logical_width,
137 page_logical_height);
138 if (!is_horizontal)
139 page_rect = page_rect.TransposedRect();
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 linked_destinations_.clear();
170 linked_destinations_valid_ = false;
171 }
172
173 // static
PageNumberForElement(Element * element,const FloatSize & page_size_in_pixels)174 int PrintContext::PageNumberForElement(Element* element,
175 const FloatSize& page_size_in_pixels) {
176 element->GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kPrinting);
177
178 LocalFrame* frame = element->GetDocument().GetFrame();
179 FloatRect page_rect(FloatPoint(0, 0), page_size_in_pixels);
180 ScopedPrintContext print_context(frame);
181 print_context->BeginPrintMode(page_rect.Width(), page_rect.Height());
182
183 LayoutBoxModelObject* box =
184 EnclosingBoxModelObject(element->GetLayoutObject());
185 if (!box)
186 return -1;
187
188 FloatSize scaled_page_size = page_size_in_pixels;
189 scaled_page_size.Scale(
190 frame->View()->LayoutViewport()->ContentsSize().Width() /
191 page_rect.Width());
192 print_context->ComputePageRectsWithPageSize(scaled_page_size);
193
194 int top = box->PixelSnappedOffsetTop(box->OffsetParent());
195 int left = box->PixelSnappedOffsetLeft(box->OffsetParent());
196 for (wtf_size_t page_number = 0; page_number < print_context->PageCount();
197 ++page_number) {
198 if (IsCoordinateInPage(top, left, print_context->PageRect(page_number)))
199 return static_cast<int>(page_number);
200 }
201 return -1;
202 }
203
CollectLinkedDestinations(Node * node)204 void PrintContext::CollectLinkedDestinations(Node* node) {
205 for (Node* i = node->firstChild(); i; i = i->nextSibling())
206 CollectLinkedDestinations(i);
207
208 auto* element = DynamicTo<Element>(node);
209 if (!node->IsLink() || !element)
210 return;
211 const AtomicString& href = element->getAttribute(html_names::kHrefAttr);
212 if (href.IsNull())
213 return;
214 KURL url = node->GetDocument().CompleteURL(href);
215 if (!url.IsValid())
216 return;
217
218 if (url.HasFragmentIdentifier() &&
219 EqualIgnoringFragmentIdentifier(url, node->GetDocument().BaseURL())) {
220 String name = url.FragmentIdentifier();
221 if (Element* element = node->GetDocument().FindAnchor(name))
222 linked_destinations_.Set(name, element);
223 }
224 }
225
OutputLinkedDestinations(GraphicsContext & context,const IntRect & page_rect)226 void PrintContext::OutputLinkedDestinations(GraphicsContext& context,
227 const IntRect& page_rect) {
228 if (!linked_destinations_valid_) {
229 // Collect anchors in the top-level frame only because our PrintContext
230 // supports only one namespace for the anchors.
231 CollectLinkedDestinations(GetFrame()->GetDocument());
232 linked_destinations_valid_ = true;
233 }
234
235 for (const auto& entry : linked_destinations_) {
236 LayoutObject* layout_object = entry.value->GetLayoutObject();
237 if (!layout_object || !layout_object->GetFrameView())
238 continue;
239 IntPoint anchor_point = layout_object->AbsoluteBoundingBoxRect().Location();
240 if (page_rect.Contains(anchor_point))
241 context.SetURLDestinationLocation(entry.key, anchor_point);
242 }
243 }
244
245 // static
PageProperty(LocalFrame * frame,const char * property_name,int page_number)246 String PrintContext::PageProperty(LocalFrame* frame,
247 const char* property_name,
248 int page_number) {
249 Document* document = frame->GetDocument();
250 ScopedPrintContext print_context(frame);
251 // Any non-zero size is OK here. We don't care about actual layout. We just
252 // want to collect @page rules and figure out what declarations apply on a
253 // given page (that may or may not exist).
254 print_context->BeginPrintMode(800, 1000);
255 scoped_refptr<const ComputedStyle> style =
256 document->StyleForPage(page_number);
257
258 // Implement formatters for properties we care about.
259 if (!strcmp(property_name, "margin-left")) {
260 if (style->MarginLeft().IsAuto())
261 return String("auto");
262 return String::Number(style->MarginLeft().Value());
263 }
264 if (!strcmp(property_name, "line-height"))
265 return String::Number(style->LineHeight().Value());
266 if (!strcmp(property_name, "font-size"))
267 return String::Number(style->GetFontDescription().ComputedPixelSize());
268 if (!strcmp(property_name, "font-family"))
269 return style->GetFontDescription().Family().Family().GetString();
270 if (!strcmp(property_name, "size"))
271 return String::Number(style->PageSize().Width()) + ' ' +
272 String::Number(style->PageSize().Height());
273
274 return String("pageProperty() unimplemented for: ") + property_name;
275 }
276
IsPageBoxVisible(LocalFrame * frame,int page_number)277 bool PrintContext::IsPageBoxVisible(LocalFrame* frame, int page_number) {
278 return frame->GetDocument()->IsPageBoxVisible(page_number);
279 }
280
PageSizeAndMarginsInPixels(LocalFrame * frame,int page_number,int width,int height,int margin_top,int margin_right,int margin_bottom,int margin_left)281 String PrintContext::PageSizeAndMarginsInPixels(LocalFrame* frame,
282 int page_number,
283 int width,
284 int height,
285 int margin_top,
286 int margin_right,
287 int margin_bottom,
288 int margin_left) {
289 DoubleSize page_size(width, height);
290 frame->GetDocument()->PageSizeAndMarginsInPixels(page_number, page_size,
291 margin_top, margin_right,
292 margin_bottom, margin_left);
293
294 return "(" + String::Number(floor(page_size.Width())) + ", " +
295 String::Number(floor(page_size.Height())) + ") " +
296 String::Number(margin_top) + ' ' + String::Number(margin_right) + ' ' +
297 String::Number(margin_bottom) + ' ' + String::Number(margin_left);
298 }
299
300 // static
NumberOfPages(LocalFrame * frame,const FloatSize & page_size_in_pixels)301 int PrintContext::NumberOfPages(LocalFrame* frame,
302 const FloatSize& page_size_in_pixels) {
303 frame->GetDocument()->UpdateStyleAndLayout(DocumentUpdateReason::kPrinting);
304
305 FloatRect page_rect(FloatPoint(0, 0), page_size_in_pixels);
306 ScopedPrintContext print_context(frame);
307 print_context->BeginPrintMode(page_rect.Width(), page_rect.Height());
308 // Account for shrink-to-fit.
309 FloatSize scaled_page_size = page_size_in_pixels;
310 scaled_page_size.Scale(
311 frame->View()->LayoutViewport()->ContentsSize().Width() /
312 page_rect.Width());
313 print_context->ComputePageRectsWithPageSize(scaled_page_size);
314 return print_context->PageCount();
315 }
316
IsFrameValid() const317 bool PrintContext::IsFrameValid() const {
318 return frame_->View() && frame_->GetDocument() &&
319 frame_->GetDocument()->GetLayoutView();
320 }
321
Trace(Visitor * visitor)322 void PrintContext::Trace(Visitor* visitor) {
323 visitor->Trace(frame_);
324 visitor->Trace(linked_destinations_);
325 }
326
use_printing_layout() const327 bool PrintContext::use_printing_layout() const {
328 return use_printing_layout_;
329 }
330
ScopedPrintContext(LocalFrame * frame)331 ScopedPrintContext::ScopedPrintContext(LocalFrame* frame)
332 : context_(
333 MakeGarbageCollected<PrintContext>(frame,
334 /*use_printing_layout=*/true)) {}
335
~ScopedPrintContext()336 ScopedPrintContext::~ScopedPrintContext() {
337 context_->EndPrintMode();
338 }
339
340 } // namespace blink
341