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