1/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6#include "PrintTargetCG.h" 7 8#include "cairo.h" 9#include "cairo-quartz.h" 10#include "mozilla/gfx/HelpersCairo.h" 11#include "nsObjCExceptions.h" 12#include "nsString.h" 13 14namespace mozilla { 15namespace gfx { 16 17PrintTargetCG::PrintTargetCG(PMPrintSession aPrintSession, PMPageFormat aPageFormat, 18 PMPrintSettings aPrintSettings, const IntSize& aSize) 19 : PrintTarget(/* aCairoSurface */ nullptr, aSize), 20 mPrintSession(aPrintSession), 21 mPageFormat(aPageFormat), 22 mPrintSettings(aPrintSettings) { 23 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 24 25 MOZ_ASSERT(mPrintSession && mPageFormat && mPrintSettings); 26 27 ::PMRetain(mPrintSession); 28 ::PMRetain(mPageFormat); 29 ::PMRetain(mPrintSettings); 30 31 // TODO: Add memory reporting like gfxQuartzSurface. 32 // RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface)); 33 34 NS_OBJC_END_TRY_IGNORE_BLOCK; 35} 36 37PrintTargetCG::~PrintTargetCG() { 38 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 39 40 ::PMRelease(mPrintSession); 41 ::PMRelease(mPageFormat); 42 ::PMRelease(mPrintSettings); 43 44 NS_OBJC_END_TRY_IGNORE_BLOCK; 45} 46 47/* static */ already_AddRefed<PrintTargetCG> PrintTargetCG::CreateOrNull( 48 PMPrintSession aPrintSession, PMPageFormat aPageFormat, PMPrintSettings aPrintSettings, 49 const IntSize& aSize) { 50 if (!Factory::CheckSurfaceSize(aSize)) { 51 return nullptr; 52 } 53 54 RefPtr<PrintTargetCG> target = 55 new PrintTargetCG(aPrintSession, aPageFormat, aPrintSettings, aSize); 56 57 return target.forget(); 58} 59 60static size_t PutBytesNull(void* info, const void* buffer, size_t count) { return count; } 61 62already_AddRefed<DrawTarget> PrintTargetCG::GetReferenceDrawTarget() { 63 if (!mRefDT) { 64 const IntSize size(1, 1); 65 66 CGDataConsumerCallbacks callbacks = {PutBytesNull, nullptr}; 67 CGDataConsumerRef consumer = CGDataConsumerCreate(nullptr, &callbacks); 68 CGContextRef pdfContext = CGPDFContextCreate(consumer, nullptr, nullptr); 69 CGDataConsumerRelease(consumer); 70 71 cairo_surface_t* similar = 72 cairo_quartz_surface_create_for_cg_context(pdfContext, size.width, size.height); 73 74 CGContextRelease(pdfContext); 75 76 if (cairo_surface_status(similar)) { 77 return nullptr; 78 } 79 80 RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForCairoSurface(similar, size); 81 82 // The DT addrefs the surface, so we need drop our own reference to it: 83 cairo_surface_destroy(similar); 84 85 if (!dt || !dt->IsValid()) { 86 return nullptr; 87 } 88 mRefDT = dt.forget(); 89 } 90 91 return do_AddRef(mRefDT); 92} 93 94nsresult PrintTargetCG::BeginPrinting(const nsAString& aTitle, const nsAString& aPrintToFileName, 95 int32_t aStartPage, int32_t aEndPage) { 96 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 97 98 // Print Core of Application Service sent print job with names exceeding 99 // 255 bytes. This is a workaround until fix it. 100 // (https://openradar.appspot.com/34428043) 101 nsAutoString adjustedTitle; 102 PrintTarget::AdjustPrintJobNameForIPP(aTitle, adjustedTitle); 103 104 if (!adjustedTitle.IsEmpty()) { 105 CFStringRef cfString = ::CFStringCreateWithCharacters( 106 NULL, reinterpret_cast<const UniChar*>(adjustedTitle.BeginReading()), 107 adjustedTitle.Length()); 108 if (cfString) { 109 ::PMPrintSettingsSetJobName(mPrintSettings, cfString); 110 ::CFRelease(cfString); 111 } 112 } 113 114 OSStatus status; 115 status = ::PMSetFirstPage(mPrintSettings, aStartPage, false); 116 NS_ASSERTION(status == noErr, "PMSetFirstPage failed"); 117 status = ::PMSetLastPage(mPrintSettings, aEndPage, false); 118 NS_ASSERTION(status == noErr, "PMSetLastPage failed"); 119 120 status = ::PMSessionBeginCGDocumentNoDialog(mPrintSession, mPrintSettings, mPageFormat); 121 122 return status == noErr ? NS_OK : NS_ERROR_ABORT; 123 124 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 125} 126 127nsresult PrintTargetCG::EndPrinting() { 128 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 129 130 ::PMSessionEndDocumentNoDialog(mPrintSession); 131 return NS_OK; 132 133 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 134} 135 136nsresult PrintTargetCG::AbortPrinting() { 137#ifdef DEBUG 138 mHasActivePage = false; 139#endif 140 return EndPrinting(); 141} 142 143nsresult PrintTargetCG::BeginPage() { 144 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 145 146 PMSessionError(mPrintSession); 147 OSStatus status = ::PMSessionBeginPageNoDialog(mPrintSession, mPageFormat, NULL); 148 if (status != noErr) { 149 return NS_ERROR_ABORT; 150 } 151 152 CGContextRef context; 153 // This call will fail if it wasn't called between the PMSessionBeginPage/ 154 // PMSessionEndPage calls: 155 ::PMSessionGetCGGraphicsContext(mPrintSession, &context); 156 157 if (!context) { 158 return NS_ERROR_FAILURE; 159 } 160 161 unsigned int width = static_cast<unsigned int>(mSize.width); 162 unsigned int height = static_cast<unsigned int>(mSize.height); 163 164 // Initially, origin is at bottom-left corner of the paper. 165 // Here, we translate it to top-left corner of the paper. 166 CGContextTranslateCTM(context, 0, height); 167 CGContextScaleCTM(context, 1.0, -1.0); 168 169 cairo_surface_t* surface = cairo_quartz_surface_create_for_cg_context(context, width, height); 170 171 if (cairo_surface_status(surface)) { 172 return NS_ERROR_FAILURE; 173 } 174 175 mCairoSurface = surface; 176 177 return PrintTarget::BeginPage(); 178 179 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 180} 181 182nsresult PrintTargetCG::EndPage() { 183 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 184 185 cairo_surface_finish(mCairoSurface); 186 mCairoSurface = nullptr; 187 188 OSStatus status = ::PMSessionEndPageNoDialog(mPrintSession); 189 if (status != noErr) { 190 return NS_ERROR_ABORT; 191 } 192 193 return PrintTarget::EndPage(); 194 195 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 196} 197 198} // namespace gfx 199} // namespace mozilla 200