169e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal/* 269e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal * Copyright (C) 2007 Alp Toker <alp@atoker.com> 369e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal * Copyright (C) 2007 Apple Inc. 469e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal * 569e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal * This library is free software; you can redistribute it and/or 669e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal * modify it under the terms of the GNU Library General Public 769e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal * License as published by the Free Software Foundation; either 869e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal * version 2 of the License, or (at your option) any later version. 969e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal * 1069e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal * This library is distributed in the hope that it will be useful, 1169e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal * but WITHOUT ANY WARRANTY; without even the implied warranty of 1269e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 1369e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal * Library General Public License for more details. 1469e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal * 1569e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal * You should have received a copy of the GNU Library General Public License 1669e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal * along with this library; see the file COPYING.LIB. If not, write to 1769e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 1869e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal * Boston, MA 02110-1301, USA. 1969e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal */ 2069e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal 2169e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal#include "config.h" 2269e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal#include "core/page/PrintContext.h" 2369e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal 2469e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal#include "core/frame/Frame.h" 2569e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal#include "core/frame/FrameView.h" 2669e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal#include "core/rendering/RenderView.h" 2769e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal#include "platform/graphics/GraphicsContext.h" 2869e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal 2969e17611504376e4d4603925f8528dfc890fd2c6Luis Sigalnamespace WebCore { 3069e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal 3169e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal// By imaging to a width a little wider than the available pixels, 3269e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal// thin pages will be scaled down a little, matching the way they 3369e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal// print in IE and Camino. This lets them use fewer sheets than they 3469e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal// would otherwise, which is presumably why other browsers do this. 3569e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal// Wide pages will be scaled down more than this. 3669e17611504376e4d4603925f8528dfc890fd2c6Luis Sigalconst float printingMinimumShrinkFactor = 1.25f; 3769e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal 3869e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal// This number determines how small we are willing to reduce the page content 3969e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal// in order to accommodate the widest line. If the page would have to be 4069e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal// reduced smaller to make the widest line fit, we just clip instead (this 4169e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal// behavior matches MacIE and Mozilla, at least) 4269e17611504376e4d4603925f8528dfc890fd2c6Luis Sigalconst float printingMaximumShrinkFactor = 2; 4369e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal 4469e17611504376e4d4603925f8528dfc890fd2c6Luis SigalPrintContext::PrintContext(Frame* frame) 4569e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal : m_frame(frame) 4669e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal , m_isPrinting(false) 4769e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal , m_linkedDestinationsValid(false) 4869e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal{ 4969e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal} 5069e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal 5169e17611504376e4d4603925f8528dfc890fd2c6Luis SigalPrintContext::~PrintContext() 5269e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal{ 5369e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal if (m_isPrinting) 5469e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal end(); 5569e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal} 5669e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal 5769e17611504376e4d4603925f8528dfc890fd2c6Luis Sigalvoid PrintContext::computePageRects(const FloatRect& printRect, float headerHeight, float footerHeight, float userScaleFactor, float& outPageHeight, bool allowHorizontalTiling) 5869e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal{ 5969e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal m_pageRects.clear(); 6069e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal outPageHeight = 0; 6169e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal 6269e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal if (!m_frame->document() || !m_frame->view() || !m_frame->document()->renderView()) 6369e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal return; 6469e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal 6569e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal if (userScaleFactor <= 0) { 6669e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal WTF_LOG_ERROR("userScaleFactor has bad value %.2f", userScaleFactor); 6769e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal return; 6869e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal } 6969e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal 7069e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal RenderView* view = m_frame->document()->renderView(); 7169e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal const IntRect& documentRect = view->documentRect(); 7269e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal FloatSize pageSize = m_frame->resizePageRectsKeepingRatio(FloatSize(printRect.width(), printRect.height()), FloatSize(documentRect.width(), documentRect.height())); 7369e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal float pageWidth = pageSize.width(); 7469e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal float pageHeight = pageSize.height(); 7569e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal 7669e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal outPageHeight = pageHeight; // this is the height of the page adjusted by margins 7769e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal pageHeight -= headerHeight + footerHeight; 7869e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal 7969e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal if (pageHeight <= 0) { 8069e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal WTF_LOG_ERROR("pageHeight has bad value %.2f", pageHeight); 8169e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal return; 8269e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal } 8369e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal 8469e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal computePageRectsWithPageSizeInternal(FloatSize(pageWidth / userScaleFactor, pageHeight / userScaleFactor), allowHorizontalTiling); 8569e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal} 8669e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal 8769e17611504376e4d4603925f8528dfc890fd2c6Luis Sigalvoid PrintContext::computePageRectsWithPageSize(const FloatSize& pageSizeInPixels, bool allowHorizontalTiling) 8869e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal{ 8969e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal m_pageRects.clear(); 9069e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal computePageRectsWithPageSizeInternal(pageSizeInPixels, allowHorizontalTiling); 9169e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal} 9269e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal 9369e17611504376e4d4603925f8528dfc890fd2c6Luis Sigalvoid PrintContext::computePageRectsWithPageSizeInternal(const FloatSize& pageSizeInPixels, bool allowInlineDirectionTiling) 9469e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal{ 9569e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal if (!m_frame->document() || !m_frame->view() || !m_frame->document()->renderView()) 9669e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal return; 9769e17611504376e4d4603925f8528dfc890fd2c6Luis Sigal 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 162void 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, AdjustViewSize); 172} 173 174float PrintContext::computeAutomaticScaleFactor(const FloatSize& availablePaperSize) 175{ 176 if (!m_frame->view()) 177 return 1; 178 179 bool useViewWidth = true; 180 if (m_frame->document() && m_frame->document()->renderView()) 181 useViewWidth = m_frame->document()->renderView()->style()->isHorizontalWritingMode(); 182 183 float viewLogicalWidth = useViewWidth ? m_frame->view()->contentsWidth() : m_frame->view()->contentsHeight(); 184 if (viewLogicalWidth < 1) 185 return 1; 186 187 float maxShrinkToFitScaleFactor = 1 / printingMaximumShrinkFactor; 188 float shrinkToFitScaleFactor = (useViewWidth ? availablePaperSize.width() : availablePaperSize.height()) / viewLogicalWidth; 189 return max(maxShrinkToFitScaleFactor, shrinkToFitScaleFactor); 190} 191 192void PrintContext::spoolPage(GraphicsContext& ctx, int pageNumber, float width) 193{ 194 // FIXME: Not correct for vertical text. 195 IntRect pageRect = m_pageRects[pageNumber]; 196 float scale = width / pageRect.width(); 197 198 ctx.save(); 199 ctx.scale(FloatSize(scale, scale)); 200 ctx.translate(-pageRect.x(), -pageRect.y()); 201 ctx.clip(pageRect); 202 m_frame->view()->paintContents(&ctx, pageRect); 203 if (ctx.supportsURLFragments()) 204 outputLinkedDestinations(ctx, m_frame->document(), pageRect); 205 ctx.restore(); 206} 207 208void PrintContext::spoolRect(GraphicsContext& ctx, const IntRect& rect) 209{ 210 // FIXME: Not correct for vertical text. 211 ctx.save(); 212 ctx.translate(-rect.x(), -rect.y()); 213 ctx.clip(rect); 214 m_frame->view()->paintContents(&ctx, rect); 215 ctx.restore(); 216} 217 218void PrintContext::end() 219{ 220 ASSERT(m_isPrinting); 221 m_isPrinting = false; 222 m_frame->setPrinting(false, FloatSize(), FloatSize(), 0, AdjustViewSize); 223 m_linkedDestinations.clear(); 224 m_linkedDestinationsValid = false; 225} 226 227static RenderBoxModelObject* enclosingBoxModelObject(RenderObject* object) 228{ 229 230 while (object && !object->isBoxModelObject()) 231 object = object->parent(); 232 if (!object) 233 return 0; 234 return toRenderBoxModelObject(object); 235} 236 237int PrintContext::pageNumberForElement(Element* element, const FloatSize& pageSizeInPixels) 238{ 239 // Make sure the element is not freed during the layout. 240 RefPtr<Element> elementRef(element); 241 element->document().updateLayout(); 242 243 RenderBoxModelObject* box = enclosingBoxModelObject(element->renderer()); 244 if (!box) 245 return -1; 246 247 Frame* frame = element->document().frame(); 248 FloatRect pageRect(FloatPoint(0, 0), pageSizeInPixels); 249 PrintContext printContext(frame); 250 printContext.begin(pageRect.width(), pageRect.height()); 251 FloatSize scaledPageSize = pageSizeInPixels; 252 scaledPageSize.scale(frame->view()->contentsSize().width() / pageRect.width()); 253 printContext.computePageRectsWithPageSize(scaledPageSize, false); 254 255 int top = box->pixelSnappedOffsetTop(); 256 int left = box->pixelSnappedOffsetLeft(); 257 size_t pageNumber = 0; 258 for (; pageNumber < printContext.pageCount(); pageNumber++) { 259 const IntRect& page = printContext.pageRect(pageNumber); 260 if (page.x() <= left && left < page.maxX() && page.y() <= top && top < page.maxY()) 261 return pageNumber; 262 } 263 return -1; 264} 265 266void PrintContext::collectLinkedDestinations(Node* node) 267{ 268 for (Node* i = node->firstChild(); i; i = i->nextSibling()) 269 collectLinkedDestinations(i); 270 271 if (!node->isLink() || !node->isElementNode()) 272 return; 273 const AtomicString& href = toElement(node)->getAttribute(HTMLNames::hrefAttr); 274 if (href.isNull()) 275 return; 276 KURL url = node->document().completeURL(href); 277 if (!url.isValid()) 278 return; 279 if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(url, node->document().baseURL())) { 280 String name = url.fragmentIdentifier(); 281 Element* element = node->document().findAnchor(name); 282 if (element) 283 m_linkedDestinations.set(name, element); 284 } 285} 286 287void PrintContext::outputLinkedDestinations(GraphicsContext& graphicsContext, Node* node, const IntRect& pageRect) 288{ 289 if (!m_linkedDestinationsValid) { 290 collectLinkedDestinations(node); 291 m_linkedDestinationsValid = true; 292 } 293 294 HashMap<String, Element*>::const_iterator end = m_linkedDestinations.end(); 295 for (HashMap<String, Element*>::const_iterator it = m_linkedDestinations.begin(); it != end; ++it) { 296 RenderObject* renderer = it->value->renderer(); 297 if (renderer) { 298 IntRect boundingBox = renderer->absoluteBoundingBoxRect(); 299 if (pageRect.intersects(boundingBox)) { 300 IntPoint point = boundingBox.minXMinYCorner(); 301 point.clampNegativeToZero(); 302 graphicsContext.addURLTargetAtPoint(it->key, point); 303 } 304 } 305 } 306} 307 308String PrintContext::pageProperty(Frame* frame, const char* propertyName, int pageNumber) 309{ 310 Document* document = frame->document(); 311 PrintContext printContext(frame); 312 printContext.begin(800); // Any width is OK here. 313 document->updateLayout(); 314 RefPtr<RenderStyle> style = document->styleForPage(pageNumber); 315 316 // Implement formatters for properties we care about. 317 if (!strcmp(propertyName, "margin-left")) { 318 if (style->marginLeft().isAuto()) 319 return String("auto"); 320 return String::number(style->marginLeft().value()); 321 } 322 if (!strcmp(propertyName, "line-height")) 323 return String::number(style->lineHeight().value()); 324 if (!strcmp(propertyName, "font-size")) 325 return String::number(style->fontDescription().computedPixelSize()); 326 if (!strcmp(propertyName, "font-family")) 327 return style->fontDescription().family().family().string(); 328 if (!strcmp(propertyName, "size")) 329 return String::number(style->pageSize().width().value()) + ' ' + String::number(style->pageSize().height().value()); 330 331 return String("pageProperty() unimplemented for: ") + propertyName; 332} 333 334bool PrintContext::isPageBoxVisible(Frame* frame, int pageNumber) 335{ 336 return frame->document()->isPageBoxVisible(pageNumber); 337} 338 339String PrintContext::pageSizeAndMarginsInPixels(Frame* frame, int pageNumber, int width, int height, int marginTop, int marginRight, int marginBottom, int marginLeft) 340{ 341 IntSize pageSize(width, height); 342 frame->document()->pageSizeAndMarginsInPixels(pageNumber, pageSize, marginTop, marginRight, marginBottom, marginLeft); 343 344 return "(" + String::number(pageSize.width()) + ", " + String::number(pageSize.height()) + ") " + 345 String::number(marginTop) + ' ' + String::number(marginRight) + ' ' + String::number(marginBottom) + ' ' + String::number(marginLeft); 346} 347 348int PrintContext::numberOfPages(Frame* frame, const FloatSize& pageSizeInPixels) 349{ 350 frame->document()->updateLayout(); 351 352 FloatRect pageRect(FloatPoint(0, 0), pageSizeInPixels); 353 PrintContext printContext(frame); 354 printContext.begin(pageRect.width(), pageRect.height()); 355 // Account for shrink-to-fit. 356 FloatSize scaledPageSize = pageSizeInPixels; 357 scaledPageSize.scale(frame->view()->contentsSize().width() / pageRect.width()); 358 printContext.computePageRectsWithPageSize(scaledPageSize, false); 359 return printContext.pageCount(); 360} 361 362void PrintContext::spoolAllPagesWithBoundaries(Frame* frame, GraphicsContext& graphicsContext, const FloatSize& pageSizeInPixels) 363{ 364 if (!frame->document() || !frame->view() || !frame->document()->renderer()) 365 return; 366 367 frame->document()->updateLayout(); 368 369 PrintContext printContext(frame); 370 printContext.begin(pageSizeInPixels.width(), pageSizeInPixels.height()); 371 372 float pageHeight; 373 printContext.computePageRects(FloatRect(FloatPoint(0, 0), pageSizeInPixels), 0, 0, 1, pageHeight); 374 375 const float pageWidth = pageSizeInPixels.width(); 376 const Vector<IntRect>& pageRects = printContext.pageRects(); 377 int totalHeight = pageRects.size() * (pageSizeInPixels.height() + 1) - 1; 378 379 // Fill the whole background by white. 380 graphicsContext.setFillColor(Color(255, 255, 255)); 381 graphicsContext.fillRect(FloatRect(0, 0, pageWidth, totalHeight)); 382 383 graphicsContext.save(); 384 graphicsContext.translate(0, totalHeight); 385 graphicsContext.scale(FloatSize(1, -1)); 386 387 int currentHeight = 0; 388 for (size_t pageIndex = 0; pageIndex < pageRects.size(); pageIndex++) { 389 // Draw a line for a page boundary if this isn't the first page. 390 if (pageIndex > 0) { 391 graphicsContext.save(); 392 graphicsContext.setStrokeColor(Color(0, 0, 255)); 393 graphicsContext.setFillColor(Color(0, 0, 255)); 394 graphicsContext.drawLine(IntPoint(0, currentHeight), 395 IntPoint(pageWidth, currentHeight)); 396 graphicsContext.restore(); 397 } 398 399 graphicsContext.save(); 400 graphicsContext.translate(0, currentHeight); 401 printContext.spoolPage(graphicsContext, pageIndex, pageWidth); 402 graphicsContext.restore(); 403 404 currentHeight += pageSizeInPixels.height() + 1; 405 } 406 407 graphicsContext.restore(); 408} 409 410} 411