{%MainUnit ../osprinters.pas} {************************************************************** Implementation for carbonprinter ***************************************************************} uses InterfaceBase, LCLIntf, LCLProc; const CleanPMRect: PMRect = (top: 0; left: 0; bottom: 0; right: 0); CleanPMOrientation: PMOrientation = 0; { TCocoaPrinterView } function TCocoaPrinterView.initWithFrame(frameRect: NSRect): id; begin PageMin := 1; PageMax := 1; PageFrom := 1; PageTo := 1; Result:=inherited initWithFrame(frameRect); //Image := NSImage.alloc.initWithSize(Size); end; procedure TCocoaPrinterView.dealloc; begin //Image.release(); inherited dealloc(); end; procedure TCocoaPrinterView.drawRect(dirtyRect: NSRect); var pageHeight: Double; lRect: NSRect; lCurPage: Integer; begin // image page printing alternative //Image.drawInRect_fromRect_operation_fraction(Self.frame, NSZeroRect, NSCompositeSourceAtop, 1.0); if Canvas = nil then Exit; pageHeight := calculatePrintHeight(); lRect := updateSize(False); // figure out which page this is lCurPage := Round((lRect.size.height - dirtyRect.origin.y) / pageHeight); Canvas.DrawRecording(Self, dirtyRect, lCurPage - PageFrom); end; // Return the number of pages available for printing function TCocoaPrinterView.knowsPageRange(range: NSRangePointer): Boolean; begin updateSize(True); range^.location := PageFrom; range^.length := PageTo - PageFrom + 1; Result := True; end; function TCocoaPrinterView.rectForPage(page: NSInteger): NSRect; var lBounds: NSRect; pageHeight, pageWidth: Double; begin pageHeight := calculatePrintHeight(); pageWidth := calculatePrintWidth(); // page Y starting pos is Bottom-Left of page 1 - (pagenr-zero-based) * pageHeight, so: // pageHeight * ((PageTo - PageFrom) - (page - PageFrom)), which simplifies to: Result := NSMakeRect(0, pageHeight * (PageTo - page), pageWidth, pageHeight); end; // Calculate the vertical size of the view that fits on a single page function TCocoaPrinterView.calculatePrintHeight: Double; var pi: NSPrintInfo; paperSize: NSSize; pageHeight, scale: Double; begin // Obtain the print info object for the current operation pi := NSPrintOperation.currentOperation.printInfo; // Calculate the page height in points paperSize := pi.paperSize; pageHeight := paperSize.height - pi.topMargin - pi.bottomMargin; // Convert height to the scaled view scale := pi.dictionary.objectForKey(NSPrintScalingFactor).floatValue; Result := pageHeight / scale; end; function TCocoaPrinterView.calculatePrintWidth: Double; var pi: NSPrintInfo; paperSize: NSSize; pageWidth, scale: Double; begin // Obtain the print info object for the current operation pi := NSPrintOperation.currentOperation.printInfo; // Calculate the page width in points paperSize := pi.paperSize; pageWidth := paperSize.width - pi.leftMargin - pi.rightMargin; // Convert height to the scaled view scale := pi.dictionary.objectForKey(NSPrintScalingFactor).floatValue; Result := pageWidth / scale; end; function TCocoaPrinterView.updateSize(ADoSetFrame: Boolean): NSRect; var Size: NSSize; begin Size.height := calculatePrintHeight(); Size.height := Size.height * (PageTo - PageFrom + 1); Size.width := calculatePrintWidth(); if ADoSetFrame then Self.setFrameSize(Size); Result := NSMakeRect(0, 0, Size.width, Size.height); end; { TCocoaPrinter } function TCocoaPrinter.CreatePageFormat(APaper: String): PMPageFormat; var I: Integer; S: TStringList; const SName = 'CreatePageFormat'; begin { if APaper = '' then begin I := -1; S := nil; end else begin S := TStringList.Create; BeginEnumPapers(S); I := S.IndexOf(APaper); end; try if I < 0 then begin Result:=nil; if OSError(PMCreatePageFormat(Result), Self, SName, 'PMCreatePageFormat') then raise EPrinter.Create('Error initializing printing for Carbon: Unable to create page format!'); OSError(PMSessionDefaultPageFormat(PrintSession, Result), Self, SName, 'PMSessionDefaultPageFormat'); end else begin OSError(PMCreatePageFormatWithPMPaper(Result, PMPaper(CFArrayGetValueAtIndex(FPaperArray, I))), Self, SName, 'PMCreatePageFormatWithPMPaper'); end; finally if S <> nil then begin EndEnumPapers; S.Free; end; end; } end; function TCocoaPrinter.ValidatePageFormat: Boolean; begin Result := False; if PMSessionValidatePageFormat(GetPrintSession(), GetPageFormat(), @Result) <> noErr then Exit; end; function TCocoaPrinter.ValidatePrintSettings: Boolean; begin Result := False; if PMSessionValidatePrintSettings(GetPrintSession(), GetPrintSettings(), @Result) <> noErr then Exit; end; function TCocoaPrinter.GetCurrentCarbonPrinter: PMPrinter; begin Result := nil; if PMSessionGetCurrentPrinter(GetPrintSession(), Result) <> noErr then Exit; { Result := CFStringToStr(PMPrinterGetName(P)); if Trim(Result) = '' then Result := ''; } end; function TCocoaPrinter.GetPrintSession: PMPrintSession; begin Result := FPrintInfo.PMPrintSession(); end; function TCocoaPrinter.GetPrintSettings: PMPrintSettings; begin Result := FPrintInfo.PMPrintSettings(); end; function TCocoaPrinter.GetPageFormat: PMPageFormat; begin Result := FPrintInfo.PMPageFormat(); end; procedure TCocoaPrinter.BeginPage; {var PaperRect: PMRect; } begin {if FBeginDocumentStatus = noErr then begin FNewPageStatus := PMSessionBeginPage(PrintSession, nil, nil); OSError(FNewPageStatus, Self, 'BeginPage', 'PMSessionBeginPage', '', kPMCancel); // update printer context if OSError(PMSessionGetCGGraphicsContext(PrintSession, FPrinterContext.CGContext), Self, 'BeginPage', 'PMSessionGetCGGraphicsContext') then FPrinterContext.Release else FPrinterContext.Reset; // translate the context from his paper (0,0) origin // to our working imageable area if PMGetAdjustedPaperRect(PageFormat, PaperRect{%H-})=noErr then CGContextTranslateCTM(FPrinterContext.CGContext, -PaperRect.left, -PaperRect.top); if Assigned(Canvas) then Canvas.Handle := HDC(FPrinterContext); end; } end; procedure TCocoaPrinter.EndPage; begin {FPrinterContext.Release; if Assigned(Canvas) then Canvas.Handle := 0; if FBeginDocumentStatus = noErr then begin if FNewPageStatus = noErr then OSError(PMSessionEndPage(PrintSession), Self, 'EndPage', 'PMSessionEndPage', '', kPMCancel); end; } end; procedure TCocoaPrinter.FindDefaultPrinter; {var P: PMPrinter; I, C: CFIndex; pa: CFArrayRef; } begin {pa:=nil; if OSError(PMServerCreatePrinterList(kPMServerLocal, pa), Self, 'DoEnumPrinters', 'PMServerCreatePrinterList') then Exit; if not Assigned(pa) then Exit; C := CFArrayGetCount(pa); for I := 0 to C - 1 do begin P := CFArrayGetValueAtIndex(pa, I); if PMPrinterIsDefault(P) then begin FDefaultPrinter := CFStringToStr(PMPrinterGetName(P)); Break; end; end; CFRelease(pa); } end; constructor TCocoaPrinter.Create; var FPrintViewRect: NSRect; begin inherited Create; FPrintViewRect := GetNSRect(0, 0, 1000, 1000); FPrintView := TCocoaPrinterView.alloc.initWithFrame(FPrintViewRect); FPrintView.Canvas := Canvas as TCocoaPrinterCanvas; FPrintOp := NSPrintOperation.printOperationWithView(FPrintView); FPrintInfo := FPrintOp.printInfo(); //CreatePrintSettings; //FPageFormat := CreatePageFormat(''); //FindDefaultPrinter; //UpdatePrinter; //DebugLn('Current ' + GetCurrentPrinterName); //DebugLn('Default ' + FDefaultPrinter); end; procedure TCocoaPrinter.DoDestroy; begin FPrintView.release(); inherited DoDestroy; end; function TCocoaPrinter.Write(const Buffer; Count: Integer; out Written: Integer): Boolean; begin Result := False; CheckRawMode(True); Written := 0; DebugLn('TCocoaPrinter.Write Error: Raw mode is not supported for Cocoa!'); end; procedure TCocoaPrinter.RawModeChanging; begin // end; procedure TCocoaPrinter.Validate; var P: String; begin ValidatePrintSettings(); ValidatePageFormat(); // if target paper is not supported, use the default P := DoGetPaperName(); if PaperSize.SupportedPapers.IndexOf(P) = -1 then DoSetPaperName(DoGetDefaultPaperName()); end; procedure TCocoaPrinter.UpdatePrinter; {var s: string; Res: PMResolution;} begin {s := GetCurrentPrinterName; if trim(s) = '' then // Observed if Default printer set to "Use last printer", and no printing done s := '*'; // so select lcl default SetPrinter(s); // set the page format resolution Res := GetOutputResolution; PMSetResolution(PageFormat, Res); Validate; } end; function TCocoaPrinter.GetOutputResolution: PMResolution; var res: OSStatus; FPrintSettings: PMPrintSettings; begin FPrintSettings := GetPrintSettings(); res := PMPrinterGetOutputResolution(GetCurrentCarbonPrinter(), FPrintSettings, Result); if res <> noErr then begin Result.vRes := 72; Result.hRes := 72; end; end; function TCocoaPrinter.DoDoGetPaperName(APageFormat: PMPageFormat): string; var FPaper: PMPaper = nil; lCFString: CFStringRef = nil; begin Result := ''; if APageFormat = nil then APageFormat := GetPageFormat(); if PMGetPageFormatPaper(APageFormat, FPaper) <> noErr then Exit; if PMPaperGetName(FPaper, lCFString) <> noErr then Exit; Result := CFStringToStr(lCFString); end; function TCocoaPrinter.GetXDPI: Integer; var dpi: PMResolution; begin dpi := GetOutputResolution; result := round(dpi.hRes); end; function TCocoaPrinter.GetYDPI: Integer; var dpi: PMResolution; begin dpi := GetOutputResolution; result := round(dpi.hRes); end; procedure TCocoaPrinter.DoBeginDoc; begin inherited DoBeginDoc; //DebugLn('TCocoaPrinter.DoBeginDoc ' + DbgS(Printing)); Validate; //FBeginDocumentStatus := PMSessionBeginCGDocument(PrintSession, PrintSettings, PageFormat); //OSError(FBeginDocumentStatus, Self, 'DoBeginDoc', 'PMSessionBeginCGDocument', '', kPMCancel); BeginPage; end; procedure TCocoaPrinter.DoNewPage; begin inherited DoNewPage; EndPage; BeginPage; end; procedure TCocoaPrinter.DoEndDoc(aAborded: Boolean); begin inherited DoEndDoc(aAborded); EndPage; FPrintOp.setShowsPrintPanel(False); FPrintOp.runOperation(); end; procedure TCocoaPrinter.DoAbort; begin inherited DoAbort; //OSError(PMSessionSetError(PrintSession, kPMCancel), Self, 'DoAbort', 'PMSessionSetError'); end; //Enum all defined printers. First printer it's default procedure TCocoaPrinter.DoEnumPrinters(Lst: TStrings); var I, PrinterCount: CFIndex; NewPrinterNSName: NSString; NewPrinterName: String; FPrinterArray: NSArray; //NewPrinter: NSPrinter; begin FPrinterArray := NSPrinter.printerNames(); FPrinterArray.retain; if FPrinterArray = nil then Exit; for I := 0 to FPrinterArray.count - 1 do begin NewPrinterNSName := NSString(FPrinterArray.objectAtIndex(i)); NewPrinterName := NSStringToString(NewPrinterNSName); // Felipe: This could be used for appending the printer to the TStrings, but what for? // also it would be hard to release the NSPrinter later //NewPrinter := NSPrinter.printerWithName(NewPrinterNSName); //NewPrinter.retain; //DebugLn(DbgS(I) + ' ' + PrinterName); if NewPrinterName = FDefaultPrinter then Lst.InsertObject(0, NewPrinterName, nil{TObject(NewPrinter)}) else Lst.AddObject(NewPrinterName, nil{TObject(NewPrinter)}); end; end; procedure TCocoaPrinter.DoResetPrintersList; begin inherited DoResetPrintersList; end; // Cocoa doesn't support this =( We need to use Carbon here // http://lists.apple.com/archives/cocoa-dev/2005/Nov/msg01227.html // See Also "Using Cocoa and Core Printing Together" // https://developer.apple.com/library/mac/technotes/tn2248/_index.html procedure TCocoaPrinter.DoEnumPapers(Lst: TStrings); var P: PMPaper; FPaperArray: CFArrayRef; I, C: CFIndex; CFString: CFStringRef; PaperName: String; CarbonCurrentPrinter: PMPrinter; begin FPaperArray := nil; CarbonCurrentPrinter := GetCurrentCarbonPrinter(); if PMPrinterGetPaperList(CarbonCurrentPrinter, FPaperArray) <> noErr then Exit; FPaperArray := CFRetain(FPaperArray); C := CFArrayGetCount(FPaperArray); for I := 0 to C - 1 do begin P := CFArrayGetValueAtIndex(FPaperArray, I); CFString:=nil; if PMPaperGetName(P, CFString) <> noErr then Continue; PaperName := CFStringToStr(CFString); Lst.Add(PaperName); end; if FPaperArray<>nil then CFRelease(FPaperArray); end; function TCocoaPrinter.DoGetPaperName: string; begin Result := DoDoGetPaperName(FPrintInfo.PMPageFormat()); end; function TCocoaPrinter.DoGetDefaultPaperName: string; var FPageFormat: PMPageFormat; begin Result := ''; FPageFormat := CreatePageFormat(''); Result := DoDoGetPaperName(FPageFormat); end; procedure TCocoaPrinter.DoSetPaperName(aName: string); var FOrientation: TPrinterOrientation; begin FOrientation := DoGetOrientation(); {if FPageFormat <> nil then PMRelease(PMObject(FPageFormat)); FPageFormat := CreatePageFormat(AName); DoSetOrientation(FOrientation);} ValidatePageFormat; end; function TCocoaPrinter.DoGetPaperRect(aName: string; var aPaperRc: TPaperRect ): Integer; {var T: PMPageFormat; PaperRect, PageRect: PMRect; S: Double; O: PMOrientation; Res: PMResolution; const SName = 'DoGetPaperRect'; } begin {Result := -1; T := CreatePageFormat(AName); try // copy scale S:=0.0; OSError(PMGetScale(PageFormat, S), Self, SName, 'PMGetScale'); OSError(PMSetScale(T, S), Self, SName, 'PMSetScale'); // copy orientation O:=CleanPMOrientation; OSError(PMGetOrientation(PageFormat, O), Self, SName, 'PMGetOrientation'); OSError(PMSetOrientation(T, O, False), Self, SName, 'PMSetOrientation'); // copy resolution Res := GetOutputResolution; OSError(PMSetResolution(T, Res), self, SName, 'PMSetResolution'); // update OSError(PMSessionValidatePageFormat(PrintSession, T, nil), Self, SName, 'PMSessionValidatePageFormat'); PaperRect:=CleanPMRect; OSError(PMGetAdjustedPaperRect(T, PaperRect), Self, SName, 'PMGetAdjustedPaperRect'); PageRect:=CleanPMRect; OSError(PMGetAdjustedPageRect(T, PageRect), Self, SName, 'PMGetAdjustedPageRect'); finally PMRelease(PMObject(T)); end; ValidatePageFormat; APaperRc.PhysicalRect.Left := 0; APaperRc.PhysicalRect.Top := 0; APaperRc.PhysicalRect.Right := Round(PaperRect.right - PaperRect.left); APaperRc.PhysicalRect.Bottom := Round(PaperRect.bottom - PaperRect.top); APaperRc.WorkRect.Left := Round(-PaperRect.left); APaperRc.WorkRect.Top := Round(-PaperRect.top); APaperRc.WorkRect.Right := Round(PageRect.right - PageRect.left - PaperRect.left); APaperRc.WorkRect.Bottom := Round(PageRect.bottom - PageRect.top - PaperRect.top); Result := 1; } end; function TCocoaPrinter.DoSetPrinter(aName: string): Integer; {var S: TStringList; P: PMPrinter; } begin {S := TStringList.Create; BeginEnumPrinters(S); try Result := S.IndexOf(AName); if Result >= 0 then begin //DebugLn('DoSetPrinter ' + DbgS(Result)); //DebugLn('TCocoaPrinter.DoSetPrinter ' + AName + ' ' + DbgS(PrintSession) + ' ' + DbgS(Printers.Objects[Result])); P := PMPrinter(CFArrayGetValueAtIndex(FPrinterArray, Integer(S.Objects[Result]))); PMRetain(PMObject(P)); if OSError(PMSessionSetCurrentPMPrinter(PrintSession, P), Self, 'DoSetPrinter', 'PMSessionSetCurrentPMPrinter') then raise EPrinter.CreateFmt('The system is unable to select printer "%s"!', [AName]); end; finally EndEnumPrinters; S.Free; end; } end; function TCocoaPrinter.DoGetCopies: Integer; var NumCopies: UInt32; begin Result := inherited DoGetCopies; NumCopies := 0; if PMGetCopies(GetPrintSettings(), NumCopies) <> noErr then Exit; Result := NumCopies; end; procedure TCocoaPrinter.DoSetCopies(aValue: Integer); begin inherited DoSetCopies(AValue); if PMSetCopies(GetPrintSettings(), AValue, False) <> noErr then Exit; FPrintInfo.updateFromPMPrintSettings(); ValidatePrintSettings; end; function TCocoaPrinter.DoGetOrientation: TPrinterOrientation; var FOrientation: PMOrientation; FPageFormat: PMPageFormat; begin Result := inherited DoGetOrientation; FOrientation := CleanPMOrientation; FPageFormat := FPrintInfo.PMPageFormat(); if PMGetOrientation(FPageFormat, FOrientation) <> noErr then Exit; case FOrientation of kPMPortrait: Result := poPortrait; kPMLandscape: Result := poLandscape; kPMReversePortrait: Result := poReversePortrait; kPMReverseLandscape: Result := poReverseLandscape; end; end; procedure TCocoaPrinter.DoSetOrientation(aValue: TPrinterOrientation); var FOrientation: PMOrientation; FPageFormat: PMPageFormat; begin inherited DoSetOrientation(aValue); case AValue of poPortrait: FOrientation := kPMPortrait; poLandscape: FOrientation := kPMLandscape; poReversePortrait: FOrientation := kPMReversePortrait; poReverseLandscape: FOrientation := kPMReverseLandscape; end; FPageFormat := FPrintInfo.PMPageFormat(); PMSetOrientation(FPageFormat, FOrientation, kPMUnlocked); FPrintInfo.updateFromPMPageFormat(); ValidatePageFormat; end; function TCocoaPrinter.GetPrinterType: TPrinterType; var IsRemote: Boolean; begin Result := ptLocal; IsRemote := false; if PMPrinterIsRemote(GetCurrentCarbonPrinter(), IsRemote) <> noErr then Exit; if IsRemote then Result := ptNetwork; end; function TCocoaPrinter.DoGetPrinterState: TPrinterState; var State: PMPrinterState; begin Result := psNoDefine; State:=0; if PMPrinterGetState(GetCurrentCarbonPrinter(), State) <> noErr then Exit; case State of kPMPrinterIdle: Result := psReady; kPMPrinterProcessing: Result := psPrinting; kPMPrinterStopped: Result := psStopped; end; end; function TCocoaPrinter.DoGetDefaultCanvasClass: TPrinterCanvasRef; begin Result := TCocoaPrinterCanvas; end; function TCocoaPrinter.GetCanPrint: Boolean; begin Result := (DoGetPrinterState() <> psStopped); end; function TCocoaPrinter.GetCanRenderCopies: Boolean; begin Result := True; end; initialization Printer := TCocoaPrinter.Create; finalization FreeAndNil(Printer);