1{%MainUnit ../osprinters.pas}
2{$IFDEF DebugCUPS}
3{$DEFINE LogPrintoutFile}
4{$ENDIF}
5
6{**************************************************************
7Implementation for cupsprinter
8***************************************************************}
9uses
10  {%H-}udlgSelectPrinter, // used to compile it on this target
11  {%H-}udlgpropertiesprinter, // used to compile it on this target
12  FileUtil, LazFileUtils;
13
14//Return always 72 because, PostScript it's 72 only
15function TCUPSPrinter.GetXDPI: Integer;
16begin
17  Result:=InternalGetResolution(True);
18end;
19
20//Return always 72 because, PostScript it's 72 only
21function TCUPSPrinter.GetYDPI: Integer;
22begin
23  Result:=InternalGetResolution(False);
24end;
25
26procedure TCUPSPrinter.DoEnumBins(Lst: TStrings);
27var
28  choice: Pppd_choice_t;
29  Option: Pppd_option_t;
30  c: Integer;
31begin
32  Lst.Clear;
33  if CupsPPD<>nil then
34  begin
35    Option := ppdFindOption(CupsPPD, PChar('InputSlot'));
36    if Option<>nil then
37    begin
38      Choice := Option^.choices;
39      c := 0;
40      while (Choice<>nil) and (c<Option^.num_choices) do
41      begin
42        lst.AddObject(Choice^.text, TObject(Choice));
43        inc(choice);
44        inc(c);
45      end;
46    end;
47  end;
48end;
49
50function TCUPSPrinter.DoGetDefaultBinName: string;
51var
52  Option: Pppd_option_t;
53  Choice: pppd_choice_t;
54begin
55  Result:=inherited DoGetDefaultBinName;
56
57  if CupsPPD<>nil then
58  begin
59    Option := ppdFindOption(CupsPPD, 'InputSlot');
60    if Option<>nil then
61    begin
62      choice := PPDOptionChoiceFrom('InputSlot', Option^.defchoice, true);
63      if choice<>nil then
64        result := choice^.text;
65    end;
66  end;
67end;
68
69function TCUPSPrinter.DoGetBinName: string;
70var
71  Choice: pppd_choice_t;
72begin
73  result := cupsGetOption('InputSlot');
74  if result<>'' then
75  begin
76    Choice := PPDOptionChoiceFrom('InputSlot', result, true);
77    if Choice<>nil then
78      result := Choice^.text
79    else
80      result := '';
81  end;
82
83  if result='' then
84    result := doGetDefaultBinName
85end;
86
87procedure TCUPSPrinter.DoSetBinName(aName: string);
88var
89  Choice: pppd_choice_t;
90begin
91  Choice := PPDOptionChoiceFrom('InputSlot', aName, false);
92  if Choice<>nil then
93    cupsAddOption('InputSlot', choice^.choice)
94  else
95    inherited doSetBinName(aName); // handle input slot not found
96end;
97
98//write count bytes from buffer to raw mode stream
99function TCUPSPrinter.Write(const Buffer; Count: Integer; out Written: Integer
100  ): Boolean;
101begin
102  result := False;
103  CheckRawMode(True);
104  if not Assigned(FRawModeStream) then
105    FRawModeStream := TMemoryStream.Create;
106  Written := FRawModeStream.Write(Buffer, Count);
107  Result := True;
108end;
109
110constructor TCUPSPrinter.Create;
111begin
112  inherited Create;
113
114  fcupsPrinters:=nil;
115  fcupsPrinter :=nil;
116  fcupsHttp    :=nil;
117  fcupsPPD     :=nil;
118  fcupsOptions :=nil;
119  fcupsNumOpts :=0;
120
121  FRawModeStream   := nil;
122  FCupsPapersCount := -1;
123end;
124
125procedure TCUPSPrinter.DoDestroy;
126begin
127  if assigned(fRawModeStream) then
128    fRawModeStream.Free;
129
130  FreeOptions;
131
132  if Assigned(fcupsHttp) then
133    httpClose(fcupsHttp);
134
135  inherited DoDestroy;
136end;
137
138procedure TCUPSPrinter.FreeOptions;
139begin
140  if Assigned(fcupsOptions) then
141    cupsFreeOptions(fcupsNumOpts,fcupsOptions);
142
143  fcupsNumOpts:=0;
144  fcupsOptions:=nil;
145  FStates := [];
146end;
147
148procedure TCUPSPrinter.cupsAddOption(aName,aValue: string);
149begin
150  if not CUPSLibInstalled then Exit;
151  fcupsNumOpts:=cupsdyn.cupsAddOption(PChar(aName),PChar(aValue),fcupsNumOpts,
152                                                                @fcupsOptions);
153  if (AName='PageSize') then
154  begin
155    Exclude(FStates,cpsPaperNameValid);
156    Exclude(FStates,cpsPaperRectValid);
157  end;
158
159  {$IFDEF DebugCUPS}
160  DebugLn('TCUPSPrinter.cupsAddOption AName=%s AValue=%s',[AName,AValue]);
161  {$ENDIF}
162end;
163
164//Return the value of option set for the selected printer
165function TCUPSPrinter.cupsGetOption(aKeyWord: string): String;
166begin
167  Result:='';
168  if not CUPSLibInstalled then Exit;
169  if (Printers.Count>0) then
170  begin
171    if not Assigned(fcupsOptions) then
172         SetOptionsOfPrinter;
173
174    Result:=cupsdyn.cupsGetOption(PChar(aKeyWord),fcupsNumOpts,fcupsOptions);
175  end;
176end;
177
178function TCUPSPrinter.CopyOptions(out AOptions: Pcups_option_t): Integer;
179var
180  i: Integer;
181begin
182  AOptions := nil;
183  Result := 0;
184  for i:=0 to fcupsNumOpts-1 do
185    Result := cupsdyn.cupsAddOption(fcupsOptions[i].name,fcupsOptions[i].value,
186               Result,@AOptions);
187end;
188
189procedure TCUPSPrinter.MergeOptions(const AOptions:Pcups_option_t; const n:Integer);
190var
191  i: Integer;
192begin
193  for i:=0 to n-1 do
194    if
195      // always merge some known options
196      (strcomp('job-sheets', AOptions[i].name)=0) or
197      // check if ppd option value is valid
198      IsOptionValueValid(AOptions[i].name, AOptions[i].value)
199    then
200      cupsAddOption(AOptions[i].name, AOptions[i].value);
201  cupsFreeOptions(n, AOptions);
202end;
203
204function TCUPSPrinter.GetResolutionOption: string;
205var
206  L1,L2: TStringlist;
207  i: Integer;
208begin
209  Result := Self.cupsGetOption('Resolution');
210  if Result='' then
211  begin
212    // get resolution from ppd
213    Result := GetPPDAttribute('DefaultResolution');
214    if Result='' then
215    begin
216      // try grouped options
217      L1 := TStringList.Create;
218      L2 := TStringList.Create;
219      try
220        i := EnumPPDChoice(L1,'Resolution',L2);
221        if i>=0 then
222          Result := L2[i]
223      finally
224        L2.Free;
225        L1.Free;
226      end;
227    end;
228  end;
229end;
230
231procedure TCUPSPrinter.DebugOptions(AOPtions:Pcups_option_t=nil; n:Integer=0);
232var
233  i: Integer;
234begin
235  if (Printers.Count>0) and CUPSLibInstalled and (fcupsPrinter<>nil) then
236  begin
237    DebugLn('**************************************************');
238    if AOptions=nil then
239    begin
240      AOptions:= fcupsOptions;
241      n := fcupsNumOpts;
242    end;
243    DebugLn('Printer "%s" Number of Options %d',[fcupsPrinter^.Name,n]);
244    for i:=0 to n-1 do
245      DebugLn('name="%s" value="%s"',[AOptions[i].name,AOptions[i].value]);
246    DebugLn('**************************************************');
247  end else
248    DebugLn('DebugOptions: There are no valid printers');
249end;
250
251procedure TCUPSPrinter.DoCupsConnect;
252begin
253  if not assigned(fcupsHttp) then
254  begin
255    if not CUPSLibInstalled then Exit;
256    fcupsHttp:=httpConnect(cupsServer(),ippPort());
257    if not Assigned(fcupsHttp) then
258       raise Exception.Create('Unable to contact server: '+GetLastError);
259  end;
260end;
261
262function TCUPSPrinter.CupsPapersListValid: boolean;
263var
264  Lst: TStringlist;
265begin
266  if fCupsPapersCount<=0 then begin
267    // paper list no exists or
268    // paper list is not enumerated yet, try it now.
269    Lst := TStringlist.Create;
270    try
271      DoEnumPapers(Lst);
272    finally
273      Lst.Free;
274    end;
275  end;
276  result := fCupsPapersCount>0;
277end;
278
279function TCUPSPrinter.InternalGetResolution(ForX: boolean): Integer;
280
281  procedure ParseResolution(s:string);
282  var
283    a,b: Integer;
284  begin
285    if s<>'' then begin
286      s := uppercase(s);
287      a := pos('X', S);
288      b := pos('D', S);
289      if b=0 then
290        b := Length(S)
291      else
292        dec(b);
293      if a>0 then begin
294        // NNNXMMMDPI (or NNN X MMM DPI)
295        FCachedResolution.x := StrToIntDef(trim(copy(S,1,a-1)), 0);
296        FCAchedResolution.y := StrToIntDef(trim(copy(S,a+1,b)), 0);
297      end else begin
298        // NNNDPI (or NNN DPI);
299        FCachedResolution.x := StrToIntDef(trim(copy(S,1,b)), 0);
300        FCachedResolution.y := FCachedResolution.x;
301      end;
302    end;
303  end;
304
305begin
306  if not (cpsResolutionValid in FStates) then begin
307    // check user defined resolution
308    FCachedResolution.x := 0;
309    FCachedResolution.y := 0;
310
311    ParseResolution(GetResolutionOption);
312
313    if (FCachedResolution.x=0) or (FCachedResolution.y=0) then
314    begin
315      FCachedResolution.x := 300;
316      FCachedResolution.y := 300;
317    end;
318
319    include(FStates, cpsResolutionValid);
320  end;
321  if ForX then
322    result := FCachedResolution.X
323  else
324    result := FCachedResolution.Y;
325end;
326
327{$IFDEF DebugCUPS}
328procedure TCUPSPrinter.DebugCapabilities;
329var
330  flags: Integer;
331
332  procedure DumpCap(const aFlag: integer; const flagMsg, Desc: string; invert: boolean=false);
333  begin
334    if (invert and (aFlag and Flags=0)) or (not invert and (aFlag and Flags<>0)) then
335      DebugLn(flagMsg, '(',Desc,')');
336  end;
337begin
338  flags := GetAttributeInteger('printer-type',CUPS_PRINTER_LOCAL);
339  DebugLn('=== CAPABILITIES ===');
340  DebugLn;
341  DumpCap(CUPS_PRINTER_CLASS or CUPS_PRINTER_REMOTE, 'CUPS_PRINTER_LOCAL    ', 'Local printer or class    ', true);
342  DumpCap(CUPS_PRINTER_CLASS     , 'CUPS_PRINTER_CLASS    ', 'Printer class             ');
343  DumpCap(CUPS_PRINTER_REMOTE    , 'CUPS_PRINTER_REMOTE   ', 'Remote printer or class   ');
344  DumpCap(CUPS_PRINTER_BW        , 'CUPS_PRINTER_BW       ', 'Can do B&W printing       ');
345  DumpCap(CUPS_PRINTER_COLOR     , 'CUPS_PRINTER_COLOR    ', 'Can do color printing     ');
346  DumpCap(CUPS_PRINTER_DUPLEX    , 'CUPS_PRINTER_DUPLEX   ', 'Can do duplexing          ');
347  DumpCap(CUPS_PRINTER_STAPLE    , 'CUPS_PRINTER_STAPLE   ', 'Can staple output         ');
348  DumpCap(CUPS_PRINTER_COPIES    , 'CUPS_PRINTER_COPIES   ', 'Can do copies             ');
349  DumpCap(CUPS_PRINTER_COLLATE   , 'CUPS_PRINTER_COLLATE  ', 'Can collage copies        ');
350  DumpCap(CUPS_PRINTER_PUNCH     , 'CUPS_PRINTER_PUNCH    ', 'Can punch output          ');
351  DumpCap(CUPS_PRINTER_COVER     , 'CUPS_PRINTER_COVER    ', 'Can cover output          ');
352  DumpCap(CUPS_PRINTER_BIND      , 'CUPS_PRINTER_BIND     ', 'Can bind output           ');
353  DumpCap(CUPS_PRINTER_SORT      , 'CUPS_PRINTER_SORT     ', 'Can sort output           ');
354  DumpCap(CUPS_PRINTER_SMALL     , 'CUPS_PRINTER_SMALL    ', 'Can do Letter/Legal/A4    ');
355  DumpCap(CUPS_PRINTER_MEDIUM    , 'CUPS_PRINTER_MEDIUM   ', 'Can do Tabloid/B/C/A3/A2  ');
356  DumpCap(CUPS_PRINTER_LARGE     , 'CUPS_PRINTER_LARGE    ', 'Can do D/E/A1/A0          ');
357  DumpCap(CUPS_PRINTER_VARIABLE  , 'CUPS_PRINTER_VARIABLE ', 'Can do variable sizes     ');
358  DumpCap(CUPS_PRINTER_IMPLICIT  , 'CUPS_PRINTER_IMPLICIT ', 'Implicit class            ');
359  DumpCap(CUPS_PRINTER_DEFAULT   , 'CUPS_PRINTER_DEFAULT  ', 'Default printer on network');
360end;
361
362procedure TCUPSPrinter.DebugPPD;
363const
364  arruitypes:array[ppd_ui_t] of string[9] = ('boolean','pickone','pickmany');
365  arrsection:array[ppd_section_t] of string[9] = ('any','document','exit','jcl','page','prolog');
366var
367  i,j,k: Integer;
368  AttrRoot  : Ppppd_attr_t;
369  Attr      : Pppd_attr_t;
370  Group     : pppd_group_t;
371  Option    : Pppd_option_t;
372  choices   : Pppd_choice_t;
373
374  function markchar(const AMark:char):char;
375  begin
376    if AMark=#1 then
377      result := '*'
378    else
379      result := ' ';
380  end;
381begin
382  DebugLn;
383  DebugLn('DebugPPD: ppdfile=',fCupsPPDName);
384  if fcupsPPD=nil then
385  begin
386    DebugLn('No valid ppd file found');
387    exit;
388  end;
389
390  DebugLn('=== HEADER ===');
391  DebugLn;
392  DebugLn('  model          : %s', [fcupsPPD^.modelname]);
393  DebugLn('  modelNumber    : %d', [fcupsPPD^.model_number]);
394  DebugLn('  manufacturer   : %s', [fcupsPPD^.manufacturer]);
395  DebugLn('  nickname       : %s', [fcupsPPD^.nickname]);
396  DebugLn('  shortnickname  : %s', [fcupsPPD^.shortnickname]);
397  DebugLn('  product        : %s', [fcupsPPD^.product]);
398  DebugLn('  attributes     : %d Current=%d', [fcupsPPD^.num_attrs,fcupsPPD^.cur_attr]);
399  DebugLn('  language_level : %d', [fcupsPPD^.language_level]);
400  DebugLn('  lang_version   : %s', [fcupsPPD^.lang_version]);
401  DebugLn('  lang_encoding  : %s', [fcupsPPD^.lang_encoding]);
402  DebugLn('  landscape      : %d', [fcupsPPD^.landscape]);
403  DebugLn('  UI groups      : %d', [fcupsPPD^.num_groups]);
404  DebugLn('  Num Papers     : %d', [fcupsPPD^.num_sizes]);
405  DebugLn('  Num Attributes : %d', [fcupsPPD^.num_attrs]);
406  DebugLn('  Num Constrains : %d', [fcupsPPD^.num_consts]);
407  DebugLn;
408  DebugLn('=== CUSTOM PAPER SUPPORT ===');
409  DebugLn;
410  DebugLn('  Custom Min 0   : %.2f',[fcupsPPD^.custom_min[0]]);
411  DebugLn('  Custom Min 1   : %.2f',[fCupsPPD^.custom_min[1]]);
412  DebugLn('  Custom Max 0   : %.2f',[fcupsPPD^.custom_max[0]]);
413  DebugLn('  Custom Max 1   : %.2f',[fcupsPPD^.custom_max[1]]);
414
415  with fcupsPPD^ do
416  DebugLn('  Custom Margins : %.2f %.2f %.2f %.2f',
417    [custom_margins[0],custom_margins[1],custom_margins[2],custom_margins[3]]);
418  DebugLn;
419  if fcupsPPD^.num_groups>0 then
420  begin
421    DebugLn('=== GROUPS ===');
422    i := 0;
423    Group := fCupsPPD^.groups;
424    while (Group<>nil) and (i<fcupsPPD^.num_groups) do
425    begin
426      DebugLn('Group %d Name="%s" Text="%s" Options=%d SubGroups=%d',
427           [i,Group^.name,Group^.text,Group^.num_options,Group^.num_subgroups]);
428      j := 0;
429      Option := group^.options;
430      while j< group^.num_options do
431      begin
432        with Option^ do
433        DebugLn('    Option %d Key="%s" Def="%s" Text="%s" UIType="%s" section="%s" Choices=%d',
434           [j,keyword,defchoice,text,arruitypes[ui],arrsection[section],num_choices]);
435        k := 0;
436        Choices := Option^.choices;
437        while k<Option^.num_choices do
438        begin
439          DebugLn('        Choice %2d %s Choice=%-20s Text="%s"',
440            [k,MarkChar(Choices^.marked),Choices^.Choice,Choices^.Text]);
441          inc(Choices);
442          inc(k);
443        end;
444        inc(Option);
445        inc(j);
446      end;
447      inc(Group);
448      inc(i);
449    end;
450  end;
451
452  DebugLn;
453  if fcupsPPD^.num_attrs>0 then
454  begin
455    DebugLn('=== Attributes ===');
456    i := 0;
457    AttrRoot := fCupsPPD^.attrs;
458    while (AttrRoot<>nil) and (i<fcupsPPD^.num_attrs) do
459    begin
460      Attr := AttrRoot^;
461      if attr<>nil then
462        DebugLn('    i=%d Name=%s Spec=%s Value=%s',[i,Attr^.Name,Attr^.Spec,Attr^.Value]);
463      inc(i);
464      inc(AttrRoot);
465    end;
466  end;
467end;
468{$ENDIF}
469
470//Print the file aFileName with a selected printer and options
471function TCUPSPrinter.PrintFile(aFileName: String): longint;
472var
473  aPrinterName : string;
474begin
475  Result:=-1;
476  //debugln(['TCUPSPrinter.PrintFile START ',aFileName]);
477  if aFileName='' then
478    raise Exception.Create('TCUPSPrinter.PrintFile missing Filename');
479  if not CUPSLibInstalled then Exit;
480  aFileName:=ExpandFileNameUTF8(aFileName);
481
482  if (Printers.Count>0) then
483  begin
484    if not Assigned(fcupsOptions) then
485      SetOptionsOfPrinter;
486
487    if Assigned(fcupsPrinter) then
488      aPrinterName:=fcupsPrinter^.Name
489    else
490      aPrinterName:='';
491
492    {$IFDEF DebugCUPS}
493    DebugOptions;
494    debugln(['TCUPSPrinter.PrintFile aPrinterName="',aPrinterName,'" aFileName="',aFileName,'" Size=',FileSizeUtf8(aFileName)]);
495    {$ENDIF}
496
497    Result:=cupsdyn.cupsPrintFile(PChar(aPrinterName),PChar(aFileName),
498                                  PChar(Self.Title),
499                                  fcupsNumOpts,fcupsOptions);
500  end;
501end;
502
503function TCUPSPrinter.GetLastError: string;
504begin
505  Result:=ippErrorString(cupsdyn.cupsLastError());
506end;
507
508function TCUPSPrinter.IsOptionValueValid(AKeyword, AValue: pchar): boolean;
509var
510  Option: pppd_option_t;
511  i: Integer;
512begin
513  result := false;
514  if (fcupsPrinter=nil) or (fcupsppd=nil) then
515    exit;
516  Option := ppdFindOption(fcupsppd, AKeyword);
517  if Option=nil then
518    exit;
519
520  i:=0;
521  while i<Option^.num_choices do
522  begin
523    if strcomp(Option^.choices[i].choice, AValue)=0 then
524    begin
525      result := true;
526      break;
527    end;
528    inc(i);
529  end;
530
531end;
532
533function TCUPSPrinter.PPDOptionChoiceFrom(OptionStr, aKeyOrValue: string;
534  IsKey:boolean): pppd_choice_t;
535var
536  i: Integer;
537  option: pppd_option_t;
538  p: pchar;
539begin
540  result := nil;
541
542  if (fcupsPrinter=nil) or (fcupsppd=nil) then
543    exit;
544
545  option := ppdFindOption(fcupsppd, pchar(OptionStr));
546  if option=nil then
547    exit;
548
549  for i:=0 to option^.num_choices-1 do
550  begin
551    if IsKey then
552      p := @option^.choices[i].choice
553    else
554      p := @option^.choices[i].text;
555    if strcomp(p, pchar(aKeyOrValue))=0 then
556    begin
557      result := @option^.choices[i];
558      break;
559    end;
560  end;
561end;
562
563
564//Set State of Job
565procedure TCUPSPrinter.SetJobState(aJobId : LongInt; aOp : ipp_op_t);
566var Request,R : Pipp_t;                         //IPP Request
567    Language  : Pcups_lang_t;                   //Default Language
568    URI       : Array[0..HTTP_MAX_URI] of Char; //Printer URI
569begin
570  if not CUPSLibInstalled then Exit;
571  if (Printers.Count>0) then
572  begin
573    if Assigned(fcupsPrinter) then
574    begin
575      R:=nil;
576      DoCupsConnect;
577      Request:=ippNew();
578      Language:=cupsLangDefault();
579
580      ippAddString(Request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
581               'attributes-charset', '', cupsLangEncoding(language));
582
583      ippAddString(Request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
584               'attributes-natural-language', '', Language^.language);
585
586      URI:=Format('http://%s:%d/jobs/%d',[cupsServer,ippPort,aJobId]);
587
588      ippAddString(Request,IPP_TAG_OPERATION,IPP_TAG_URI,'job-uri','',URI);
589      ippAddString(Request,IPP_TAG_OPERATION,IPP_TAG_NAME,'requesting-user-name','',cupsUser());
590
591      Request^.request.op.operation_id := aOp;
592      Request^.request.op.request_id   := 1;
593
594      //Do the request and get back a response...
595      R:=cupsDoRequest(fcupsHttp, Request, '/jobs/');
596      if Assigned(R) then
597      begin
598        if (R^.request.status.status_code>IPP_OK_CONFLICT) then
599          ippDelete(R);
600      end;
601    end;
602  end;
603end;
604
605function TCUPSPrinter.GetCupsRequest : Pipp_t;
606var Request   : Pipp_t; //IPP Request
607    Language  : Pcups_lang_t;     //Default Language
608    URI       : Array[0..HTTP_MAX_URI] of Char; //Printer URI
609begin
610  Result:=Nil;
611  if not CUPSLibInstalled then Exit;
612  if (Printers.Count>0) then
613  begin
614    if Assigned(fcupsPrinter) then
615    begin
616      DoCupsConnect;
617      Request:=ippNew();
618      {Build an IPP_GET_PRINTER_ATTRIBUTES request,
619      which requires the following attributes:
620        attributes-charset
621        attributes-natural-language
622        printer-uri}
623      Request^.request.op.operation_id := IPP_GET_PRINTER_ATTRIBUTES;
624      Request^.request.op.request_id   := 1;
625      Language:=cupsLangDefault;
626
627      ippAddString(Request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
628               'attributes-charset', '', cupsLangEncoding(language));
629
630      ippAddString(Request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
631               'attributes-natural-language', '', Language^.language);
632
633      // or this syntax >>
634      //URI:=Format('http://%s:%d/printers/%s',[cupsServer,ippPort,fcupsPrinter^.name]);
635      URI:=Format('ipp://localhost/printers/%s',[fcupsPrinter^.name]);
636      ippAddString(Request,IPP_TAG_OPERATION,IPP_TAG_URI,'printer-uri','',URI);
637
638      //Do the request and get back a response...
639      Result:=cupsDoRequest(fcupsHttp, Request, '/');
640      if Assigned(Result) then
641      begin
642        if (Result^.request.status.status_code>IPP_OK_CONFLICT) then
643        begin
644          ippDelete(Result);
645          Result:=nil;
646        end;
647      end;
648    end;
649  end;
650end;
651
652//Initialize the options with the default options of selected printer
653procedure TCUPSPrinter.SetOptionsOfPrinter;
654Var Opts : Pcups_option_t;
655    Opt  : Pcups_option_t;
656    i    : Integer;
657begin
658  //if not CUPSLibInstalled then
659    Exit;
660  if (Printers.Count>0) then
661  begin
662    if Assigned(fcupsPrinter) then
663    begin
664      Opts := fcupsPrinter^.Options;
665      for i:=0 to fcupsPrinter^.num_options-1 do
666      begin
667        Opt:=@Opts[i];
668        cupsAddOption(Opt^.Name,Opt^.Value);
669      end;
670    end;
671  end;
672end;
673
674//Enum all options associed with aKeyWord
675function TCUPSPrinter.EnumPPDChoice(Lst : TStrings;
676  const aKeyWord : string; OptNames: TStrings = nil) : Integer;
677var i         : integer;
678    Option    : Pppd_option_t;
679    Choice    : Pppd_choice_t;
680begin
681  Result:=-1;
682  if not CUPSLibInstalled then Exit;
683  if not Assigned(Lst) then Exit;
684  Lst.Clear;
685
686  if (Printers.Count>0) then
687  begin
688    if Assigned(fcupsPrinter) then
689    begin
690      if Assigned(fcupsPPD) then
691      begin
692        Option:=nil;
693        Option:=ppdFindOption(fcupsPPD,PChar(aKeyWord));
694
695        If Assigned(Option) then
696        begin
697          for i:=0 to Option^.num_choices-1 do
698          begin
699            Choice:=@Option^.choices[i];
700            if Choice^.marked=#1 then
701              Result:=i;
702
703            Lst.Add(Choice^.text);
704            if Assigned(OptNames) then
705              OptNames.Add(Choice^.choice);
706          end;
707
708          //Not marked choice then the choice is default
709          if (Result<0) and (Lst.Count>0) then begin
710            Result:=Lst.IndexOf(OPtion^.defchoice);
711            if (Result<0)and Assigned(OptNames) then
712              Result := OptNames.IndexOf(Option^.DefChoice);
713          end;
714        end;
715      end;
716    end;
717  end;
718end;
719
720function TCUPSPrinter.GetPPDAttribute(const aName: string): string;
721var
722  i         : integer;
723  AttrRoot  : PPppd_attr_t;
724  Attr      : Pppd_attr_t;
725begin
726  Result:='';
727  if not CUPSLibInstalled then
728    Exit;
729
730  if (Printers.Count>0) and (fcupsPrinter<>nil) and (fcupsPPD<>nil) then
731  begin
732    i := 0;
733    AttrRoot := fCupsPPD^.attrs;
734    while (AttrRoot<>nil) and (i<fcupsPPD^.num_attrs) do
735    begin
736      Attr := AttrRoot^;
737      if attr<>nil then
738      begin
739        if (StrComp(pchar(AName), Attr^.name)=0) then
740        begin
741          result := attr^.value;
742          break;
743        end;
744      end;
745      inc(i);
746      inc(AttrRoot);
747    end;
748  end;
749end;
750
751procedure TCUPSPrinter.GetEnumAttributeString(aName: PChar; Lst: TStrings);
752var
753  Reponse   : Pipp_t; //IPP Reponse
754  Attribute : Pipp_attribute_t; //Current attribute
755  i         : Integer;
756begin
757  if not assigned(Lst) then
758    raise Exception.Create('Lst must be assigned');
759  if not CUPSLibInstalled then begin
760    DebugLn(['TCUPSPrinter.GetEnumAttributeString CUPSLibInstalled not installed']);
761    Exit;
762  end;
763
764  Reponse:=GetCupsRequest;
765  if not Assigned(Reponse) then begin
766    DebugLn(['TCUPSPrinter.GetEnumAttributeString no Response']);
767  end else begin
768    try
769      Attribute:=ippFindAttribute(Reponse,aName, IPP_TAG_ZERO);
770      if Assigned(Attribute) then begin
771        for i:=0 to Attribute^.num_values-1 do
772        begin
773          if Attribute^.value_tag=IPP_TAG_INTEGER then
774            Lst.add(IntToStr(Pipp_value_t(@Attribute^.values)[i].aInteger))
775          else
776            Lst.add(Pipp_value_t(@Attribute^.values)[i]._string.text);
777        end;
778      end else begin
779        DebugLn(['TCUPSPrinter.GetEnumAttributeString Attribute not found: ',aName]);
780      end;
781    finally
782      ippDelete(Reponse);
783    end;
784  end;
785end;
786
787function TCUPSPrinter.GetAttributeInteger(aName: PChar; DefaultValue : Integer): Integer;
788var
789  Reponse   : Pipp_t; //IPP Reponse
790  Attribute : Pipp_attribute_t; //Current attribute
791begin
792  Result:=DefaultValue;
793  if not CUPSLibInstalled then Exit;
794
795  Reponse:=GetCupsRequest;
796  if Assigned(Reponse) then
797  begin
798    try
799      Attribute:=ippFindAttribute(Reponse,aName, IPP_TAG_ZERO);
800      if Assigned(Attribute) then
801        Result:=Attribute^.values[0].aInteger;
802    finally
803      ippDelete(Reponse);
804    end;
805  end;
806end;
807
808function TCUPSPrinter.GetAttributeString(aName: PChar;
809  const DefaultValue : string): string;
810var
811  Reponse   : Pipp_t; //IPP Reponse
812  Attribute : Pipp_attribute_t; //Current attribute
813begin
814  Result:=DefaultValue;
815  if not CUPSLibInstalled then Exit;
816  Reponse:=GetCupsRequest;
817  if Assigned(Reponse) then
818  begin
819    try
820      Attribute:=ippFindAttribute(Reponse,aName, IPP_TAG_ZERO);
821      if Assigned(Attribute) then
822        Result:=Attribute^.values[0]._string.text
823      else begin
824        DebugLn(['TCUPSPrinter.GetAttributeString failed: aName="',aName,'"']);
825      end;
826    finally
827      ippDelete(Reponse);
828    end;
829  end;
830end;
831
832function TCUPSPrinter.GetAttributeBoolean(aName: PChar;
833  DefaultValue : Boolean): Boolean;
834var
835  Reponse   : Pipp_t; //IPP Reponse
836  Attribute : Pipp_attribute_t; //Current attribute
837begin
838  Result:=DefaultValue;
839  if not CUPSLibInstalled then Exit;
840  Reponse:=GetCupsRequest;
841  if Assigned(Reponse) then
842  begin
843    try
844      Attribute:=ippFindAttribute(Reponse,aName, IPP_TAG_ZERO);
845      if Assigned(Attribute) then
846        Result:=(Attribute^.values[0].aBoolean=#1);
847    finally
848      ippDelete(Reponse);
849    end;
850  end;
851end;
852
853//Override this methode for assign an
854//file name at Canvas
855procedure TCUPSPrinter.DoBeginDoc;
856var
857  NewPath: String;
858  fs: TFileStream;
859
860  function TryTemporaryPath(const Path: string): Boolean;
861  var
862    CurPath: String;
863  begin
864    Result:=false;
865    CurPath:=CleanAndExpandDirectory(Path);
866    if CurPath='' then exit(false);
867    if not DirectoryIsWritable(CurPath) then exit;
868    NewPath:=CurPath;
869    Result:=true;
870  end;
871
872begin
873  if FBeginDocCount>0 then
874    raise Exception.Create('TCUPSPrinter.DoBeginDoc already called. Maybe you forgot an EndDoc?');
875  inherited DoBeginDoc;
876  inc(FBeginDocCount);
877
878  if (not TryTemporaryPath('~/tmp/'))
879  and (not TryTemporaryPath('/tmp/'))
880  and (not TryTemporaryPath('/var/tmp/')) then
881    NewPath:='';
882
883  FOutputFileName := AppendPathDelim(NewPath)+
884    'OutPrinter_'+FormatDateTime('yyyymmmddd-hhnnss',Now);
885
886  if RawMode then
887    FOutputFileName := FOutputFileName + '.raw'
888  else begin
889    FOutputFileName := FOutputFileName + '.ps';
890    TFilePrinterCanvas(Canvas).OutputFileName := FOutputFileName;
891  end;
892
893  // test writing, on error this raises exception showing the user the filename
894  fs:=TFileStream.Create(FOutputFilename,fmCreate);
895  try
896    fs.Write(FOutputFilename[1],1);
897  finally
898    fs.free;
899  end;
900  DeleteFileUTF8(FOutputFilename);
901end;
902
903//If not aborted, send PostScript file to printer.
904//After, delete this file.
905procedure TCUPSPrinter.DoEndDoc(aAborted: Boolean);
906var
907  CupsResult: LongInt;
908begin
909  inherited DoEndDoc(aAborted);
910  dec(FBeginDocCount);
911  Exclude(FStates,cpsPaperRectValid);
912
913  if RawMode then begin
914
915    if not aAborted and (FRawModeStream<>nil)
916      and (FRawModeStream.Size>0) then
917    begin
918      try
919        FRawModeStream.SaveToFile(FOutputFileName);
920      finally
921        FRawModeStream.Clear;
922      end;
923    end;
924
925  end else
926    TFilePrinterCanvas(Canvas).OutputFileName:='';
927
928  if not aAborted then begin
929    if not FileExistsUTF8(FOutputFileName) then
930      raise Exception.Create('Unable to write to "'+FOutputFileName+'"');
931    {$IFDEF LogPrintoutFile}
932    CopyFile(FOutputFileName, 'printjob'+ExtractFileExt(FOutputFileName));
933    {$ENDIF}
934    try
935      {$IFNDEF DoNotPrint}
936      if Filename<>'' then
937        CopyFile(FOutputFileName, FileName)
938      else begin
939        CupsResult:=PrintFile(FOutputFileName);
940        if CupsResult<=0 then
941          raise Exception.Create('CUPS printing: '+GetLastError);
942      end;
943      {$ENDIF}
944    finally
945      DeleteFileUTF8(FOutputFilename);
946    end;
947  end;
948end;
949
950procedure TCUPSPrinter.DoNewPage;
951begin
952  // just to flag that we want the old 'newpage' pagination way
953  // instead of the new 'beginpage/endpage'
954end;
955
956procedure TCUPSPrinter.DoResetPrintersList;
957begin
958  if Assigned(fcupsPPD) then
959  begin
960    ppdClose(fcupsPPD);
961    fcupsPPD:=nil;
962  end;
963
964  if fcupsPPDName<>'' then
965  begin
966    DeleteFileUTF8(fcupsPPDName);
967    fcupsPPDName:='';
968  end;
969
970  FreeOptions;
971  if Assigned(fcupsPrinters) and CUPSLibInstalled then begin
972    cupsFreeDests(Printers.Count,fcupsPrinters);
973    fCupsPrinter := nil;
974  end;
975
976  inherited DoResetPrintersList;
977end;
978
979procedure TCUPSPrinter.DoEnumPrinters(Lst: TStrings);
980Var i,Num   : Integer;
981    P       : Pcups_dest_t;
982begin
983  inherited DoEnumPrinters(Lst);
984  {$IFDEF NOPRINTERS}
985  Lst.Clear;
986  Exit;
987  {$ENDIF}
988  if not CUPSLibInstalled then Exit;
989
990  Num:=cupsGetDests(@fcupsPrinters);
991  For i:=0 to Num-1 do
992  begin
993    P:=nil;
994    P:=@fcupsPrinters[i];
995    if Assigned(P) then
996    begin
997      if P^.is_default<>0 then
998        Lst.Insert(0,P^.name)
999      else
1000        Lst.Add(P^.name);
1001    end;
1002  end;
1003end;
1004
1005procedure TCUPSPrinter.DoEnumPapers(Lst: TStrings);
1006var
1007  choice: Pppd_choice_t;
1008  Option: Pppd_option_t;
1009  c: Integer;
1010begin
1011  //DebugLn(['TCUPSPrinter.DoEnumPapers ',dbgsName(Self)]);
1012
1013  //TODO: note that we are returning here the list of paper "keys"
1014  //      not the human readable paper names. Modify cups support
1015  //      to return human readable paper names.
1016
1017  Lst.Clear;
1018  FCupsDefaultPaper := '';
1019  if CupsPPD<>nil then
1020  begin
1021    Option := ppdFindOption(CupsPPD, PChar('PageSize'));
1022    Choice := Option^.choices;
1023    fCupsDefaultPaper := Option^.defchoice;
1024    c := 0;
1025    while (Choice<>nil) and (c<Option^.num_choices) do
1026    begin
1027      lst.AddObject(Choice^.Choice, TObject(Choice));
1028      inc(choice);
1029      inc(c);
1030    end;
1031  end;
1032
1033  fCupsPapersCount := lst.Count;
1034end;
1035
1036function TCUPSPrinter.DoSetPrinter(aName: string): Integer;
1037Var i  : Integer;
1038    P  : Pcups_dest_t;
1039    Fn : String;
1040begin
1041  //debugln('TCUPSPrinter.DoSetPrinter aName="',aName,'"');
1042  Result:=inherited DoSetPrinter(aName);
1043  if not CUPSLibInstalled then Exit;
1044  //debugln('TCUPSPrinter.DoSetPrinter B Printers.Count=',dbgs(Printers.Count));
1045
1046  //Set the current printer. If aName='' then use a default Printer (index 0)
1047  If (Printers.Count>0) then
1048  begin
1049    if (aName<>'') and Assigned(fcupsPPD) then
1050    begin
1051      //Printer changed ?
1052      i:=Printers.IndexOf(aName);
1053      if i=PrinterIndex then
1054      begin
1055        Result:=PrinterIndex;
1056        //debugln('TCUPSPrinter.DoSetPrinter no change');
1057        Exit;
1058      end;
1059    end;
1060
1061    //Clear all existing options
1062    FreeOptions;
1063
1064    if Assigned(fcupsPPD) then
1065    begin
1066      ppdClose(fcupsPPD);
1067      fcupsPPD:=nil;
1068
1069      if fcupsPPDName<>'' then
1070      begin
1071        DeleteFileUTF8(fcupsPPDName);
1072        fcupsPPDName:='';
1073      end;
1074    end;
1075
1076
1077    if aName='' then
1078      i:=0
1079    else
1080      i:=Printers.IndexOf(aName);
1081
1082    if i>-1 then
1083    begin
1084      Result:=i;
1085
1086      P:=nil;
1087      P:=cupsGetDest(PChar(aName),nil,Printers.Count,fcupsPrinters);
1088      if not Assigned(P) then
1089        raise Exception.Create(Format('"%s" is not a valid printer.',[aName]));
1090      fcupsPrinter:=P;
1091
1092      //Open linked ppdfile
1093      Fn:=cupsGetPPD(PChar(aName));
1094      fcupsPPD:=ppdOpenFile(PChar(Fn));
1095      fcupsPPDName:=Fn;
1096      {$IFDEF DebugCUPS}
1097      DebugPPD;
1098      DebugCapabilities;
1099      {$ENDIF}
1100    end;
1101  end
1102  else
1103  begin
1104    PrinterIndex:=-1;
1105    fcupsPPD:=nil;
1106  end;
1107end;
1108
1109function TCUPSPrinter.DoGetCopies: Integer;
1110begin
1111  if not (cpsCopiesValid in FStates) then begin
1112    fCachedCopies:=inherited DoGetCopies;
1113
1114    //Get default value if defined
1115    fCachedCopies:=GetAttributeInteger('copies-default',fCachedCopies);
1116    //Get Copies in options or return default value
1117    fCachedCopies:=StrToIntdef(cupsGetOption('copies'),fCachedCopies);
1118    {$IFDEF UseCache}
1119    Include(FStates,cpsCopiesValid);
1120    {$ENDIF}
1121  end;
1122  Result:=fCachedCopies;
1123end;
1124
1125procedure TCUPSPrinter.DoSetCopies(aValue: Integer);
1126var i : Integer;
1127begin
1128  {$IFDEF UseCache}
1129  if aValue=DoGetCopies then exit;
1130  Exclude(FStates,cpsCopiesValid);
1131  {$ENDIF}
1132  inherited DoSetCopies(aValue);
1133
1134  if Printers.Count>0 then
1135  begin
1136    if not Assigned(fcupsOptions) then
1137      SetOptionsOfPrinter;
1138    i:=aValue;
1139    if i<1 then i:=1;
1140    cupsAddOption('copies',IntToStr(i));
1141  end;
1142end;
1143
1144function TCUPSPrinter.DoGetOrientation: TPrinterOrientation;
1145var i  : Integer;
1146begin
1147  if not (cpsOrientationValid in FStates) then begin
1148    if Printers.Count>0 then
1149    begin
1150      //Default orientation value
1151      i:=GetAttributeInteger('orientation-requested-default',3);
1152      // check if rotation is automatic or out-of-range
1153      if not (i in [3,4,5,6]) then
1154        i:=3; // yep, then for us this means portait
1155      fCachedOrientation:=TPrinterOrientation(i-3);
1156    end;
1157    Include(FStates,cpsOrientationValid);
1158  end;
1159  Result:=fCachedOrientation;
1160  {$IFDEF DebugCUPS}
1161  DebugLn('DoGetOrientation: result=%d',[ord(Result)]);
1162  {$ENDIF}
1163end;
1164
1165procedure TCUPSPrinter.DoSetOrientation(aValue: TPrinterOrientation);
1166begin
1167  if aValue=DoGetOrientation then
1168    exit;
1169  Exclude(FStates,cpsPaperRectValid);
1170  inherited DoSetOrientation(aValue);
1171  fcachedOrientation := AValue;
1172  Include(FStates,cpsOrientationValid);
1173end;
1174
1175function TCUPSPrinter.DoGetDefaultPaperName: string;
1176begin
1177  if not (cpsDefaultPaperNameValid in FStates) then begin
1178    fCachedGetDefaultPaperName:='';
1179    if not CupsPapersListValid then
1180      FCachedGetDefaultPaperName:=PaperSize.DefaultPaperName
1181    else begin
1182      if FCupsDefaultPaper<>'' then
1183        fCachedGetDefaultPaperName:= FCupsDefaultPaper
1184      else
1185        fCachedGetDefaultPaperName:=
1186                 GetAttributeString('media-default',fCachedGetDefaultPaperName);
1187      {$IFDEF UseCache}
1188      Include(FStates,cpsDefaultPaperNameValid);
1189      {$ENDIF}
1190    end;
1191  end;
1192  Result:=fCachedGetDefaultPaperName;
1193end;
1194
1195function TCUPSPrinter.DoGetPaperName: string;
1196begin
1197  if not (cpsPaperNameValid in FStates) then begin
1198    // paper is not yet retrieved for first time
1199    // first try to see if there is a list of papers available
1200    if not CupsPapersListValid then
1201      FCachedPaperName := PaperSize.PaperName
1202    else begin
1203      fCachedPaperName := cupsGetOption('PageSize');
1204      {$IFDEF UseCache}
1205      Include(FStates,cpsPaperNameValid);
1206      {$ENDIF}
1207    end;
1208  end;
1209  Result:=fCachedPaperName;
1210end;
1211
1212procedure TCUPSPrinter.DoSetPaperName(aName: string);
1213begin
1214  {$IFDEF UseCache}
1215  if aName=DoGetPaperName then exit;
1216  Exclude(FStates,cpsPaperNameValid);
1217  {$ENDIF}
1218  inherited DoSetPaperName(aName);
1219
1220  if FCupsPapersCount<=0 then
1221    PaperSize.PaperName:=AName
1222  else
1223    cupsAddOption('PageSize',aName)
1224end;
1225
1226//Initialise aPaperRc with the aName paper rect
1227//Result : -1 no result
1228//          0 aPaperRc.WorkRect is a margins
1229//          1 aPaperRc.WorkRect is really the work rect
1230function TCUPSPrinter.DoGetPaperRect(aName: string;
1231  var aPaperRc: TPaperRect): Integer;
1232
1233var
1234  P : Pppd_size_t;
1235  Ky,Kx: Double;
1236begin
1237  if (not (cpsPaperRectValid in FStates)) or
1238    (fCachePaperRectName<>aName) then
1239  begin
1240    fCachePaperRectName:=aName;
1241    FillChar(fCachePaperRect,SizeOf(fCachePaperRect),0);
1242    fCachePaperRectResult:=inherited DoGetPaperRect(aName, aPaperRc);
1243    {$IFDEF UseCache}
1244    Include(FStates,cpsPaperRectValid);
1245    {$ENDIF}
1246
1247    P:=nil;
1248    if CUPSLibInstalled and Assigned(fcupsPPD) then
1249    begin
1250      P:=ppdPageSize(fcupsPPD,PChar(aName));
1251      if Assigned(P) then
1252      begin
1253        fCachePaperRectResult:=1; //CUPS return margins
1254
1255        // Margins.
1256        //
1257        // Cups gives dimensions based on postcript language
1258        // user space coordinates system which is something like
1259        //
1260        //  +y                                 +--> +x
1261        //   ^           but our system is     |
1262        //   |                                 v
1263        //   +--> +x                           +y
1264        //
1265        //  so values in x are the same, but we need to invert values in y,
1266        //  the given bottom value is the margin size at the bottom, we need
1267        //  to re-calc. our bottom offset, and the given top value is offset
1268        //  top value of imageable area, we need to re-calc. our top offset,
1269        //  which is the margin size at the top of the page.
1270        //
1271        //  The current implementation assumes that paper is fed short-edge-first
1272        //  either in portrait orientation, or in landscape orientation.
1273        //
1274        //  In landscape orientation, printable margins should preserved.
1275        //  It's based on a 90 degree counterclock wise paper rotation
1276        //
1277        //     FEED DIRECTION             FEED DIRECTION
1278        //
1279        //           /\                         /\
1280        //          /  \                       /  \
1281        //           ||                         ||
1282        //           ||                         ||
1283        //
1284        //     PORTRAIT                   LANDSCAPE
1285        //     +-----------------+        +-----------------+
1286        //     |        t        |        |        t        |
1287        //     |   +---------+   |        |   +---------+   |
1288        //     |   |   ( )   |   |        |   |   |   / |   |
1289        //     | l |  --+--  | r |        | l |()-+---  | r |
1290        //     |   |   / \   |   |        |   |   |   \ |   |
1291        //     |   +---------+   |        |   +---------+   |
1292        //     |        b        |        |        b        |
1293        //     +-----------------+        +-----------------+
1294        //
1295        //     REVERSE PORTRAIT           REVERSE LANDSCAPE
1296        //     +-----------------+        +-----------------+
1297        //     |        t        |        |        t        |
1298        //     |   +---------+   |        |   +---------+   |
1299        //     |   |   \ /   |   |        |   | \   |   |   |
1300        //     | l |  --+--  | r |        | l |  ---+-()| r |
1301        //     |   |   ( )   |   |        |   | /   |   |   |
1302        //     |   +---------+   |        |   +---------+   |
1303        //     |        b        |        |        b        |
1304        //     +-----------------+        +-----------------+
1305        //
1306        Kx := Printer.XDPI/72;
1307        Ky := Printer.YDPI/72;
1308        if Orientation in [poPortrait, poReversePortrait] then begin
1309          fCachePaperRect.PhysicalRect.Right:=Round(P^.Width*Kx);
1310          fCachePaperRect.PhysicalRect.Bottom:=Round(P^.Length*Ky);
1311          fCachePaperRect.WorkRect.Left:=Round(P^.Left*Kx);
1312          fCachePaperRect.WorkRect.Right:=Round(P^.Right*Kx);
1313          fCachePaperRect.WorkRect.Top:=Round((P^.Length-P^.Top)*Ky);
1314          fCachePaperRect.WorkRect.Bottom:=Round((P^.Length-P^.Bottom)*Ky);
1315        end else begin
1316          FCachePaperRect.PhysicalRect.Right:=Round(P^.Length*Kx);
1317          FCachePaperRect.PhysicalRect.Bottom:=Round(P^.Width*Ky);
1318          FCachePaperRect.WorkRect.Left:=Round((P^.Length-P^.Top)*Kx);
1319          FCachePaperRect.WorkRect.Right:=Round((P^.Length-P^.Bottom)*Kx);
1320          FCachePaperRect.WorkRect.Top:=Round((P^.Width-P^.Right)*Ky);
1321          FCachePaperRect.WorkRect.Bottom:=Round((p^.width - P^.left)*Ky);
1322        end;
1323
1324        {$IFDEF DebugCUPS}
1325        with P^ do
1326        DebugLn('ORG: Width=%f Length=%f Left=%f Right=%f Top=%f Bottom=%f Name=%s',
1327          [Width,Length,Left,Right,Top,Bottom,string(Name)]);
1328        with FCachePaperRect do
1329        DebugLn('NEW: Width=%d Length=%d Left=%d Top=%d Right=%d Bottom=%d ml=%d mt=%d mr=%d mb=%d',
1330          [PhysicalRect.Right,PhysicalRect.Bottom,WorkRect.Left,WorkRect.Top,WorkRect.Right,WorkRect.Bottom,
1331           WorkRect.Left,WorkRect.Top,PhysicalRect.Right-WorkRect.Right,
1332           PhysicalRect.Bottom-WorkRect.Bottom]);
1333        {$ENDIF}
1334      end;
1335    end;
1336
1337    if P=nil then begin
1338      FCachePaperRect := PaperSize.PaperRectOf[AName];
1339      fCachePaperRectResult:=1
1340    end;
1341
1342  end;
1343  Result:=fCachePaperRectResult;
1344  aPaperRc:=fCachePaperRect;
1345end;
1346
1347
1348function TCUPSPrinter.DoGetPrinterState: TPrinterState;
1349var //Request   : Pipp_t; //IPP Request
1350    //Reponse   : Pipp_t; //IPP Reponse
1351    //Attribute : Pipp_attribute_t; //Current attribute
1352    //Language  : Pcups_lang_t;     //Default Language
1353    aState    : ipp_pstate_t;     //Printer state
1354    //URI       : Array[0..HTTP_MAX_URI] of Char; //Printer URI
1355begin
1356  Result:=inherited DoGetPrinterState;
1357
1358  aState:=ipp_pstate_t(GetAttributeInteger('printer-state',0));
1359  Case aState of
1360    IPP_PRINTER_IDLE       : Result:=psReady;
1361    IPP_PRINTER_PROCESSING : Result:=psPrinting;
1362    IPP_PRINTER_STOPPED    : Result:=psStopped;
1363  end;
1364end;
1365
1366function TCUPSPrinter.DoGetDefaultCanvasClass: TPrinterCanvasRef;
1367begin
1368  {$IFDEF UseCairo}
1369  Result := TCairoPsCanvas;
1370  {$ELSE}
1371  Result := TPostscriptPrinterCanvas;
1372  {$ENDIF}
1373end;
1374
1375function TCUPSPrinter.GetPrinterType: TPrinterType;
1376Var i : Integer;
1377begin
1378  Result:=inherited GetPrinterType;
1379  i:=GetAttributeInteger('printer-type',CUPS_PRINTER_LOCAL);
1380  If (i and CUPS_PRINTER_REMOTE)=CUPS_PRINTER_REMOTE then
1381    Result:=ptNetWork;
1382end;
1383
1384function TCUPSPrinter.GetCanPrint: Boolean;
1385begin
1386  Result:=inherited GetCanPrint;
1387  Result:=GetAttributeBoolean('printer-is-accepting-jobs',Result)
1388end;
1389
1390initialization
1391  if Assigned(Printer) then
1392    Printer.Free;
1393
1394  Printer:=TCUPSPrinter.Create;
1395
1396FINALIZATION
1397  // Free the printer before unloading library
1398  Printer.Free;
1399  Printer:=nil;
1400  //Unload CUPSLib if loaded
1401  FinalizeCups;
1402
1403END.
1404
1405