1 {-------------------------------------------------------------------------------
2 The contents of this file are subject to the Mozilla Public License
3 Version 1.1 (the "License"); you may not use this file except in compliance
4 with the License. You may obtain a copy of the License at
5 http://www.mozilla.org/MPL/
6 
7 Software distributed under the License is distributed on an "AS IS" basis,
8 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
9 the specific language governing rights and limitations under the License.
10 
11 The Original Code is: SynEditExport.pas, released 2000-04-16.
12 
13 The Original Code is partly based on the mwExport.pas file from the
14 mwEdit component suite by Martin Waldenburg and other developers, the Initial
15 Author of this file is Michael Hieke.
16 Portions created by Michael Hieke are Copyright 2000 Michael Hieke.
17 Portions created by James D. Jacobson are Copyright 1999 Martin Waldenburg.
18 All Rights Reserved.
19 
20 Contributors to the SynEdit project are listed in the Contributors.txt file.
21 
22 Alternatively, the contents of this file may be used under the terms of the
23 GNU General Public License Version 2 or later (the "GPL"), in which case
24 the provisions of the GPL are applicable instead of those above.
25 If you wish to allow use of your version of this file only under the terms
26 of the GPL and not to allow others to use your version of this file
27 under the MPL, indicate your decision by deleting the provisions above and
28 replace them with the notice and other provisions required by the GPL.
29 If you do not delete the provisions above, a recipient may use your version
30 of this file under either the MPL or the GPL.
31 
32 $Id: syneditexport.pas 53362 2016-11-12 16:31:17Z bart $
33 
34 You may retrieve the latest version of this file at the SynEdit home page,
35 located at http://SynEdit.SourceForge.net
36 
37 Known Issues:
38 -------------------------------------------------------------------------------}
39 
40 { Base class for exporting a programming language source file or part of it to
41   a formatted output like HTML or RTF and copying this to the Windows clipboard
42   or saving it to a file. }
43 unit SynEditExport;
44 
45 {$I SynEdit.inc}
46 {.$define debug_synexport}
47 
48 interface
49 
50 uses
51   Classes,
52   SysUtils,
53   SynEditHighlighter, SynEditTextBase, SynEditTextBuffer,
54   FileUtil, LazUTF8, FPCAdds, LCLType, LCLProc,
55   Graphics, Clipbrd;
56 
57 type
58   PSynReplaceCharsArray = ^TSynReplaceCharsArray;
59   { Array to hold the replacements strings for chars that are invalid for the
60     output format, occurences of the chars that have a corresponding entry in
61     this array are replaced with the string the entry points to.  Descendant
62     classes have to fill it accordingly. }
63   TSynReplaceCharsArray = array[char] of PChar;
64 
65   { Base exporter class, implements the buffering and the common functionality
66     to track the changes of token attributes, to export to the clipboard or to
67     save the output to a file. Descendant classes have to implement only the
68     actual formatting of tokens. }
69 
70   { TSynCustomExporter }
71 
72   TSynCustomExporter = class(TComponent)
73   private
74     fBuffer: TMemoryStream;
75     fFirstAttribute: boolean;
76     fImmediateAttrWrite: Boolean;
77     procedure AssignFont(Value: TFont);
78     procedure SetFont(Value: TFont);
79     procedure SetHighlighter(Value: TSynCustomHighlighter);
80     procedure SetTitle(const Value: string);
81   protected
82     fBackgroundColor: TColor;
83     fClipboardFormat: UINT;
84     fDefaultFilter: string;
85     fExportAsText: boolean;
86     fFont: TFont;
87     fHighlighter: TSynCustomHighlighter;
88     fLastBG: TColor;
89     fLastFG: TColor;
90     fLastStyle: TFontStyles;
91     fReplaceReserved: TSynReplaceCharsArray;
92     fTitle: string;
93     fUseBackground: boolean;
94     { Adds a string to the output buffer. }
95     procedure AddData(const AText: string);
96     { Adds a string and a trailing newline to the output buffer. }
97     procedure AddDataNewLine(const AText: string);
98     { Adds a newline to the output buffer. }
99     procedure AddNewLine;
100     { Copies the data under this format to the clipboard. The clipboard has to
101       be opened explicitly when more than one format is to be set. }
102     procedure CopyToClipboardFormat(AFormat: UINT);
103     { Has to be overridden in descendant classes to add the closing format
104       strings to the output buffer.  The parameters can be used to track what
105       changes are made for the next token. }
106     procedure FormatAttributeDone(BackgroundChanged, ForegroundChanged: boolean;
107       FontStylesChanged: TFontStyles); virtual; abstract;
108     { Has to be overridden in descendant classes to add the opening format
109       strings to the output buffer.  The parameters can be used to track what
110       changes have been made in respect to the previous token. }
111     procedure FormatAttributeInit(BackgroundChanged, ForegroundChanged: boolean;
112       FontStylesChanged: TFontStyles); virtual; abstract;
113     { Has to be overridden in descendant classes to add the closing format
114       strings to the output buffer after the last token has been written. }
115     procedure FormatAfterLastAttribute; virtual; abstract;
116 {begin}                                                                         //mh 2000-10-10
117     { Has to be overridden in descendant classes to add the opening format
118       strings to the output buffer when the first token is about to be written. }
119     procedure FormatBeforeFirstAttribute(BackgroundChanged,
120       ForegroundChanged: boolean; FontStylesChanged: TFontStyles);
121       virtual; abstract;
122 {end}                                                                           //mh 2000-10-10
123 
124     { The Format*Immediate methods apply formatting based entirely on the
125       current token attribute, they do not take the attribute of the previous
126       token into account }
127     procedure FormatBeforeFirstAttributeImmediate(BG, FG: TColor); virtual; abstract;
128     procedure FormatAfterLastAttributeImmediate; virtual; abstract;
129     procedure FormatAttributeInitImmediate(Attri: TSynHighlighterAttributes; IsSpace: Boolean); virtual; abstract;
130     procedure FormatAttributeDoneImmediate(Attri: TSynHighlighterAttributes; IsSpace: Boolean); virtual; abstract;
131 
132     { Has to be overridden in descendant classes to add the formatted text of
133       the actual token text to the output buffer. }
134     procedure FormatToken(Token: string); virtual;
135     procedure FormatTokenImmediate(Token: String; Attri: TSynHighlighterAttributes; IsSpace: Boolean);
136     { Has to be overridden in descendant classes to add a newline in the output
137       format to the output buffer. }
138     procedure FormatNewLine; virtual; abstract;
139     { Returns the size of the formatted text in the output buffer, to be used
140       in the format header or footer. }
GetBufferSizenull141     function GetBufferSize: integer;
142     { The clipboard format the exporter creates as native format. }
GetClipboardFormatnull143     function GetClipboardFormat: UINT; virtual;
144     { Has to be overridden in descendant classes to return the correct output
145       format footer. }
GetFooternull146     function GetFooter: string; virtual; abstract;
147     { Has to be overridden in descendant classes to return the name of the
148       output format. }
GetFormatNamenull149     function GetFormatName: string; virtual;
150     { Has to be overridden in descendant classes to return the correct output
151       format header. }
GetHeadernull152     function GetHeader: string; virtual; abstract;
153     { Inserts a data block at the given position into the output buffer.  Is
154       used to insert the format header after the exporting, since some header
155       data may be known only after the conversion is done. }
156     procedure InsertData(APos: integer; const AText: string);
157 {$IFDEF SYN_MBCSSUPPORT}
158     { Replaces multibyte chars with the equivalent in the output format. }
ReplaceMBCSnull159     function ReplaceMBCS(Char1, Char2: char): string; virtual;
160 {$ENDIF}
161     { Returns a string that has all the invalid chars of the output format
162       replaced with the entries in the replacement array. }
ReplaceReservedCharsnull163     function ReplaceReservedChars(AToken: string; out IsSpace: boolean): string;
164     procedure SetExportAsText(Value: boolean); virtual;  //TSynExportHtml needs to override it
165     { Sets the token attribute of the next token to determine the changes
166       of colors and font styles so the properties of the next token can be
167       added to the output buffer. }
168     procedure SetTokenAttribute(IsSpace: boolean;
169       Attri: TSynHighlighterAttributes); virtual;
ValidatedColornull170     function ValidatedColor(AColor, ADefColor: TColor): TColor;
171   public
172     { Creates an instance of the exporter. }
173     constructor Create(AOwner: TComponent); override;
174     { Destroys an instance of the exporter. }
175     destructor Destroy; override;
176     { Clears the output buffer and any internal data that relates to the last
177       exported text. }
178     procedure Clear; virtual;
179     { Copies the output buffer contents to the clipboard, as the native format
180       or as text depending on the ExportAsText property. }
181     procedure CopyToClipboard;
182     { Exports everything in the strings parameter to the output buffer. }
183     procedure ExportAll(ALines: TStrings);
184     { Exports the given range of the strings parameter to the output buffer. }
185     procedure ExportRange(ALines: TStrings; Start, Stop: TPoint);
186     { Saves the contents of the output buffer to a file. }
187     procedure SaveToFile(const AFileName: string);
188     { Saves the contents of the output buffer to a stream. }
189     procedure SaveToStream(AStream: TStream);
190   public
191     { Default background color for text that has no token attribute assigned or
192       for token attributes that have the background set to default. }
193     property Color: TColor read fBackgroundColor write fBackgroundColor;
194     { Filter string for the output format for SaveAs file dialogs. }
195     property DefaultFilter: string read fDefaultFilter write fDefaultFilter;
196     property ExportAsText: boolean read fExportAsText write SetExportAsText;
197     { The font to be used for the output format. The font color is used for text
198       that has no token attribute assigned or for token attributes that have
199       the background set to default. }
200     property Font: TFont read fFont write SetFont;
201     { The output format of the exporter. }
202     property FormatName: string read GetFormatName;
203     { The highlighter to use for exporting. }
204     property Highlighter: TSynCustomHighlighter
205       read fHighlighter write SetHighlighter;
206     property ImmediateAttrWrite: Boolean read fImmediateAttrWrite write fImmediateAttrWrite default false;
207     { The title to embedd into the output header. }
208     property Title: string read fTitle write SetTitle;
209     { Use the token attribute background for the exporting. }
210     property UseBackground: boolean read fUseBackground write fUseBackground;
211   end;
212 
213 implementation
214 
215 uses
216   SynEditMiscProcs, SynEditStrConst;
217 
218 { TSynCustomExporter }
219 
220 constructor TSynCustomExporter.Create(AOwner: TComponent);
221 begin
222   inherited Create(AOwner);
223   fBuffer := TMemoryStream.Create;
224   {*****************}
225   fClipboardFormat := CF_TEXT;
226   fFont := TFont.Create;
227   fBackgroundColor := clWindow;
228   AssignFont(nil);
229   Clear;
230   fTitle := SYNS_Untitled;
231 end;
232 
233 destructor TSynCustomExporter.Destroy;
234 begin
235   fFont.Free;
236   fBuffer.Free;
237   inherited Destroy;
238 end;
239 
240 procedure TSynCustomExporter.AddData(const AText: string);
241 begin
242   if AText <> '' then
243     fBuffer.Write(AText[1], Length(AText));
244 end;
245 
246 procedure TSynCustomExporter.AddDataNewLine(const AText: string);
247 begin
248   AddData(AText);
249   AddNewLine;
250 end;
251 
252 procedure TSynCustomExporter.AddNewLine;
253 const
254   NL: array[0..1] of char = #13#10;
255 begin
256   fBuffer.Write(NL[0], High(NL)-Low(NL)+1);
257 end;
258 
259 procedure TSynCustomExporter.AssignFont(Value: TFont);
260 begin
261   if Value <> nil then
262     fFont.Assign(Value)
263   else begin
264     fFont.Name := 'Courier New';
265     fFont.Size := 10;
266     fFont.Color := clBlack;
267     fFont.Style := [];
268   end;
269 end;
270 
271 procedure TSynCustomExporter.Clear;
272 begin
273   fBuffer.Position := 0;
274   // Size is ReadOnly in Delphi 2
275   fBuffer.SetSize(0);
276   fLastStyle := [];
277   fLastBG := clWindow;
278   fLastFG := clWindowText;
279 end;
280 
281 procedure TSynCustomExporter.CopyToClipboard;
282 begin
283   if fExportAsText then
284     CopyToClipboardFormat(CF_TEXT)
285   else
286     CopyToClipboardFormat(GetClipboardFormat);
287 end;
288 
289 procedure TSynCustomExporter.CopyToClipboardFormat(AFormat: UINT);
290 begin
291   fBuffer.Position:=0;
292   //if we don't clear the clipboard, external applications will only ever see the
293   //first Copy we put there
294   ClipBoard.Clear;
295   ClipBoard.AddFormat(AFormat,fBuffer);
296 end;
297 
298 procedure TSynCustomExporter.ExportAll(ALines: TStrings);
299 begin
300   ExportRange(ALines, Point(1, 1), Point(MaxInt, MaxInt));
301 end;
302 
303 procedure TSynCustomExporter.ExportRange(ALines: TStrings; Start, Stop: TPoint);
304 var
305   i, X, l: integer;
306   Token: string;
307   IsSpace: boolean;
308   Attri: TSynHighlighterAttributes;
309   TheLines: TSynEditStringsBase;
310 begin
311   // abort if not all necessary conditions are met
312   if not Assigned(ALines) or not Assigned(Highlighter) or (ALines.Count = 0)
313     or (Start.Y > ALines.Count) or (Start.Y > Stop.Y)
314   then
315     Abort;
316 
317   Stop.Y := Max(1, Min(Stop.Y, ALines.Count));
318   Stop.X := Max(1, Min(Stop.X, Length(ALines[Stop.Y - 1]) + 1));
319   Start.X := Max(1, Min(Start.X, Length(ALines[Start.Y - 1]) + 1));
320   if (Start.Y = Stop.Y) and (Start.X >= Stop.X) then
321     Abort;
322 
323   if ALines is TSynEditStringsBase then
324     TheLines := TSynEditStringsBase(ALines)
325   else begin
326     TheLines := TSynEditStringList.Create();
327     TheLines.Assign(ALines);
328   end;
329 
330   Highlighter.AttachToLines(TheLines);
331   try
332     Highlighter.CurrentLines := TheLines;
333     Highlighter.ScanRanges;
334 
335     // initialization
336     fBuffer.Position := 0;
337     fBuffer.SetSize(Max($1000, (Stop.Y - Start.Y) * 128));
338 
339     // export all the lines into fBuffer
340     fFirstAttribute := TRUE;
341 
342 
343     for i := Start.Y to Stop.Y do begin
344       Highlighter.StartAtLineIndex(i - 1);
345       X := 1;
346       while not Highlighter.GetEOL do begin
347         Attri := Highlighter.GetTokenAttribute;
348         Token := Highlighter.GetToken;
349         l := UTF8Length(Token);
350 
351         if (i = Start.Y) and (X < Start.X) then
352           UTF8Delete(Token, 1, Start.X - X);
353 
354         X := X + l; // TODO: compound chars
355         if Token = '' then
356           continue;
357 
358         if (i = Stop.Y) and (X >= Stop.X) then begin
359           UTF8Delete(Token, 1 + X - Stop.X, MaxInt);
360           if Token = '' then
361             continue;
362         end;
363 
364         Token := ReplaceReservedChars(Token, IsSpace);
365         if fImmediateAttrWrite then begin
366            if fFirstAttribute then begin
367              FormatBeforeFirstAttributeImmediate(fBackgroundColor, fFont.Color);
368              fFirstAttribute := False;
369            end;
370            FormatTokenImmediate(Token, Attri ,IsSpace);
371         end else begin
372           SetTokenAttribute(IsSpace, Attri);
373           FormatToken(Token);
374         end;
375         Highlighter.Next;
376       end;
377 
378       FormatNewLine;
379     end;
380 
381     if not fFirstAttribute then begin
382       if fImmediateAttrWrite then
383         FormatAfterLastAttributeImmediate
384       else
385         FormatAfterLastAttribute;
386     end;
387     // insert header
388     fBuffer.SetSize(integer(fBuffer.Position));
389     InsertData(0, GetHeader);
390     // add footer
391     AddData(GetFooter);
392     // Size is ReadOnly in Delphi 2
393     fBuffer.SetSize(integer(fBuffer.Position));
394   finally
395     Highlighter.DetachFromLines(TheLines);
396     if TheLines <> ALines then
397       TheLines.Free;
398   end;
399 end;
400 
401 procedure TSynCustomExporter.FormatToken(Token: string);
402 begin
403   AddData(Token);
404 end;
405 
406 procedure TSynCustomExporter.FormatTokenImmediate(Token: String;
407   Attri: TSynHighlighterAttributes; IsSpace: Boolean);
408 begin
409   {$ifdef debug_synexport}
410   debugln(['TSynCustomExporter.FormatTokenImmediate: Token = "', Token,'", IsSpace = ',IsSpace]);
411   {$endif}
412   FormatAttributeInitImmediate(Attri, IsSpace);
413   FormatToken(Token);
414   FormatAttributeDoneImmediate(Attri, IsSpace);
415 end;
416 
GetBufferSizenull417 function TSynCustomExporter.GetBufferSize: integer;
418 begin
419   Result := integer(fBuffer.Size);
420 end;
421 
GetClipboardFormatnull422 function TSynCustomExporter.GetClipboardFormat: UINT;
423 begin
424   Result := fClipboardFormat;
425 end;
426 
GetFormatNamenull427 function TSynCustomExporter.GetFormatName: string;
428 begin
429   Result := '';
430 end;
431 
432 procedure TSynCustomExporter.InsertData(APos: integer; const AText: string);
433 var
434   Len, ToMove, SizeNeeded: TStreamSeekType;
435   Dest: PChar;
436 begin
437   Len := Length(AText);
438   if Len > 0 then begin
439     ToMove := fBuffer.Position;
440     SizeNeeded := ToMove + Len;
441     if fBuffer.Size < SizeNeeded then
442       // Size is ReadOnly in Delphi 2
443       fBuffer.SetSize((SizeNeeded + $1800) and not $FFF); // increment in pages
444     Dest := fBuffer.Memory;
445     Dest:=Dest+Len;
446     Move(fBuffer.Memory^, Dest^, TCompareMemSize(ToMove));
447     fBuffer.Position := 0;
448     fBuffer.Write(AText[1], TMemStreamSeekType(Len));
449     fBuffer.Position := ToMove + Len;
450   end;
451 end;
452 
453 {$IFDEF SYN_MBCSSUPPORT}
ReplaceMBCSnull454 function TSynCustomExporter.ReplaceMBCS(Char1, Char2: char): string;
455 begin
456   SetLength(Result, 2);
457   Result[1] := Char1;
458   Result[2] := Char2;
459 end;
460 {$ENDIF}
461 
ReplaceReservedCharsnull462 function TSynCustomExporter.ReplaceReservedChars(AToken: string;
463   out IsSpace: boolean): string;
464 var
465   I, ISrc, IDest, SrcLen, DestLen: integer;
466   Replace: string;
467   c: char;                                                                      //mh 2000-10-10
468 begin
469   IsSpace := TRUE;
470   if AToken <> '' then begin
471     SrcLen := Length(AToken);
472     ISrc := 1;
473     DestLen := SrcLen;
474     IDest := 1;
475     SetLength(Result, DestLen);
476     while ISrc <= SrcLen do begin
477       c := AToken[ISrc];
478       IsSpace := IsSpace and (c = ' ');
479       if fReplaceReserved[c] <> nil then begin
480         Replace := StrPas(fReplaceReserved[c]);
481         Inc(ISrc);
482 {$IFDEF SYN_MBCSSUPPORT}
483 //      end else if ByteType(AToken, ISrc) <> mbSingleByte then begin
484       end else if (AToken[ISrc] in LeadBytes) and (AToken[ISrc + 1] <> #0) then //mh 2000-10-10
485       begin
486         Replace := ReplaceMBCS(AToken[ISrc], AToken[ISrc + 1]);
487         Inc(ISrc, 2);
488 {$ENDIF}
489       end else begin
490         if IDest > DestLen then begin
491           Inc(DestLen, 32);
492           SetLength(Result, DestLen);
493         end;
494         Result[IDest] := c;
495         Inc(ISrc);
496         Inc(IDest);
497         continue;
498       end;
499       if IDest + Length(Replace) - 1 > DestLen then begin
500         Inc(DestLen, Max(32, IDest + Length(Replace) - DestLen));
501         SetLength(Result, DestLen);
502       end;
503       for I := 1 to Length(Replace) do begin
504         Result[IDest] := Replace[I];
505         Inc(IDest);
506       end;
507     end;
508     SetLength(Result, IDest - 1);
509   end else
510     Result := '';
511 end;
512 
513 procedure TSynCustomExporter.SaveToFile(const AFileName: string);
514 begin
515   fBuffer.Position := 0;
516   fBuffer.SaveToFile(UTF8ToSys(AFileName));
517 end;
518 
519 procedure TSynCustomExporter.SaveToStream(AStream: TStream);
520 begin
521   fBuffer.Position := 0;
522   fBuffer.SaveToStream(AStream);
523 end;
524 
525 procedure TSynCustomExporter.SetExportAsText(Value: boolean);
526 begin
527   if fExportAsText <> Value then begin
528     fExportAsText := Value;
529     Clear;
530   end;
531 end;
532 
533 procedure TSynCustomExporter.SetFont(Value: TFont);
534 begin
535   AssignFont(Value);
536 end;
537 
538 procedure TSynCustomExporter.SetHighlighter(Value: TSynCustomHighlighter);
539 begin
540   if fHighlighter <> Value then begin
541     fHighlighter := Value;
542     if fHighlighter <> nil then
543       fHighlighter.FreeNotification(Self);
544     Clear;
545   end;
546 end;
547 
548 procedure TSynCustomExporter.SetTitle(const Value: string);
549 begin
550   if fTitle <> Value then begin
551     if Value <> '' then
552       fTitle := Value
553     else
554       fTitle := SYNS_Untitled;
555   end;
556 end;
557 
558 procedure TSynCustomExporter.SetTokenAttribute(IsSpace: boolean;
559   Attri: TSynHighlighterAttributes);
560 var
561   ChangedBG: boolean;
562   ChangedFG: boolean;
563   ChangedStyles: TFontStyles;
564 
565 begin
566   if fFirstAttribute then begin
567     fFirstAttribute := FALSE;
568     fLastBG := ValidatedColor(Attri.Background, fBackgroundColor);
569     fLastFG := ValidatedColor(Attri.Foreground, fFont.Color);
570     fLastStyle := Attri.Style;
571 {begin}                                                                         //mh 2000-10-10
572     FormatBeforeFirstAttribute(UseBackground and (fLastBG <> fBackgroundColor),
573       fLastFG <> fFont.Color, Attri.Style);
574 (*
575     FormatAttributeInit(UseBackground and (fLastBG <> fBackgroundColor),
576       fLastFG <> fFont.Color, Attri.Style);
577 *)
578 {end}                                                                           //mh 2000-10-10
579   end else begin
580     ChangedBG := UseBackground and
581       (fLastBG <> ValidatedColor(Attri.Background, fBackgroundColor));
582     ChangedFG := not IsSpace and
583       (fLastFG <> ValidatedColor(Attri.Foreground, fFont.Color));
584     // which font style bits are to reset?
585     if not IsSpace then
586       ChangedStyles := fLastStyle - Attri.Style
587     else
588       ChangedStyles := [];
589     if ChangedBG or ChangedFG or (fLastStyle <> Attri.Style) then begin
590       FormatAttributeDone(ChangedBG, ChangedFG, ChangedStyles);
591       // which font style bits are to set?
592       if not IsSpace then
593         ChangedStyles := Attri.Style - fLastStyle
594       else
595         ChangedStyles := [];
596       fLastBG := ValidatedColor(Attri.Background, fBackgroundColor);
597       if not IsSpace then begin
598         fLastFG := ValidatedColor(Attri.Foreground, fFont.Color);
599         fLastStyle := Attri.Style;
600       end;
601       FormatAttributeInit(ChangedBG, ChangedFG, ChangedStyles);
602     end;
603   end;
604 end;
605 
ValidatedColornull606 function TSynCustomExporter.ValidatedColor(AColor, ADefColor: TColor): TColor;
607 begin
608   if AColor = clNone then
609     Result := ADefColor
610   else
611     Result := AColor;
612 end;
613 
614 end.
615 
616