1 /* 2 * Copyright (C) 2007 Alp Toker <alp (at) 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 "config.h" 22 #include "core/page/PrintContext.h" 23 24 #include "core/page/Frame.h" 25 #include "core/page/FrameView.h" 26 #include "core/platform/graphics/GraphicsContext.h" 27 #include "core/rendering/RenderView.h" 28 #include "wtf/text/WTFString.h" 29 30 namespace WebCore { 31 32 // By imaging to a width a little wider than the available pixels, 33 // thin pages will be scaled down a little, matching the way they 34 // print in IE and Camino. This lets them use fewer sheets than they 35 // would otherwise, which is presumably why other browsers do this. 36 // Wide pages will be scaled down more than this. 37 const float printingMinimumShrinkFactor = 1.25f; 38 39 // This number determines how small we are willing to reduce the page content 40 // in order to accommodate the widest line. If the page would have to be 41 // reduced smaller to make the widest line fit, we just clip instead (this 42 // behavior matches MacIE and Mozilla, at least) 43 const float printingMaximumShrinkFactor = 2; 44 45 PrintContext::PrintContext(Frame* frame) 46 : m_frame(frame) 47 , m_isPrinting(false) 48 , m_linkedDestinationsValid(false) 49 { 50 } 51 52 PrintContext::~PrintContext() 53 { 54 if (m_isPrinting) 55 end(); 56 } 57 58 void PrintContext::computePageRects(const FloatRect& printRect, float headerHeight, float footerHeight, float userScaleFactor, float& outPageHeight, bool allowHorizontalTiling) 59 { 60 m_pageRects.clear(); 61 outPageHeight = 0; 62 63 if (!m_frame->document() || !m_frame->view() || !m_frame->document()->renderView()) 64 return; 65 66 if (userScaleFactor <= 0) { 67 LOG_ERROR("userScaleFactor has bad value %.2f", userScaleFactor); 68 return; 69 } 70 71 RenderView* view = m_frame->document()->renderView(); 72 const IntRect& documentRect = view->documentRect(); 73 FloatSize pageSize = m_frame->resizePageRectsKeepingRatio(FloatSize(printRect.width(), printRect.height()), FloatSize(documentRect.width(), documentRect.height())); 74 float pageWidth = pageSize.width(); 75 float pageHeight = pageSize.height(); 76 77 outPageHeight = pageHeight; // this is the height of the page adjusted by margins 78 pageHeight -= headerHeight + footerHeight; 79 80 if (pageHeight <= 0) { 81 LOG_ERROR("pageHeight has bad value %.2f", pageHeight); 82 return; 83 } 84 85 computePageRectsWithPageSizeInternal(FloatSize(pageWidth / userScaleFactor, pageHeight / userScaleFactor), allowHorizontalTiling); 86 } 87 88 void PrintContext::computePageRectsWithPageSize(const FloatSize& pageSizeInPixels, bool allowHorizontalTiling) 89 { 90 m_pageRects.clear(); 91 computePageRectsWithPageSizeInternal(pageSizeInPixels, allowHorizontalTiling); 92 } 93 94 void PrintContext::computePageRectsWithPageSizeInternal(const FloatSize& pageSizeInPixels, bool allowInlineDirectionTiling) 95 { 96 if (!m_frame->document() || !m_frame->view() || !m_frame->document()->renderView()) 97 return; 98 99 RenderView* view = m_frame->document()->renderView(); 100 101 IntRect docRect = view->documentRect(); 102 103 int pageWidth = pageSizeInPixels.width(); 104 int pageHeight = pageSizeInPixels.height(); 105 106 bool isHorizontal = view->style()->isHorizontalWritingMode(); 107 108 int docLogicalHeight = isHorizontal ? docRect.height() : docRect.width(); 109 int pageLogicalHeight = isHorizontal ? pageHeight : pageWidth; 110 int pageLogicalWidth = isHorizontal ? pageWidth : pageHeight; 111 112 int inlineDirectionStart; 113 int inlineDirectionEnd; 114 int blockDirectionStart; 115 int blockDirectionEnd; 116 if (isHorizontal) { 117 if (view->style()->isFlippedBlocksWritingMode()) { 118 blockDirectionStart = docRect.maxY(); 119 blockDirectionEnd = docRect.y(); 120 } else { 121 blockDirectionStart = docRect.y(); 122 blockDirectionEnd = docRect.maxY(); 123 } 124 inlineDirectionStart = view->style()->isLeftToRightDirection() ? docRect.x() : docRect.maxX(); 125 inlineDirectionEnd = view->style()->isLeftToRightDirection() ? docRect.maxX() : docRect.x(); 126 } else { 127 if (view->style()->isFlippedBlocksWritingMode()) { 128 blockDirectionStart = docRect.maxX(); 129 blockDirectionEnd = docRect.x(); 130 } else { 131 blockDirectionStart = docRect.x(); 132 blockDirectionEnd = docRect.maxX(); 133 } 134 inlineDirectionStart = view->style()->isLeftToRightDirection() ? docRect.y() : docRect.maxY(); 135 inlineDirectionEnd = view->style()->isLeftToRightDirection() ? docRect.maxY() : docRect.y(); 136 } 137 138 unsigned pageCount = ceilf((float)docLogicalHeight / pageLogicalHeight); 139 for (unsigned i = 0; i < pageCount; ++i) { 140 int pageLogicalTop = blockDirectionEnd > blockDirectionStart ? 141 blockDirectionStart + i * pageLogicalHeight : 142 blockDirectionStart - (i + 1) * pageLogicalHeight; 143 if (allowInlineDirectionTiling) { 144 for (int currentInlinePosition = inlineDirectionStart; 145 inlineDirectionEnd > inlineDirectionStart ? currentInlinePosition < inlineDirectionEnd : currentInlinePosition > inlineDirectionEnd; 146 currentInlinePosition += (inlineDirectionEnd > inlineDirectionStart ? pageLogicalWidth : -pageLogicalWidth)) { 147 int pageLogicalLeft = inlineDirectionEnd > inlineDirectionStart ? currentInlinePosition : currentInlinePosition - pageLogicalWidth; 148 IntRect pageRect(pageLogicalLeft, pageLogicalTop, pageLogicalWidth, pageLogicalHeight); 149 if (!isHorizontal) 150 pageRect = pageRect.transposedRect(); 151 m_pageRects.append(pageRect); 152 } 153 } else { 154 int pageLogicalLeft = inlineDirectionEnd > inlineDirectionStart ? inlineDirectionStart : inlineDirectionStart - pageLogicalWidth; 155 IntRect pageRect(pageLogicalLeft, pageLogicalTop, pageLogicalWidth, pageLogicalHeight); 156 if (!isHorizontal) 157 pageRect = pageRect.transposedRect(); 158 m_pageRects.append(pageRect); 159 } 160 } 161 } 162 163 void PrintContext::begin(float width, float height) 164 { 165 // This function can be called multiple times to adjust printing parameters without going back to screen mode. 166 m_isPrinting = true; 167 168 FloatSize originalPageSize = FloatSize(width, height); 169 FloatSize minLayoutSize = m_frame->resizePageRectsKeepingRatio(originalPageSize, FloatSize(width * printingMinimumShrinkFactor, height * printingMinimumShrinkFactor)); 170 171 // This changes layout, so callers need to make sure that they don't paint to screen while in printing mode. 172 m_frame->setPrinting(true, minLayoutSize, originalPageSize, printingMaximumShrinkFactor / printingMinimumShrinkFactor, AdjustViewSize); 173 } 174 175 float PrintContext::computeAutomaticScaleFactor(const FloatSize& availablePaperSize) 176 { 177 if (!m_frame->view()) 178 return 1; 179 180 bool useViewWidth = true; 181 if (m_frame->document() && m_frame->document()->renderView()) 182 useViewWidth = m_frame->document()->renderView()->style()->isHorizontalWritingMode(); 183 184 float viewLogicalWidth = useViewWidth ? m_frame->view()->contentsWidth() : m_frame->view()->contentsHeight(); 185 if (viewLogicalWidth < 1) 186 return 1; 187 188 float maxShrinkToFitScaleFactor = 1 / printingMaximumShrinkFactor; 189 float shrinkToFitScaleFactor = (useViewWidth ? availablePaperSize.width() : availablePaperSize.height()) / viewLogicalWidth; 190 return max(maxShrinkToFitScaleFactor, shrinkToFitScaleFactor); 191 } 192 193 void PrintContext::spoolPage(GraphicsContext& ctx, int pageNumber, float width) 194 { 195 // FIXME: Not correct for vertical text. 196 IntRect pageRect = m_pageRects[pageNumber]; 197 float scale = width / pageRect.width(); 198 199 ctx.save(); 200 ctx.scale(FloatSize(scale, scale)); 201 ctx.translate(-pageRect.x(), -pageRect.y()); 202 ctx.clip(pageRect); 203 m_frame->view()->paintContents(&ctx, pageRect); 204 if (ctx.supportsURLFragments()) 205 outputLinkedDestinations(ctx, m_frame->document(), pageRect); 206 ctx.restore(); 207 } 208 209 void PrintContext::spoolRect(GraphicsContext& ctx, const IntRect& rect) 210 { 211 // FIXME: Not correct for vertical text. 212 ctx.save(); 213 ctx.translate(-rect.x(), -rect.y()); 214 ctx.clip(rect); 215 m_frame->view()->paintContents(&ctx, rect); 216 ctx.restore(); 217 } 218 219 void PrintContext::end() 220 { 221 ASSERT(m_isPrinting); 222 m_isPrinting = false; 223 m_frame->setPrinting(false, FloatSize(), FloatSize(), 0, AdjustViewSize); 224 m_linkedDestinations.clear(); 225 m_linkedDestinationsValid = false; 226 } 227 228 static RenderBoxModelObject* enclosingBoxModelObject(RenderObject* object) 229 { 230 231 while (object && !object->isBoxModelObject()) 232 object = object->parent(); 233 if (!object) 234 return 0; 235 return toRenderBoxModelObject(object); 236 } 237 238 int PrintContext::pageNumberForElement(Element* element, const FloatSize& pageSizeInPixels) 239 { 240 // Make sure the element is not freed during the layout. 241 RefPtr<Element> elementRef(element); 242 element->document()->updateLayout(); 243 244 RenderBoxModelObject* box = enclosingBoxModelObject(element->renderer()); 245 if (!box) 246 return -1; 247 248 Frame* frame = element->document()->frame(); 249 FloatRect pageRect(FloatPoint(0, 0), pageSizeInPixels); 250 PrintContext printContext(frame); 251 printContext.begin(pageRect.width(), pageRect.height()); 252 FloatSize scaledPageSize = pageSizeInPixels; 253 scaledPageSize.scale(frame->view()->contentsSize().width() / pageRect.width()); 254 printContext.computePageRectsWithPageSize(scaledPageSize, false); 255 256 int top = box->pixelSnappedOffsetTop(); 257 int left = box->pixelSnappedOffsetLeft(); 258 size_t pageNumber = 0; 259 for (; pageNumber < printContext.pageCount(); pageNumber++) { 260 const IntRect& page = printContext.pageRect(pageNumber); 261 if (page.x() <= left && left < page.maxX() && page.y() <= top && top < page.maxY()) 262 return pageNumber; 263 } 264 return -1; 265 } 266 267 void PrintContext::collectLinkedDestinations(Node* node) 268 { 269 for (Node* i = node->firstChild(); i; i = i->nextSibling()) 270 collectLinkedDestinations(i); 271 272 if (!node->isLink() || !node->isElementNode()) 273 return; 274 const AtomicString& href = toElement(node)->getAttribute(HTMLNames::hrefAttr); 275 if (href.isNull()) 276 return; 277 KURL url = node->document()->completeURL(href); 278 if (!url.isValid()) 279 return; 280 if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(url, node->document()->baseURL())) { 281 String name = url.fragmentIdentifier(); 282 Element* element = node->document()->findAnchor(name); 283 if (element) 284 m_linkedDestinations.set(name, element); 285 } 286 } 287 288 void PrintContext::outputLinkedDestinations(GraphicsContext& graphicsContext, Node* node, const IntRect& pageRect) 289 { 290 if (!m_linkedDestinationsValid) { 291 collectLinkedDestinations(node); 292 m_linkedDestinationsValid = true; 293 } 294 295 HashMap<String, Element*>::const_iterator end = m_linkedDestinations.end(); 296 for (HashMap<String, Element*>::const_iterator it = m_linkedDestinations.begin(); it != end; ++it) { 297 RenderObject* renderer = it->value->renderer(); 298 if (renderer) { 299 IntRect boundingBox = renderer->absoluteBoundingBoxRect(); 300 if (pageRect.intersects(boundingBox)) { 301 IntPoint point = boundingBox.minXMinYCorner(); 302 point.clampNegativeToZero(); 303 graphicsContext.addURLTargetAtPoint(it->key, point); 304 } 305 } 306 } 307 } 308 309 String PrintContext::pageProperty(Frame* frame, const char* propertyName, int pageNumber) 310 { 311 Document* document = frame->document(); 312 PrintContext printContext(frame); 313 printContext.begin(800); // Any width is OK here. 314 document->updateLayout(); 315 RefPtr<RenderStyle> style = document->styleForPage(pageNumber); 316 317 // Implement formatters for properties we care about. 318 if (!strcmp(propertyName, "margin-left")) { 319 if (style->marginLeft().isAuto()) 320 return String("auto"); 321 return String::number(style->marginLeft().value()); 322 } 323 if (!strcmp(propertyName, "line-height")) 324 return String::number(style->lineHeight().value()); 325 if (!strcmp(propertyName, "font-size")) 326 return String::number(style->fontDescription().computedPixelSize()); 327 if (!strcmp(propertyName, "font-family")) 328 return style->fontDescription().family().family().string(); 329 if (!strcmp(propertyName, "size")) 330 return String::number(style->pageSize().width().value()) + ' ' + String::number(style->pageSize().height().value()); 331 332 return String("pageProperty() unimplemented for: ") + propertyName; 333 } 334 335 bool PrintContext::isPageBoxVisible(Frame* frame, int pageNumber) 336 { 337 return frame->document()->isPageBoxVisible(pageNumber); 338 } 339 340 String PrintContext::pageSizeAndMarginsInPixels(Frame* frame, int pageNumber, int width, int height, int marginTop, int marginRight, int marginBottom, int marginLeft) 341 { 342 IntSize pageSize(width, height); 343 frame->document()->pageSizeAndMarginsInPixels(pageNumber, pageSize, marginTop, marginRight, marginBottom, marginLeft); 344 345 return "(" + String::number(pageSize.width()) + ", " + String::number(pageSize.height()) + ") " + 346 String::number(marginTop) + ' ' + String::number(marginRight) + ' ' + String::number(marginBottom) + ' ' + String::number(marginLeft); 347 } 348 349 int PrintContext::numberOfPages(Frame* frame, const FloatSize& pageSizeInPixels) 350 { 351 frame->document()->updateLayout(); 352 353 FloatRect pageRect(FloatPoint(0, 0), pageSizeInPixels); 354 PrintContext printContext(frame); 355 printContext.begin(pageRect.width(), pageRect.height()); 356 // Account for shrink-to-fit. 357 FloatSize scaledPageSize = pageSizeInPixels; 358 scaledPageSize.scale(frame->view()->contentsSize().width() / pageRect.width()); 359 printContext.computePageRectsWithPageSize(scaledPageSize, false); 360 return printContext.pageCount(); 361 } 362 363 void PrintContext::spoolAllPagesWithBoundaries(Frame* frame, GraphicsContext& graphicsContext, const FloatSize& pageSizeInPixels) 364 { 365 if (!frame->document() || !frame->view() || !frame->document()->renderer()) 366 return; 367 368 frame->document()->updateLayout(); 369 370 PrintContext printContext(frame); 371 printContext.begin(pageSizeInPixels.width(), pageSizeInPixels.height()); 372 373 float pageHeight; 374 printContext.computePageRects(FloatRect(FloatPoint(0, 0), pageSizeInPixels), 0, 0, 1, pageHeight); 375 376 const float pageWidth = pageSizeInPixels.width(); 377 const Vector<IntRect>& pageRects = printContext.pageRects(); 378 int totalHeight = pageRects.size() * (pageSizeInPixels.height() + 1) - 1; 379 380 // Fill the whole background by white. 381 graphicsContext.setFillColor(Color(255, 255, 255)); 382 graphicsContext.fillRect(FloatRect(0, 0, pageWidth, totalHeight)); 383 384 graphicsContext.save(); 385 graphicsContext.translate(0, totalHeight); 386 graphicsContext.scale(FloatSize(1, -1)); 387 388 int currentHeight = 0; 389 for (size_t pageIndex = 0; pageIndex < pageRects.size(); pageIndex++) { 390 // Draw a line for a page boundary if this isn't the first page. 391 if (pageIndex > 0) { 392 graphicsContext.save(); 393 graphicsContext.setStrokeColor(Color(0, 0, 255)); 394 graphicsContext.setFillColor(Color(0, 0, 255)); 395 graphicsContext.drawLine(IntPoint(0, currentHeight), 396 IntPoint(pageWidth, currentHeight)); 397 graphicsContext.restore(); 398 } 399 400 graphicsContext.save(); 401 graphicsContext.translate(0, currentHeight); 402 printContext.spoolPage(graphicsContext, pageIndex, pageWidth); 403 graphicsContext.restore(); 404 405 currentHeight += pageSizeInPixels.height() + 1; 406 } 407 408 graphicsContext.restore(); 409 } 410 411 } 412