{%MainUnit ../osprinters.pas} {************************************************************** Implementation for carbonprinter ***************************************************************} Uses InterfaceBase, LCLIntf, CarbonProc, LCLProc, dl; { TCarbonPrinterContext } function TCarbonPrinterContext.GetSize: TPoint; var R: PMRect; begin Result.X := 0; Result.Y := 0; if Printer = nil then Exit; R:=CleanPMRect; if OSError(PMGetAdjustedPaperRect((Printer as TCarbonPrinter).PageFormat, R), Self, 'GetSize', 'PMGetUnadjustedPaperRect') then Exit; Result.X := Round(R.right - R.left); Result.Y := Round(R.bottom - R.top); end; procedure TCarbonPrinterContext.Release; begin // redirect drawing to dummy context when not able printing page CGContext := DefaultContext.CGContext; end; procedure TCarbonPrinterContext.Reset; begin inherited Reset; if CGContext <> nil then begin // flip and offset CTM from lower to upper left corner CGContextTranslateCTM(CGContext, 0, GetSize.Y); CGContextScaleCTM(CGContext, 1, -1); end; end; { TCarbonPrinter } procedure TCarbonPrinter.CreatePrintSession; begin if OSError(PMCreateSession(FPrintSession), Self, 'GetPrintSession', 'PMCreateSession') then raise EPrinter.Create('Error initializing printing for Carbon: Unable to create print session!'); end; procedure TCarbonPrinter.CreatePrintSettings; const SName = 'CreatePrintSettings'; begin if OSError(PMCreatePrintSettings(FPrintSettings), Self, SName, 'PMCreatePrintSettings') then raise EPrinter.Create('Error initializing printing for Carbon: Unable to create print settings!'); OSError(PMSessionDefaultPrintSettings(PrintSession, FPrintSettings), Self, SName, 'PMSessionDefaultPrintSettings'); end; function TCarbonPrinter.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 TCarbonPrinter.ValidatePageFormat: Boolean; begin Result := False; OSError(PMSessionValidatePageFormat(PrintSession, PageFormat, @Result), Self, 'ValidatePageFormat', 'PMSessionValidatePageFormat'); end; function TCarbonPrinter.ValidatePrintSettings: Boolean; begin Result := False; OSError(PMSessionValidatePrintSettings(PrintSession, PrintSettings, @Result), Self, 'ValidatePrintSettings', 'PMSessionValidatePrintSettings'); end; function TCarbonPrinter.GetCurrentPrinter: PMPrinter; begin Result:=nil; OSError(PMSessionGetCurrentPrinter(PrintSession, Result), Self, 'GetCurrentPrinter', 'PMSessionGetCurrentPrinter'); end; function TCarbonPrinter.GetCurrentPrinterName: String; var P: PMPrinter; begin Result := ''; P := GetCurrentPrinter; if P <> nil then Result := CFStringToStr(PMPrinterGetName(P)); if Trim(Result) = '' then Result := ''; end; procedure TCarbonPrinter.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 TCarbonPrinter.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 TCarbonPrinter.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; procedure TCarbonPrinter.BeginEnumPrinters(Lst: TStrings); var P: PMPrinter; I, C: CFIndex; NewPrinterName: String; begin FPrinterArray := nil; if OSError(PMServerCreatePrinterList(kPMServerLocal, FPrinterArray), Self, 'DoEnumPrinters', 'PMServerCreatePrinterList') then Exit; C := CFArrayGetCount(FPrinterArray); for I := 0 to C - 1 do begin P := CFArrayGetValueAtIndex(FPrinterArray, I); NewPrinterName := CFStringToStr(PMPrinterGetName(P)); //DebugLn(DbgS(I) + ' ' + PrinterName); if NewPrinterName = FDefaultPrinter then Lst.InsertObject(0, NewPrinterName, TObject(I)) else Lst.AddObject(NewPrinterName, TObject(I)); end; end; procedure TCarbonPrinter.EndEnumPrinters; begin if FPrinterArray<>nil then CFRelease(FPrinterArray); end; procedure TCarbonPrinter.BeginEnumPapers(Lst: TStrings); var P: PMPaper; I, C: CFIndex; CFString: CFStringRef; PaperName: String; const SName = 'DoEnumPapers'; begin FPaperArray := nil; if OSError(PMPrinterGetPaperList(GetCurrentPrinter, FPaperArray), Self, SName, 'PMPrinterGetPaperList') then Exit; FPaperArray := CFRetain(FPaperArray); C := CFArrayGetCount(FPaperArray); for I := 0 to C - 1 do begin P := CFArrayGetValueAtIndex(FPaperArray, I); CFString:=nil; if OSError(PMPaperGetName(P, CFString), Self, SName, 'PMPaperGetName') then Continue; PaperName := CFStringToStr(CFString); //MacOSX 10.4 returns wrong paper name in case of US Letter. //In system we can choose US Letter, but here it returns Letter.Issue #17698 if PaperName = 'Letter' then PaperName := 'US Letter' else if PaperName = 'Legal' then PaperName := 'US Legal'; Lst.Add(PaperName); end; end; procedure TCarbonPrinter.EndEnumPapers; begin if FPaperArray<>nil then CFRelease(FPaperArray); end; constructor TCarbonPrinter.Create; begin inherited Create; CreatePrintSession; CreatePrintSettings; FPageFormat := CreatePageFormat(''); FPrinterContext := TCarbonPrinterContext.Create; FindDefaultPrinter; UpdatePrinter; //DebugLn('Current ' + GetCurrentPrinterName); //DebugLn('Default ' + FDefaultPrinter); end; procedure TCarbonPrinter.DoDestroy; begin FPrinterContext.Free; if FPrintSettings <> nil then PMRelease(PMObject(FPrintSettings)); if FPageFormat <> nil then PMRelease(PMObject(FPageFormat)); if FPrintSession <> nil then PMRelease(PMObject(FPrintSession)); inherited DoDestroy; end; function TCarbonPrinter.Write(const Buffer; Count: Integer; out Written: Integer): Boolean; begin Result := False; CheckRawMode(True); Written := 0; DebugLn('TCarbonPrinter.Write Error: Raw mode is not supported for Carbon!'); end; procedure TCarbonPrinter.RawModeChanging; begin // end; procedure TCarbonPrinter.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 TCarbonPrinter.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; type TPMPrinterGetOutputResolution = function( printer: PMPrinter; printSettings: PMPrintSettings; var resolutionP: PMResolution ): OSStatus; cdecl; var _PMPrinterGetOutputResolution: TPMPrinterGetOutputResolution = nil; _PMPrinterGetOutputResolutionLoaded: Boolean; function TCarbonPrinter.GetOutputResolution: PMResolution; var res: OSStatus; r : PMresolution; prn: PMPrinter; cnt: UInt32; i : Integer; begin prn := GetCurrentPrinter; if not _PMPrinterGetOutputResolutionLoaded then begin // loading in run-time, because the function isn't available on OSX 10.4 _PMPrinterGetOutputResolutionLoaded := true; _PMPrinterGetOutputResolution := TPMPrinterGetOutputResolution(dlsym(RTLD_DEFAULT,'PMPrinterGetOutputResolution')); end; if Assigned(_PMPrinterGetOutputResolution) then begin // the function might return kPMKeyNotFound, see function description in MacOSAll res := _PMPrinterGetOutputResolution(prn, PrintSettings, Result{%H-}); if (res=kPMKeyNotFound) and (FDefaultResolution.Valid) then begin res := noErr; Result.hRes := fDefaultResolution.HorzRes; Result.vRes := fDefaultResolution.VertRes; end; end else res := noErr+1; if res <> noErr then begin res := PMPrinterGetPrinterResolutionCount(prn, cnt{%H-}); if res = noErr then begin PMPrinterGetIndexedPrinterResolution(prn, 1, Result); for i := 2 to cnt do begin if PMPrinterGetIndexedPrinterResolution(prn, i, r{%H-}) = noErr then if (r.hRes > Result.hRes) and (r.vRes > Result.vRes) then Result := r; end; end; end; if res<>noErr then begin Result.vRes:=72; Result.hRes:=72; end; end; function TCarbonPrinter.GetXDPI: Integer; var dpi: PMResolution; begin dpi := GetOutputResolution; result := round(dpi.hRes); end; function TCarbonPrinter.GetYDPI: Integer; var dpi: PMResolution; begin dpi := GetOutputResolution; result := round(dpi.hRes); end; procedure TCarbonPrinter.DoBeginDoc; begin inherited DoBeginDoc; //DebugLn('TCarbonPrinter.DoBeginDoc ' + DbgS(Printing)); Validate; FBeginDocumentStatus := PMSessionBeginCGDocument(PrintSession, PrintSettings, PageFormat); OSError(FBeginDocumentStatus, Self, 'DoBeginDoc', 'PMSessionBeginCGDocument', '', kPMCancel); FNewPageStatus := kPMCancel; BeginPage; end; procedure TCarbonPrinter.DoNewPage; begin inherited DoNewPage; EndPage; BeginPage; end; procedure TCarbonPrinter.DoEndDoc(aAborted: Boolean); begin inherited DoEndDoc(aAborted); EndPage; if FBeginDocumentStatus = noErr then OSError(PMSessionEndDocument(PrintSession), Self, 'DoEndDoc', 'PMSessionEndDocument', '', kPMCancel); end; procedure TCarbonPrinter.DoAbort; begin inherited DoAbort; OSError(PMSessionSetError(PrintSession, kPMCancel), Self, 'DoAbort', 'PMSessionSetError'); end; //Enum all defined printers. First printer it's default procedure TCarbonPrinter.DoEnumPrinters(Lst: TStrings); begin BeginEnumPrinters(Lst); EndEnumPrinters; end; procedure TCarbonPrinter.DoResetPrintersList; begin inherited DoResetPrintersList; end; procedure TCarbonPrinter.DoEnumPapers(Lst: TStrings); begin BeginEnumPapers(Lst); EndEnumPapers; end; function TCarbonPrinter.DoGetPaperName: string; var P: PMPaper; CFString: CFStringRef; const SName = 'DoGetPaperName'; begin Result := ''; P:=nil; if OSError(PMGetPageFormatPaper(PageFormat, P), Self, SName, 'PMGetPageFormatPaper') then Exit; CFString:=nil; if OSError(PMPaperGetName(P, CFString), Self, SName, 'PMPaperGetName') then Exit; Result := CFStringToStr(CFString); end; function TCarbonPrinter.DoGetDefaultPaperName: string; var T: PMPageFormat; begin Result := ''; T := FPageFormat; FPageFormat := CreatePageFormat(''); Result := DoGetPaperName; if T <> nil then begin PMRelease(PMObject(FPageFormat)); FPageFormat := T; end; end; procedure TCarbonPrinter.DoSetPaperName(AName: string); var O: TPrinterOrientation; begin O := DoGetOrientation; if FPageFormat <> nil then PMRelease(PMObject(FPageFormat)); FPageFormat := CreatePageFormat(AName); DoSetOrientation(O); ValidatePageFormat; end; function TCarbonPrinter.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 TCarbonPrinter.DoSetPrinter(aName: string): Integer; var S: TStringList; P: PMPrinter; ResCount: UInt32; begin S := TStringList.Create; BeginEnumPrinters(S); try Result := S.IndexOf(AName); if Result >= 0 then begin //DebugLn('DoSetPrinter ' + DbgS(Result)); //DebugLn('TCarbonPrinter.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]); // with FDefaultResolution do begin ResCount := 0; Valid := (PMPrinterGetPrinterResolutionCount(P, ResCount)=noErr) and (ResCount>1); if Valid then Valid := GetDefaultPPDResolution(P, HorzRes, VertRes); end; end; finally EndEnumPrinters; S.Free; end; end; function TCarbonPrinter.DoGetCopies: Integer; var C: UInt32; begin Result := inherited DoGetCopies; C:=0; if OSError(PMGetCopies(PrintSettings, C), Self, 'DoGetCopies', 'PMGetCopies') then Exit; Result := C; end; procedure TCarbonPrinter.DoSetCopies(AValue: Integer); begin inherited DoSetCopies(AValue); OSError(PMSetCopies(PrintSettings, AValue, False), Self, 'DoSetCopies', 'PMSetCopies'); ValidatePrintSettings; end; function TCarbonPrinter.DoGetOrientation: TPrinterOrientation; var O: PMOrientation; begin Result := inherited DoGetOrientation; O:=CleanPMOrientation; if OSError(PMGetOrientation(PageFormat, O), Self, 'DoGetOrientation', 'PMGetOrientation') then Exit; case O of kPMPortrait: Result := poPortrait; kPMLandscape: Result := poLandscape; kPMReversePortrait: Result := poReversePortrait; kPMReverseLandscape: Result := poReverseLandscape; end; end; procedure TCarbonPrinter.DoSetOrientation(AValue: TPrinterOrientation); var O: PMOrientation; begin inherited DoSetOrientation(aValue); case AValue of poPortrait: O := kPMPortrait; poLandscape: O := kPMLandscape; poReversePortrait: O := kPMReversePortrait; poReverseLandscape: O := kPMReverseLandscape; end; OSError(PMSetOrientation(PageFormat, O, kPMUnlocked), Self, 'DoSetOrientation', 'PMSetOrientation'); ValidatePageFormat; end; function TCarbonPrinter.GetPrinterType: TPrinterType; var IsRemote: Boolean; begin Result := ptLocal; IsRemote:=false; OSError(PMPrinterIsRemote(GetCurrentPrinter,IsRemote), Self, 'GetPrinterType', 'PMPrinterIsRemote'); if IsRemote then Result := ptNetwork end; function TCarbonPrinter.DoGetPrinterState: TPrinterState; var State: PMPrinterState; begin Result := psNoDefine; State:=0; if OSError(PMPrinterGetState(GetCurrentPrinter, State), Self, 'DoGetPrinterState', 'PMPrinterGetState') then Exit; case State of kPMPrinterIdle: Result := psReady; kPMPrinterProcessing: Result := psPrinting; kPMPrinterStopped: Result := psStopped; end; end; function TCarbonPrinter.GetCanPrint: Boolean; begin Result := (DoGetPrinterState <> psStopped); end; function TCarbonPrinter.GetCanRenderCopies: Boolean; begin Result := True; end; initialization Printer := TCarbonPrinter.Create; finalization FreeAndNil(Printer);