/* * Copyright (C) 2007 Alp Toker * Copyright (C) 2007, 2016 Apple Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "PrintContext.h" #include "ElementTraversal.h" #include "GraphicsContext.h" #include "Frame.h" #include "FrameView.h" #include "RenderView.h" #include "StyleInheritedData.h" #include "StyleResolver.h" #include "StyleScope.h" #include namespace WebCore { PrintContext::PrintContext(Frame* frame) : m_frame(frame) { } PrintContext::~PrintContext() { if (m_isPrinting) end(); } void PrintContext::computePageRects(const FloatRect& printRect, float headerHeight, float footerHeight, float userScaleFactor, float& outPageHeight, bool allowHorizontalTiling) { m_pageRects.clear(); outPageHeight = 0; if (!m_frame->document() || !m_frame->view() || !m_frame->document()->renderView()) return; if (userScaleFactor <= 0) { LOG_ERROR("userScaleFactor has bad value %.2f", userScaleFactor); return; } RenderView* view = m_frame->document()->renderView(); const IntRect& documentRect = view->documentRect(); FloatSize pageSize = m_frame->resizePageRectsKeepingRatio(FloatSize(printRect.width(), printRect.height()), FloatSize(documentRect.width(), documentRect.height())); float pageWidth = pageSize.width(); float pageHeight = pageSize.height(); outPageHeight = pageHeight; // this is the height of the page adjusted by margins pageHeight -= headerHeight + footerHeight; if (pageHeight <= 0) { LOG_ERROR("pageHeight has bad value %.2f", pageHeight); return; } computePageRectsWithPageSizeInternal(FloatSize(pageWidth / userScaleFactor, pageHeight / userScaleFactor), allowHorizontalTiling); } void PrintContext::computePageRectsWithPageSize(const FloatSize& pageSizeInPixels, bool allowHorizontalTiling) { m_pageRects.clear(); computePageRectsWithPageSizeInternal(pageSizeInPixels, allowHorizontalTiling); } void PrintContext::computePageRectsWithPageSizeInternal(const FloatSize& pageSizeInPixels, bool allowInlineDirectionTiling) { if (!m_frame->document() || !m_frame->view() || !m_frame->document()->renderView()) return; RenderView* view = m_frame->document()->renderView(); IntRect docRect = view->documentRect(); int pageWidth = pageSizeInPixels.width(); int pageHeight = pageSizeInPixels.height(); bool isHorizontal = view->style().isHorizontalWritingMode(); int docLogicalHeight = isHorizontal ? docRect.height() : docRect.width(); int pageLogicalHeight = isHorizontal ? pageHeight : pageWidth; int pageLogicalWidth = isHorizontal ? pageWidth : pageHeight; int inlineDirectionStart; int inlineDirectionEnd; int blockDirectionStart; int blockDirectionEnd; if (isHorizontal) { if (view->style().isFlippedBlocksWritingMode()) { blockDirectionStart = docRect.maxY(); blockDirectionEnd = docRect.y(); } else { blockDirectionStart = docRect.y(); blockDirectionEnd = docRect.maxY(); } inlineDirectionStart = view->style().isLeftToRightDirection() ? docRect.x() : docRect.maxX(); inlineDirectionEnd = view->style().isLeftToRightDirection() ? docRect.maxX() : docRect.x(); } else { if (view->style().isFlippedBlocksWritingMode()) { blockDirectionStart = docRect.maxX(); blockDirectionEnd = docRect.x(); } else { blockDirectionStart = docRect.x(); blockDirectionEnd = docRect.maxX(); } inlineDirectionStart = view->style().isLeftToRightDirection() ? docRect.y() : docRect.maxY(); inlineDirectionEnd = view->style().isLeftToRightDirection() ? docRect.maxY() : docRect.y(); } unsigned pageCount = ceilf((float)docLogicalHeight / pageLogicalHeight); for (unsigned i = 0; i < pageCount; ++i) { int pageLogicalTop = blockDirectionEnd > blockDirectionStart ? blockDirectionStart + i * pageLogicalHeight : blockDirectionStart - (i + 1) * pageLogicalHeight; if (allowInlineDirectionTiling) { for (int currentInlinePosition = inlineDirectionStart; inlineDirectionEnd > inlineDirectionStart ? currentInlinePosition < inlineDirectionEnd : currentInlinePosition > inlineDirectionEnd; currentInlinePosition += (inlineDirectionEnd > inlineDirectionStart ? pageLogicalWidth : -pageLogicalWidth)) { int pageLogicalLeft = inlineDirectionEnd > inlineDirectionStart ? currentInlinePosition : currentInlinePosition - pageLogicalWidth; IntRect pageRect(pageLogicalLeft, pageLogicalTop, pageLogicalWidth, pageLogicalHeight); if (!isHorizontal) pageRect = pageRect.transposedRect(); m_pageRects.append(pageRect); } } else { int pageLogicalLeft = inlineDirectionEnd > inlineDirectionStart ? inlineDirectionStart : inlineDirectionStart - pageLogicalWidth; IntRect pageRect(pageLogicalLeft, pageLogicalTop, pageLogicalWidth, pageLogicalHeight); if (!isHorizontal) pageRect = pageRect.transposedRect(); m_pageRects.append(pageRect); } } } void PrintContext::begin(float width, float height) { // This function can be called multiple times to adjust printing parameters without going back to screen mode. m_isPrinting = true; FloatSize originalPageSize = FloatSize(width, height); FloatSize minLayoutSize = m_frame->resizePageRectsKeepingRatio(originalPageSize, FloatSize(width * minimumShrinkFactor(), height * minimumShrinkFactor())); // This changes layout, so callers need to make sure that they don't paint to screen while in printing mode. m_frame->setPrinting(true, minLayoutSize, originalPageSize, maximumShrinkFactor() / minimumShrinkFactor(), AdjustViewSize); } float PrintContext::computeAutomaticScaleFactor(const FloatSize& availablePaperSize) { if (!m_frame->view()) return 1; bool useViewWidth = true; if (m_frame->document() && m_frame->document()->renderView()) useViewWidth = m_frame->document()->renderView()->style().isHorizontalWritingMode(); float viewLogicalWidth = useViewWidth ? m_frame->view()->contentsWidth() : m_frame->view()->contentsHeight(); if (viewLogicalWidth < 1) return 1; float maxShrinkToFitScaleFactor = 1 / maximumShrinkFactor(); float shrinkToFitScaleFactor = (useViewWidth ? availablePaperSize.width() : availablePaperSize.height()) / viewLogicalWidth; return std::max(maxShrinkToFitScaleFactor, shrinkToFitScaleFactor); } void PrintContext::spoolPage(GraphicsContext& ctx, int pageNumber, float width) { // FIXME: Not correct for vertical text. IntRect pageRect = m_pageRects[pageNumber]; float scale = width / pageRect.width(); ctx.save(); ctx.scale(scale); ctx.translate(-pageRect.x(), -pageRect.y()); ctx.clip(pageRect); m_frame->view()->paintContents(ctx, pageRect); outputLinkedDestinations(ctx, *m_frame->document(), pageRect); ctx.restore(); } void PrintContext::spoolRect(GraphicsContext& ctx, const IntRect& rect) { // FIXME: Not correct for vertical text. ctx.save(); ctx.translate(-rect.x(), -rect.y()); ctx.clip(rect); m_frame->view()->paintContents(ctx, rect); outputLinkedDestinations(ctx, *m_frame->document(), rect); ctx.restore(); } void PrintContext::end() { ASSERT(m_isPrinting); m_isPrinting = false; m_frame->setPrinting(false, FloatSize(), FloatSize(), 0, AdjustViewSize); m_linkedDestinations = nullptr; } static inline RenderBoxModelObject* enclosingBoxModelObject(RenderElement* renderer) { while (renderer && !is(*renderer)) renderer = renderer->parent(); return downcast(renderer); } int PrintContext::pageNumberForElement(Element* element, const FloatSize& pageSizeInPixels) { // Make sure the element is not freed during the layout. RefPtr elementRef(element); element->document().updateLayout(); auto* box = enclosingBoxModelObject(element->renderer()); if (!box) return -1; Frame* frame = element->document().frame(); FloatRect pageRect(FloatPoint(0, 0), pageSizeInPixels); PrintContext printContext(frame); printContext.begin(pageRect.width(), pageRect.height()); FloatSize scaledPageSize = pageSizeInPixels; scaledPageSize.scale(frame->view()->contentsSize().width() / pageRect.width()); printContext.computePageRectsWithPageSize(scaledPageSize, false); int top = roundToInt(box->offsetTop()); int left = roundToInt(box->offsetLeft()); size_t pageNumber = 0; for (; pageNumber < printContext.pageCount(); pageNumber++) { const IntRect& page = printContext.pageRect(pageNumber); if (page.x() <= left && left < page.maxX() && page.y() <= top && top < page.maxY()) return pageNumber; } return -1; } void PrintContext::collectLinkedDestinations(Document& document) { for (Element* child = document.documentElement(); child; child = ElementTraversal::next(*child)) { String outAnchorName; if (Element* element = child->findAnchorElementForLink(outAnchorName)) m_linkedDestinations->add(outAnchorName, *element); } } void PrintContext::outputLinkedDestinations(GraphicsContext& graphicsContext, Document& document, const IntRect& pageRect) { if (!graphicsContext.supportsInternalLinks()) return; if (!m_linkedDestinations) { m_linkedDestinations = std::make_unique>>(); collectLinkedDestinations(document); } for (const auto& it : *m_linkedDestinations) { RenderElement* renderer = it.value->renderer(); if (!renderer) continue; FloatPoint point = renderer->absoluteAnchorRect().minXMinYCorner(); point = point.expandedTo(FloatPoint()); if (!pageRect.contains(roundedIntPoint(point))) continue; graphicsContext.addDestinationAtPoint(it.key, point); } } String PrintContext::pageProperty(Frame* frame, const char* propertyName, int pageNumber) { ASSERT(frame); ASSERT(frame->document()); auto& document = *frame->document(); PrintContext printContext(frame); printContext.begin(800); // Any width is OK here. document.updateLayout(); auto style = document.styleScope().resolver().styleForPage(pageNumber); // Implement formatters for properties we care about. if (!strcmp(propertyName, "margin-left")) { if (style->marginLeft().isAuto()) return ASCIILiteral { "auto" }; return String::number(style->marginLeft().value()); } if (!strcmp(propertyName, "line-height")) return String::number(style->lineHeight().value()); if (!strcmp(propertyName, "font-size")) return String::number(style->fontDescription().computedPixelSize()); if (!strcmp(propertyName, "font-family")) return style->fontDescription().firstFamily(); if (!strcmp(propertyName, "size")) return String::number(style->pageSize().width.value()) + ' ' + String::number(style->pageSize().height.value()); return makeString("pageProperty() unimplemented for: ", propertyName); } bool PrintContext::isPageBoxVisible(Frame* frame, int pageNumber) { return frame->document()->isPageBoxVisible(pageNumber); } String PrintContext::pageSizeAndMarginsInPixels(Frame* frame, int pageNumber, int width, int height, int marginTop, int marginRight, int marginBottom, int marginLeft) { IntSize pageSize(width, height); frame->document()->pageSizeAndMarginsInPixels(pageNumber, pageSize, marginTop, marginRight, marginBottom, marginLeft); return "(" + String::number(pageSize.width()) + ", " + String::number(pageSize.height()) + ") " + String::number(marginTop) + ' ' + String::number(marginRight) + ' ' + String::number(marginBottom) + ' ' + String::number(marginLeft); } bool PrintContext::beginAndComputePageRectsWithPageSize(Frame& frame, const FloatSize& pageSizeInPixels) { if (!frame.document() || !frame.view() || !frame.document()->renderView()) return false; frame.document()->updateLayout(); begin(pageSizeInPixels.width(), pageSizeInPixels.height()); // Account for shrink-to-fit. FloatSize scaledPageSize = pageSizeInPixels; scaledPageSize.scale(frame.view()->contentsSize().width() / pageSizeInPixels.width()); computePageRectsWithPageSize(scaledPageSize, false); return true; } int PrintContext::numberOfPages(Frame& frame, const FloatSize& pageSizeInPixels) { PrintContext printContext(&frame); if (!printContext.beginAndComputePageRectsWithPageSize(frame, pageSizeInPixels)) return -1; return printContext.pageCount(); } void PrintContext::spoolAllPagesWithBoundaries(Frame& frame, GraphicsContext& graphicsContext, const FloatSize& pageSizeInPixels) { PrintContext printContext(&frame); if (!printContext.beginAndComputePageRectsWithPageSize(frame, pageSizeInPixels)) return; const float pageWidth = pageSizeInPixels.width(); const Vector& pageRects = printContext.pageRects(); int totalHeight = pageRects.size() * (pageSizeInPixels.height() + 1) - 1; // Fill the whole background by white. graphicsContext.setFillColor(Color(255, 255, 255)); graphicsContext.fillRect(FloatRect(0, 0, pageWidth, totalHeight)); graphicsContext.save(); int currentHeight = 0; for (size_t pageIndex = 0; pageIndex < pageRects.size(); pageIndex++) { // Draw a line for a page boundary if this isn't the first page. if (pageIndex > 0) { #if PLATFORM(COCOA) int boundaryLineY = currentHeight; #else int boundaryLineY = currentHeight - 1; #endif graphicsContext.save(); graphicsContext.setStrokeColor(Color(0, 0, 255)); graphicsContext.setFillColor(Color(0, 0, 255)); graphicsContext.drawLine(IntPoint(0, boundaryLineY), IntPoint(pageWidth, boundaryLineY)); graphicsContext.restore(); } graphicsContext.save(); graphicsContext.translate(0, currentHeight); printContext.spoolPage(graphicsContext, pageIndex, pageWidth); graphicsContext.restore(); currentHeight += pageSizeInPixels.height() + 1; } graphicsContext.restore(); } }