2
$Id: pscanvas.pp,v 1.2 2003/11/03 12:44:52 daniel Exp $
3
This file is part of the Free Pascal run time library.
4
Copyright (c) 2003 by the Free Pascal development team
6
TPostScriptCanvas implementation.
8
See the file COPYING.FPC, included in this distribution,
9
for details about the copyright.
11
This program is distributed in the hope that it will be useful,
12
but WITHOUT ANY WARRANTY; without even the implied warranty of
13
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15
**********************************************************************}
16
{ ---------------------------------------------------------------------
17
This code is heavily based on Tony Maro's initial TPostScriptCanvas
18
implementation in the LCL, but was adapted to work with the custom
19
canvas code and to work with streams instead of strings.
20
---------------------------------------------------------------------}
31
Classes, SysUtils,fpimage,fpcanvas;
36
TPSPaintType = (ptColored, ptUncolored);
37
TPSTileType = (ttConstant, ttNoDistortion, ttFast);
38
TPostScriptCanvas = class; // forward reference
40
{Remember, modifying a pattern affects that pattern for the ENTIRE document!}
41
TPSPattern = class(TFPCanvasHelper)
43
FStream : TMemoryStream;
44
FPatternCanvas : TPostScriptCanvas;
46
FOnChange: TNotifyEvent;
49
FPaintType: TPSPaintType;
50
FPostScript: TStringList;
51
FTilingType: TPSTileType;
54
function GetpostScript: TStringList;
55
procedure SetBBox(const AValue: TRect);
56
procedure SetName(const AValue: String);
57
procedure SetPaintType(const AValue: TPSPaintType);
58
procedure SetTilingType(const AValue: TPSTileType);
59
procedure SetXStep(const AValue: Real);
60
procedure SetYStep(const AValue: Real);
64
destructor Destroy; override;
66
property BBox: TRect read FBBox write SetBBox;
67
property PaintType: TPSPaintType read FPaintType write SetPaintType;
68
property TilingType: TPSTileType read FTilingType write SetTilingType;
69
property XStep: Real read FXStep write SetXStep;
70
property YStep: Real read FYStep write SetYStep;
71
property Name: String read FName write SetName;
72
property GetPS: TStringList read GetPostscript;
73
property OldName: string read FOldName write FOldName; // used when notifying that name changed
74
property OnChange: TNotifyEvent read FOnChange write FOnChange;
75
Property PatternCanvas : TPostScriptCanvas Read FPatternCanvas;
77
PPSPattern = ^TPSPattern; // used for array
79
{ Pen and brush object both right now...}
80
TPSPen = class(TFPCustomPen)
83
procedure SetPattern(const AValue: TPSPattern);
85
destructor Destroy; override;
86
property Pattern: TPSPattern read FPattern write SetPattern;
87
function AsString: String;
90
TPSBrush = Class(TFPCustomBrush)
92
Function GetAsString : String;
94
Property AsString : String Read GetAsString;
97
TPSFont = Class(TFPCustomFont)
100
{ Custom canvas-like object that handles postscript code }
101
TPostScriptCanvas = class(TFPCustomCanvas)
103
FHeight,FWidth : Integer;
105
FLineSpacing: Integer;
108
function TranslateY(Ycoord: Integer): Integer; // Y axis is backwards in postscript
110
procedure ResetPos; // reset back to last moveto location
111
procedure SetWidth (AValue : integer); override;
112
function GetWidth : integer; override;
113
procedure SetHeight (AValue : integer); override;
114
function GetHeight : integer; override;
116
Procedure WritePS(Const Cmd : String);
117
Procedure WritePS(Const Fmt : String; Args : Array of Const);
118
procedure DrawRectangle(const Bounds: TRect; DoFill : Boolean);
119
procedure DrawEllipse(const Bounds: TRect; DoFill : Boolean);
121
constructor Create(AStream : TStream);
122
destructor Destroy; override;
123
function DoCreateDefaultFont : TFPCustomFont; override;
124
function DoCreateDefaultPen : TFPCustomPen; override;
125
function DoCreateDefaultBrush : TFPCustomBrush; override;
126
property LineSpacing: Integer read FLineSpacing write FLineSpacing;
127
Procedure DoMoveTo(X1,Y1 : Integer); override;
128
Procedure DoLineTo(X1,Y1 : Integer); override;
129
Procedure DoLine(X1,Y1,X2,Y2 : Integer); override;
130
Procedure DoRectangle(Const Bounds : TRect); override;
131
Procedure DoRectangleFill(Const Bounds : TRect); override;
132
procedure DoPolyline(Const Points: Array of TPoint); override;
133
procedure DoEllipse(const Bounds: TRect); override;
134
procedure DoEllipseFill(const Bounds: TRect); override;
135
procedure DoPie(x,y,awidth,aheight,angle1,angle2 : Integer);
136
//procedure Pie(x,y,width,height,SX,SY,EX,EY : Integer);
137
procedure Writeln(AString: String);
138
procedure TextOut(X,Y: Integer; const Text: String);
139
//procedure Chord(x,y,width,height,angle1,angle2 : Integer);
140
//procedure Chord(x,y,width,height,SX,SY,EX,EY : Integer);
141
//procedure PolyBezier(Points: PPoint; NumPts: Integer;
142
// Filled: boolean{$IFDEF VER1_1} = False{$ENDIF};
143
// Continuous: boolean{$IFDEF VER1_1} = False{$ENDIF});
144
//procedure PolyBezier(const Points: array of TPoint;
145
// Filled: boolean{$IFDEF VER1_1} = False{$ENDIF};
146
// Continuous: boolean{$IFDEF VER1_1} = False{$ENDIF});
147
//procedure PolyBezier(const Points: array of TPoint);
148
//procedure Polygon(const Points: array of TPoint;
149
// Winding: Boolean{$IFDEF VER1_1} = False{$ENDIF};
150
// StartIndex: Integer{$IFDEF VER1_1} = 0{$ENDIF};
151
// NumPts: Integer {$IFDEF VER1_1} = -1{$ENDIF});
152
//procedure Polygon(Points: PPoint; NumPts: Integer;
153
// Winding: boolean{$IFDEF VER1_1} = False{$ENDIF});
154
//Procedure Polygon(const Points: array of TPoint);
155
//Procedure FillRect(const Rect : TRect);
156
//procedure FloodFill(X, Y: Integer; FillColor: TFPColor; FillStyle: TFillStyle);
157
//Procedure RoundRect(X1, Y1, X2, Y2: Integer; RX,RY : Integer);
158
//Procedure RoundRect(const Rect : TRect; RX,RY : Integer);
159
Property Stream : TStream read FStream;
162
{ Encapsulates ALL the postscript and uses the TPostScriptCanvas object for a single page }
163
TPostScript = class(TComponent)
165
FDocStarted : Boolean;
168
FCanvas: TPostScriptCanvas;
170
FLineSpacing: Integer;
171
FPageNumber: Integer;
174
FPatterns: TList; // array of pointers to pattern objects
175
procedure SetHeight(const AValue: Integer);
176
procedure SetLineSpacing(const AValue: Integer);
177
procedure SetWidth(const AValue: Integer);
178
procedure UpdateBoundingBox;
179
procedure PatternChanged(Sender: TObject);
180
procedure InsertPattern(APattern: TPSPattern); // adds the pattern to the postscript
181
Procedure SetStream (Value : TStream);
182
Function GetCreator : String;
184
Procedure WritePS(Const Cmd : String);
185
Procedure WritePS(Const Fmt : String; Args : Array of Const);
186
Procedure WriteDocumentHeader; virtual;
187
Procedure WriteStandardFont; virtual;
188
Procedure WritePage; virtual;
189
Procedure FreePatterns;
190
Procedure CheckStream;
192
Constructor Create(AOwner : TComponent);
193
destructor Destroy; override;
195
procedure AddPattern(APSPattern: TPSPattern);
196
function FindPattern(AName: String): TPSPattern;
197
function DelPattern(AName: String): Boolean;
198
function NewPattern(AName: String): TPSPattern;
199
property Canvas: TPostScriptCanvas read FCanvas;
200
property Height: Integer read FHeight write SetHeight;
201
property Width: Integer read FWidth write SetWidth;
202
property PageNumber: Integer read FPageNumber;
203
property Title: String read FTitle write FTitle;
204
property LineSpacing: Integer read FLineSpacing write SetLineSpacing;
208
Property Stream : TStream Read FStream Write SetStream;
209
Property Creator : String Read GetCreator Write FCreator;
215
SErrNoStreamAssigned = 'Invalid operation: No stream assigned';
216
SErrDocumentAlreadyStarted = 'Cannot start document twice.';
219
const lineending=#10;
221
procedure freeandnil(var o:Tobject);
230
{ TPostScriptCanvas ----------------------------------------------------------}
232
Procedure TPostScriptCanvas.WritePS(const Cmd : String);
235
If length(Cmd)>0 then
236
FStream.Write(Cmd[1],Length(Cmd));
237
FStream.Write(LineEnding,SizeOf(LineEnding));
240
Procedure TPostScriptCanvas.WritePS(Const Fmt : String; Args : Array of Const);
243
WritePS(Format(Fmt,Args));
246
{ Y coords in postscript are backwards... }
247
function TPostScriptCanvas.TranslateY(Ycoord: Integer): Integer;
249
Result:=Height-Ycoord;
252
{ Adds a fill finishing line to any path we desire to fill }
253
procedure TPostScriptCanvas.AddFill;
255
WritePs('gsave '+(Brush as TPSBrush).AsString+' fill grestore');
258
{ Return to last moveto location }
259
procedure TPostScriptCanvas.ResetPos;
261
WritePS(inttostr(LastX)+' '+inttostr(TranslateY(LastY))+' moveto');
264
constructor TPostScriptCanvas.Create(AStream : TStream);
269
Height := 792; // length of page in points at 72 ppi
270
{ // Choose a standard font in case the user doesn't
271
FFontFace := 'AvantGarde-Book';
273
FLineSpacing := MPostScript.LineSpacing;
275
FPen := TPSPen.Create;
278
FPen.OnChange := @PenChanged;
280
FBrush := TPSPen.Create;
283
// don't notify us that the brush changed...
287
destructor TPostScriptCanvas.Destroy;
297
procedure TPostScriptCanvas.SetWidth (AValue : integer);
303
function TPostScriptCanvas.GetWidth : integer;
309
procedure TPostScriptCanvas.SetHeight (AValue : integer);
315
function TPostScriptCanvas.GetHeight : integer;
322
{ Move draw location }
323
procedure TPostScriptCanvas.DoMoveTo(X1, Y1: Integer);
330
WritePS(inttostr(X1)+' '+inttostr(Y)+' moveto');
335
{ Draw a line from current location to these coords }
336
procedure TPostScriptCanvas.DoLineTo(X1, Y1: Integer);
343
WritePS(inttostr(X1)+' '+inttostr(Y)+' lineto');
348
procedure TPostScriptCanvas.DoLine(X1, Y1, X2, Y2: Integer);
353
Y12 := TranslateY(Y1);
354
Y22 := TranslateY(Y2);
355
WritePS('newpath '+inttostr(X1)+' '+inttostr(Y12)+' moveto '+
356
inttostr(X2)+' '+inttostr(Y22)+' lineto closepath stroke');
357
// go back to last moveto position
363
procedure TPostScriptCanvas.DoRectangleFill(const Bounds: TRect);
366
DrawRectangle(Bounds,true)
369
procedure TPostScriptCanvas.DoRectangle(const Bounds: TRect);
372
DrawRectangle(Bounds,False);
375
procedure TPostScriptCanvas.DrawRectangle(const Bounds: TRect; DoFill : Boolean);
381
Y12 := TranslateY(Bounds.Top);
382
Y22 := TranslateY(Bounds.Bottom);
383
WritePS('stroke newpath');
386
WritePS(inttostr(Left)+' '+inttostr(Y12)+' moveto');
387
WritePS(inttostr(Right)+' '+inttostr(Y12)+' lineto');
388
WritePS(inttostr(Right)+' '+inttostr(Y22)+' lineto');
389
WritePS(inttostr(Left)+' '+inttostr(Y22)+' lineto');
391
WritePS('closepath');
392
If DoFill and (Brush.Style<>bsClear) then
398
{ Draw a series of lines }
399
procedure TPostScriptCanvas.DoPolyline(Const Points: Array of TPoint);
403
MoveTo(Points[0].X, Points[0].Y);
404
For i := 1 to High(Points) do
405
LineTo(Points[i].X, Points[i].Y);
409
{ This was a pain to figure out... }
411
procedure TPostScriptCanvas.DoEllipse(Const Bounds : TRect);
414
DrawEllipse(Bounds,False);
417
procedure TPostScriptCanvas.DoEllipseFill(Const Bounds : TRect);
420
DrawEllipse(Bounds,true);
423
procedure TPostScriptCanvas.DrawEllipse(Const Bounds : TRect; DoFill : Boolean);
428
centerX, centerY: Integer;
431
// set radius to half the width
434
radius := (Right-Left) div 2;
437
YRatio := (Bottom - Top) / (Right-Left);
439
CenterX := (Right+Left) div 2;
440
CenterY := (Top+Bottom) div 2;
442
WritePS('newpath '+inttostr(CenterX)+' '+inttostr(TranslateY(CenterY))+' translate');
444
WritePS(inttostr(radius)+' 0 moveto');
446
WritePS('gsave 1 '+format('%.3f',[YRatio])+' scale');
447
WritePS('0 0 '+inttostr(radius)+' 0 360 arc');
448
if DoFill and (Brush.Style<>bsClear) then
450
// reset scale for drawing line thickness so it doesn't warp
451
YRatio := 1 / YRatio;
452
WritePS('1 '+format('%.2f',[YRatio])+' scale stroke grestore');
454
WritePS(inttostr(-CenterX)+' '+inttostr(-TranslateY(CenterY))+' translate closepath stroke');
458
procedure TPostScriptCanvas.DoPie(x, y, AWidth, AHeight, angle1, angle2: Integer);
460
// set zero at center
461
WritePS('newpath '+inttostr(X)+' '+inttostr(TranslateY(Y))+' translate');
463
WritePS('gsave '+inttostr(AWidth)+' '+inttostr(Aheight)+' scale');
464
//WritePS('gsave 1 1 scale');
466
WritePS('0 0 moveto');
467
WritePS('0 0 1 '+inttostr(angle1)+' '+inttostr(angle2)+' arc closepath');
468
if Brush.Style<>bsClear then
470
// reset scale so we don't change the line thickness
471
// adding 0.01 to compensate for scaling error - there may be a deeper problem here...
472
WritePS(format('%.6f',[(real(1) / X)+0.01])+' '+format('%.6f',[(real(1) / Y)+0.01])+' scale stroke grestore');
473
// close out and return origin
474
WritePS(inttostr(-X)+' '+inttostr(-TranslateY(Y))+' translate closepath stroke');
478
{ Writes text with a carriage return }
479
procedure TPostScriptCanvas.Writeln(AString: String);
481
TextOut(LastX, LastY, AString);
482
LastY := LastY+Font.Size+FLineSpacing;
483
MoveTo(LastX, LastY);
487
{ Output text, restoring draw location }
488
procedure TPostScriptCanvas.TextOut(X, Y: Integer; const Text: String);
493
WritePS(inttostr(X)+' '+inttostr(Y1)+' moveto');
494
WritePS('('+Text+') show');
495
ResetPos; // move back to last moveto location
498
function TPostScriptCanvas.DoCreateDefaultFont : TFPCustomFont;
501
Result:=TPSFont.Create;
505
function TPostScriptCanvas.DoCreateDefaultPen : TFPCustomPen;
508
Result:=TPSPen.Create;
511
function TPostScriptCanvas.DoCreateDefaultBrush : TFPCustomBrush;
514
Result:=TPSBrush.Create;
519
{ TPostScript -------------------------------------------------------------- }
521
procedure TPostScript.SetHeight(const AValue: Integer);
523
if FHeight=AValue then exit;
526
// filter down to the canvas height property
527
if assigned(FCanvas) then
528
FCanvas.Height := FHeight;
531
procedure TPostScript.SetLineSpacing(const AValue: Integer);
533
if FLineSpacing=AValue then exit;
534
FLineSpacing:=AValue;
535
// filter down to the canvas
536
if assigned(FCanvas) then FCanvas.LineSpacing := AValue;
539
procedure TPostScript.SetWidth(const AValue: Integer);
541
if FWidth=AValue then exit;
546
{ Take our sizes and change the boundingbox line }
547
procedure TPostScript.UpdateBoundingBox;
551
// need to not hard-link this to line 1
552
FDocument[1] := '%%BoundingBox: 0 0 '+inttostr(FWidth)+' '+inttostr(FHeight);
556
{ Pattern changed so update the postscript code }
557
procedure TPostScript.PatternChanged(Sender: TObject);
559
// called anytime a pattern changes. Update the postscript code.
560
// look for and delete the current postscript code for this pattern
561
// then paste the pattern back into the code before the first page
562
InsertPattern(Sender As TPSPattern);
565
{ Places a pattern definition into the bottom of the header in postscript }
566
procedure TPostScript.InsertPattern(APattern: TPSPattern);
569
MyStrings: TStringList;
572
if FDocument.Count < 1 then begin
573
// added pattern when no postscript exists - this shouldn't happen
574
raise exception.create('Pattern inserted with no postscript existing');
578
for I := 0 to FDocument.count - 1 do begin
579
if (FDocument[I] = '%%Page: 1 1') then begin
581
// insert into just before that
582
MyStrings := APattern.GetPS;
583
for J := 0 to MyStrings.Count - 1 do begin
584
FDocument.Insert(I-1+J, MyStrings[j]);
592
constructor TPostScript.Create(AOwner : TComponent);
594
inherited create(AOwner);
596
FHeight := 792; // 11 inches at 72 dpi
597
FWidth := 612; // 8 1/2 inches at 72 dpi
600
Procedure TPostScript.WritePS(const Cmd : String);
603
If length(Cmd)>0 then
604
FStream.Write(Cmd[1],Length(Cmd));
605
FStream.Write(LineEnding,SizeOf(LineEnding));
608
Procedure TPostScript.WritePS(Const Fmt : String; Args : Array of Const);
611
WritePS(Format(Fmt,Args));
614
Procedure TPostScript.WriteDocumentHeader;
617
WritePS('%!PS-Adobe-3.0');
618
WritePS('%%BoundingBox: 0 0 612 792');
619
WritePS('%%Creator: '+Creator);
620
WritePS('%%Title: '+FTitle);
621
WritePS('%%Pages: (atend)');
622
WritePS('%%PageOrder: Ascend');
626
Procedure TPostScript.WriteStandardFont;
629
// Choose a standard font in case the user doesn't
630
WritePS('/AvantGarde-Book findfont');
631
WritePS('10 scalefont');
635
Procedure TPostScript.FreePatterns;
641
If Assigned(FPatterns) then
643
For I:=0 to FPatterns.Count-1 do
644
TObject(FPatterns[i]).Free;
645
FreeAndNil(FPatterns);
649
destructor TPostScript.Destroy;
657
{ add a pattern to the array }
658
procedure TPostScript.AddPattern(APSPattern: TPSPattern);
660
If Not Assigned(FPatterns) then
661
FPatterns:=Tlist.Create;
662
FPatterns.Add(APSPattern);
665
{ Find a pattern object by it's name }
667
function TPostScript.FindPattern(AName: String): TPSPattern;
674
If Assigned(FPatterns) then
676
I:=Fpatterns.Count-1;
677
While (Result=Nil) and (I>=0) do
678
if TPSPattern(FPatterns[I]).Name = AName then
679
result := TPSPattern(FPatterns[i])
685
function TPostScript.DelPattern(AName: String): Boolean;
687
// can't do that yet...
692
{ Create a new pattern and inserts it into the array for safe keeping }
693
function TPostScript.NewPattern(AName: String): TPSPattern;
695
MyPattern: TPSPattern;
697
MyPattern := TPSPattern.Create;
698
AddPattern(MyPattern);
699
MyPattern.Name := AName;
700
MyPattern.OnChange := @PatternChanged;
701
MyPattern.OldName := '';
702
// add this to the postscript now...
703
InsertPattern(MyPattern);
707
{ Start a new document }
708
procedure TPostScript.BeginDoc;
716
Raise Exception.Create(SErrDocumentAlreadyStarted);
717
FCanvas:=TPostScriptCanvas.Create(FStream);
718
FCanvas.Height:=Self.Height;
719
FCanvas.Width:=Self.width;
722
// start our first page
728
Procedure TPostScript.WritePage;
731
WritePS('%%Page: '+inttostr(FPageNumber)+' '+inttostr(FPageNumber));
735
{ Copy current page into the postscript and start a new one }
736
procedure TPostScript.NewPage;
738
// dump the current page into our postscript first
739
// put end page definition...
742
FPageNumber := FPageNumber+1;
746
{ Finish off the document }
747
procedure TPostScript.EndDoc;
749
// Start printing the document after closing out the pages
752
WritePS('%%Pages: '+inttostr(FPageNumber));
753
// okay, the postscript is all ready, so dump it to the text file
759
Function TPostScript.GetCreator : String;
762
If (FCreator='') then
769
Procedure TPostScript.SetStream (Value : TStream);
772
if (FStream<>Value) then
774
If (FStream<>Nil) and FDocStarted then
781
Procedure TPostScript.CheckStream;
784
If Not Assigned(FStream) then
785
Raise Exception.Create(SErrNoStreamAssigned);
790
procedure TPSPen.SetPattern(const AValue: TPSPattern);
792
if FPattern<>AValue then
800
destructor TPSPen.Destroy;
802
// Do NOT free the pattern object from here...
807
{ Return the pen definition as a postscript string }
808
function TPSPen.AsString: String;
812
if FPattern <> nil then
814
if FPattern.PaintType = ptColored then
815
Result:='/Pattern setcolorspace '+FPattern.Name+' setcolor '
818
Result:='[/Pattern /DeviceRGB] setcolorspace '+inttostr(Color.Red)+' '+inttostr(Color.Green)+' '+
819
inttostr(Color.Blue)+' '+FPattern.Name+' setcolor ';
822
else // no pattern do this:
823
Result:=inttostr(Color.Red)+' '+inttostr(Color.Green)+' '+
824
inttostr(Color.Blue)+' setrgbcolor ';
825
Result := Result + format('%f',[Width])+' setlinewidth ';
830
{ Returns the pattern definition as postscript }
831
function TPSPattern.GetpostScript: TStringList;
838
// If nothing in the canvas, error
839
if FStream.Size=0 then
840
raise exception.create('Empty pattern');
844
add('%% PATTERN '+FName);
845
add('/'+FName+'proto 12 dict def '+FName+'proto begin');
846
add('/PatternType 1 def');
847
add(Format('/PaintType %d def',[ord(FPaintType)+1]));
848
add(Format('/TilingType %d def',[ord(FTilingType)+1]));
849
add('/BBox ['+inttostr(FBBox.Left)+' '+inttostr(FBBox.Top)+' '+inttostr(FBBox.Right)+' '+inttostr(FBBox.Bottom)+'] def');
850
add('/XStep '+format('%f',[FXStep])+' def');
851
add('/YStep '+format('%f',[FYstep])+' def');
852
add('/PaintProc { begin');
854
SetLength(S,FStream.Size);
855
FStream.Seek(0,soFromBeginning);
856
FStream.Read(S[1],FStream.Size);
858
// add support for custom matrix later
859
add('end } def end '+FName+'proto [1 0 0 1 0 0] makepattern /'+FName+' exch def');
860
add('%% END PATTERN '+FName);
862
Result := FPostScript;
865
procedure TPSPattern.SetBBox(const AValue: TRect);
867
{ if FBBox<>AValue then
870
FPatternCanvas.Height := FBBox.Bottom - FBBox.Top;
876
procedure TPSPattern.SetName(const AValue: String);
879
if (FName<>AValue) then
886
procedure TPSPattern.Changed;
888
if Assigned(FOnChange) then FOnChange(Self);
891
procedure TPSPattern.SetPaintType(const AValue: TPSPaintType);
893
if FPaintType=AValue then exit;
898
procedure TPSPattern.SetTilingType(const AValue: TPSTileType);
900
if FTilingType=AValue then exit;
905
procedure TPSPattern.SetXStep(const AValue: Real);
907
if FXStep=AValue then exit;
912
procedure TPSPattern.SetYStep(const AValue: Real);
914
if FYStep=AValue then exit;
919
constructor TPSPattern.Create;
921
FPostScript := TStringList.Create;
922
FPaintType := ptColored;
923
FTilingType := ttConstant;
924
FStream:=TmemoryStream.Create;
925
FPatternCanvas := TPostScriptCanvas.Create(FStream);
929
destructor TPSPattern.Destroy;
937
{ ---------------------------------------------------------------------
939
---------------------------------------------------------------------}
942
Function TPSBrush.GetAsString : String;