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