{ /*************************************************************************** postscriptprinter.pas --------------------- Printer object Initial Revision : Mon Nov 05 2002 ***************************************************************************/ ***************************************************************************** This file is part of the Lazarus Component Library (LCL) See the file COPYING.modifiedLGPL.txt, included in this distribution, for details about the license. ***************************************************************************** Author: Tony Maro } unit PostScriptPrinter; {$mode objfpc}{$H+} interface uses Classes, SysUtils, LCLProc, Graphics, GraphMath, LCLIntf, Forms; // uses lcllinux or winapi for RGB conversions and FORMS for application object { Defines a special canvas type object and override drawing methods to make the postscript code... Defines a TPSPattern object that handles creation of patterns to be used in fills and paints TPostScript manages a list of patterns and inserts the definitions into the postscript code and manages when they are changed A pattern definition can access pattern definitions within the same postscript object, as long as the TPSPattern object pointer is placed into the canvas pen/brush at the time the new pattern is made } type TPostScript = class; TPSPaintType = (ptColored, ptUncolored); TPSTileType = (ttConstant, ttNoDistortion, ttFast); TPostScriptCanvas = class; // forward reference { Remember, modifying a pattern affects that pattern for the ENTIRE document! } TPSPattern = class(TObject) private FOldName: String; FOnChange: TNotifyEvent; FBBox: TRect; FCanvas: TPostScriptCanvas; FName: String; FPaintType: TPSPaintType; FPostScript: TStringList; FTilingType: TPSTileType; FXStep: Real; FYStep: Real; function GetpostScript: TStringList; procedure SetBBox(const AValue: TRect); procedure SetName(const AValue: String); procedure SetPaintType(const AValue: TPSPaintType); procedure SetTilingType(const AValue: TPSTileType); procedure SetXStep(const AValue: Real); procedure SetYStep(const AValue: Real); protected public constructor Create; destructor Destroy; override; procedure Changed; property BBox: TRect read FBBox write SetBBox; property PaintType: TPSPaintType read FPaintType write SetPaintType; property TilingType: TPSTileType read FTilingType write SetTilingType; property XStep: Real read FXStep write SetXStep; property YStep: Real read FYStep write SetYStep; property Name: String read FName write SetName; property Canvas: TPostScriptCanvas read FCanvas; property GetPS: TStringList read GetPostscript; property OldName: string read FOldName write FOldName; // used when notifying that name Changed property OnChange: TNotifyEvent read FOnChange write FOnChange; end; PPSPattern = ^TPSPattern; // used for array { basic pen object - modify later for better splitting of brush object } TPSObject = class(TObject) private FOnChange: TNotifyEvent; protected procedure Changed; virtual; procedure Lock; procedure UnLock; public property OnChange: TNotifyEvent read FOnChange write FOnChange; end; { Pen and brush object both right now...} TPSPen = class(TPSObject) private FColor: TColor; FPattern: TPSPattern; FWidth: Real; procedure SetPattern(const AValue: TPSPattern); protected procedure SetColor(Value : TColor); procedure Setwidth(value : Real); public constructor Create; destructor Destroy; override; procedure Assign(Source: TPSPen); property Color: TColor read FColor write SetColor; property Pattern: TPSPattern read FPattern write SetPattern; property Width: Real read FWidth write SetWidth; function AsString: String; end; { Custom canvas-like object that handles postscript code } TPostScriptCanvas = class(TObject) private FBrush: TPSPen; FFontFace: String; FFontSize: Integer; FHeight: Integer; FLineSpacing: Integer; FColor: TColor; // canvas color - implement later FPen: TPSPen; LastX: Integer; LastY: Integer; FPostScript: TStringList; function GetColor: TColor; procedure SetBrush(const AValue: TPSPen); procedure SetColor(const AValue: TColor); procedure SetFontFace(const AValue: String); procedure SetFontSize(const AValue: Integer); procedure SetPen(const AValue: TPSPen); function TranslateY(Ycoord: Integer): Integer; // Y axis is backwards in postscript procedure AddFill; procedure ResetPos; // reset back to last moveto location procedure PenChanged(Sender: TObject); public MPostScript: TPostScript; constructor Create(APostScript: TPostScript); destructor Destroy; override; procedure Clear; property PostScript: TStringList read FPostScript write FPostScript; property FontFace: String read FFontFace write SetFontFace; property FontSize: Integer read FFontSize write SetFontSize; property LineSpacing: Integer read FLineSpacing write FLineSpacing; procedure MoveTo(X1,Y1 : Integer); procedure LineTo(X1,Y1 : Integer); procedure Line(X1,Y1,X2,Y2 : Integer); procedure Rectangle(X1,Y1,X2,Y2 : Integer); procedure Rectangle(const Rect: TRect); procedure Polyline(Points: PPoint; NumPts: Integer); procedure Ellipse(x1, y1, x2, y2: Integer); procedure Ellipse(const Rect: TRect); procedure RadialPie(x,y,width,mheight,angle1,angle2 : Integer); //procedure Pie(x,y,width,height,SX,SY,EX,EY : Integer); procedure Writeln(const AString: String); procedure TextOut(X,Y: Integer; const Text: String); //procedure Chord(x,y,width,height,angle1,angle2 : Integer); //procedure Chord(x,y,width,height,SX,SY,EX,EY : Integer); //procedure PolyBezier(Points: PPoint; NumPts: Integer; // Filled: boolean{$IFNDEF VER1_0} = False{$ENDIF}; // Continuous: boolean{$IFNDEF VER1_0} = False{$ENDIF}); //procedure PolyBezier(const Points: array of TPoint; // Filled: boolean{$IFNDEF VER1_0} = False{$ENDIF}; // Continuous: boolean{$IFNDEF VER1_0} = False{$ENDIF}); //procedure PolyBezier(const Points: array of TPoint); //procedure Polygon(const Points: array of TPoint; // Winding: Boolean{$IFNDEF VER1_0} = False{$ENDIF}; // StartIndex: Integer{$IFNDEF VER1_0} = 0{$ENDIF}; // NumPts: Integer {$IFNDEF VER1_0} = -1{$ENDIF}); //procedure Polygon(Points: PPoint; NumPts: Integer; // Winding: boolean{$IFNDEF VER1_0} = False{$ENDIF}); //procedure Polygon(const Points: array of TPoint); //procedure FillRect(const Rect : TRect); //procedure FloodFill(X, Y: Integer; FillColor: TColor; FillStyle: TFillStyle); //procedure RoundRect(X1, Y1, X2, Y2: Integer; RX,RY : Integer); //procedure RoundRect(const Rect : TRect; RX,RY : Integer); property Height: Integer read FHeight write FHeight; // set so we can translate Y coords property Color: TColor read GetColor write SetColor; property Pen: TPSPen read FPen write SetPen; property Brush: TPSPen read FBrush write SetBrush; end; { Encapsulates ALL the postscript and uses the TPostScriptCanvas object for a single page } TPostScript = class(TObject) private FCanvas: TPostScriptCanvas; FHeight: Integer; FLineSpacing: Integer; FPageNumber: Integer; FTitle: String; FWidth: Integer; FDocument: TStringList; Patterns: PPSPattern; // array of pointers to pattern objects NumPatterns: Integer; // number of patterns in array procedure SetHeight(const AValue: Integer); procedure SetLineSpacing(const AValue: Integer); procedure SetTitle(const AValue: String); procedure SetWidth(const AValue: Integer); procedure GrabCanvas; procedure UpdateBoundingBox; procedure PatternChanged(Sender: TObject); procedure InsertPattern(APattern: TPSPattern); // adds the pattern to the postscript procedure RemovePattern(APattern: TPSPattern); // remove the pattern from the postscript public constructor Create; destructor Destroy; override; procedure AddPattern(APSPattern: TPSPattern); function FindPattern(AName: String): TPSPattern; function DelPattern(AName: String): Boolean; function NewPattern(AName: String): TPSPattern; property Canvas: TPostScriptCanvas read FCanvas; property Height: Integer read FHeight write SetHeight; property Width: Integer read FWidth write SetWidth; property Document: TStringList read FDocument; property PageNumber: Integer read FPageNumber; property Title: String read FTitle write SetTitle; property LineSpacing: Integer read FLineSpacing write SetLineSpacing; procedure BeginDoc; procedure NewPage; procedure EndDoc; end; implementation { TPostScriptCanvas ----------------------------------------------------------} { Y coords in postscript are backwards... } function TPostScriptCanvas.TranslateY(Ycoord: Integer): Integer; begin Result := FHeight - Ycoord; end; { Adds a fill finishing line to any path we desire to fill } procedure TPostScriptCanvas.AddFill; begin FPostScript.Add('gsave '+FBrush.AsString+' fill grestore'); end; { Sets the current font face} procedure TPostScriptCanvas.SetFontFace(const AValue: String); var MyString: String; begin if FFontFace=AValue then exit; if pos(' ',AValue) > 0 then FFontFace := '('+AValue+')' else FFontFace:=AValue; MyString := '/'+FFontFace+' '+IntToStr(FFontSize)+' selectfont'; // set the pen info FPostScript.Add(MyString); end; function TPostScriptCanvas.GetColor: TColor; begin Result := FColor; end; procedure TPostScriptCanvas.SetBrush(const AValue: TPSPen); begin if FBrush=AValue then exit; FBrush:=AValue; end; procedure TPostScriptCanvas.SetColor(const AValue: TColor); begin FColor := AValue; end; procedure TPostScriptCanvas.SetFontSize(const AValue: Integer); begin if FFontSize=AValue then exit; FFontSize:=AValue; FPostScript.Add('/'+FFontFace+' '+IntToStr(AValue)+' selectfont'); end; procedure TPostScriptCanvas.SetPen(const AValue: TPSPen); begin // change to ASSIGN method? if FPen=AValue then exit; FPen:=AValue; end; { Return to last moveto location } procedure TPostScriptCanvas.ResetPos; begin // any routines that you specify a start location when calling such as // textout, ellipse, etc. should not affect the default cursor location. FPostScript.Add(IntToStr(LastX)+' '+IntToStr(TranslateY(LastY))+' moveto'); end; { This is called when drawing pen is Changed but NOT when brush changes } procedure TPostScriptCanvas.PenChanged(Sender: TObject); begin if FPostScript[FPostScript.Count-2] = '%%PEN' then begin // last operation was a pen, so delete it FPostScript.Delete(FPostScript.Count-1); FPostScript.Delete(FPostScript.Count-1); end; FPostScript.Add('%%PEN'); FPostScript.Add(FPen.AsString); end; constructor TPostScriptCanvas.Create(APostScript: TPostScript); begin MPostScript := APostScript; FPostScript := TStringList.Create; FHeight := 792; // length of page in points at 72 ppi // Choose a standard font in case the user doesn't FFontFace := 'AvantGarde-Book'; SetFontSize(10); if Assigned(MPostScript) then begin FLineSpacing := MPostScript.LineSpacing; end; FPen := TPSPen.Create; FPen.Width := 1; FPen.Color := 0; FPen.OnChange := @PenChanged; FBrush := TPSPen.Create; FBrush.Width := 1; FBrush.Color := -1; // don't notify us that the brush Changed... end; destructor TPostScriptCanvas.Destroy; begin FPostScript.Free; FPen.Free; FBrush.Free; inherited Destroy; end; { Clear the postscript canvas AND the graphic canvas (Add later) } procedure TPostScriptCanvas.clear; begin // clear the canvas for the next page FPostScript.Clear; // Choose a standard font in case the user doesn't FPostScript.Add('/AvantGarde-Book findfont'); FPostScript.Add('10 scalefont'); FPostScript.Add('setfont'); // also clear the canvas itself if we plan to embed the bitmap into // the postscript // also grab the latest canvas height just in case it's Changed FHeight := 792; if Assigned(MPostScript) then FHeight := MPostScript.Height; end; { Move draw location } procedure TPostScriptCanvas.MoveTo(X1, Y1: Integer); var Y: Integer; begin Y := TranslateY(Y1); FPostScript.Add(IntToStr(X1)+' '+IntToStr(Y)+' moveto'); LastX := X1; LastY := Y1; end; { Draw a line from current location to these coords } procedure TPostScriptCanvas.LineTo(X1, Y1: Integer); var Y: Integer; begin Y := TranslateY(Y1); FPostScript.Add(IntToStr(X1)+' '+IntToStr(Y)+' lineto'); LastX := X1; LastY := Y1; end; procedure TPostScriptCanvas.Line(X1, Y1, X2, Y2: Integer); var Y12, Y22: Integer; begin Y12 := TranslateY(Y1); Y22 := TranslateY(Y2); FPostScript.Add('newpath '+IntToStr(X1)+' '+IntToStr(Y12)+' moveto '+ IntToStr(X2)+' '+IntToStr(Y22)+' lineto closepath stroke'); // go back to last moveto position ResetPos; end; procedure TPostScriptCanvas.Rectangle(X1, Y1, X2, Y2: Integer); var Y12, Y22: Integer; begin Y12 := TranslateY(Y1); Y22 := TranslateY(Y2); FPostScript.Add('stroke newpath'); FPostScript.Add(IntToStr(X1)+' '+IntToStr(Y12)+' moveto'); FPostScript.Add(IntToStr(X2)+' '+IntToStr(Y12)+' lineto'); FPostScript.Add(IntToStr(X2)+' '+IntToStr(Y22)+' lineto'); FPostScript.Add(IntToStr(X1)+' '+IntToStr(Y22)+' lineto'); FPostScript.Add('closepath'); if FBrush.Color > -1 then AddFill; FPostScript.Add('stroke'); ResetPos; end; { Draw a rectangle } procedure TPostScriptCanvas.Rectangle(const Rect: TRect); var Y12, Y22: Integer; begin Y12 := TranslateY(Rect.Top); Y22 := TranslateY(Rect.Bottom); FPostScript.Add('stroke newpath'); FPostScript.Add(IntToStr(Rect.Left)+' '+IntToStr(Y12)+' moveto'); FPostScript.Add(IntToStr(Rect.Right)+' '+IntToStr(Y12)+' lineto'); FPostScript.Add(IntToStr(Rect.Right)+' '+IntToStr(Y22)+' lineto'); FPostScript.Add(IntToStr(Rect.Left)+' '+IntToStr(Y22)+' lineto'); FPostScript.Add('closepath'); if FBrush.Color > -1 then AddFill; FPostScript.Add('stroke'); ResetPos; end; { Draw a series of lines } procedure TPostScriptCanvas.Polyline(Points: PPoint; NumPts: Integer); var i : Longint; begin If (NumPts <= 1) or (Points = nil) then exit; MoveTo(Points[0].X, Points[0].Y); For i := 1 to NumPts - 1 do LineTo(Points[i].X, Points[i].Y); ResetPos; end; { This was a pain to figure out... } procedure TPostScriptCanvas.Ellipse(x1, y1, x2, y2: Integer); var radius: Integer; YRatio: Real; centerX, centerY: Integer; begin // set radius to half the width radius := (x2 - x1) div 2; //calculate ratios if radius <1 then exit; // do nothing YRatio := real(Y2 - Y1) / (X2-X1); // find center CenterX := ((X2 - X1) div 2) + X1; CenterY := ((Y2 - Y1) div 2) + Y1; FPostScript.Add('newpath '+IntToStr(CenterX)+' '+IntToStr(TranslateY(CenterY))+' translate'); // move to edge FPostScript.Add(IntToStr(radius)+' 0 moveto'); // now draw it FPostScript.Add('gsave 1 '+format('%.3f',[YRatio])+' scale'); FPostScript.Add('0 0 '+IntToStr(radius)+' 0 360 arc'); if FBrush.Color > -1 then AddFill; // reset scale for drawing line thickness so it doesn't warp YRatio := 1 / YRatio; FPostScript.Add('1 '+format('%.2f',[YRatio])+' scale stroke grestore'); // move origin back FPostScript.Add(IntToStr(-CenterX)+' '+IntToStr(-TranslateY(CenterY))+' translate closepath stroke'); ResetPos; end; procedure TPostScriptCanvas.Ellipse(const Rect: TRect); begin self.Ellipse(Rect.Left, Rect.Top, Rect.Right, Rect.Bottom); end; procedure TPostScriptCanvas.RadialPie(x, y, width, mheight, angle1, angle2: Integer); begin // set zero at center FPostScript.Add('newpath '+IntToStr(X)+' '+IntToStr(TranslateY(Y))+' translate'); // scale it FPostScript.Add('gsave '+IntToStr(width)+' '+IntToStr(mheight)+' scale'); //FPostScript.Add('gsave 1 1 scale'); // draw line to edge FPostScript.Add('0 0 moveto'); FPostScript.Add('0 0 1 '+IntToStr(angle1)+' '+IntToStr(angle2)+' arc closepath'); if FBrush.Color > -1 then AddFill; // reset scale so we don't change the line thickness // adding 0.01 to compensate for scaling error - there may be a deeper problem here... FPostScript.Add(format('%.6f',[(real(1) / X)+0.01])+' '+format('%.6f',[(real(1) / Y)+0.01])+' scale stroke grestore'); // close out and return origin FPostScript.Add(IntToStr(-X)+' '+IntToStr(-TranslateY(Y))+' translate closepath stroke'); ResetPos; end; { Writes text with a carriage return } procedure TPostScriptCanvas.Writeln(const AString: String); begin TextOut(LastX, LastY, AString); LastY := LastY+FFontSize+FLineSpacing; MoveTo(LastX, LastY); end; { Output text, restoring draw location } procedure TPostScriptCanvas.TextOut(X, Y: Integer; const Text: String); var Y1: Integer; begin Y1 := TranslateY(Y); FPostScript.Add(IntToStr(X)+' '+IntToStr(Y1)+' moveto'); FPostScript.Add('('+Text+') show'); ResetPos; // move back to last moveto location end; { TPostScript -------------------------------------------------------------- } procedure TPostScript.SetHeight(const AValue: Integer); begin if FHeight=AValue then exit; FHeight:=AValue; UpdateBoundingBox; // filter down to the canvas height property if assigned(FCanvas) then FCanvas.Height := FHeight; end; procedure TPostScript.SetLineSpacing(const AValue: Integer); begin if FLineSpacing=AValue then exit; FLineSpacing:=AValue; // filter down to the canvas if assigned(FCanvas) then FCanvas.LineSpacing := AValue; end; procedure TPostScript.SetTitle(const AValue: String); begin if FTitle=AValue then exit; FTitle:=AValue; // need to not hard-link these... FDocument[3] := '%%Title: '+AValue; end; procedure TPostScript.SetWidth(const AValue: Integer); begin if FWidth=AValue then exit; FWidth:=AValue; UpdateBoundingBox; end; { Places the current canvas object into the document } procedure TPostScript.GrabCanvas; var I: Integer; begin // internally calls this at the end of a page... I := 0; while I < FCanvas.PostScript.Count do begin Document.Add(FCanvas.PostScript[I]); I := I+1; end; end; { Take our sizes and change the boundingbox line } procedure TPostScript.UpdateBoundingBox; begin // need to not hard-link this to line 1 FDocument[1] := '%%BoundingBox: 0 0 '+IntToStr(FWidth)+' '+IntToStr(FHeight); end; { Pattern Changed so update the postscript code } procedure TPostScript.PatternChanged(Sender: TObject); begin // called anytime a pattern changes. Update the postscript code. // look for and delete the current postscript code for this pattern // then paste the pattern back into the code before the first page RemovePattern(Sender As TPSPattern); InsertPattern(Sender As TPSPattern); end; { Places a pattern definition into the bottom of the header in postscript } procedure TPostScript.InsertPattern(APattern: TPSPattern); var I, J: Integer; MyStrings: TStringList; begin I := 0; if FDocument.Count < 1 then begin // added pattern when no postscript exists - this shouldn't happen raise exception.create('Pattern inserted with no postscript existing'); exit; end; for I := 0 to FDocument.count - 1 do begin if (FDocument[I] = '%%Page: 1 1') then begin // found it! // insert into just before that MyStrings := APattern.GetPS; for J := 0 to MyStrings.Count - 1 do begin FDocument.Insert(I-1+J, MyStrings[j]); end; exit; end; end; end; {Remove a pattern from the postscript code } procedure TPostScript.RemovePattern(APattern: TPSPattern); var I: Integer; MyName: String; begin // this does NOT destroy the object, just removes from postscript if APattern.OldName <> '' then MyName := APattern.OldName else MyName := APattern.name; I := 0; if FDocument.Count < 1 then begin // added pattern when no postscript exists - this shouldn't happen raise exception.create('Pattern removed with no postscript existing'); exit; end; for I := 0 to FDocument.Count - 1 do begin if (FDocument[I] = '%% PATTERN '+MyName) then begin // found it... // delete until gone while I < FDocument.Count - 1 do begin // stay within our limites if (FDocument[I] = '%% END PATTERN '+MyName) then begin FDocument.Delete(I); APattern.oldName := ''; exit; end else FDocument.Delete(I); end; end; end; end; constructor TPostScript.Create; begin inherited create; FDocument := TStringList.Create; // Set some defaults FHeight := 792; // 11 inches at 72 dpi FWidth := 612; // 8 1/2 inches at 72 dpi FCanvas := TPostScriptCanvas.Create(Self); FDocument.Clear; FDocument.Add('%!PS-Adobe-3.0'); FDocument.Add('%%BoundingBox: 0 0 612 792'); FDocument.Add('%%Creator: '+Application.ExeName); FDocument.Add('%%Title: '+FTitle); FDocument.Add('%%Pages: (atend)'); FDocument.Add('%%PageOrder: Ascend'); // Choose a standard font in case the user doesn't FDocument.Add('/AvantGarde-Book findfont'); FDocument.Add('10 scalefont'); FDocument.Add('setfont'); // start our first page FPageNumber := 1; FDocument.Add('%%Page: 1 1'); // I'm still not sure why u put the page # twice FDocument.Add('newpath'); end; destructor TPostScript.Destroy; var I: Integer; begin FCanvas.Free; FDocument.Free; // destroy the patterns if NumPatterns > 0 then begin for I := 0 to NuMPatterns-1 do begin Patterns[i].Free; end; end; // free the pattern pointer memory Reallocmem(Patterns, 0); inherited Destroy; end; { Add a pattern to the array } procedure TPostScript.AddPattern(APSPattern: TPSPattern); begin // does NOT create the pattern, just insert in the array of patterns NumPatterns := NumPatterns+1; reallocmem(Patterns, sizeof(TPSPattern) * NumPatterns); Patterns[NumPatterns-1] := APSPattern; end; { Find a pattern object by it's name } function TPostScript.FindPattern(AName: String): TPSPattern; var I: Integer; begin Result := nil; if NumPatterns < 1 then exit; for I := 0 to NumPatterns-1 do begin if Patterns[I].Name = AName then begin result := Patterns[i]; exit; end; end; end; function TPostScript.DelPattern(AName: String): Boolean; begin {$IFNDEF DisableChecks} if AName<>'' then DebugLn('[TPostScript.DelPattern] ToDo '); {$ENDIF} // can't do that yet... Result:=false; end; { Create a new pattern and inserts it into the array for safe keeping } function TPostScript.NewPattern(AName: String): TPSPattern; var MyPattern: TPSPattern; begin MyPattern := TPSPattern.Create; AddPattern(MyPattern); MyPattern.Name := AName; MyPattern.OnChange := @PatternChanged; MyPattern.OldName := ''; // Add this to the postscript now... InsertPattern(MyPattern); result := MyPattern; end; { Start a new document } procedure TPostScript.BeginDoc; var I: Integer; begin FCanvas.Clear; FDocument.Clear; // destroy the patterns if NumPatterns > 0 then begin for I := 0 to NuMPatterns-1 do begin Patterns[i].Free; Patterns[i]:=nil; end; NumPatterns:=0; end; // free the pattern pointer memory Reallocmem(Patterns, 0); FDocument.Add('%!PS-Adobe-3.0'); FDocument.Add('%%BoundingBox: 0 0 612 792'); FDocument.Add('%%Creator: '+Application.ExeName); FDocument.Add('%%Title: '+FTitle); FDocument.Add('%%Pages: (atend)'); FDocument.Add('%%PageOrder: Ascend'); // Choose a standard font in case the user doesn't FDocument.Add('/AvantGarde-Book findfont'); FDocument.Add('10 scalefont'); FDocument.Add('setfont'); // start our first page FPageNumber := 1; FDocument.Add('%%Page: 1 1'); // I'm still not sure why u put the page # twice FDocument.Add('newpath'); UpdateBoundingBox; end; { Copy current page into the postscript and start a new one } procedure TPostScript.NewPage; begin // dump the current page into our postscript first GrabCanvas; // put end page definition... FDocument.Add('stroke'); FDocument.Add('showpage'); FPageNumber := FPageNumber+1; // start new page definition... FDocument.Add('%%Page: '+IntToStr(FPageNumber)+' '+IntToStr(FPageNumber)); FDocument.Add('newpath'); FCanvas.Clear; end; { Finish off the document } procedure TPostScript.EndDoc; begin // dump the canvas into the postscript code GrabCanvas; // Start printing the document after closing out the pages FDocument.Add('stroke'); FDocument.Add('showpage'); FDocument.Add('%%Pages: '+IntToStr(FPageNumber)); // okay, the postscript is all ready, so dump it to the text file // or to the printer FPageNumber := 0; end; { TPSObject } procedure TPSObject.Changed; begin if Assigned(FOnChange) then FOnChange(Self); end; procedure TPSObject.Lock; begin end; procedure TPSObject.UnLock; begin end; { TPSPen } procedure TPSPen.SetPattern(const AValue: TPSPattern); begin if FPattern=AValue then exit; FPattern:=AValue; Changed; end; procedure TPSPen.SetColor(Value: TColor); begin FColor := Value; Changed; end; procedure TPSPen.Setwidth(value: Real); begin FWidth := Value; Changed; end; constructor TPSPen.Create; begin FPattern := nil; end; destructor TPSPen.Destroy; begin // Do NOT free the pattern object from here... inherited Destroy; end; procedure TPSPen.Assign(Source: TPSPen); begin if source = nil then exit; FWidth := Source.Width; FColor := Source.Color; FPattern := Source.Pattern; end; { Return the pen definition as a postscript string } function TPSPen.AsString: String; var MyOut: String; begin MyOut := ''; // set all the features of this pen... if FPattern <> nil then begin // we have a pattern // uh... let's make it work for both colored and uncolored patterns // first for colored: if FPattern.PaintType = ptColored then MyOut := '/Pattern setcolorspace '+FPattern.Name+' setcolor ' else begin // now for uncolored, use color from pen MyOut := '[/Pattern /DeviceRGB] setcolorspace '+IntToStr(GetRValue(FColor))+' '+IntToStr(GetGValue(FColor))+' '+ IntToStr(GetBValue(FColor))+' '+FPattern.Name+' setcolor '; end; end else // no pattern do this: MyOut := IntToStr(GetRValue(FColor))+' '+IntToStr(GetGValue(FColor))+' '+ IntToStr(GetBValue(FColor))+' setrgbcolor '; MyOut := MyOut + format('%f',[FWidth])+' setlinewidth '; Result := MyOut; end; { TPSPattern } { Returns the pattern definition as postscript } function TPSPattern.GetpostScript: TStringList; var I: Integer; begin // If nothing in the canvas, error if FCanvas.Postscript.Count < 1 then begin raise exception.create('Empty pattern'); exit; end; FPostScript.Clear; With FPostScript do begin Add('%% PATTERN '+FName); Add('/'+FName+'proto 12 dict def '+FName+'proto begin'); Add('/PatternType 1 def'); case FPaintType of ptColored: Add('/PaintType 1 def'); ptUncolored: Add('/PaintType 2 def'); end; case FTilingType of ttConstant: Add('/TilingType 1 def'); ttNoDistortion: Add('/TilingType 2 def'); ttFast: Add('/TilingType 3 def'); end; Add('/BBox ['+IntToStr(FBBox.Left)+' '+IntToStr(FBBox.Top)+' '+IntToStr(FBBox.Right)+' '+IntToStr(FBBox.Bottom)+'] def'); Add('/XStep '+format('%f',[FXStep])+' def'); Add('/YStep '+format('%f',[FYstep])+' def'); Add('/PaintProc { begin'); // insert the canvas for I := 0 to FCanvas.PostScript.Count - 1 do begin Add(FCanvas.PostScript[I]); end; // Add support for custom matrix later Add('end } def end '+FName+'proto [1 0 0 1 0 0] makepattern /'+FName+' exch def'); Add('%% END PATTERN '+FName); end; Result := FPostScript; end; procedure TPSPattern.SetBBox(const AValue: TRect); begin if FBBox=AValue then exit; FBBox:=AValue; //FCanvas.Width := FBBox.Right - FBBox.Left; FCanvas.Height := FBBox.Bottom - FBBox.Top; Changed; end; procedure TPSPattern.SetName(const AValue: String); begin FOldName := FName; if FName=AValue then exit; FName:=AValue; Changed; end; procedure TPSPattern.Changed; begin if Assigned(FOnChange) then FOnChange(Self); end; procedure TPSPattern.SetPaintType(const AValue: TPSPaintType); begin if FPaintType=AValue then exit; FPaintType:=AValue; Changed; end; procedure TPSPattern.SetTilingType(const AValue: TPSTileType); begin if FTilingType=AValue then exit; FTilingType:=AValue; Changed; end; procedure TPSPattern.SetXStep(const AValue: Real); begin if FXStep=AValue then exit; FXStep:=AValue; Changed; end; procedure TPSPattern.SetYStep(const AValue: Real); begin if FYStep=AValue then exit; FYStep:=AValue; Changed; end; constructor TPSPattern.Create; begin FPostScript := TStringList.Create; FPaintType := ptColored; FTilingType := ttConstant; FCanvas := TPostScriptCanvas.Create(nil); FName := 'Pattern1'; end; destructor TPSPattern.Destroy; begin FPostScript.Free; FCanvas.Free; inherited Destroy; end; end.