mORMot and Open Source friends
Check-in [525eb2f95b]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:{1042} added withNewLine optional parameter to DrawText*() methods so that you may be able to append some text without creating a new paragraph - from a proposal patch by Mike Lamusse (mogulza): thanks for sharing!
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 525eb2f95b72d651cf9337b9eb8b2cb764447401
User & Date: ab 2015-03-05 12:58:14
Context
2015-03-05
22:16
{1043} introducing new SynBidirSock.pas unit to implement bidirectional client and server protocol, e.g. WebSockets - not finished nor tested yet, but first step for feature request [aa230e5299] check-in: 629dffc3d7 user: ab tags: trunk
12:58
{1042} added withNewLine optional parameter to DrawText*() methods so that you may be able to append some text without creating a new paragraph - from a proposal patch by Mike Lamusse (mogulza): thanks for sharing! check-in: 525eb2f95b user: ab tags: trunk
11:03
{1041} avoid theoritical overflow in QueryPerformanceCounter() for Darwin/MacOS check-in: 0f757e2d72 user: ab tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SQLite3/mORMotReport.pas.

29
30
31
32
33
34
35

36
37
38
39
40
41
42
...
199
200
201
202
203
204
205
206


207
208
209
210
211
212
213
...
377
378
379
380
381
382
383
384



385
386
387
388
389
390
391
...
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
...
553
554
555
556
557
558
559

560
561
562
563
564
565
566
...
652
653
654
655
656
657
658



659
660
661
662
663



664
665
666
667
668


669
670
671
672

673
674
675
676
677
678
679
...
826
827
828
829
830
831
832



833

834
835
836
837
838
839
840
...
856
857
858
859
860
861
862
863







864
865
866
867
868
869
870
....
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
....
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
....
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
....
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
....
2597
2598
2599
2600
2601
2602
2603
2604

2605

2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
....
2656
2657
2658
2659
2660
2661
2662

2663
2664
2665
2666
2667
2668
2669
2670
....
2697
2698
2699
2700
2701
2702
2703


2704
2705
2706
2707
2708
2709
2710
2711
2712
....
2762
2763
2764
2765
2766
2767
2768

2769
2770
2771
2772
2773
2774
2775
....
2897
2898
2899
2900
2901
2902
2903
2904



2905
2906
2907
2908
2909
2910
2911
2912
2913
....
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
....
3204
3205
3206
3207
3208
3209
3210

3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
....
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269


3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
....
3541
3542
3543
3544
3545
3546
3547

3548
3549
3550
3551
3552
3553
3554
....
3992
3993
3994
3995
3996
3997
3998
3999

4000
4001
4002
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
....
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109



4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
....
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154







4155
4156
4157
4158
4159
4160
4161
4162
4163

4164
4165
4166
4167
4168
4169
4170
....
4191
4192
4193
4194
4195
4196
4197

4198
4199
4200
4201
4202
4203
4204
4205
4206

4207
4208
4209
4210
4211
4212
4213
....
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
4243
....
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312







4313
4314
4315
4316
4317
4318
4319
....
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
4353
4354
4355
4356
4357
....
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
4430
4431
4432

4433
4434
4435
4436
4437
4438
4439
4440
4441
  the Initial Developer. All Rights Reserved.
  Portions created by Arnaud Bouchez for Synopse are Copyright (C) 2015
  Arnaud Bouchez. All Rights Reserved.

  Contributor(s):
  - Celery
  - Leo


  Alternatively, the contents of this file may be used under the terms of
  either the GNU General Public License Version 2 or later (the "GPL"), or
  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  in which case the provisions of the GPL or the LGPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of either the GPL or the LGPL, and not to allow others to
................................................................................
  - added setter method for ZoomStatus property (during preview) - [dd656b470b]
  - added TGDIPages.ExportPDFStream() method - to be used e.g. on servers
  - fixed [cfdc644038] about truncated parenthesis in pdf export for caCurrency
  - fixed [e7ffb69131] about TGDIPages.DrawGraphic() when the TGraphic is Empty
  - allow preview as a blank colored component at design time (thanks to Celery)
  - added VisibleButtons optional parameter to TGDIPages.ShowPreviewForm method
    as requested by [4d64a52675]




*)

interface

{.$define MOUSE_CLICK_PERFORM_ZOOM} // old not user-friendly behavior
{.$define RENDERPAGES} // TRenderBox and TRenderPages are not yet finished
................................................................................
    fCanvasText: string;
    fBeforeGroupText: string;
    fGroupPage: TMetafile;
    fPages: TGDIPageContentDynArray;
    fHeaderLines: TObjectList;
    fFooterLines: TObjectList;
    fColumns: array of TColRec;
    fColumnHeaderList: TStringList;



{$ifdef MOUSE_CLICK_PERFORM_ZOOM}
    fZoomTimer: TTimer;
{$endif}
    fPtrHdl: THandle;

    fTabCount: integer;
    fCurrentPrinter: string;
................................................................................
    procedure SetZoom(zoom: integer);
    procedure SetZoomStatus(aZoomStatus: TZoomStatus); 
    procedure ZoomTimerInternal(X,Y: integer; ZoomIn: boolean);
    procedure ZoomTimer(Sender: TObject);

    procedure LineInternal(start,finish: integer; DoubleLine: boolean);
    procedure PrintFormattedLine(s: SynUnicode; flags: integer;
      const aBookmark: string=''; const aLink: string='');
    procedure LeftOrJustifiedWrap(const s: SynUnicode);
    procedure RightOrCenterWrap(const s: SynUnicode);
    procedure GetTextLimitsPx(var LeftOffset, RightOffset: integer);
    procedure HandleTabsAndPrint(const leftstring: SynUnicode;
      var rightstring: SynUnicode; leftOffset, rightOffset: integer);
    procedure PreviewPaint(Sender: TObject);
    procedure PreviewMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
