| 1 | /* |
| 2 | * Copyright (C) 2007 Alp Toker <alp@atoker.com> |
| 3 | * Copyright (C) 2007, 2016 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 "config.h" |
| 22 | #include "PrintContext.h" |
| 23 | |
| 24 | #include "ElementTraversal.h" |
| 25 | #include "GraphicsContext.h" |
| 26 | #include "Frame.h" |
| 27 | #include "FrameView.h" |
| 28 | #include "LengthBox.h" |
| 29 | #include "RenderView.h" |
| 30 | #include "RuntimeEnabledFeatures.h" |
| 31 | #include "StyleInheritedData.h" |
| 32 | #include "StyleResolver.h" |
| 33 | #include "StyleScope.h" |
| 34 | #include <wtf/text/StringConcatenateNumbers.h> |
| 35 | |
| 36 | namespace WebCore { |
| 37 | |
| 38 | PrintContext::PrintContext(Frame* frame) |
| 39 | : FrameDestructionObserver(frame) |
| 40 | { |
| 41 | } |
| 42 | |
| 43 | PrintContext::~PrintContext() |
| 44 | { |
| 45 | if (m_isPrinting) |
| 46 | end(); |
| 47 | } |
| 48 | |
| 49 | void PrintContext::(const FloatRect& printRect, float , float , float userScaleFactor, float& outPageHeight, bool allowHorizontalTiling) |
| 50 | { |
| 51 | if (!frame()) |
| 52 | return; |
| 53 | |
| 54 | auto& frame = *this->frame(); |
| 55 | m_pageRects.clear(); |
| 56 | outPageHeight = 0; |
| 57 | |
| 58 | if (!frame.document() || !frame.view() || !frame.document()->renderView()) |
| 59 | return; |
| 60 | |
| 61 | if (userScaleFactor <= 0) { |
| 62 | LOG_ERROR("userScaleFactor has bad value %.2f" , userScaleFactor); |
| 63 | return; |
| 64 | } |
| 65 | |
| 66 | RenderView* view = frame.document()->renderView(); |
| 67 | const IntRect& documentRect = view->documentRect(); |
| 68 | FloatSize pageSize = frame.resizePageRectsKeepingRatio(FloatSize(printRect.width(), printRect.height()), FloatSize(documentRect.width(), documentRect.height())); |
| 69 | float pageWidth = pageSize.width(); |
| 70 | float pageHeight = pageSize.height(); |
| 71 | |
| 72 | outPageHeight = pageHeight; // this is the height of the page adjusted by margins |
| 73 | pageHeight -= headerHeight + footerHeight; |
| 74 | |
| 75 | if (pageHeight <= 0) { |
| 76 | LOG_ERROR("pageHeight has bad value %.2f" , pageHeight); |
| 77 | return; |
| 78 | } |
| 79 | |
| 80 | computePageRectsWithPageSizeInternal(FloatSize(pageWidth / userScaleFactor, pageHeight / userScaleFactor), allowHorizontalTiling); |
| 81 | } |
| 82 | |
| 83 | FloatBoxExtent PrintContext::computedPageMargin(FloatBoxExtent printMargin) |
| 84 | { |
| 85 | if (!frame() || !frame()->document()) |
| 86 | return printMargin; |
| 87 | if (!RuntimeEnabledFeatures::sharedFeatures().pageAtRuleSupportEnabled()) |
| 88 | return printMargin; |
| 89 | // FIXME Currently no pseudo class is supported. |
| 90 | auto style = frame()->document()->styleScope().resolver().styleForPage(0); |
| 91 | |
| 92 | float pixelToPointScaleFactor = 1 / CSSPrimitiveValue::conversionToCanonicalUnitsScaleFactor(CSSPrimitiveValue::CSS_PT); |
| 93 | return { style->marginTop().isAuto() ? printMargin.top() : style->marginTop().value() * pixelToPointScaleFactor, |
| 94 | style->marginRight().isAuto() ? printMargin.right() : style->marginRight().value() * pixelToPointScaleFactor, |
| 95 | style->marginBottom().isAuto() ? printMargin.bottom() : style->marginBottom().value() * pixelToPointScaleFactor, |
| 96 | style->marginLeft().isAuto() ? printMargin.left() : style->marginLeft().value() * pixelToPointScaleFactor }; |
| 97 | } |
| 98 | |
| 99 | FloatSize PrintContext::computedPageSize(FloatSize pageSize, FloatBoxExtent printMargin) |
| 100 | { |
| 101 | auto computedMargin = computedPageMargin(printMargin); |
| 102 | if (computedMargin == printMargin) |
| 103 | return pageSize; |
| 104 | |
| 105 | auto horizontalMarginDelta = (printMargin.left() - computedMargin.left()) + (printMargin.right() - computedMargin.right()); |
| 106 | auto verticalMarginDelta = (printMargin.top() - computedMargin.top()) + (printMargin.bottom() - computedMargin.bottom()); |
| 107 | return { pageSize.width() + horizontalMarginDelta, pageSize.height() + verticalMarginDelta }; |
| 108 | } |
| 109 | |
| 110 | void PrintContext::(const FloatSize& pageSizeInPixels, bool allowHorizontalTiling) |
| 111 | { |
| 112 | m_pageRects.clear(); |
| 113 | computePageRectsWithPageSizeInternal(pageSizeInPixels, allowHorizontalTiling); |
| 114 | } |
| 115 | |
| 116 | void PrintContext::(const FloatSize& pageSizeInPixels, bool allowInlineDirectionTiling) |
| 117 | { |
| 118 | if (!frame()) |
| 119 | return; |
| 120 | |
| 121 | auto& frame = *this->frame(); |
| 122 | if (!frame.document() || !frame.view() || !frame.document()->renderView()) |
| 123 | return; |
| 124 | |
| 125 | RenderView* view = frame.document()->renderView(); |
| 126 | |
| 127 | IntRect docRect = view->documentRect(); |
| 128 | |
| 129 | int pageWidth = pageSizeInPixels.width(); |
| 130 | int pageHeight = pageSizeInPixels.height(); |
| 131 | |
| 132 | bool isHorizontal = view->style().isHorizontalWritingMode(); |
| 133 | |
| 134 | int docLogicalHeight = isHorizontal ? docRect.height() : docRect.width(); |
| 135 | int pageLogicalHeight = isHorizontal ? pageHeight : pageWidth; |
| 136 | int pageLogicalWidth = isHorizontal ? pageWidth : pageHeight; |
| 137 | |
| 138 | int inlineDirectionStart; |
| 139 | int inlineDirectionEnd; |
| 140 | int blockDirectionStart; |
| 141 | int blockDirectionEnd; |
| 142 | if (isHorizontal) { |
| 143 | if (view->style().isFlippedBlocksWritingMode()) { |
| 144 | blockDirectionStart = docRect.maxY(); |
| 145 | blockDirectionEnd = docRect.y(); |
| 146 | } else { |
| 147 | blockDirectionStart = docRect.y(); |
| 148 | blockDirectionEnd = docRect.maxY(); |
| 149 | } |
| 150 | inlineDirectionStart = view->style().isLeftToRightDirection() ? docRect.x() : docRect.maxX(); |
| 151 | inlineDirectionEnd = view->style().isLeftToRightDirection() ? docRect.maxX() : docRect.x(); |
| 152 | } else { |
| 153 | if (view->style().isFlippedBlocksWritingMode()) { |
| 154 | blockDirectionStart = docRect.maxX(); |
| 155 | blockDirectionEnd = docRect.x(); |
| 156 | } else { |
| 157 | blockDirectionStart = docRect.x(); |
| 158 | blockDirectionEnd = docRect.maxX(); |
| 159 | } |
| 160 | inlineDirectionStart = view->style().isLeftToRightDirection() ? docRect.y() : docRect.maxY(); |
| 161 | inlineDirectionEnd = view->style().isLeftToRightDirection() ? docRect.maxY() : docRect.y(); |
| 162 | } |
| 163 | |
| 164 | unsigned pageCount = ceilf((float)docLogicalHeight / pageLogicalHeight); |
| 165 | for (unsigned i = 0; i < pageCount; ++i) { |
| 166 | int pageLogicalTop = blockDirectionEnd > blockDirectionStart ? |
| 167 | blockDirectionStart + i * pageLogicalHeight : |
| 168 | blockDirectionStart - (i + 1) * pageLogicalHeight; |
| 169 | if (allowInlineDirectionTiling) { |
| 170 | for (int currentInlinePosition = inlineDirectionStart; |
| 171 | inlineDirectionEnd > inlineDirectionStart ? currentInlinePosition < inlineDirectionEnd : currentInlinePosition > inlineDirectionEnd; |
| 172 | currentInlinePosition += (inlineDirectionEnd > inlineDirectionStart ? pageLogicalWidth : -pageLogicalWidth)) { |
| 173 | int pageLogicalLeft = inlineDirectionEnd > inlineDirectionStart ? currentInlinePosition : currentInlinePosition - pageLogicalWidth; |
| 174 | IntRect (pageLogicalLeft, pageLogicalTop, pageLogicalWidth, pageLogicalHeight); |
| 175 | if (!isHorizontal) |
| 176 | pageRect = pageRect.transposedRect(); |
| 177 | m_pageRects.append(pageRect); |
| 178 | } |
| 179 | } else { |
| 180 | int pageLogicalLeft = inlineDirectionEnd > inlineDirectionStart ? inlineDirectionStart : inlineDirectionStart - pageLogicalWidth; |
| 181 | IntRect (pageLogicalLeft, pageLogicalTop, pageLogicalWidth, pageLogicalHeight); |
| 182 | if (!isHorizontal) |
| 183 | pageRect = pageRect.transposedRect(); |
| 184 | m_pageRects.append(pageRect); |
| 185 | } |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | void PrintContext::begin(float width, float height) |
| 190 | { |
| 191 | if (!frame()) |
| 192 | return; |
| 193 | |
| 194 | auto& frame = *this->frame(); |
| 195 | // This function can be called multiple times to adjust printing parameters without going back to screen mode. |
| 196 | m_isPrinting = true; |
| 197 | |
| 198 | FloatSize originalPageSize = FloatSize(width, height); |
| 199 | FloatSize minLayoutSize = frame.resizePageRectsKeepingRatio(originalPageSize, FloatSize(width * minimumShrinkFactor(), height * minimumShrinkFactor())); |
| 200 | |
| 201 | // This changes layout, so callers need to make sure that they don't paint to screen while in printing mode. |
| 202 | frame.setPrinting(true, minLayoutSize, originalPageSize, maximumShrinkFactor() / minimumShrinkFactor(), AdjustViewSize); |
| 203 | } |
| 204 | |
| 205 | float PrintContext::computeAutomaticScaleFactor(const FloatSize& availablePaperSize) |
| 206 | { |
| 207 | if (!frame()) |
| 208 | return 1; |
| 209 | |
| 210 | auto& frame = *this->frame(); |
| 211 | if (!frame.view()) |
| 212 | return 1; |
| 213 | |
| 214 | bool useViewWidth = true; |
| 215 | if (frame.document() && frame.document()->renderView()) |
| 216 | useViewWidth = frame.document()->renderView()->style().isHorizontalWritingMode(); |
| 217 | |
| 218 | float viewLogicalWidth = useViewWidth ? frame.view()->contentsWidth() : frame.view()->contentsHeight(); |
| 219 | if (viewLogicalWidth < 1) |
| 220 | return 1; |
| 221 | |
| 222 | float maxShrinkToFitScaleFactor = 1 / maximumShrinkFactor(); |
| 223 | float shrinkToFitScaleFactor = (useViewWidth ? availablePaperSize.width() : availablePaperSize.height()) / viewLogicalWidth; |
| 224 | return std::max(maxShrinkToFitScaleFactor, shrinkToFitScaleFactor); |
| 225 | } |
| 226 | |
| 227 | void PrintContext::spoolPage(GraphicsContext& ctx, int pageNumber, float width) |
| 228 | { |
| 229 | if (!frame()) |
| 230 | return; |
| 231 | |
| 232 | auto& frame = *this->frame(); |
| 233 | // FIXME: Not correct for vertical text. |
| 234 | IntRect = m_pageRects[pageNumber]; |
| 235 | float scale = width / pageRect.width(); |
| 236 | |
| 237 | ctx.save(); |
| 238 | ctx.scale(scale); |
| 239 | ctx.translate(-pageRect.x(), -pageRect.y()); |
| 240 | ctx.clip(pageRect); |
| 241 | frame.view()->paintContents(ctx, pageRect); |
| 242 | outputLinkedDestinations(ctx, *frame.document(), pageRect); |
| 243 | ctx.restore(); |
| 244 | } |
| 245 | |
| 246 | void PrintContext::spoolRect(GraphicsContext& ctx, const IntRect& rect) |
| 247 | { |
| 248 | if (!frame()) |
| 249 | return; |
| 250 | |
| 251 | auto& frame = *this->frame(); |
| 252 | // FIXME: Not correct for vertical text. |
| 253 | ctx.save(); |
| 254 | ctx.translate(-rect.x(), -rect.y()); |
| 255 | ctx.clip(rect); |
| 256 | frame.view()->paintContents(ctx, rect); |
| 257 | outputLinkedDestinations(ctx, *frame.document(), rect); |
| 258 | ctx.restore(); |
| 259 | } |
| 260 | |
| 261 | void PrintContext::end() |
| 262 | { |
| 263 | if (!frame()) |
| 264 | return; |
| 265 | |
| 266 | auto& frame = *this->frame(); |
| 267 | ASSERT(m_isPrinting); |
| 268 | m_isPrinting = false; |
| 269 | frame.setPrinting(false, FloatSize(), FloatSize(), 0, AdjustViewSize); |
| 270 | m_linkedDestinations = nullptr; |
| 271 | } |
| 272 | |
| 273 | static inline RenderBoxModelObject* enclosingBoxModelObject(RenderElement* renderer) |
| 274 | { |
| 275 | while (renderer && !is<RenderBoxModelObject>(*renderer)) |
| 276 | renderer = renderer->parent(); |
| 277 | return downcast<RenderBoxModelObject>(renderer); |
| 278 | } |
| 279 | |
| 280 | int PrintContext::pageNumberForElement(Element* element, const FloatSize& pageSizeInPixels) |
| 281 | { |
| 282 | // Make sure the element is not freed during the layout. |
| 283 | RefPtr<Element> elementRef(element); |
| 284 | element->document().updateLayout(); |
| 285 | |
| 286 | auto* box = enclosingBoxModelObject(element->renderer()); |
| 287 | if (!box) |
| 288 | return -1; |
| 289 | |
| 290 | Frame* frame = element->document().frame(); |
| 291 | FloatRect (FloatPoint(0, 0), pageSizeInPixels); |
| 292 | PrintContext printContext(frame); |
| 293 | printContext.begin(pageRect.width(), pageRect.height()); |
| 294 | FloatSize scaledPageSize = pageSizeInPixels; |
| 295 | scaledPageSize.scale(frame->view()->contentsSize().width() / pageRect.width()); |
| 296 | printContext.computePageRectsWithPageSize(scaledPageSize, false); |
| 297 | |
| 298 | int top = roundToInt(box->offsetTop()); |
| 299 | int left = roundToInt(box->offsetLeft()); |
| 300 | size_t pageNumber = 0; |
| 301 | for (; pageNumber < printContext.pageCount(); pageNumber++) { |
| 302 | const IntRect& page = printContext.pageRect(pageNumber); |
| 303 | if (page.x() <= left && left < page.maxX() && page.y() <= top && top < page.maxY()) |
| 304 | return pageNumber; |
| 305 | } |
| 306 | return -1; |
| 307 | } |
| 308 | |
| 309 | void PrintContext::collectLinkedDestinations(Document& document) |
| 310 | { |
| 311 | for (Element* child = document.documentElement(); child; child = ElementTraversal::next(*child)) { |
| 312 | String outAnchorName; |
| 313 | if (Element* element = child->findAnchorElementForLink(outAnchorName)) |
| 314 | m_linkedDestinations->add(outAnchorName, *element); |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | void PrintContext::outputLinkedDestinations(GraphicsContext& graphicsContext, Document& document, const IntRect& ) |
| 319 | { |
| 320 | if (!graphicsContext.supportsInternalLinks()) |
| 321 | return; |
| 322 | |
| 323 | if (!m_linkedDestinations) { |
| 324 | m_linkedDestinations = std::make_unique<HashMap<String, Ref<Element>>>(); |
| 325 | collectLinkedDestinations(document); |
| 326 | } |
| 327 | |
| 328 | for (const auto& it : *m_linkedDestinations) { |
| 329 | RenderElement* renderer = it.value->renderer(); |
| 330 | if (!renderer) |
| 331 | continue; |
| 332 | |
| 333 | FloatPoint point = renderer->absoluteAnchorRect().minXMinYCorner(); |
| 334 | point = point.expandedTo(FloatPoint()); |
| 335 | |
| 336 | if (!pageRect.contains(roundedIntPoint(point))) |
| 337 | continue; |
| 338 | |
| 339 | graphicsContext.addDestinationAtPoint(it.key, point); |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | String PrintContext::pageProperty(Frame* frame, const char* propertyName, int pageNumber) |
| 344 | { |
| 345 | ASSERT(frame); |
| 346 | ASSERT(frame->document()); |
| 347 | |
| 348 | auto& document = *frame->document(); |
| 349 | PrintContext printContext(frame); |
| 350 | printContext.begin(800); // Any width is OK here. |
| 351 | document.updateLayout(); |
| 352 | auto style = document.styleScope().resolver().styleForPage(pageNumber); |
| 353 | |
| 354 | // Implement formatters for properties we care about. |
| 355 | if (!strcmp(propertyName, "margin-left" )) { |
| 356 | if (style->marginLeft().isAuto()) |
| 357 | return "auto"_s ; |
| 358 | return String::numberToStringFixedPrecision(style->marginLeft().value()); |
| 359 | } |
| 360 | if (!strcmp(propertyName, "line-height" )) |
| 361 | return String::numberToStringFixedPrecision(style->lineHeight().value()); |
| 362 | if (!strcmp(propertyName, "font-size" )) |
| 363 | return String::number(style->fontDescription().computedPixelSize()); |
| 364 | if (!strcmp(propertyName, "font-family" )) |
| 365 | return style->fontDescription().firstFamily(); |
| 366 | if (!strcmp(propertyName, "size" )) |
| 367 | return makeString(FormattedNumber::fixedPrecision(style->pageSize().width.value()), ' ', FormattedNumber::fixedPrecision(style->pageSize().height.value())); |
| 368 | |
| 369 | return makeString("pageProperty() unimplemented for: " , propertyName); |
| 370 | } |
| 371 | |
| 372 | bool PrintContext::isPageBoxVisible(Frame* frame, int pageNumber) |
| 373 | { |
| 374 | return frame->document()->isPageBoxVisible(pageNumber); |
| 375 | } |
| 376 | |
| 377 | String PrintContext::pageSizeAndMarginsInPixels(Frame* frame, int pageNumber, int width, int height, int marginTop, int marginRight, int marginBottom, int marginLeft) |
| 378 | { |
| 379 | IntSize pageSize(width, height); |
| 380 | frame->document()->pageSizeAndMarginsInPixels(pageNumber, pageSize, marginTop, marginRight, marginBottom, marginLeft); |
| 381 | |
| 382 | return makeString('(', pageSize.width(), ", " , pageSize.height(), ") " , marginTop, ' ', marginRight, ' ', marginBottom, ' ', marginLeft); |
| 383 | } |
| 384 | |
| 385 | bool PrintContext::beginAndComputePageRectsWithPageSize(Frame& frame, const FloatSize& pageSizeInPixels) |
| 386 | { |
| 387 | if (!frame.document() || !frame.view() || !frame.document()->renderView()) |
| 388 | return false; |
| 389 | |
| 390 | frame.document()->updateLayout(); |
| 391 | |
| 392 | begin(pageSizeInPixels.width(), pageSizeInPixels.height()); |
| 393 | // Account for shrink-to-fit. |
| 394 | FloatSize scaledPageSize = pageSizeInPixels; |
| 395 | scaledPageSize.scale(frame.view()->contentsSize().width() / pageSizeInPixels.width()); |
| 396 | computePageRectsWithPageSize(scaledPageSize, false); |
| 397 | |
| 398 | return true; |
| 399 | } |
| 400 | |
| 401 | int PrintContext::numberOfPages(Frame& frame, const FloatSize& pageSizeInPixels) |
| 402 | { |
| 403 | PrintContext printContext(&frame); |
| 404 | if (!printContext.beginAndComputePageRectsWithPageSize(frame, pageSizeInPixels)) |
| 405 | return -1; |
| 406 | |
| 407 | return printContext.pageCount(); |
| 408 | } |
| 409 | |
| 410 | void PrintContext::spoolAllPagesWithBoundaries(Frame& frame, GraphicsContext& graphicsContext, const FloatSize& pageSizeInPixels) |
| 411 | { |
| 412 | PrintContext printContext(&frame); |
| 413 | if (!printContext.beginAndComputePageRectsWithPageSize(frame, pageSizeInPixels)) |
| 414 | return; |
| 415 | |
| 416 | const float pageWidth = pageSizeInPixels.width(); |
| 417 | const Vector<IntRect>& = printContext.pageRects(); |
| 418 | int totalHeight = pageRects.size() * (pageSizeInPixels.height() + 1) - 1; |
| 419 | |
| 420 | // Fill the whole background by white. |
| 421 | graphicsContext.setFillColor(Color(255, 255, 255)); |
| 422 | graphicsContext.fillRect(FloatRect(0, 0, pageWidth, totalHeight)); |
| 423 | |
| 424 | graphicsContext.save(); |
| 425 | |
| 426 | int currentHeight = 0; |
| 427 | for (size_t pageIndex = 0; pageIndex < pageRects.size(); pageIndex++) { |
| 428 | // Draw a line for a page boundary if this isn't the first page. |
| 429 | if (pageIndex > 0) { |
| 430 | #if PLATFORM(COCOA) |
| 431 | int boundaryLineY = currentHeight; |
| 432 | #else |
| 433 | int boundaryLineY = currentHeight - 1; |
| 434 | #endif |
| 435 | graphicsContext.save(); |
| 436 | graphicsContext.setStrokeColor(Color(0, 0, 255)); |
| 437 | graphicsContext.setFillColor(Color(0, 0, 255)); |
| 438 | graphicsContext.drawLine(IntPoint(0, boundaryLineY), IntPoint(pageWidth, boundaryLineY)); |
| 439 | graphicsContext.restore(); |
| 440 | } |
| 441 | |
| 442 | graphicsContext.save(); |
| 443 | graphicsContext.translate(0, currentHeight); |
| 444 | printContext.spoolPage(graphicsContext, pageIndex, pageWidth); |
| 445 | graphicsContext.restore(); |
| 446 | |
| 447 | currentHeight += pageSizeInPixels.height() + 1; |
| 448 | } |
| 449 | |
| 450 | graphicsContext.restore(); |
| 451 | } |
| 452 | |
| 453 | } |
| 454 | |