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