................................................................................
    // get the hot region
    fLinks: TStringList;
    fLinksCurrent: integer;
    /// Strings[] are the outline titles, and Objects[] are TGDIPagereference
    // to get the Y position of the destination
    fOutline: TStringList;
    fInternalUnicodeString: SynUnicode;

    PreviewForm: TForm;
    PreviewButtons: array of TButton;
    PreviewPageCountLabel: TLabel;
    procedure CMFontChanged(var Msg: TMessage); message CM_FONTCHANGED;
    procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
    procedure WMEraseBkgnd(var Message: TWmEraseBkgnd); message WM_ERASEBKGND;
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
................................................................................
    procedure BeginDoc;
    /// Clear the current Report document
    procedure Clear; virtual;
    /// draw some text as a paragraph, with the current alignment
    // - this method does all word-wrapping and formating if necessary
    // - this method handle multiple paragraphs inside s (separated by newlines -
    // i.e. #13)



    procedure DrawText(const s: string); {$ifdef HASINLINE}inline;{$endif}
    /// draw some UTF-8 text as a paragraph, with the current alignment
    // - this method does all word-wrapping and formating if necessary
    // - this method handle multiple paragraphs inside s (separated by newlines -
    // i.e. #13)



    procedure DrawTextU(const s: RawUTF8); {$ifdef HASINLINE}inline;{$endif}
    /// draw some Unicode text as a paragraph, with the current alignment
    // - this method does all word-wrapping and formating if necessary
    // - this method handle multiple paragraphs inside s (separated by newlines -
    // i.e. #13)


    procedure DrawTextW(const s: SynUnicode);
    /// draw some text as a paragraph, with the current alignment
    // - this method use format() like parameterss
    procedure DrawTextFmt(const s: string; const Args: array of const);

    /// get the formating flags associated to a Title
    function TitleFlags: integer;
    /// draw some text as a paragraph title
    // - the outline level can be specified, if UseOutline property is enabled
    // - if aBookmark is set, a bookmark is created at this position
    // - if aLink is set, a link to the specified bookmark name (in aLink) is made
    procedure DrawTitle(const s: SynUnicode; DrawBottomLine: boolean=false; OutlineLevel: Integer=0;
................................................................................
    /// Adds text to the page footer at the specified horizontal position and
    // using to current font. No Line feed will be triggered.
    // - if XPos=-1, will put the text at the current right margin
    procedure AddTextToFooterAt(const s: SynUnicode; XPos: integer);
    /// Will add the current 'Page n/n' text at the specified position
    // - PageText must be of format 'Page %d/%d', in the desired language
    // - if XPos=-1, will put the text at the current right margin



    procedure AddPagesToFooterAt(const PageText: string; XPos: integer);


    /// register a column, with proper alignment
    procedure AddColumn(left, right: integer; align: TColAlign; bold: boolean);
    /// register same alignement columns, with percentage of page column width
    // - sum of all percent width should be 100, but can be of any value
    // - negative widths are converted into absolute values, but
    // corresponding alignment is set to right
................................................................................
    // - call this method once with all columns text as CSV
    procedure AddColumnHeadersFromCSV(var CSV: PWideChar;
      WithBottomGrayLine: boolean; BoldFont: boolean=false; RowLineHeight: integer=0);
    /// draw some text, split across every columns
    // - if BackgroundColor is not clNone (i.e. clRed or clNavy or clBlack), the
    // row is printed on white with this background color (e.g. to highlight errors)
    procedure DrawTextAcrossCols(const StringArray: array of SynUnicode;
      BackgroundColor: TColor=clNone);







    /// draw some text, split across every columns
    // - this method expect the text to be separated by commas
    // - if BackgroundColor is not clNone (i.e. clRed or clNavy or clBlack), the
    // row is printed on white with this background color (e.g. to highlight errors)
    procedure DrawTextAcrossColsFromCSV(var CSV: PWideChar; BackgroundColor: TColor=clNone);
    /// draw (double if specified) lines at the bottom of all currency columns
    procedure DrawLinesInCurrencyCols(doublelines: boolean);
................................................................................
    SpaceAfterCount: integer;
    /// associated link bookmark name
    // - from fLinksBookMarkName[LinkNumber-1], no link set for 0
    LinkNumber: integer;
  end;

  PRenderBoxLayout = ^TRenderBoxLayout;
  
  /// the internal "drawing" box structure used by TRenderBox
  // - TRenderBox.InternalRender populate fLayout[] with this structures,
  // ready to be drawn to the document Canvas
  TRenderBoxLayout = packed record
    /// pointer of the words in the fText[] array
    Text: PWideChar;
    /// number of PWideChar starting at Text^
................................................................................


implementation

uses
  {$ifdef ISDELPHIXE3}System.UITypes,{$endif}
  Types, Clipbrd, Consts;

type
  //TZStrings: used by ColumnHeaderList to store #0 terminated char arrays
  //eg: A column header row might look like - 'Column One'#0'Column Two'#0
  TZStrings = SynUnicode;


// Miscellaneous functions ...

function TextExtent(Canvas: TCanvas; const Text: SynUnicode; Len: integer=0): TSize;
begin
  Result.cX := 0;
  Result.cY := 0;
................................................................................
  result.ColRight := 0;
  if Cardinal(col)<Cardinal(length(fColumns)) then
    result := fColumns[col];
end;

procedure TGDIPages.PrintColumnHeaders;
var
  i,j,SavedFontSize,FontCol: integer;
  SavedFontStyle: TFontStyles;
  SavedAlign: TTextAlign;
  SavedWordWrapLeftCols: boolean;
  headers: array[0..MAXCOLS-1] of SynUnicode;
  zStr: TZStrings;

  function GetSubstringFromStringArray(var s: TZStrings): SynUnicode;
  begin
    result := PWideChar(pointer(s)); // result := next #0 ended string in s
    delete(s,1,length(result)+1);
  end;

begin
  if (fColumnHeaderList.Count = 0) or (fColumns=nil) then exit;
  CheckYPos;

  fColumnHeaderPrinted := true;   //stops an endless loop
  SavedFontSize := Font.size;
  SavedFontStyle := font.style;
  SavedAlign := fAlign;
  SavedWordWrapLeftCols := WordWrapLeftCols;
  WordWrapLeftCols := false;

  if Assigned(fStartColumnHeader) then
    fStartColumnHeader(Self);
  FontCol := fCanvas.Font.Color;
  for i := 0 to fColumnHeaderList.Count-1 do begin
    SetFontWithFlags(integer(fColumnHeaderList.Objects[i]));
    fCanvas.Font.Color := clBlack;
    j := 0;
    zStr := fColumnHeaderList[i];
    while (j < MAXCOLS) and (zStr<>'') do begin
      headers[j] := GetSubstringFromStringArray(zStr);
      inc(j);
    end;
    fDrawTextAcrossColsDrawingHeader := true;
    DrawTextAcrossCols(slice(headers,j));
    fDrawTextAcrossColsDrawingHeader := false;
  end;
  fCanvas.Font.Color := FontCol;
  if Assigned(fEndColumnHeader) then
    fEndColumnHeader(Self);
  // add a small space below the column headers
  // inc(fCurrentYPos,fLineHeight shr 2);
................................................................................
      MoveTo(start,Y);
      LineTo(finish,Y);
    end;
  end;
end;

procedure TGDIPages.PrintFormattedLine(s: SynUnicode; flags: integer;
  const aBookmark: string; const aLink: string);
var i, xpos: integer;
    leftOffset, rightOffset: integer;
begin
  s := RightTrim(s);
  i := pos(PAGENUMBER,LowerCaseU(s));
  if i > 0 then begin
    delete(s,i,14);
................................................................................
  fCurrentTextTop := fCurrentYPos;
  fCurrentTextPage := PageCount;
  GetTextLimitsPx(leftOffset,rightOffset);
  if flags and (FORMAT_SINGLELINE or FORMAT_DOUBLELINE)<>0 then begin
    LineInternal(leftOffset,rightOffset,flags and FORMAT_DOUBLELINE=FORMAT_DOUBLELINE);
    NewLine;
  end else
  if s = '' then

    NewLine else

  if (flags and FORMAT_XPOS_MASK <> 0) then begin
    xpos := ((flags and FORMAT_XPOS_MASK) shr 16)-2;
    if xpos<0 then
      xpos := RightMarginPos else
      inc(xpos);
    DrawTextAt(s,xpos);
  end else
  if (falign in  [taLeft,taJustified]) then
    LeftOrJustifiedWrap(s) else
    RightOrCenterWrap(s);
  if aBookmark<>'' then
    AddBookMark(aBookmark,fCurrentTextTop);
  if aLink<>'' then
    AddLink(aLink,Rect(PrinterPxToMmX(leftOffset),PrinterPxToMmY(fCurrentTextTop),
      PrinterPxToMmX(rightOffset),PrinterPxToMmY(fCurrentTextTop+fLineHeight)),
      fCurrentTextPage);
    // first line of written text is added
end;

procedure TGDIPages.LeftOrJustifiedWrap(const s: SynUnicode);
var indent, leftOffset, rightOffset, LineWidth: integer;
    leftstring, rightstring: SynUnicode;
    firstLoop: boolean;
begin
  leftstring := s;
  Indent := MmToPrinterPxX(fHangIndent);
  firstLoop := true;
................................................................................
    // prepending any further text overrun into rightstring ...
    HandleTabsAndPrint(leftstring, rightstring, leftOffset, rightOffset);
    if length(rightstring)=0 then
      break;
    leftstring := rightstring;
    NewLine;
  until false;

  NewLine;
end;

procedure TGDIPages.RightOrCenterWrap(const s: SynUnicode);
var i,leftOffset,rightOffset, LineWidth: integer;
    leftstring,rightstring: SynUnicode;
    offset: integer;
begin
................................................................................
  NewLine;
end;

procedure TGDIPages.GetTextLimitsPx(var LeftOffset, RightOffset: integer);
begin
  // Offsets (in Printer pixels) based on current page margins
  LeftOffset := fPageMarginsPx.left;


  RightOffset := fPhysicalSizePx.x-fPageMarginsPx.right;
  if RightOffset<=LeftOffset then
    raise Exception.Create('GetTextLimitsPx: wrong margins');
end;

procedure TGDIPages.HandleTabsAndPrint(const leftstring: SynUnicode;
  var rightstring: SynUnicode; leftOffset, rightOffset: integer);
const
    // if a tabstop is very close to the right margin, it may spoil justifying...
................................................................................
  // OK, no TABS now in ls...
  InternalUnicodeString(ls,PW,PWLen,@size);
  // print ls into (remaining) linewidth at (leftOffset, fCurrentYPos)
  if (falign = taLeft) or (rightstring = '') then begin // left aligned
    if BiDiMode=bdRightToLeft then
      leftOffset := rightOffset-size.cx;
    TextOut(fCanvas,leftOffset,fCurrentYPos,PW,PWLen);

    // don't care about line width: it should be always equal or smaller,
    // and we are left aligned
  end else begin // justified
    spacecount := 0;
    for i := 1 to length(ls) do
      if ls[i] = ' ' then
        inc(spacecount);
................................................................................
    if (PageRightButton.X<>0) and
       (cardinal(X-PageRightButton.X)<10) and
       (cardinal(Y-PageRightButton.Y)<20) then begin
      Page := Page+1;
      exit;
    end;
  end;
  if (Button=mbLeft) and (ssDouble in Shift) then begin



    Zoom := PAGE_WIDTH; // double click on page -> reset zoom to page width
  end else
{$ifndef MOUSE_CLICK_PERFORM_ZOOM}
  if Button=mbLeft then begin
    fButtonDown.X := (X shr 3)shl 3; // move 8 pixels by 8 pixels
    fButtonDown.Y := (Y shr 3)shl 3;
    fButtonDownScroll.X := HorzScrollBar.Position;
    fButtonDownScroll.Y := VertScrollBar.Position;
    Screen.Cursor := crHandPoint;
................................................................................
  Font.Size := 12;
  fLineSpacing := lsSingle;
  fOrientation := poPortrait;
  fUseOutlines := true;

  fHeaderLines := TObjectList.Create;
  fFooterLines := TObjectList.Create;
  fColumnHeaderList := TStringList.create;

{$ifdef MOUSE_CLICK_PERFORM_ZOOM}
  fZoomTimer := TTimer.create(Self);
  fZoomTimer.Interval := 200;
  fZoomTimer.OnTimer := ZoomTimer;
  fZoomTimer.enabled := false;
{$else}
................................................................................
  fPreviewSurface.OnMouseUp := PreviewMouseUp;
  fPreviewSurface.OnMouseMove := PreviewMouseMove;
  fZoomStatus := zsPercent;
  fZoom := 100;
  fBookmarks := TStringList.Create;
  fLinks := TStringList.Create;
  fOutline := TStringList.Create;

end;

destructor TGDIPages.Destroy;
begin
  Clear;
  fHeaderLines.free;
  fFooterLines.free;
  fColumnHeaderList.free;
  fPreviewSurface.free;
  PreviewSurfaceBitmap.Free;
{$ifdef MOUSE_CLICK_PERFORM_ZOOM}
  fZoomTimer.free;
{$endif}
  fOutline.Free;
  fLinks.Free;
................................................................................
    zsPercent:   zoom := fzoom;
    zsPageWidth: zoom := PAGE_WIDTH;
    else zoom := PAGE_FIT;
  end;
  fButtonDown.X := -1; // so MouseMove() won't scroll paintbox
end;

procedure TGDIPages.DrawText(const s: string);
begin
  DrawTextW(StringToSynUnicode(s));
end;

procedure TGDIPages.DrawTextW(const s: SynUnicode);
var P, Start: PWideChar;
    tmpStr: SynUnicode;
begin
  if Self=nil then exit;
  CheckYPos;
  if s = '' then


    NewLine else begin
    // split NewLine characters (#13 or #13#10) into multi lines
    P := pointer(s);
    while P^ <> #0 do begin
      Start := P;
      while not (ord(P^) in [0, 10, 13]) do Inc(P);
      SetString(tmpStr, Start, P-Start);
      if not fInHeaderOrFooter then
        fCanvasText := fCanvasText+SynUnicodeToString(tmpStr)+#13#10;
      PrintFormattedLine(tmpStr, FORMAT_DEFAULT);
      if P^ = #13 then Inc(P);
      if P^ = #10 then Inc(P);
    end;
  end;
end;

procedure TGDIPages.DrawTextU(const s: RawUTF8);
begin
  DrawTextW(UTF8ToSynUnicode(s));
end;

procedure TGDIPages.DrawTitle(const s: SynUnicode; DrawBottomLine: boolean=false;
  OutlineLevel: Integer=0; const aBookmark: string=''; const aLink: string='');
var H: integer;
    str: string;
begin
................................................................................
end;

procedure TGDIPages.NewLine;
begin
  if Self=nil then exit; // avoid GPF
  CheckHeaderDone;
  inc(fCurrentYPos, GetLineHeight);

//  fCanvasText := fCanvasText+#13#10;
end;

procedure TGDIPages.NewHalfLine;
begin
  if Self=nil then exit; // avoid GPF
  CheckHeaderDone;
................................................................................
  if fFooterLines.Count = 0 then
    CalcFooterGap;
  Foot := THeaderFooter.Create(Self,false,s,true);
  Foot.State.Flags := Foot.State.Flags or ((XPos+2) shl 16);
  fFooterLines.Add(Foot);
end;

procedure TGDIPages.AddPagesToFooterAt(const PageText: string; XPos: integer);

begin
  if fPagesToFooterText<>'' then
    exit; // only add once
  fPagesToFooterText := PageText;
  if XPos<0 then
    fPagesToFooterAt.X := -1 else
    fPagesToFooterAt.X := MmToPrinterPxX(XPos);
  fPagesToFooterAt.Y := fFooterHeight;
  fPagesToFooterState := SavedState;
end;

function TGDIPages.GetColumnCount: integer;
begin
  if Self=nil then
    result := 0 else
................................................................................
    left := right;
  end;
end;

procedure TGDIPages.AddColumnHeaders(const headers: array of SynUnicode;
  WithBottomGrayLine: boolean=false; BoldFont: boolean=false;
  RowLineHeight: integer=0; flags: integer=0);
var i: integer;
    zStr: TZStrings;
begin
  if Self=nil then exit; // avoid GPF
  if flags=0 then begin
    if BoldFont then
      Font.Style := [fsBold];
    flags := TextFormatsToFlags;
  end;
  zStr := '';



  for i := Low(headers) to High(headers) do
    zStr := zStr + headers[i]+#0;
  fColumnHeaderList.AddObject(zStr,pointer(flags));
  fColumnHeaderPrinted := false;
  fColumnHeaderPrintedAtLeastOnce := false;
  fColumnsWithBottomGrayLine := WithBottomGrayLine;
  fColumnsRowLineHeight := RowLineHeight;
  if BoldFont then
    Font.Style := [];
end;
................................................................................
    AddColumnHeaders(CSVToArray(CSV,length(fColumns)),
      WithBottomGrayLine,BoldFont,RowLineHeight);
end;

procedure TGDIPages.DrawTextAcrossColsFromCSV(var CSV: PWideChar; BackgroundColor: TColor=clNone);
begin
  if Self<>nil then // avoid GPF
    DrawTextAcrossCols(CSVToArray(CSV,length(fColumns)),BackgroundColor);
end;

/// round inverted color to white or black
function clAlways(cl: TColor): TColor;
begin
  if ((GetRValue(longword(cl)) * 2) +
      (GetGValue(longword(cl)) * 3) +
      (GetBValue(longword(cl)) * 2)) < 600 then
    result := clWhite else
    result := clBlack;
end;

procedure TGDIPages.DrawTextAcrossCols(const StringArray: array of SynUnicode;
  BackgroundColor: TColor = clNone);







function HasCRLF(const s: SynUnicode): boolean;
var i: integer;
begin
  result := true;
  for i := 0 to length(s)-1 do
    if s[i+1]<' ' then
      exit;
  result := false;
end;

function WrapText(s: SynUnicode; MaxWidth: integer; Lines: PSynUnicodeDynArray): integer;
var j,k,sp: integer;
begin
  result := 0; // returns the line count
  if Lines<>nil then
    SetLength(Lines^,0);
  repeat
................................................................................
      SetLength(Lines^,length(Lines^)+1);
      Lines^[high(Lines^)] := copy(s,1,sp-1);
    end;
    inc(result); // update lines count
    s := trim(copy(s,sp,maxInt)); // trim ' ',#13,#10 for next line
  until s='';
end;

var RowRect: TRect;
    lh: integer;
var max, i, j, k, c, H, ParenthW, LinesCount, X: integer;
    s: SynUnicode;
    line: string;
    Lines: TSynUnicodeDynArray;
    PW: PWideChar;
    PWLen, Options: integer;
    size: TSize;

begin
  if Self=nil then exit; // avoid GPF
  max := high(fColumns);
  if (max<0) or (length(StringArray)=0) then
    exit; // no column defined
  if High(StringArray)<max then
    max := High(StringArray);
................................................................................
        LinesCount := k; // calculate maximum line count
    end;
    if (LinesCount>1) and not HasSpaceForLines(LinesCount) then begin
      NewPageInternal;
      CheckHeaderDone;
    end;
  end;
  if (fColumnHeaderList.Count > 0) and not fColumnHeaderPrinted then begin
    i := fColumnHeaderList.Count + 2;
    if not HasSpaceForLines(i) then
      NewPageInternal;
    PrintColumnHeaders;
  end;
  // prepare column write
  if Assigned(fGroupPage) then
    fColumnsUsedInGroup := true;
................................................................................
            RowRect.Left := ColRight-size.cx-ParenthW;
        end;
        caCenter:
          RowRect.Left := ColLeft+(ColRight-ColLeft-size.cx)shr 1;
        caRight:
          if BiDiMode=bdLeftToRight then
            RowRect.Left := ColRight-size.cx-ParenthW;
        caCurrency:
          begin
            if fNegsToParenthesesInCurrCols then
              InternalUnicodeString(ConvertNegsToParentheses(s),PW,PWLen,@size);
            RowRect.Left := ColRight-size.cx-ParenthW;
            // no bdRightToleft handling necessary for caCurrency
          end;
      end;
      dec(RowRect.Left,ParenthW);
      ExtTextOutW(Handle,RowRect.Left+ParenthW,fCurrentYPos,Options,@RowRect,PW,PWLen,nil);







      inc(RowRect.Left,size.cx+ParenthW);
      if ColBold then
        Font.Style := Font.Style-[fsBold];
    end;
  end;
  if not fDrawTextAcrossColsDrawingHeader or
     not fColumnHeaderPrintedAtLeastOnce then begin
................................................................................
    LineInternal(GetColumnRec(0).ColLeft,RowRect.Right,false);
    inc(fCurrentYPos, H);
    fCanvas.Pen.Color := c;
  end;
  NewLine;
end;


procedure TGDIPages.DrawLinesInCurrencyCols(doublelines: boolean);
var i: integer;
begin
  if Self=nil then exit; // avoid GPF
  CheckYPos;
  if (fColumnHeaderList.Count > 0) and not fColumnHeaderPrinted then begin
    i := fColumnHeaderList.Count + 2;
    if not HasSpaceForLines(i) then
      NewPageInternal;
    PrintColumnHeaders;
  end;
  for i := 0 to high(fColumns) do
    with fColumns[i] do
      if ColAlign = caCurrency then
................................................................................
  SetLength(fColumns,0);
  ClearColumnHeaders;
end;

procedure TGDIPages.ClearColumnHeaders;
begin
  if Self=nil then exit; // avoid GPF
  fColumnHeaderList.clear;
end;

function TGDIPages.CreatePictureMetaFile(Width, Height: integer;
  out MetaCanvas: TCanvas): TMetaFile;
begin
  if Self=nil then
    result := nil else begin
    result := CreateMetaFile(MmToPrinterPxX(Width),MmToPrinterPxY(Height));
    MetaCanvas := CreateMetafileCanvas(result);
  end;
end;

procedure TGDIPages.DrawTextFmt(const s: string; const Args: array of const);

begin
  DrawText(format(s,Args));
end;

function TGDIPages.TitleFlags: integer;
begin
  result := ((Font.Size*12) div 10) or FORMAT_BOLD or FORMAT_LEFT;
end;







>







 







|
>
>







 







|
>
>
>







 







|
|







 







>







 







>
>
>
|




>
>
>
|




>
>
|


|
>







 







>
>
>
|
>







 







|
>
>
>
>
>
>
>







 







|







 







<
<
<
<
<
<







 







|



<
<

<
|
<
<
<
<
<
|












|
|

<
<
<
<
<
<

|







 







|







 







|
>
|
>








|










|







 







>
|







 







>
>

|







 







>







 







|
>
>
>
|
<







 







<







 







>







<







 







|

|


|





|
>
>
|








|






|

|







 







>







 







|
>







|







 







|
<







|
>
>
>
|
<
|







 







|












|
|
>
>
>
>
>
>
>









>







 







>


|






>







 







|
|







 







|
<
|
|
|
|
|



>
>
>
>
>
>
>







 







<





|
|







 







|












|
>

|







29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
...
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
...
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
...
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
...
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
...
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
...
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
...
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
....
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
....
1465
1466
1467
1468
1469
1470
1471






1472
1473
1474
1475
1476
1477
1478
....
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414


2415

2416





2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432






2433
2434
2435
2436
2437
2438
2439
2440
2441
....
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
....
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
....
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
....
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
....
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
....
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921

2922
2923
2924
2925
2926
2927
2928
....
3171
3172
3173
3174
3175
3176
3177

3178
3179
3180
3181
3182
3183
3184
....
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232

3233
3234
3235
3236
3237
3238
3239
....
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
....
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
....
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
....
4111
4112
4113
4114
4115
4116
4117
4118

4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130

4131
4132
4133
4134
4135
4136
4137
4138
....
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
....
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
....
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
....
4325
4326
4327
4328
4329
4330
4331
4332

4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
4353
4354
....
4371
4372
4373
4374
4375
4376
4377

4378
4379
4380
4381
4382
4383
4384
4385
4386
4387
4388
4389
4390
4391
....
4446
4447
4448
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
  the Initial Developer. All Rights Reserved.
  Portions created by Arnaud Bouchez for Synopse are Copyright (C) 2015
  Arnaud Bouchez. All Rights Reserved.

  Contributor(s):
  - Celery
  - Leo
  - Mike Lamusse (mogulza)

  Alternatively, the contents of this file may be used under the terms of
  either the GNU General Public License Version 2 or later (the "GPL"), or
  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  in which case the provisions of the GPL or the LGPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of either the GPL or the LGPL, and not to allow others to
................................................................................
  - added setter method for ZoomStatus property (during preview) - [dd656b470b]
  - added TGDIPages.ExportPDFStream() method - to be used e.g. on servers
  - fixed [cfdc644038] about truncated parenthesis in pdf export for caCurrency
  - fixed [e7ffb69131] about TGDIPages.DrawGraphic() when the TGraphic is Empty
  - allow preview as a blank colored component at design time (thanks to Celery)
  - added VisibleButtons optional parameter to TGDIPages.ShowPreviewForm method
    as requested by [4d64a52675]
  - added withNewLine optional parameter to DrawText*() methods so that you
    may be able to append some text without creating a new paragraph - from a
    proposal patch by Mike Lamusse (mogulza): thanks for sharing!

*)

interface

{.$define MOUSE_CLICK_PERFORM_ZOOM} // old not user-friendly behavior
{.$define RENDERPAGES} // TRenderBox and TRenderPages are not yet finished
................................................................................
    fCanvasText: string;
    fBeforeGroupText: string;
    fGroupPage: TMetafile;
    fPages: TGDIPageContentDynArray;
    fHeaderLines: TObjectList;
    fFooterLines: TObjectList;
    fColumns: array of TColRec;
    fColumnHeaderList: array of record
      headers: TSynUnicodeDynArray;
      flags: integer;
    end;
{$ifdef MOUSE_CLICK_PERFORM_ZOOM}
    fZoomTimer: TTimer;
{$endif}
    fPtrHdl: THandle;

    fTabCount: integer;
    fCurrentPrinter: string;
................................................................................
    procedure SetZoom(zoom: integer);
    procedure SetZoomStatus(aZoomStatus: TZoomStatus); 
    procedure ZoomTimerInternal(X,Y: integer; ZoomIn: boolean);
    procedure ZoomTimer(Sender: TObject);

    procedure LineInternal(start,finish: integer; DoubleLine: boolean);
    procedure PrintFormattedLine(s: SynUnicode; flags: integer;
      const aBookmark: string=''; const aLink: string=''; withNewLine: boolean=true);
    procedure LeftOrJustifiedWrap(const s: SynUnicode; withNewLine: boolean=true);
    procedure RightOrCenterWrap(const s: SynUnicode);
    procedure GetTextLimitsPx(var LeftOffset, RightOffset: integer);
    procedure HandleTabsAndPrint(const leftstring: SynUnicode;
      var rightstring: SynUnicode; leftOffset, rightOffset: integer);
    procedure PreviewPaint(Sender: TObject);
    procedure PreviewMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
................................................................................
    // get the hot region
    fLinks: TStringList;
    fLinksCurrent: integer;
    /// Strings[] are the outline titles, and Objects[] are TGDIPagereference
    // to get the Y position of the destination
    fOutline: TStringList;
    fInternalUnicodeString: SynUnicode;
    fForcedLeftOffset : integer;
    PreviewForm: TForm;
    PreviewButtons: array of TButton;
    PreviewPageCountLabel: TLabel;
    procedure CMFontChanged(var Msg: TMessage); message CM_FONTCHANGED;
    procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
    procedure WMEraseBkgnd(var Message: TWmEraseBkgnd); message WM_ERASEBKGND;
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
................................................................................
    procedure BeginDoc;
    /// Clear the current Report document
    procedure Clear; virtual;
    /// draw some text as a paragraph, with the current alignment
    // - this method does all word-wrapping and formating if necessary
    // - this method handle multiple paragraphs inside s (separated by newlines -
    // i.e. #13)
    // - by default, will write a paragraph, unless withNewLine is set to FALSE,
    // so that the next DrawText() will continue drawing at the current position 
    procedure DrawText(const s: string; withNewLine: boolean=true);
      {$ifdef HASINLINE}inline;{$endif} 
    /// draw some UTF-8 text as a paragraph, with the current alignment
    // - this method does all word-wrapping and formating if necessary
    // - this method handle multiple paragraphs inside s (separated by newlines -
    // i.e. #13)
    // - by default, will write a paragraph, unless withNewLine is set to FALSE,
    // so that the next DrawText() will continue drawing at the current position 
    procedure DrawTextU(const s: RawUTF8; withNewLine: boolean=true);
      {$ifdef HASINLINE}inline;{$endif}
    /// draw some Unicode text as a paragraph, with the current alignment
    // - this method does all word-wrapping and formating if necessary
    // - this method handle multiple paragraphs inside s (separated by newlines -
    // i.e. #13)
    // - by default, will write a paragraph, unless withNewLine is set to FALSE,
    // so that the next DrawText() will continue drawing at the current position 
    procedure DrawTextW(const s: SynUnicode; withNewLine: boolean=true); 
    /// draw some text as a paragraph, with the current alignment
    // - this method use format() like parameterss
    procedure DrawTextFmt(const s: string; const Args: array of const;
      withNewLine: boolean=true);
    /// get the formating flags associated to a Title
    function TitleFlags: integer;
    /// draw some text as a paragraph title
    // - the outline level can be specified, if UseOutline property is enabled
    // - if aBookmark is set, a bookmark is created at this position
    // - if aLink is set, a link to the specified bookmark name (in aLink) is made
    procedure DrawTitle(const s: SynUnicode; DrawBottomLine: boolean=false; OutlineLevel: Integer=0;
................................................................................
    /// Adds text to the page footer at the specified horizontal position and
    // using to current font. No Line feed will be triggered.
    // - if XPos=-1, will put the text at the current right margin
    procedure AddTextToFooterAt(const s: SynUnicode; XPos: integer);
    /// Will add the current 'Page n/n' text at the specified position
    // - PageText must be of format 'Page %d/%d', in the desired language
    // - if XPos=-1, will put the text at the current right margin
    // - if the vertical position does not fit your need, you could set
    // YPosMultiplier to a value which will be multipled by fFooterHeight to
    // compute the YPos
    procedure AddPagesToFooterAt(const PageText: string; XPos: integer;
      YPosMultiplier: integer=1);

    /// register a column, with proper alignment
    procedure AddColumn(left, right: integer; align: TColAlign; bold: boolean);
    /// register same alignement columns, with percentage of page column width
    // - sum of all percent width should be 100, but can be of any value
    // - negative widths are converted into absolute values, but
    // corresponding alignment is set to right
................................................................................
    // - call this method once with all columns text as CSV
    procedure AddColumnHeadersFromCSV(var CSV: PWideChar;
      WithBottomGrayLine: boolean; BoldFont: boolean=false; RowLineHeight: integer=0);
    /// draw some text, split across every columns
    // - if BackgroundColor is not clNone (i.e. clRed or clNavy or clBlack), the
    // row is printed on white with this background color (e.g. to highlight errors)
    procedure DrawTextAcrossCols(const StringArray: array of SynUnicode;
      BackgroundColor: TColor=clNone); overload;
    /// draw some text, split across every columns
    // - you can specify an optional bookmark name to be used to link a column
    // content via a AddLink() call
    // - if BackgroundColor is not clNone (i.e. clRed or clNavy or clBlack), the
    // row is printed on white with this background color (e.g. to highlight errors)
    procedure DrawTextAcrossCols(const StringArray, LinkArray: array of SynUnicode;
      BackgroundColor: TColor=clNone); overload;
    /// draw some text, split across every columns
    // - this method expect the text to be separated by commas
    // - if BackgroundColor is not clNone (i.e. clRed or clNavy or clBlack), the
    // row is printed on white with this background color (e.g. to highlight errors)
    procedure DrawTextAcrossColsFromCSV(var CSV: PWideChar; BackgroundColor: TColor=clNone);
    /// draw (double if specified) lines at the bottom of all currency columns
    procedure DrawLinesInCurrencyCols(doublelines: boolean);
................................................................................
    SpaceAfterCount: integer;
    /// associated link bookmark name
    // - from fLinksBookMarkName[LinkNumber-1], no link set for 0
    LinkNumber: integer;
  end;

  PRenderBoxLayout = ^TRenderBoxLayout;

  /// the internal "drawing" box structure used by TRenderBox
  // - TRenderBox.InternalRender populate fLayout[] with this structures,
  // ready to be drawn to the document Canvas
  TRenderBoxLayout = packed record
    /// pointer of the words in the fText[] array
    Text: PWideChar;
    /// number of PWideChar starting at Text^
................................................................................


implementation

uses
  {$ifdef ISDELPHIXE3}System.UITypes,{$endif}
  Types, Clipbrd, Consts;







// Miscellaneous functions ...

function TextExtent(Canvas: TCanvas; const Text: SynUnicode; Len: integer=0): TSize;
begin
  Result.cX := 0;
  Result.cY := 0;
................................................................................
  result.ColRight := 0;
  if Cardinal(col)<Cardinal(length(fColumns)) then
    result := fColumns[col];
end;

procedure TGDIPages.PrintColumnHeaders;
var
  i,SavedFontSize,FontCol: integer;
  SavedFontStyle: TFontStyles;
  SavedAlign: TTextAlign;
  SavedWordWrapLeftCols: boolean;




begin





  if (fColumnHeaderList = nil) or (fColumns=nil) then exit;
  CheckYPos;

  fColumnHeaderPrinted := true;   //stops an endless loop
  SavedFontSize := Font.size;
  SavedFontStyle := font.style;
  SavedAlign := fAlign;
  SavedWordWrapLeftCols := WordWrapLeftCols;
  WordWrapLeftCols := false;

  if Assigned(fStartColumnHeader) then
    fStartColumnHeader(Self);
  FontCol := fCanvas.Font.Color;
  for i := 0 to High(fColumnHeaderList) do begin
    SetFontWithFlags(fColumnHeaderList[i].flags);
    fCanvas.Font.Color := clBlack;






    fDrawTextAcrossColsDrawingHeader := true;
    DrawTextAcrossCols(fColumnHeaderList[i].headers,[],clNone);
    fDrawTextAcrossColsDrawingHeader := false;
  end;
  fCanvas.Font.Color := FontCol;
  if Assigned(fEndColumnHeader) then
    fEndColumnHeader(Self);
  // add a small space below the column headers
  // inc(fCurrentYPos,fLineHeight shr 2);
................................................................................
      MoveTo(start,Y);
      LineTo(finish,Y);
    end;
  end;
end;

procedure TGDIPages.PrintFormattedLine(s: SynUnicode; flags: integer;
  const aBookmark: string; const aLink: string; withNewLine: boolean); 
var i, xpos: integer;
    leftOffset, rightOffset: integer;
begin
  s := RightTrim(s);
  i := pos(PAGENUMBER,LowerCaseU(s));
  if i > 0 then begin
    delete(s,i,14);
................................................................................
  fCurrentTextTop := fCurrentYPos;
  fCurrentTextPage := PageCount;
  GetTextLimitsPx(leftOffset,rightOffset);
  if flags and (FORMAT_SINGLELINE or FORMAT_DOUBLELINE)<>0 then begin
    LineInternal(leftOffset,rightOffset,flags and FORMAT_DOUBLELINE=FORMAT_DOUBLELINE);
    NewLine;
  end else
  if s = '' then begin
    if withNewLine then
      NewLine;
  end else
  if (flags and FORMAT_XPOS_MASK <> 0) then begin
    xpos := ((flags and FORMAT_XPOS_MASK) shr 16)-2;
    if xpos<0 then
      xpos := RightMarginPos else
      inc(xpos);
    DrawTextAt(s,xpos);
  end else
  if (falign in  [taLeft,taJustified]) then
    LeftOrJustifiedWrap(s,withNewLine) else
    RightOrCenterWrap(s);
  if aBookmark<>'' then
    AddBookMark(aBookmark,fCurrentTextTop);
  if aLink<>'' then
    AddLink(aLink,Rect(PrinterPxToMmX(leftOffset),PrinterPxToMmY(fCurrentTextTop),
      PrinterPxToMmX(rightOffset),PrinterPxToMmY(fCurrentTextTop+fLineHeight)),
      fCurrentTextPage);
    // first line of written text is added
end;

procedure TGDIPages.LeftOrJustifiedWrap(const s: SynUnicode; withNewLine: boolean); 
var indent, leftOffset, rightOffset, LineWidth: integer;
    leftstring, rightstring: SynUnicode;
    firstLoop: boolean;
begin
  leftstring := s;
  Indent := MmToPrinterPxX(fHangIndent);
  firstLoop := true;
................................................................................
    // prepending any further text overrun into rightstring ...
    HandleTabsAndPrint(leftstring, rightstring, leftOffset, rightOffset);
    if length(rightstring)=0 then
      break;
    leftstring := rightstring;
    NewLine;
  until false;
  if withNewLine then
    NewLine; 
end;

procedure TGDIPages.RightOrCenterWrap(const s: SynUnicode);
var i,leftOffset,rightOffset, LineWidth: integer;
    leftstring,rightstring: SynUnicode;
    offset: integer;
begin
................................................................................
  NewLine;
end;

procedure TGDIPages.GetTextLimitsPx(var LeftOffset, RightOffset: integer);
begin
  // Offsets (in Printer pixels) based on current page margins
  LeftOffset := fPageMarginsPx.left;
  if fForcedLeftOffset <> -1 then
    leftOffset := fForcedLeftOffset; 
  RightOffset := fPhysicalSizePx.x-fPageMarginsPx.right;
  if RightOffset < LeftOffset then
    raise Exception.Create('GetTextLimitsPx: wrong margins');
end;

procedure TGDIPages.HandleTabsAndPrint(const leftstring: SynUnicode;
  var rightstring: SynUnicode; leftOffset, rightOffset: integer);
const
    // if a tabstop is very close to the right margin, it may spoil justifying...
................................................................................
  // OK, no TABS now in ls...
  InternalUnicodeString(ls,PW,PWLen,@size);
  // print ls into (remaining) linewidth at (leftOffset, fCurrentYPos)
  if (falign = taLeft) or (rightstring = '') then begin // left aligned
    if BiDiMode=bdRightToLeft then
      leftOffset := rightOffset-size.cx;
    TextOut(fCanvas,leftOffset,fCurrentYPos,PW,PWLen);
    fForcedLeftOffset := leftOffset+size.cx; 
    // don't care about line width: it should be always equal or smaller,
    // and we are left aligned
  end else begin // justified
    spacecount := 0;
    for i := 1 to length(ls) do
      if ls[i] = ' ' then
        inc(spacecount);
................................................................................
    if (PageRightButton.X<>0) and
       (cardinal(X-PageRightButton.X)<10) and
       (cardinal(Y-PageRightButton.Y)<20) then begin
      Page := Page+1;
      exit;
    end;
  end;
  if (Button=mbLeft) and (ssDouble in Shift) then 
    // allows dblclick to alternate between PAGE_FIT and PAGE_WIDTH
    if ZoomStatus = zsPageWidth then
      Zoom := PAGE_FIT else
      Zoom := PAGE_WIDTH else

{$ifndef MOUSE_CLICK_PERFORM_ZOOM}
  if Button=mbLeft then begin
    fButtonDown.X := (X shr 3)shl 3; // move 8 pixels by 8 pixels
    fButtonDown.Y := (Y shr 3)shl 3;
    fButtonDownScroll.X := HorzScrollBar.Position;
    fButtonDownScroll.Y := VertScrollBar.Position;
    Screen.Cursor := crHandPoint;
................................................................................
  Font.Size := 12;
  fLineSpacing := lsSingle;
  fOrientation := poPortrait;
  fUseOutlines := true;

  fHeaderLines := TObjectList.Create;
  fFooterLines := TObjectList.Create;


{$ifdef MOUSE_CLICK_PERFORM_ZOOM}
  fZoomTimer := TTimer.create(Self);
  fZoomTimer.Interval := 200;
  fZoomTimer.OnTimer := ZoomTimer;
  fZoomTimer.enabled := false;
{$else}
................................................................................
  fPreviewSurface.OnMouseUp := PreviewMouseUp;
  fPreviewSurface.OnMouseMove := PreviewMouseMove;
  fZoomStatus := zsPercent;
  fZoom := 100;
  fBookmarks := TStringList.Create;
  fLinks := TStringList.Create;
  fOutline := TStringList.Create;
  fForcedLeftOffset := -1; 
end;

destructor TGDIPages.Destroy;
begin
  Clear;
  fHeaderLines.free;
  fFooterLines.free;

  fPreviewSurface.free;
  PreviewSurfaceBitmap.Free;
{$ifdef MOUSE_CLICK_PERFORM_ZOOM}
  fZoomTimer.free;
{$endif}
  fOutline.Free;
  fLinks.Free;
................................................................................
    zsPercent:   zoom := fzoom;
    zsPageWidth: zoom := PAGE_WIDTH;
    else zoom := PAGE_FIT;
  end;
  fButtonDown.X := -1; // so MouseMove() won't scroll paintbox
end;

procedure TGDIPages.DrawText(const s: string; withNewLine : boolean);
begin
  DrawTextW(StringToSynUnicode(s), withNewLine);
end;

procedure TGDIPages.DrawTextW(const s: SynUnicode; withNewLine: boolean);
var P, Start: PWideChar;
    tmpStr: SynUnicode;
begin
  if Self=nil then exit;
  CheckYPos;
  if s = '' then begin
    if withNewLine then
      NewLine;
  end else begin
    // split NewLine characters (#13 or #13#10) into multi lines
    P := pointer(s);
    while P^ <> #0 do begin
      Start := P;
      while not (ord(P^) in [0, 10, 13]) do Inc(P);
      SetString(tmpStr, Start, P-Start);
      if not fInHeaderOrFooter then
        fCanvasText := fCanvasText+SynUnicodeToString(tmpStr)+#13#10;
      PrintFormattedLine(tmpStr, FORMAT_DEFAULT, '', '', withNewLine);
      if P^ = #13 then Inc(P);
      if P^ = #10 then Inc(P);
    end;
  end;
end;

procedure TGDIPages.DrawTextU(const s: RawUTF8; withNewLine: boolean);
begin
  DrawTextW(UTF8ToSynUnicode(s),withNewLine);
end;

procedure TGDIPages.DrawTitle(const s: SynUnicode; DrawBottomLine: boolean=false;
  OutlineLevel: Integer=0; const aBookmark: string=''; const aLink: string='');
var H: integer;
    str: string;
begin
................................................................................
end;

procedure TGDIPages.NewLine;
begin
  if Self=nil then exit; // avoid GPF
  CheckHeaderDone;
  inc(fCurrentYPos, GetLineHeight);
  fForcedLeftOffset := -1; 
//  fCanvasText := fCanvasText+#13#10;
end;

procedure TGDIPages.NewHalfLine;
begin
  if Self=nil then exit; // avoid GPF
  CheckHeaderDone;
................................................................................
  if fFooterLines.Count = 0 then
    CalcFooterGap;
  Foot := THeaderFooter.Create(Self,false,s,true);
  Foot.State.Flags := Foot.State.Flags or ((XPos+2) shl 16);
  fFooterLines.Add(Foot);
end;

procedure TGDIPages.AddPagesToFooterAt(const PageText: string;
  XPos,YPosMultiplier: integer);
begin
  if fPagesToFooterText<>'' then
    exit; // only add once
  fPagesToFooterText := PageText;
  if XPos<0 then
    fPagesToFooterAt.X := -1 else
    fPagesToFooterAt.X := MmToPrinterPxX(XPos);
  fPagesToFooterAt.Y := fFooterHeight * YPosMultiplier;
  fPagesToFooterState := SavedState;
end;

function TGDIPages.GetColumnCount: integer;
begin
  if Self=nil then
    result := 0 else
................................................................................
    left := right;
  end;
end;

procedure TGDIPages.AddColumnHeaders(const headers: array of SynUnicode;
  WithBottomGrayLine: boolean=false; BoldFont: boolean=false;
  RowLineHeight: integer=0; flags: integer=0);
var n,i: integer;

begin
  if Self=nil then exit; // avoid GPF
  if flags=0 then begin
    if BoldFont then
      Font.Style := [fsBold];
    flags := TextFormatsToFlags;
  end;
  n := length(fColumnHeaderList);
  SetLength(fColumnHeaderList,n+1);
  fColumnHeaderList[n].flags := flags;
  SetLength(fColumnHeaderList[n].headers,Length(headers));
  for i := 0 to high(headers) do

    fColumnHeaderList[n].headers[i] := headers[i];
  fColumnHeaderPrinted := false;
  fColumnHeaderPrintedAtLeastOnce := false;
  fColumnsWithBottomGrayLine := WithBottomGrayLine;
  fColumnsRowLineHeight := RowLineHeight;
  if BoldFont then
    Font.Style := [];
end;
................................................................................
    AddColumnHeaders(CSVToArray(CSV,length(fColumns)),
      WithBottomGrayLine,BoldFont,RowLineHeight);
end;

procedure TGDIPages.DrawTextAcrossColsFromCSV(var CSV: PWideChar; BackgroundColor: TColor=clNone);
begin
  if Self<>nil then // avoid GPF
    DrawTextAcrossCols(CSVToArray(CSV,length(fColumns)),[],BackgroundColor); 
end;

/// round inverted color to white or black
function clAlways(cl: TColor): TColor;
begin
  if ((GetRValue(longword(cl)) * 2) +
      (GetGValue(longword(cl)) * 3) +
      (GetBValue(longword(cl)) * 2)) < 600 then
    result := clWhite else
    result := clBlack;
end;

procedure TGDIPages.DrawTextAcrossCols(const StringArray: array of SynUnicode; 
  BackgroundColor: TColor);
begin
  DrawTextAcrossCols(StringArray,[],BackgroundColor);
end;

procedure TGDIPages.DrawTextAcrossCols(const StringArray, LinkArray: array of SynUnicode;
  BackgroundColor: TColor);

function HasCRLF(const s: SynUnicode): boolean;
var i: integer;
begin
  result := true;
  for i := 0 to length(s)-1 do
    if s[i+1]<' ' then
      exit;
  result := false;
end;

function WrapText(s: SynUnicode; MaxWidth: integer; Lines: PSynUnicodeDynArray): integer;
var j,k,sp: integer;
begin
  result := 0; // returns the line count
  if Lines<>nil then
    SetLength(Lines^,0);
  repeat
................................................................................
      SetLength(Lines^,length(Lines^)+1);
      Lines^[high(Lines^)] := copy(s,1,sp-1);
    end;
    inc(result); // update lines count
    s := trim(copy(s,sp,maxInt)); // trim ' ',#13,#10 for next line
  until s='';
end;

var RowRect: TRect;
    lh: integer;
    max, i, j, k, c, H, ParenthW, LinesCount, X: integer;
    s: SynUnicode;
    line: string;
    Lines: TSynUnicodeDynArray;
    PW: PWideChar;
    PWLen, Options: integer;
    size: TSize;
    r: TRect;
begin
  if Self=nil then exit; // avoid GPF
  max := high(fColumns);
  if (max<0) or (length(StringArray)=0) then
    exit; // no column defined
  if High(StringArray)<max then
    max := High(StringArray);
................................................................................
        LinesCount := k; // calculate maximum line count
    end;
    if (LinesCount>1) and not HasSpaceForLines(LinesCount) then begin
      NewPageInternal;
      CheckHeaderDone;
    end;
  end;
  if (fColumnHeaderList<>nil) and not fColumnHeaderPrinted then begin
    i := length(fColumnHeaderList) + 2;
    if not HasSpaceForLines(i) then
      NewPageInternal;
    PrintColumnHeaders;
  end;
  // prepare column write
  if Assigned(fGroupPage) then
    fColumnsUsedInGroup := true;
................................................................................
            RowRect.Left := ColRight-size.cx-ParenthW;
        end;
        caCenter:
          RowRect.Left := ColLeft+(ColRight-ColLeft-size.cx)shr 1;
        caRight:
          if BiDiMode=bdLeftToRight then
            RowRect.Left := ColRight-size.cx-ParenthW;
        caCurrency: begin

          if fNegsToParenthesesInCurrCols then
            InternalUnicodeString(ConvertNegsToParentheses(s),PW,PWLen,@size);
          RowRect.Left := ColRight-size.cx-ParenthW;
          // no bdRightToleft handling necessary for caCurrency
        end;
      end;
      dec(RowRect.Left,ParenthW);
      ExtTextOutW(Handle,RowRect.Left+ParenthW,fCurrentYPos,Options,@RowRect,PW,PWLen,nil);
      if (i<length(LinkArray)) and (LinkArray[i]<>'') then begin
        r.Left := PrinterPxToMmX(rowrect.Left);
        r.Top := PrinterPxToMmX(rowrect.Top);
        r.right := PrinterPxToMmX(rowrect.left+(rowrect.right-fColumns[0].ColLeft) div (max+1));
        r.Bottom := PrinterPxToMmX(rowrect.Bottom);
        AddLink(LinkArray[i],r);
      end;
      inc(RowRect.Left,size.cx+ParenthW);
      if ColBold then
        Font.Style := Font.Style-[fsBold];
    end;
  end;
  if not fDrawTextAcrossColsDrawingHeader or
     not fColumnHeaderPrintedAtLeastOnce then begin
................................................................................
    LineInternal(GetColumnRec(0).ColLeft,RowRect.Right,false);
    inc(fCurrentYPos, H);
    fCanvas.Pen.Color := c;
  end;
  NewLine;
end;


procedure TGDIPages.DrawLinesInCurrencyCols(doublelines: boolean);
var i: integer;
begin
  if Self=nil then exit; // avoid GPF
  CheckYPos;
  if (fColumnHeaderList<>nil) and not fColumnHeaderPrinted then begin
    i := length(fColumnHeaderList) + 2;
    if not HasSpaceForLines(i) then
      NewPageInternal;
    PrintColumnHeaders;
  end;
  for i := 0 to high(fColumns) do
    with fColumns[i] do
      if ColAlign = caCurrency then
................................................................................
  SetLength(fColumns,0);
  ClearColumnHeaders;
end;

procedure TGDIPages.ClearColumnHeaders;
begin
  if Self=nil then exit; // avoid GPF
  fColumnHeaderList := nil;
end;

function TGDIPages.CreatePictureMetaFile(Width, Height: integer;
  out MetaCanvas: TCanvas): TMetaFile;
begin
  if Self=nil then
    result := nil else begin
    result := CreateMetaFile(MmToPrinterPxX(Width),MmToPrinterPxY(Height));
    MetaCanvas := CreateMetafileCanvas(result);
  end;
end;

procedure TGDIPages.DrawTextFmt(const s: string; const Args: array of const;
  withNewLine: boolean);
begin
  DrawText(format(s,Args),withNewLine);
end;

function TGDIPages.TitleFlags: integer;
begin
  result := ((Font.Size*12) div 10) or FORMAT_BOLD or FORMAT_LEFT;
end;

Changes to SynopseCommit.inc.

1
'1.18.1041'
|
1
'1.18.1042'