#1 Re: mORMot 1 » mORMot and current FPC trunk doesn't work » 2016-12-21 22:29:32

Thanks for the info! I'll stick with r35095 for now.

#2 mORMot 1 » mORMot and current FPC trunk doesn't work » 2016-12-21 10:46:18

reddwarf
Replies: 10

When initializing mORMot with current FPC trunk the following exception is raised:

[Debugger Exception Notification]

Project Server raised exception class 'ESynException' with message:
TJSONRecordTextDefinition.Create: TSynMapSymbol text definition is not accurate, or the type has not been defined as PACKED record: RTTI size is 7735812 bytes but text definition generated 12 bytes

In file '..\..\units\SynCommons.pas' at line 38490

Backtrace:

#0 fpc_raiseexception(0x760a28, 0x1, 0x18655f8) at ..\inc\except.inc:158
#1 CREATE(0x18655f8, 0x1, 0x760a28, 0x760948 'Name:RawUTF8 Start,Stop:integer') at ..\..\units\SynCommons.pas:38490
#2 FROMCACHE(0x754994, 0x760a28, 0x760948 'Name:RawUTF8 Start,Stop:integer') at ..\..\units\SynCommons.pas:38471
#3 REGISTERFROMTEXT(0x180a920, 0x760a28, 0x760948 'Name:RawUTF8 Start,Stop:integer') at ..\..\units\SynCommons.pas:37428
#4 REGISTERCUSTOMJSONSERIALIZERFROMTEXT(0x754650, {}, 3) at ..\..\units\SynCommons.pas:46831
#5 SYNLOG_$$_init at :5500
#6 fpc_initializeunits at ..\inc\system.inc:914
#7 main at Server.lpr:16

This issue was opened somewhere in revisions 35123-35128 by svenbarth, which basically were patches by Maciej Izak for http://mantis.freepascal.org/view.php?id=30687.

Any chance to get this working?

+ SynSSPI.pas doesn't compile in FPC either (easy to fix: semicolons are missing).

#3 Re: Other components » Include SynTaskDialog to Lazarus LCL » 2016-09-21 16:32:57

Thank you both. I included it as LCLTaskDialog.pas: http://mantis.freepascal.org/view.php?id=30625
I removed the Delphi-specific IFDEFs and reused LCL icons. I also added a LCL header. If you don't like any of my modifications, please tell me so.

#5 Re: Other components » Include SynTaskDialog to Lazarus LCL » 2016-09-21 01:02:15

Great! Do you have a contact to Ulrich as well?

#6 Other components » Include SynTaskDialog to Lazarus LCL » 2016-09-19 22:39:38

reddwarf
Replies: 6

Hello Arnauld!

The Lazarus team is interested in including the cross-platform SynTaskDialog ( https://github.com/synopse/mORMot/tree/ … og4Lazarus ) into the LCL core. I'd like to write a Delphi-TTaskDialog clone based upon SynTaskDialog. The file has to be renamed (probably to TaskDialog.pas or TaskDlg.pas) and the license has to be enhanced to the LCL license (FPC modified LGPL).

The reason to include SynTaskDialog into core LCL is that a lot of users need such a basic dialog and the LCL itself needs a cross-platforms TTaskDialog clone.

Are you willing to relicense SynTaskDialog.pas (we need permission from Ulrich Gerhardt as well; we don't need the FMX port from Gergely Kovacs) and give us permission to include it into the LCL? Of course you'll be acknowledged as the original author and all the kudos will stay with you smile

Ondrej

#7 Re: Other components » SynTaskDialog.pas for Lazarus » 2015-03-06 12:56:32

I sent the full source to Arnauld already. If you want me to send it to you as well, write me an email through the forum.

#8 Re: Other components » SynTaskDialog.pas for Lazarus » 2015-03-05 14:28:03

Allow me one more question: what about initializing the TTaskDialog to an empty object? Doesn't it have to be done? What if there is some garbage data from previous calls?

procedure Test;
var Task: TTaskDialog;
begin
  Task.Inst := 'Saving application settings';
  Task.Content := 'This is the content';
  Task.Radios := 'Store settings in registry'#10'Store settings in XML file';
  Task.Verify := 'Do no ask for this setting next time';
  Task.VerifyChecked := true;
  Task.Footer := 'XML file is perhaps a better choice';
  Task.Execute([],0,[],tiQuestion,tfiInformation,200);
  ShowMessage(IntToStr(Task.RadioRes)); // 200=Registry, 201=XML
  if Task.VerifyChecked then
    ShowMessage(Task.Verify);
end;

#9 Other components » SynTaskDialog.pas for Lazarus » 2015-03-05 13:17:24

reddwarf
Replies: 5

Hello Arnauld!

I modified your SynTaskDialog.pas to be compatible with Lazarus (Windows, Linux, OSX). I also fixed some small issues. I would be happy if you added my changes to the trunk so that I have them available when I update Synopse!

Here is the patch code (if you want me to send you the whole file, I can do so as well).

3c3
< // licensed under a MPL/GPL/LGPL tri-license; version 1.18
---
> // licensed under a MPL/GPL/LGPL tri-license; version 1.19
32a33
>   - Ondrej Pokorny (reddwarf)
70a72,79
>   Version 1.19 (Ondrej Pokorny)
>   - added Lazarus support (native on Windows Vista+, emulated on all other
>     platforms - Windows, Linux and OSX tested)
>   - added external translation function for the emulated dialog
>    (TaskDialog_Translate)
>   - Alt+F4 is blocked now in emulated dialog (same behaviour in native)
>   - TODO: platform-independent icons
> 
78a88,100
>   {$IF (CompilerVersion >= 25.0)}// Delphi XE4 UP
>     {$LEGACYIFEND ON}
>   {$IFEND}
>   {$IF CompilerVersion >= 20}// Delphi 2009 IP
>     {$define WITHPOPUPPARENT}
>     {$define WITHNATIVEINT}
>   {$IFEND}
> {$ENDIF}
> {$IFDEF FPC}
>   {$MODE DELPHI}
>   {$define WITHUXTHEME}
>   {$define WITHPOPUPPARENT}
>   {$define WITHNATIVEINT}
82c104,111
<   Windows, CommCtrl, Classes, SysUtils, Consts, Messages,
---
>   {$IFDEF FPC}
>   LCLType, LCLStrConsts, LCLIntf,
>   {$ENDIF}
>   {$IFDEF MSWINDOWS}
>   Windows, CommCtrl, Messages,
>   {$ENDIF}
>   Classes, SysUtils,
>   {$IFNDEF FPC}Consts,{$ENDIF}
100a130
> {$IFDEF MSWINDOWS}
106c136
< 
---
> {$ENDIF}
164a195,198
>     procedure KeyDown(var Key: Word; Shift: TShiftState); override;
> 
>     constructor CreateNew(AOwner: TComponent; Num: Integer = 0); override;
>   public
278c312
<     VerifyChecked: BOOL;
---
>     VerifyChecked: Boolean;
301c335
<       aFooterIcon: TTaskDialogFooterIcon=tfiWarning;
---
>       {%H-}aFooterIcon: TTaskDialogFooterIcon=tfiWarning;
303c337
<       aNonNative: boolean=false; aEmulateClassicStyle: boolean = false;
---
>       {%H-}aNonNative: boolean=false; aEmulateClassicStyle: boolean = false;
424a459,464
> //function for translating the captions
> type
>   TTaskDialogTranslate = function(const aString: string): string;
> var
>   TaskDialog_Translate: TTaskDialogTranslate;
> 
426a467
> {$IFDEF MSWINDOWS}
428c469
< 
---
> {$ENDIF}
437,442c478,483
<     cbOK:     result := @SMsgDlgOK;
<     cbYes:    result := @SMsgDlgYes;
<     cbNo:     result := @SMsgDlgNo;
<     cbCancel: result := @SMsgDlgCancel;
<     cbRetry:  result := @SMsgDlgRetry;
<     cbClose:  result := @SCloseButton;
---
>     cbOK:     result := {$ifndef fpc}@SMsgDlgOK{$else}@rsMbOK{$endif};
>     cbYes:    result := {$ifndef fpc}@SMsgDlgYes{$else}@rsMbYes{$endif};
>     cbNo:     result := {$ifndef fpc}@SMsgDlgNo{$else}@rsMbNo{$endif};
>     cbCancel: result := {$ifndef fpc}@SMsgDlgCancel{$else}@rsMbCancel{$endif};
>     cbRetry:  result := {$ifndef fpc}@SMsgDlgRetry{$else}@rsMbRetry{$endif};
>     cbClose:  result := {$ifndef fpc}@SCloseButton{$else}@rsMbClose{$endif};
446a488,495
> function TD_Trans(const aString: string): string;
> begin
>   if Assigned(TaskDialog_Translate) then
>     Result := TaskDialog_Translate(aString)
>   else
>     Result := aString;
> end;
> 
455c504,505
<   Caption := LoadResString(TD_BTNS(Btn));
---
>   Caption := TD_Trans(LoadResString(TD_BTNS(Btn)));
> 
489a540,559
>   {$IFDEF FPC}
>   function StripHotkey(const Text: string): string;
>   var
>     I: Integer;
>   begin
>     Result := Text;
>     I := 1;
>     while I <= Length(Result) do
>     begin
>       if Result[i] = cHotkeyPrefix then
>         if SysLocale.FarEast and
>           ((I > 1) and (Length(Result)-I >= 2) and
>            (Result[I-1] = '(') and (Result[I+2] = ')')) then
>           Delete(Result, I-1, 4)
>         else
>           Delete(Result, I, 1);
>       Inc(I);
>     end;
>   end;
>   {$ENDIF}
491c561
<   result := StripHotkey(s);
---
>   Result := StripHotkey(s);
496a567
> {$IFDEF MSWINDOWS}
497a569,591
>   {$IFDEF FPC}
>     {$EXTERNALSYM IDI_APPLICATION}
>     IDI_APPLICATION = MakeIntResource(32512);
>     {$EXTERNALSYM IDI_HAND}
>     IDI_HAND = MakeIntResource(32513);
>     {$EXTERNALSYM IDI_QUESTION}
>     IDI_QUESTION = MakeIntResource(32514);
>     {$EXTERNALSYM IDI_EXCLAMATION}
>     IDI_EXCLAMATION = MakeIntResource(32515);
>     {$EXTERNALSYM IDI_ASTERISK}
>     IDI_ASTERISK = MakeIntResource(32516);
>     {$EXTERNALSYM IDI_WINLOGO}
>     IDI_WINLOGO = MakeIntResource(32517);
>     {$EXTERNALSYM IDI_SHIELD}
>     IDI_SHIELD  = MakeIntResource(32518);
>     {$EXTERNALSYM IDI_WARNING}
>     IDI_WARNING = IDI_EXCLAMATION;
>     {$EXTERNALSYM IDI_ERROR}
>     IDI_ERROR = IDI_HAND;
>     {$EXTERNALSYM IDI_INFORMATION}
>     IDI_INFORMATION = IDI_ASTERISK;
>   {$ENDIF}
> 
505a600
> {$ENDIF MSWINDOWS}
510,513c605,608
<     tiWarning:   result := SMsgDlgWarning;
<     tiQuestion:  result := SMsgDlgConfirm;
<     tiError:     result := SMsgDlgError;
<     tiInformation, tiShield: result := SMsgDlgInformation;
---
>     tiWarning:   result := {$ifndef fpc}SMsgDlgWarning{$else}rsMtWarning{$endif};
>     tiQuestion:  result := {$ifndef fpc}SMsgDlgConfirm{$else}rsMtConfirmation{$endif};
>     tiError:     result := {$ifndef fpc}SMsgDlgError{$else}rsMtError{$endif};
>     tiInformation, tiShield: result := {$ifndef fpc}SMsgDlgInformation{$else}rsMtInformation{$endif};
515a611
>   result := TD_Trans(result);
517a614
> {$IFDEF MSWINDOWS}
526a624
> {$ENDIF}
531a630,638
> function _WS(const aString: string): WideString;
> begin
>   {$IFDEF FPC}
>   Result := UTF8Decode(aString);
>   {$ELSE}
>   Result := WS(aString);
>   {$ENDIF}
> end;
> 
541a649
> {$IFDEF MSWINDOWS}
597a706
> {$ENDIF}
618c727
<     result := WideString(CR(tmp));
---
>     result := _WS(CR(tmp));
642c751
<     result := WS(CR(aText));
---
>     result := _WS(CR(aText));
643a753
> {$IFDEF MSWINDOWS}
668c778,784
< var Config: TTASKDIALOGCONFIG;
---
> {$ENDIF}
> var
>     {$IFDEF MSWINDOWS}
>     Config: TTASKDIALOGCONFIG;
>     Pic: TIcon;
>     Bmp: TBitmap;
>     {$ENDIF}
674,675d789
<     Pic: TIcon;
<     Bmp: TBitmap;
704c818,819
<   R.Bottom := DrawText(result.Canvas.Handle,pointer(Text),-1,R,DT_CALCRECT or DT_WORDBREAK);
---
>   DrawText(result.Canvas.Handle,PChar(Text),Length(Text),R,DT_CALCRECT or DT_WORDBREAK);//lazarus does not return box height on OSX (Lazarus bug), the height is stored in the rect in all cases, so we don't need to use the result
> 
766c881
<       aParent := Application.Handle;
---
>       aParent := {$ifndef fpc}Application.Handle{$else}0{$endif};
767a883
>   {$ifdef MSWINDOWS}
805a922
>   {$endif MSWINDOWS}
817a935,938
>     {$IFDEF FPC}
>     if FontHeight = 0 then
>       FontHeight := Screen.SystemFont.Height;
>     {$ENDIF}
840c961
<       {$ifdef WITHUXTHEME}
---
>       {$ifdef WITHUXTHEME}{$ifndef FPC}
842c963
<       {$endif}
---
>       {$endif}{$endif}
848a970
>     {$IFDEF MSWINDOWS}//TODO: custom icons for other platforms
858c980,982
<     end else begin
---
>     end else
>     {$ENDIF}
>     begin
994c1118
<           AddButton(LoadResString(TD_BTNS(B)), TD_BTNMOD[b]);
---
>           AddButton(TD_Trans(LoadResString(TD_BTNS(B))), TD_BTNMOD[b]);
1015a1140
>       {$IFDEF MSWINDOWS}//TODO: custom icons for other platforms
1036a1162,1163
>       {$ENDIF}
>       begin
1037a1165
>       end;
1041a1170,1187
> 
>     //set form parent
>     {$IFDEF WITHPOPUPPARENT}
>     if aParent <> 0 then
>       for I := 0 to Screen.CustomFormCount-1 do
>         if Screen.CustomForms[i].Handle = aParent then
>         begin
>           Dialog.Form.PopupParent := Screen.CustomForms[i];
>           Break;
>         end;
>     if not Assigned(Dialog.Form.PopupParent) then
>       Dialog.Form.PopupParent := Screen.ActiveCustomForm;
>     if Assigned(Dialog.Form.PopupParent) then
>     begin
>       Dialog.Form.PopupMode := pmExplicit;
>     end;
>     {$ENDIF}
> 
1061a1208
> {$IFDEF MSWINDOWS}
1063a1211
> {$ENDIF}
1068c1216,1218
<       Dialog.Form.Element[element].Caption := CR(Text) else
---
>       Dialog.Form.Element[element].Caption := CR(Text)
>     {$IFDEF MSWINDOWS}
>     else
1070c1220,1221
<         {$ifdef UNICODE}NativeInt{$else}integer{$endif}(pointer(WS(Text))));
---
>         {$ifdef WITHNATIVEINT}NativeInt{$else}integer{$endif}(pointer(_WS(Text))))
>     {$ENDIF};
1082a1234,1240
> constructor TEmulatedTaskDialog.CreateNew(AOwner: TComponent; Num: Integer);
> begin
>   inherited CreateNew(AOwner, Num);
> 
>   KeyPreview := True;
> end;
> 
1094a1253,1260
> procedure TEmulatedTaskDialog.KeyDown(var Key: Word; Shift: TShiftState);
> begin
>   if (Key = VK_F4) and (ssAlt in Shift) then//IMPORTANT: native task dialog blocks Alt+F4 to close the dialog -> we have to block it as well
>     Key := 0;
> 
>   inherited KeyDown(Key, Shift);
> end;
> 
1115a1282,1284
>     {$IFDEF FPC}
>     DefaultFont.Size := 11;//Lazarus seems not to like the height property -> we must use Size
>     {$ELSE}
1116a1286
>     {$ENDIF}
1121a1292,1294
>     {$IFDEF FPC}
>     DefaultFont.Size := 10;//Lazarus seems not to like the height property -> we must use Size
>     {$ELSE}
1122a1296
>     {$ENDIF}
1124a1299
>   {$IFDEF MSWINDOWS}
1125a1301
>   {$ENDIF}
1128a1305
>   {$IFDEF MSWINDOWS}//TODO: lazarus resource
1129a1307
>   {$ENDIF}
1131a1310
>   {$IFDEF MSWINDOWS}//TODO: lazarus resource
1132a1312
>   {$ENDIF}

#10 Re: mORMot 1 » many-to-many relation and retrieving a list of Dest objects » 2015-02-19 16:16:56

Thanks Arnauld, I'll try to add this functionality by myself and if I succeed, I'll send you the modifications back!

#11 Re: mORMot 1 » many-to-many relation and retrieving a list of Dest objects » 2015-02-18 17:35:41

Thanks a lot for your answers, Arnauld!

I read all the information around TSQLRecordMany, but I must be dumb smile

I use this code:

  xUserApp := TSQLUserApplication.Create;
  xUserApp.FillMany(
    DBConnection,
    UserId,
    MyWhere);

  while xUserApp.FillOne do
  begin
    xUserApp.Dest.DoSomething;
  end;

The xUserApp.Dest contains the Integer value of ID -> how do I get the Dest object quickly and easily?

#12 Re: mORMot 1 » many-to-many relation and retrieving a list of Dest objects » 2015-02-16 13:35:36

Thank you very much for your fast answer!

You are right, when I try to get a list of users with a linked application, it works fine:

  Result := TSQLUser.CreateAndFillPrepareMany(
    fDBConnection,
    PUTF8Char('(%.%=?)'),
    [TSQLUserApplication.SQLTableName, TSQLUserApplication.Consts.DestApplication],
    [fApplicationId]
    );

But I need it the other way, too.

+ I don't need mORMot to handle it automatically, if it's too much work. It would be OK for me if I could add a custom table JOIN definition to CreateAndFillPrepare (like you have e.g. the custom WHERE clause).

(+ Regarding PUTF8Char: I am probably not using the latest version of mORMot. I'll update ASAP.)

The requested information:
fRecordManySourceProp.ObjectClass.ClassName = TSQLUser
fRecordManyDestProp.ObjectClass.ClassName = TSQLApplication
That means that fRecordManySourceProp.ObjectClass.ClassName=PClass(self)^ is false (self is TSQLApplication).

#13 mORMot 1 » many-to-many relation and retrieving a list of Dest objects » 2015-02-16 12:15:43

reddwarf
Replies: 6

Hello Arnaud,

I am having trouble with many-to-many relation (TSQLUser = Source, TSQLApplication = Dest):

  TSQLUser = class;
  TSQLApplication = class;
  TSQLUserApplication = class;

  TSQLUser = class(TSQLRecord)
  private
    fFirstName: DBString;
    fLastName: DBString;

    fApplicationList: TSQLUserApplication;

  public type
    Consts = class(Cols)
    public const
      PluralLC = 'users';
      SingularLC = 'user';

      FirstName = 'FirstName';
      LastName = 'LastName';
    end;
  published
    property FirstName: DBString read fFirstName write fFirstName;
    property LastName: DBString read fLastName write fLastName;

    property ApplicationList: TSQLUserApplication read fApplicationList;
  end;

  TSQLApplication = class(TSQLRecord)
  private
    fCompanyName: DBString;

    fUserList: TSQLUserApplication;
  public type
    Consts = class(Cols)
    public const
      PluralLC = 'applications';
      SingularLC = 'application';

      CompanyName = 'CompanyName';
    end;
  published
    property CompanyName: DBString read fCompanyName write fCompanyName;

    property UserList: TSQLUserApplication read fUserList;
  end;

  TSQLUserApplication = class(TSQLRecordMany)
  public type
    Consts = class(Cols)
    public const
      SourceUser = 'Source';
      DestApplication = 'Dest';
    end;
  private
    fSource: TSQLUser;
    fDest: TSQLApplication;
  published
    property Source: TSQLUser read fSource write fSource;
    property Dest: TSQLApplication read fDest write fDest;
  end;

I want to get a list of applications linked with a user. I do it by this code:

  Result := TSQLApplication.CreateAndFillPrepareMany(
    fDBConnection,
    PUTF8Char('(%.%=?)'),
    [TSQLUserApplication.SQLTableName, TSQLUserApplication.Consts.SourceUser],
    [myUserId]
    );

However, I am getting an assertion failure in TSQLRecord.FillPrepareMany on this line:

      Assert((fRecordManySourceProp.ObjectClass=PClass(self)^)
         and (fRecordManyDestProp.ObjectClass<>nil));

Could you please tell me what I am doing wrong and point me to the correct direction?

BTW, I'll need also to get a list of users linked with an application.

#14 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2012-03-26 22:14:39

I'd have another suggestion according to SynTaskDialog. Your solution is not very good if task dialog is shown in modal windows - it can be hidden behind the modal window, unless you use aParent and run Win7. I'd change the aParent parameter in Execute() to TControl (aParent: TControl=nil;) and then change this code

  if aParent=0 then
    aParent := Application.Handle;

to

var xParentForm: TCustomForm;
begin

[ ... ]

  if not Assigned(aParent) then
    xParentForm := Screen.ActiveCustomForm
  else if (aParent is TCustomForm) then
    xParentForm := TCustomForm(aParent)
  else
    xParentForm := GetParentForm(aParent);

I use TControl because then aParent can easily be used as Self even from TFrame or custom components etc. GetParentForm() does the job automatically.

Then you change

Config.hwndParent := aParent;

to

Config.hwndParent := xParentForm.Handle;

and

result := Form.ShowModal;

to

Form.PopupParent := xParentForm;
Form.PopupMode := pmExplicit;
result := Form.ShowModal;

These changes become obvious if you run this code - your task dialog will hide behind a modal window and you can click the button even twice:

procedure TForm1.ShowTaskDialog(Sender: TObject);
var Task: SynTaskDialog.TTaskDialog;
begin
  Task.Inst := 'Saving application settings';
  Task.Content := 'This is the content';
  Task.Radios := 'Store settings in registry'#10'Store settings in XML file';
  Task.Verify := 'Do no ask for this setting next time';
  Task.VerifyChecked := true;
  Task.Footer := 'XML file is perhaps a better choice';
  Task.Execute([],0,[],tiQuestion,tfiInformation,200);
end;

procedure TForm1.BtnShowParentModalClick(Sender: TObject);
var xMF: TForm;
  xBtn: TButton;
begin
  xMF := TForm.CreateNew(TControl(Sender));
  try
    xMF.Left := Self.Left;
    xMF.Top := Self.Top;
    xMF.Width := 1000;
    xMF.Height := 500;
    //xMF.WindowState := wsMaximized;

    xBtn := TButton.Create(xMF);
    xBtn.Parent := xMF;
    xBtn.Caption := 'Show Task Dialog';
    xBtn.Width := 200;
    xBtn.OnClick := ShowTaskDialog;

    xMF.PopupParent := Self;
    xMF.PopupMode := pmExplicit;
    xMF.ShowModal;

  finally
    xMF.Free;
  end;
end;

Some parts of code (PopupParent := ...; PopupMode := ...;) may have to be excluded with conditions for older delphi versions.

Of course you can use the old aParent parameter, but if the dialog must be emulated, setting the PopupParent and PopupMode is very useful. The TControl parameter is also much more comfortable in my opinion.

+ btw I started using your logging system. It's great code and great help for me! Thanks!

#15 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2012-02-26 12:42:45

Hello!

I decided to use your SynTaskDialog in my projects now and I faced the problem of unicode support lack for non-unicode Delphi versions. I updated your code - tested under D2007 and XE2. Now nice unicode dialogs can be shown in D2007 and WinXP.

I use TntUnicodeControls for D2007 and emulation on XP. It can be switched off with the USETNTPACK directive (then, of course, D2007 unicode support for emulated dialog is lost, but the Win7 dialog under D2007 is still shown fine).

Honestly, I nearly haven't changed your code, only the type definitions and voila, everything works fine wink Good job from you!

Best

SynTaskDialog.pas:

/// implement TaskDialog window (native on Vista/Seven, emulated on XP)
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.16
unit SynTaskDialog;

{
    This file is part of Synopse framework.

    Synopse framework. Copyright (C) 2012 Arnaud Bouchez
      Synopse Informatique - http://synopse.info

  *** BEGIN LICENSE BLOCK *****
  Version: MPL 1.1/GPL 2.0/LGPL 2.1

  The contents of this file are subject to the Mozilla Public License Version
  1.1 (the "License"); you may not use this file except in compliance with
  the License. You may obtain a copy of the License at
  http://www.mozilla.org/MPL

  Software distributed under the License is distributed on an "AS IS" basis,
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  for the specific language governing rights and limitations under the License.

  The Original Code is Synopse framework.

  The Initial Developer of the Original Code is Arnaud Bouchez.

  Portions created by the Initial Developer are Copyright (C) 2012
  the Initial Developer. All Rights Reserved.

  Contributor(s):
  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
  use your version of this file under the terms of the MPL, indicate your
  decision by deleting the provisions above and replace them with the notice
  and other provisions required by the GPL or the LGPL. If you do not delete
  the provisions above, a recipient may use your version of this file under
  the terms of any one of the MPL, the GPL or the LGPL.

  ***** END LICENSE BLOCK *****

  Version 1.13
  - initial release

  Version 1.15
  - new tdfQueryMasked function to display * in the tdfQuery editor field

  Version 1.16
  - fixed issue when changing the current application with Alt+Tab - see
    http://synopse.info/fossil/tktview?name=01395e5932
  - fixed compiler error when using the unit with runtime packages enabled
    (known compiler issue about string resources, referenced as E2201)
  - some aesthetical rendering changes and code clean-up (e.g. no temporary
    form necessary), thanks to uligerhardt proposals

  Ondrej 2012-02-26
  - added WideString Support even for Delphi<=D2007 + Win 7
  - added TntControls for true unicode support even for Delphi<=D2007 + Win XP
}

interface

{$IFDEF CONDITIONALEXPRESSIONS}  // Delphi 6 or newer
  {$ifndef VER140} // Delphi 6
    {$define WITHUXTHEME} // Themes unit exists till Delphi 7
  {$endif}
{$ENDIF}

{$DEFINE USETNTPACK}//TRUE UNICODE SUPPORT FOR <Delphi2007 and WinXP
uses
  Windows, Classes, SysUtils, Consts, SynWideSupp,
  {$ifdef USETMSPACK}
  AdvGlowButton, AdvMenus, TaskDialog, TaskDialogEx,
  {$else}
    {$ifdef USETNTPACK}
    TntClasses, TntMenus, TntStdCtrls, TntButtons, TntExtCtrls, TntForms,
    {$else}
    Menus,
    {$endif USETNTPACK}
  {$endif USETMSPACK}
  {$ifndef UNICODE}WideStrUtils, {$endif}
  Graphics, Forms, Controls, StdCtrls, ExtCtrls, Buttons;

var
  /// will map a generic OK picture from SynTaskDialog.res
  BitmapOK: TBitmap;
  /// will map a generic Arrow picture from SynTaskDialog.res
  BitmapArrow: TBitmap;

  /// will map a default font, according to the available
  // - if Calibri is installed, will use it
  // - will fall back to Tahoma otherwise
  DefaultFont: TFont;

{$ifndef USETMSPACK}
  /// is filled once in the initialization block below
  // - you can set this reference to nil to force Delphi dialogs even
  // on Vista/Seven (e.g. make sense if TaskDialogBiggerButtons=true)
  TaskDialogIndirect: function(AConfig: pointer; Res: PInteger;
    ResRadio: PInteger; VerifyFlag: PBOOL): HRESULT; stdcall;

type

  /// the standard kind of common buttons handled by the Task Dialog
  TCommonButton = (
    cbOK, cbYes, cbNo, cbCancel, cbRetry, cbClose);

  /// set of standard kind of common buttons handled by the Task Dialog
  TCommonButtons = set of TCommonButton;

  /// the available main icons for the Task Dialog
  TTaskDialogIcon = (
    tiBlank, tiWarning, tiQuestion, tiError, tiInformation, tiNotUsed, tiShield);

  /// the available footer icons for the Task Dialog
  TTaskDialogFooterIcon = (
    tfiBlank, tfiWarning, tfiQuestion, tfiError, tfiInformation, tfiShield);

  /// the available configuration flags for the Task Dialog
  // - most are standard TDF_* flags used for Vista/Seven native API
  // (see http://msdn.microsoft.com/en-us/library/bb787473(v=vs.85).aspx
  // for TASKDIALOG_FLAGS)
  // - tdfQuery and tdfQueryMasked are custom flags, implemented in pure Delphi
  // code to handle input query
  // - our emulation code will handle only tdfUseCommandLinks,
  // tdfUseCommandLinksNoIcon, and tdfQuery options
  TTaskDialogFlag = (
    tdfEnableHyperLinks, tdfUseHIconMain, tdfUseHIconFooter,
    tdfAllowDialogCancellation, tdfUseCommandLinks, tdfUseCommandLinksNoIcon,
    tdfExpandFooterArea, tdfExpandByDefault, tdfVerificationFlagChecked,
    tdfShowProgressBar, tdfShowMarqueeProgressBar, tdfCallbackTimer,
    tdfPositionRelativeToWindow, tdfRtlLayout, tdfNoDefaultRadioButton,
    tdfCanBeMinimized, tdfQuery, tdfQueryMasked);

  /// set of available configuration flags for the Task Dialog
  TTaskDialogFlags = set of TTaskDialogFlag;

  /// implements a TaskDialog
  // - will use the new TaskDialog API under Vista/Seven, and emulate it with
  // pure Delphi code and standard themed VCL components under XP or 2K
  // - create a TTaskDialog object/record on the stack will initialize all
  // its string parameters to '' (it's a SHAME that since Delphi 2009, objects
  // are not initialized any more: we have to define this type as object before
  // Delphi 2009, and as record starting with Delphi 2009)
  // - set the appropriate string parameters, then call Execute() with all
  // additional parameters
  // - RadioRes/SelectionRes/VerifyChecked will be used to reflect the state
  // after dialog execution
  // - here is a typical usage:
  // !var Task: TTaskDialog;
  // !begin
  // !  Task.Inst := 'Saving application settings';
  // !  Task.Content := 'This is the content';
  // !  Task.Radios := 'Store settings in registry'#10'Store settings in XML file';
  // !  Task.Verify := 'Do no ask for this setting next time';
  // !  Task.VerifyChecked := true;
  // !  Task.Footer := 'XML file is perhaps a better choice';
  // !  Task.Execute([],0,[],tiQuestion,tfiInformation,200);
  // !  ShowMessage(IntToStr(Task.RadioRes)); // 200=Registry, 201=XML
  // !  if Task.VerifyChecked then
  // !    ShowMessage(Task.Verify);
  // !end;
  TTaskDialog = {$ifdef UNICODE}record{$else}object{$endif}
    /// the main title of the dialog window
    // - if left void, the title of the application main form is used
    Title: SynWideString;
    /// the main instruction (first line on top of window)
    // - any '\n' will be converted into a line feed
    // - if left void, the text is taken from the current dialog icon kind
    Inst: SynWideString;
    /// the dialog's primary content content text
    // - any '\n' will be converted into a line feed
    Content: SynWideString;
    /// a #13#10 or #10 separated list of custom buttons
    // - they will be identified with an ID number starting at 100
    // - by default, the buttons will be created at the dialog bottom, just
    // like the common buttons
    // - if tdfUseCommandLinks flag is set, the custom buttons will be created
    // as big button in the middle of the dialog window; in this case, any
    // '\n' will be converted as note text (shown with smaller text under native
    // Vista/Seven TaskDialog, or as popup hint within Delphi emulation)
    Buttons: SynWideString;
    /// a #13#10 or #10 separated list of custom radio buttons
    // - they will be identified with an ID number starting at 200
    // - aRadioDef parameter can be set to define the default selected value
    // - '\n' will be converted as note text (shown with smaller text under
    // native Vista/Seven TaskDialog, or as popup hint within Delphi emulation)
    Radios: SynWideString;
    /// the expanded information content text
    // - any '\n' will be converted into a line feed
    // - the Delphi emulation will always show the Info content (there is no
    // collapse/expand button)
    Info: SynWideString;
    /// the button caption to be displayed when the information is collapsed
    // - not used under XP: the Delphi emulation will always show the Info content
    InfoExpanded: SynWideString;
    /// the button caption to be displayed when the information is expanded
    // - not used under XP: the Delphi emulation will always show the Info content
    InfoCollapse: SynWideString;
    /// the footer content text
    // - any '\n' will be converted into a line feed
    Footer: SynWideString;
    /// the text of the bottom most optional checkbox
    Verify: SynWideString;
    /// a #13#10 or #10 separated list of items to be selected
    // - if set, a Combo Box will be displayed to select
    // - if tdfQuery is in the flags, the combo box will be in edition mode,
    // and the user will be able to edit the Query text or fill the field
    // with one item of the selection
    // - this selection is not handled via the Vista/Seven TaskDialog, but
    // with our Delphi emulation code (via a TComboBox)
    Selection: SynWideString;
    /// some text to be edited
    // - if tdfQuery is in the flags, will contain the default query text
    // - if Selection is set, the
    Query: SynWideString;
    /// the selected radio item
    // - first is numeroted 0
    RadioRes: integer;
    /// after execution, contains the selected item from the Selection list
    SelectionRes: integer;
    /// reflect the the bottom most optional checkbox state
    // - if Verify is not '', should be set before execution
    // - after execution, will contain the final checkbox state
    VerifyChecked: BOOL;
    /// launch the TaskDialog form
    // - some common buttons can be set via aCommonButtons
    // - in emulation mode, aFlags will handle only tdfUseCommandLinks,
    // tdfUseCommandLinksNoIcon, and tdfQuery options
    // - will return 0 on error, or the Button ID (e.g. mrOk for the OK button
    // or 100 for the first custom button defined in Buttons string)
    // - if Buttons was defined, aButtonDef can set the selected Button ID 
    // - if Radios was defined, aRadioDef can set the selected Radio ID
    // - aDialogIcon and aFooterIcon are used to specify the displayed icons
    // - aWidth can be used to force a custom form width (in pixels)
    // - aParent can be set to any HWND - by default, Application.DialogHandle
    // - if aNonNative is TRUE, the Delphi emulation code will always be used
    // - aEmulateClassicStyle can be set to enforce conformity with the non themed
    // user interface - see @http://synopse.info/forum/viewtopic.php?pid=2867#p2867
    function Execute(aCommonButtons: TCommonButtons=[];
      aButtonDef: integer=0; aFlags: TTaskDialogFlags=[];
      aDialogIcon: TTaskDialogIcon=tiInformation;
      aFooterIcon: TTaskDialogFooterIcon=tfiWarning;
      aRadioDef: integer=0; aWidth: integer=0; aParent: HWND=0;
      aNonNative: boolean=false; aEmulateClassicStyle: boolean = false): integer;
  end;

  /// a wrapper around the TTaskDialog.Execute method
  // - used to provide a "flat" access to task dialog parameters
  TTaskDialogEx = {$ifdef UNICODE}record{$else}object{$endif}
    /// the associated main TTaskDialog instance
    Base: TTaskDialog;
    /// some common buttons to be displayed
    CommonButtons: TCommonButtons;
    /// the default button ID
    ButtonDef: integer;
    /// the associated configuration flags for this Task Dialog
    // - in emulation mode, aFlags will handle only tdfUseCommandLinks,
    // tdfUseCommandLinksNoIcon, and tdfQuery options
    Flags: TTaskDialogFlags;
    /// used to specify the dialog icon
    DialogIcon: TTaskDialogIcon;
    /// used to specify the footer icon
    FooterIcon: TTaskDialogFooterIcon;
    /// the default radio button ID
    RadioDef: integer;
    /// can be used to force a custom form width (in pixels)
    Width: integer;
    /// if TRUE, the Delphi emulation code will always be used
    NonNative: boolean;
    /// can be used to enforce conformity with the non themed user interface
    EmulateClassicStyle: boolean;
    /// main (and unique) method showing the dialog itself
    // - is in fact a wrapper around the TTaskDialog.Execute method
    function Execute(aParent: HWND=0): integer;
  end;
{$endif USETMSPACK}

type
{$ifdef USETMSPACK}
  /// a TMS PopupMenu
  TSynPopupMenu = TAdvPopupMenu;

  TSynButtonParent = TAdvGlowButton;
{$else}
{$ifdef USETNTPACK}
  TSynPopupMenu = TTntPopupMenu;

  TSynButtonParent = {$ifdef WITHUXTHEME}TTntBitBtn{$else}TTntButton{$endif};
{$else}
  /// a generic VCL popup menu
  TSynPopupMenu = TPopupMenu;

  TSynButtonParent = {$ifdef WITHUXTHEME}TBitBtn{$else}TButton{$endif};
{$endif USETNTPACK}
{$endif USETMSPACK}

{$ifdef USETNTPACK}
  TSynEdit = TTntEdit;
  TSynPanel = TTntPanel;
  TSynCheckBox = TTntCheckBox;
  TSynComboBox = TTntComboBox;
  TSynRadioButton = TTntRadioButton;
  TSynLabel = TTntLabel;
  TSynForm = TTntForm;

  TSynWideStrings = TTntStrings;
  TSynWideStringList = TTntStringList;
{$else}
  TSynEdit = TEdit;
  TSynPanel = TPanel;
  TSynCheckBox = TCheckBox;
  TSynComboBox = TComboBox;
  TSynRadioButton = TRadioButton;
  TSynLabel = TLabel;
  TSynForm = TForm;

  TSynWideStrings = TStrings;
  TSynWideStringList = TStringList;
{$endif USETNTPACK}

  /// a generic Button to be used in the User Interface
  // - is always a Themed button: under Delphi 6, since TBitBtn is not themed,
  // it will be a row TButton with no glyph... never mind...
  TSynButton = class(TSynButtonParent)
  protected
{$ifndef USETMSPACK}
    fDropDownMenu: TSynPopupMenu;
{$endif}
  public
    /// create a standard button instance
    // - ModalResult/Default/Cancel properties will be set as exepcted for this
    // kind of button
    constructor CreateKind(Owner: TWinControl; Btn: TCommonButton;
      Left, Right, Width, Height: integer);
    /// set the glyph of the button
    // - set nothing under Delphi 6
    procedure SetBitmap(Bmp: TBitmap);
{$ifndef USETMSPACK}
    /// drop down the associated Popup Menu
    procedure DoDropDown;
    /// the associated Popup Menu to drop down
    property DropDownMenu: TSynPopupMenu read fDropDownMenu write fDropDownMenu;
{$endif}
  end;

/// return the text without the '&' characters within
function UnAmp(const s: SynWideString): SynWideString;

{$ifndef USETMSPACK}
var
{
  /// if set to TRUE, buttons will be bigger than default
  // - can be useful e.g. for touch screens
  // - will work only for the Delphi emulated version (aNonNative=true) of
  // TSynTask - could be combined with @TaskDialogIndirect := nil;
  TaskDialogBiggerButtons: boolean = false;
}
  /// a default Task Dialog wrapper instance
  // - can be used to display some information with less parameters
  DefaultTaskDialog: TTaskDialogEx = (
    (*
    CommonButtons: [];
    ButtonDef: 0;
    Flags: [];
    *)
    DialogIcon: tiInformation;
    FooterIcon: tfiWarning;
    (*
    RadioDef: 0;
    Width: 0;
    NonNative: false;
    EmulateClassicStyle: false;
    *)
    );
{$endif}

implementation

{$R SynTaskDialog.res}


const
  TD_BTNS: array[TCommonButton] of string = (
    SMsgDlgOK, SMsgDlgYes, SMsgDlgNo, SMsgDlgCancel, SMsgDlgRetry,
    SCloseButton);
  TD_BTNMOD: array[TCommonButton] of Integer = (
    mrOk, mrYes, mrNo, mrCancel, mrRetry, mrAbort);

{ TSynButton }

constructor TSynButton.CreateKind(Owner: TWinControl; Btn: TCommonButton;
  Left, Right, Width, Height: integer);
begin
  Create(Owner);
  Parent := Owner;
  SetBounds(Left,Right,Width,Height);
  Caption := TD_BTNS[Btn];
  ModalResult := TD_BTNMOD[Btn];
  case Btn of
    cbOK:     Default := true;
    cbCancel: Cancel := true;
  end;
  case Btn of
    cbOK: SetBitmap(BitmapOK);
  end;
end;

{$ifndef USETMSPACK}
procedure TSynButton.DoDropDown;
begin
  if DropDownMenu<>nil then
    with ClientToScreen(BoundsRect.TopLeft) do
      DropDownMenu.Popup(X,Y+Height);
end;
{$endif}

procedure TSynButton.SetBitmap(Bmp: TBitmap);
begin
  if Bmp<>nil then
    {$ifdef USETMSPACK}
    Picture.Assign(Bmp);
    {$else}
      {$ifdef WITHUXTHEME}
      Glyph := Bmp;
      {$else}
      // Delphi 6 TBitBtn has no theming -> use generic TButton without glyph
      {$endif}
    {$endif}
end;

function UnAmp(const s: SynWideString): SynWideString;
var i: integer;
begin
  result := s;
  repeat
    i := pos('&',result);
    if i=0 then
      exit;
    delete(result,i,1);
  until false;
end;


{$ifndef USETMSPACK}

const
  TD_ICONS: array[TTaskDialogIcon] of integer = (
    17, 84, 99, 98, 81, 0, 78);
  TD_ICONS_IDENT: array[TTaskDialogIcon] of string = (
    '', SMsgDlgWarning, SMsgDlgConfirm, SMsgDlgError, SMsgDlgInformation,
    '', SMsgDlgInformation);
  TD_FOOTERICONS: array[TTaskDialogFooterIcon] of integer = (
    17, 84, 99, 98, 65533, 65532);
  WIN_ICONS: array[TTaskDialogIcon] of PChar = (
    nil, IDI_WARNING, IDI_QUESTION, IDI_ERROR, IDI_INFORMATION, nil, IDI_WINLOGO);
  WIN_FOOTERICONS: array[TTaskDialogFooterIcon] of PChar = (
    nil, IDI_WARNING, IDI_QUESTION, IDI_ERROR, IDI_INFORMATION, IDI_WINLOGO);

procedure InitComCtl6;
var OSVersionInfo: TOSVersionInfo;
begin
  OSVersionInfo.dwOSVersionInfoSize := sizeof(OSVersionInfo);
  GetVersionEx(OSVersionInfo);
  if OSVersionInfo.dwMajorVersion<6 then
    @TaskDialogIndirect := nil else
    @TaskDialogIndirect := GetProcAddress(GetModuleHandle(comctl32),'TaskDialogIndirect');
end;

{ TTaskDialog }

type
  // see http://msdn.microsoft.com/en-us/library/bb787473(v=VS.85).aspx
  PTASKDIALOG_BUTTON = ^TTASKDIALOG_BUTTON;
  TTASKDIALOG_BUTTON = packed record
    nButtonID: integer;
    pszButtonText: PWideChar;
  end;

  TTASKDIALOGCONFIG = packed record
    cbSize: integer;
    hwndParent: HWND;
    hInstance: THandle;
    dwFlags: integer;
    dwCommonButtons: integer;
    pszWindowTitle: PWideChar;
    hMainIcon: integer;
    pszMainInstruction: PWideChar;
    pszContent: PWideChar;
    cButtons: integer;
    pButtons: PTASKDIALOG_BUTTON;
    nDefaultButton: integer;
    cRadioButtons: integer;
    pRadioButtons: PTASKDIALOG_BUTTON;
    nDefaultRadioButton: integer;
    pszVerificationText: PWideChar;
    pszExpandedInformation: PWideChar;
    pszExpandedControlText: PWideChar;
    pszCollapsedControlText: PWideChar;
    hFooterIcon: HICON;
    pszFooter: PWideChar;
    pfCallback: pointer;
    lpCallbackData: pointer;
    cxWidth: integer;
  end;


function TTaskDialog.Execute(aCommonButtons: TCommonButtons;
  aButtonDef: integer; aFlags: TTaskDialogFlags;
  aDialogIcon: TTaskDialogIcon; aFooterIcon: TTaskDialogFooterIcon;
  aRadioDef, aWidth: integer; aParent: HWND; aNonNative: boolean;
  aEmulateClassicStyle: boolean): integer;

function CR(const aText: SynWideString): SynWideString;
begin
  if pos('\n',aText)=0 then
    result := aText else
    result := SynWideStringReplace(aText,'\n',#10,[rfReplaceAll]);
end;
function GetNextStringLineToWS(var P: PSynWideChar): SynWideString;
var S: PSynWideChar;
begin
  if P=nil then
    result := '' else begin
    S := P;
    while S[0]>=' ' do
      inc(S);
    SetString(result,P,S-P);
    result := CR(result);
    while (S^<>#0) and (S^<' ') do inc(S); // ignore e.g. #13 or #10
    if S^<>#0 then
      P := S else
      P := nil;
  end;
end;
var aHint: SynWideString;
function NoCR(const aText: SynWideString): SynWideString;
var i: integer;
begin
  result := aText;
  aHint := '';
  i := pos('\n',result);
  if i>0 then begin
    aHint := CR(copy(result,i+2,maxInt));
    SetLength(result,i-1);
  end;
end;
function N(const aText: SynWideString): SynWideString;
begin
  if aText='' then
    result := '' else
    result := CR(aText);
end;
var RU: array of SynWideString;
    RUCount: integer;
    But: array of TTASKDIALOG_BUTTON;
procedure AddRU(Text: SynWideString; var n: integer; firstID: integer);
var P: PSynWideChar;
begin
  if Text='' then
    exit;
  Text := SysUtils.trim(Text);
  P := @Text[1]; // '\n' handling in GetNextStringLineToWS(P) will change P^
  while P<>nil do begin
    if length(RU)<=RUCount then begin
      SetLength(RU,RUCount+16);
      SetLength(But,RUCount+16);
    end;
    RU[RUCount] := GetNextStringLineToWS(P);
    with But[RUCount] do begin
      nButtonID := n+firstID;
      pszButtonText := pointer(RU[RUCount]);
    end;
    inc(n);
    inc(RUCount);
  end;
end;
var Config: TTASKDIALOGCONFIG;
    i, X, Y, XB, IconBorder, FontHeight: integer;
    Par: TWinControl;
    Panel: TSynPanel;
    CurrTabOrder: TTabOrder;
    Form: TSynForm;
    Image: TImage;
    Pic: TIcon;
    Bmp: TBitmap;
    Edit: TSynEdit;
    Combo: TSynComboBox;
    List: TSynWideStrings;
    B: TCommonButton;
    CommandLink: TSynButton;
    Rad: array of TSynRadioButton;
    Verif: TSynCheckBox;
function AddLabel(const Text: SynWideString; BigFont: boolean): TSynLabel;
begin
  result := TSynLabel.Create(Form);
  result.Parent := Par;
  result.WordWrap := true;
  if BigFont then begin
    if aEmulateClassicStyle then begin
      result.Font.Height := FontHeight-2;
      result.Font.Style := [fsBold]
    end else begin
      result.Font.Height := FontHeight-4;
      result.Font.Color := $B00000;
    end;
  end else
    result.Font.Height := FontHeight;
  result.Left := X;
  result.Top := Y;
  result.Width := aWidth-X-8;
  result.Caption := CR(Text);
  inc(Y,result.Height+16);
end;
procedure AddBevel;
var BX: integer;
begin
  with TBevel.Create(Form) do begin
    Parent := Par;
    if (Image<>nil) and (Y<Image.Top+Image.Height) then
      BX := X else
      BX := 2;
    SetBounds(BX,Y,aWidth-BX-2,2);
  end;
  inc(Y,16);
end;
function AddButton(s: SynWideString; ModalResult: integer): TSynButton;
var WB: integer;
begin
  s := UnAmp(s);
  WB := Form.Canvas.TextWidth(s)+52;
  dec(XB,WB);
  if XB<X shr 1 then begin
    XB := aWidth-WB;
    inc(Y,32);
  end;
  result := TSynButton.Create(Form);
  result.Parent := Par;
    if aEmulateClassicStyle then
      result.SetBounds(XB,Y,WB-10,22) else
      result.SetBounds(XB,Y,WB-12,28);
  result.Caption := s;
  result.ModalResult := ModalResult;
  result.TabOrder := CurrTabOrder;
  case ModalResult of
    mrOk: begin
      result.Default := true;
      if aCommonButtons=[cbOk] then
        result.Cancel := true;
    end;
    mrCancel: result.Cancel := true;
  end;
  if ModalResult=aButtonDef then
    Form.ActiveControl := result;
end;

begin
  if (byte(aCommonButtons)=0) and (Buttons='') then begin
    aCommonButtons := [cbOk];
    if aButtonDef=0 then
      aButtonDef := mrOk;
  end;
  if Title='' then
    if Application.MainForm=nil then
      Title := Application.Title else
      Title := Application.MainForm.Caption;
  if (Inst='') and (TD_ICONS_IDENT[aDialogIcon]<>'') then
    Inst := TD_ICONS_IDENT[aDialogIcon];
  if aParent=0 then
    aParent := Application.Handle;
  if Assigned(TaskDialogIndirect) and not aNonNative and
     not (tdfQuery in aFlags) and (Selection='') then begin
    // use Vista/Seven TaskDialog implementation (not tdfQuery nor Selection)
    FillChar(Config,sizeof(Config),0);
    Config.cbSize := sizeof(Config);
    Config.hwndParent := aParent;
    Config.pszWindowTitle := pointer(N(Title));
    Config.pszMainInstruction := pointer(N(Inst));
    Config.pszContent := pointer(N(Content));
    RUCount := 0;
    AddRU(Buttons,Config.cButtons,100);
    AddRU(Radios,Config.cRadioButtons,200);
    if Config.cButtons>0 then
      Config.pButtons := @But[0];
    if Config.cRadioButtons>0 then
      Config.pRadioButtons := @But[Config.cButtons];
    Config.pszVerificationText := pointer(N(Verify));
    Config.pszExpandedInformation := pointer(N(Info));
    Config.pszExpandedControlText := pointer(N(InfoExpanded));
    Config.pszCollapsedControlText := pointer(N(InfoCollapse));
    Config.pszFooter := pointer(N(Footer));
    Config.dwCommonButtons := byte(aCommonButtons);
    if (Verify<>'') and VerifyChecked then
      include(aFlags,tdfVerificationFlagChecked);
    if (Config.cButtons=0) and (aCommonButtons=[cbOk]) then
      Include(aFlags,tdfAllowDialogCancellation); // just OK -> Esc/Alt+F4 close
    Config.dwFlags := integer(aFlags);
    Config.hMainIcon := TD_ICONS[aDialogIcon];
    Config.hFooterIcon := TD_FOOTERICONS[aFooterIcon];
    Config.nDefaultButton := aButtonDef;
    Config.nDefaultRadioButton := aRadioDef;
    Config.cxWidth := aWidth;
    if TaskDialogIndirect(@Config,@result,@RadioRes,@VerifyChecked)<>S_OK then
      result := 0; // error (mostly invalid argument)
  end else begin
    // use our native (naive?) Delphi implementation
    Verif := nil;
    Combo := nil;
    Edit := nil;
    Form := TSynForm.Create(Application);
    try
      // initialize form properties
      Form.BorderStyle := bsDialog;
      Form.BorderIcons := [];
      Form.Position := poScreenCenter;
      if not aEmulateClassicStyle then
        Form.Font := DefaultFont;
      FontHeight := Form.Font.Height;
      if aWidth=0 then begin
        aWidth := Form.Canvas.TextWidth(Inst);
        if (aWidth>300) or (Form.Canvas.TextWidth(Content)>300) or
           (length(Buttons)>40) then
          aWidth := 480 else
          aWidth := 420;
      end;
      Form.ClientWidth := aWidth;
      Form.Height := 200;
      Form.Caption := Title;
      // create a white panel for the main dialog part
      Panel := TSynPanel.Create(Form);
      Panel.Parent := Form;
      Panel.Align := alTop;
      Panel.BorderStyle := bsNone;
      Panel.BevelOuter := bvNone;
      if not aEmulateClassicStyle then begin
        {$ifdef HASINLINE}
        Panel.BevelEdges := [beBottom];
        Panel.BevelKind := bkFlat;
        {$endif}
        Panel.Color := clWhite;
        {$ifdef WITHUXTHEME}
        Panel.ParentBackground := false; // clWhite not used otherwise
        {$endif}
      end;
      Par := Panel;
      // handle main dialog icon
      if aEmulateClassicStyle then
        IconBorder := 10 else
        IconBorder := 24;
       if WIN_ICONS[aDialogIcon]<>nil then begin
        Image := TImage.Create(Form);
        Image.Parent := Par;
        Image.Picture.Icon.Handle := LoadIcon(0,WIN_ICONS[aDialogIcon]);
        Image.SetBounds(IconBorder,IconBorder,Image.Picture.Icon.Width,Image.Picture.Icon.Height);
        X := Image.Width+IconBorder*2;
        Y := Image.Top;
        if aEmulateClassicStyle then
          inc(Y, 8);
      end else begin
        Image := nil;
        if not aEmulateClassicStyle then
          IconBorder := IconBorder*2;
        X := IconBorder;
        Y := IconBorder;
      end;
      // add main texts (Instruction, Content, Information)
      AddLabel(Inst,true);
      AddLabel(Content,false);
      if Info<>'' then
        // no information collapse/expand yet: it's always expanded
        AddLabel(Info,false);
      // add command links buttons
      if (tdfUseCommandLinks in aFlags) and (Buttons<>'') then
        with TSynWideStringList.Create do
        try
          inc(Y,8);
          Text := SysUtils.trim(Buttons);
          for i := 0 to Count-1 do begin
            CommandLink := TSynButton.Create(Form);
            with CommandLink do begin
              Parent := Par;
              Font.Height := FontHeight-3;
              if aEmulateClassicStyle then
                SetBounds(X,Y,aWidth-10-X,40) else
                SetBounds(X,Y,aWidth-16-X,40);
              Caption := NoCR(Strings[i]);
              if aHint<>'' then begin
                ShowHint := true;
                Hint := aHint; // note shown as Hint
              end;
              inc(Y,Height+2);
              ModalResult := i+100;
              if ModalResult=aButtonDef then
                Form.ActiveControl := CommandLink;
              if aEmulateClassicStyle then begin
                Font.Height := FontHeight - 2;
                Font.Style := [fsBold]
              end;
              {$ifdef WITHUXTHEME}
              if aEmulateClassicStyle then begin
                Margin := 7;
                Spacing := 7;
              end else begin
                Margin := 24;
                Spacing := 10;
              end;
              if not (tdfUseCommandLinksNoIcon in aFlags) then
                SetBitmap(BitmapArrow);
              {$endif}
            end;
          end;
          inc(Y,24);
        finally
          Free;
        end;
      // add radio buttons
      if Radios<>'' then
        with TSynWideStringList.Create do
        try
          Text := SysUtils.trim(Radios);
          SetLength(Rad,Count);
          for i := 0 to Count-1 do begin
            Rad[i] := TSynRadioButton.Create(Form);
            with Rad[i] do begin
              Parent := Par;
              SetBounds(X+16,Y,aWidth-32-X,6-FontHeight);
              Caption := NoCR(Strings[i]);
              if aHint<>'' then begin
                ShowHint := true;
                Hint := aHint; // note shown as Hint
              end;
              inc(Y,Height);
              if (i=0) or (i+200=aRadioDef) then
                Checked := true;
            end;
          end;
          inc(Y,24);
        finally
          Free;
        end;
      // add selection list or query editor
      if Selection<>'' then begin
        List := TSynWideStringList.Create;
        try
          Combo := TSynComboBox.Create(Form);
          Combo.Parent := Par;
          Combo.SetBounds(X,Y,aWidth-32-X,22);
          if tdfQuery in aFlags then
            Combo.Style := csDropDown else
            Combo.Style := csDropDownList;
          List.Text := trim(Selection);
          Combo.Items.Assign(List);
          Combo.ItemIndex := List.IndexOf(Query);
          inc(Y,42);
        finally
          List.Free;
        end;
      end else
        if tdfQuery in aFlags then begin
          Edit := TSynEdit.Create(Form);
          Edit.Parent := Par;
          Edit.SetBounds(X,Y,aWidth-16-X,22);
          Edit.Text := Query;
          if tdfQueryMasked in aFlags then
            Edit.PasswordChar := '*';
          inc(Y,42);
        end;
      // from now we won't add components to the white panel, but to the form
      Panel.Height := Y;
      Par := Form;
      // add buttons and verification checkbox
      if (byte(aCommonButtons)<>0) or (Verify<>'') or
         ((Buttons<>'') and not (tdfUseCommandLinks in aFlags)) then begin
        CurrTabOrder := Panel.TabOrder;
        inc(Y, 16);
        XB := aWidth;
        if not (tdfUseCommandLinks in aFlags) then
          with TSynWideStringList.Create do
          try
            Text := SysUtils.trim(Buttons);
            for i := Count-1 downto 0 do
              AddButton(Strings[i],i+100);
          finally
            Free;
          end;
        for B := high(B) downto low(B) do
          if B in aCommonButtons then
            AddButton(TD_BTNS[b], TD_BTNMOD[b]);
        if Verify<>'' then begin
          Verif := TSynCheckBox.Create(Form);
          with Verif do begin
            Parent := Par;
            if X+16+Form.Canvas.TextWidth(Verify)>XB then begin
              inc(Y,32);
              XB := aWidth;
            end;
            SetBounds(X,Y,XB-X,24);
            Caption := Verify;
            Checked := VerifyChecked;
          end;
        end;
        inc(Y,36);
      end else
        XB := 0;
      // add footer text with optional icon
      if Footer<>'' then begin
        if XB<>0 then
          AddBevel else
          inc(Y,16);
        if WIN_FOOTERICONS[aFooterIcon]<>nil then begin
          Image := TImage.Create(Form);
          Image.Parent := Par;
          Pic := TIcon.Create;
          Bmp := TBitmap.Create;
          try
            Pic.Handle := LoadIcon(0,WIN_FOOTERICONS[aFooterIcon]);
            Bmp.Transparent := true;
            Bmp.Canvas.Brush.Color := Form.Color;
            Bmp.Width := Pic.Width shr 1;
            Bmp.Height := Pic.Height shr 1;
            DrawIconEx(Bmp.Canvas.Handle,0,0,Pic.Handle,Bmp.Width,Bmp.Height,0,
              Bmp.Canvas.Brush.Handle,DI_NORMAL);
            Image.Picture.Bitmap := Bmp;
            Image.SetBounds(24,Y,Bmp.Width,Bmp.Height);
            X := 40+Bmp.Width;
          finally
            Bmp.Free;
            Pic.Free;
          end;
        end else
          X := 24;
        AddLabel(Footer,false);
      end;
      // display the form
      Form.ClientHeight := Y;
      // retrieve the results
      result := Form.ShowModal;
      if Combo<>nil then begin
        SelectionRes := Combo.ItemIndex;
        Query := Combo.Text;
      end else
      if Edit<>nil then
        Query := Edit.Text;
      if Verif<>nil then
        VerifyChecked := Verif.Checked;
      RadioRes := 0;
      for i := 0 to high(Rad) do
        if Rad[i].Checked then
          RadioRes := i+200;
    finally
      Form.Free;
    end;
  end;
end;


{ TTaskDialogEx }

function TTaskDialogEx.Execute(aParent: HWND): integer;
begin
  Result := Base.Execute(CommonButtons, ButtonDef, Flags, DialogIcon, FooterIcon,
    RadioDef, Width, aParent, NonNative, EmulateClassicStyle);
end;

{$endif USETMSPACK}


initialization
  DefaultFont := TFont.Create;
  DefaultFont.Style := [];
  if Screen.Fonts.IndexOf('Calibri')>=0 then begin
    DefaultFont.Height := -14;
    DefaultFont.Name := 'Calibri';
  end else begin
    if Screen.Fonts.IndexOf('Tahoma')>=0 then
      DefaultFont.Name := 'Tahoma' else
      DefaultFont.Name := 'Arial';
    DefaultFont.Height := -13;
  end;
  {$ifndef USETMSPACK}
  InitComCtl6;
  assert(ord(tdfCanBeMinimized)=15);
  {$endif USETMSPACK}
  BitmapOK := TBitmap.Create;
  BitmapOK.LoadFromResourceName(HInstance,'btnOk'); // SQLite3btnok.bmp
  BitmapOK.Transparent := true;
  BitmapArrow := TBitmap.Create;
  BitmapArrow.LoadFromResourceName(HInstance,'btnArrow'); // SQLite3btnArrow.bmp
  BitmapArrow.Transparent := true;

finalization
  DefaultFont.Free;
  BitmapArrow.Free;
  BitmapOK.Free;

end.

SynWideSupp.pas:

unit SynWideSupp;

interface

uses SysUtils
  {$IFNDEF UNICODE}, WideStrUtils{$ENDIF}
;

type
  {$IFDEF UNICODE}
    SynWideString = String;
    PSynWideChar = PChar;
  {$ELSE}
    SynWideString = WideString;
    PSynWideChar = PWideChar;
  {$ENDIF}

function SynWideStringReplace(const S, OldPattern, NewPattern: SynWideString;
  Flags: TReplaceFlags): SynWideString; inline;

implementation

function SynWideStringReplace(const S, OldPattern, NewPattern: SynWideString;
  Flags: TReplaceFlags): SynWideString;
begin
  {$IFDEF UNICODE}
  Result := StringReplace(S, OldPattern, NewPattern, Flags);
  {$ELSE}
  Result := WideStringReplace(S, OldPattern, NewPattern, Flags);
  {$ENDIF}
end;

end.

#16 Re: Other components » oExport » 2011-12-14 02:53:42

this is a problem of Excel 2007, as the file "import-ods.ods" was created directly in LibreOffice and not by my code.

try to open "my-export.ods" - this is a file generated by my application. it can be opened without problems in my excel 2007.

#17 Other components » oExport » 2011-12-13 16:38:55

reddwarf
Replies: 4

as I promised, I released oExport, my native Delphi2007 XLSX/ODS suite. it can import/export XLSX (Excel 2007) and ODS (OpenOffice Calc) files.
license is the same as SynPDF (MPL/GPL/LGPL tri-license)

(+) NATIVE: no external libraries or excel/calc installation needed.
(-) only Delphi 2007... but someone can help rewrite the code for D2007+ wink

http://www.kluug.at/xlsx-ods-delphi.php

enjoy

#18 Re: Other components » ScreenCast in Delphi » 2011-12-09 17:08:34

thanks!

most of the external libraries are needed only for the audio and only by the recorder...

the player needs only the ogg/mp3 codec libraries to play audio - there is no chance to get around that, to my mind... if you don't need any audio, you don't need the dll libraries, of course.

the file format is just a zip archive with an xml definition and an audio and png files wink simple wink

#19 Other components » ScreenCast in Delphi » 2011-12-09 14:10:26

reddwarf
Replies: 2

I wrote a screencasting software in Delphi and I'll release the source code for it in some days. If you find it interesting, you can take a look at the current progress at http://www.kluug.at/videotutorial.php . You can play with a dll for C++/Delphi and with the recording application.
I think it is a good alternative to other screencasting software since it offers loseless video compression and a simple native video format.

#20 Re: PDF Engine » AddFromRichEdit - font style not preserved » 2011-07-13 21:29:33

that's interesting - I downloaded your demo program and it does exactly what it should - the correct formatting is preserved in the PDF - bold, alignment, underline, font size... (of course only in "Report PDF" Button, "Demo PDF" does not render the pdf text)...
everything works for me.

I use Delphi 2007
+ I am sorry to say that I have not Delphi 7 and so I can't check your code with it, but in Delphi 2007 the code works fine

#21 Re: PDF Engine » AddFromRichEdit - font style not preserved » 2011-07-13 14:55:24

Hi, for RTF you need to draw it on a TMetafileCanvas and then you draw the metafile on the VCLCanvas. This creates the best results, i think.

There is some example code in this forum - you have just to search a little bit...

http://www.synopse.info/forum/viewtopic.php?id=76

#22 Re: PDF Engine » Bezier curves and brush styles in EnumEMFFunc » 2011-06-29 09:15:16

Sorry, I'm now very happy with SynPDF as it is. Everybody has a restricted time capacity wink

Ondrej

#23 Re: PDF Engine » How do I add a barcode in a PDF. » 2011-03-24 23:49:36

eraldo: i don't think so - just follow my/arnauld's instructions and you will be able to generate a pdf barcode with very little effort. look at the examples in TPSysTools and do what you want. there are so many different barcode types, for example. the demos are very straightforward.

to arnauld: the problem with wmf/emf originally generated with TPSysTools is that when they are scaled, "new white lines" can occur in the barcode because of rounding errors when scaling. this is because a bar with the width of 2px is generated with 2 bars with the width of 1px. that's the problem. but maybe someone changed it since i had the problem wink

#24 Re: PDF Engine » How do I add a barcode in a PDF. » 2011-03-22 23:41:37

use TPSysTools to generate a barcode + export it to bitmap/WMF + draw it onto PDFCanvas.

but I had best results by generating a 1:1 barcode with TPSysTools onto a bitmap - then you have to scan the image for each pixel and generate a rectangle over each "black area". this is needed because TPSysTools generate one rectangle per barcode pixel which can end up in non consistent "black areas" on the print because the rectangles are scaled and so rounding errors can occure. I know it is not the most efficient method, but quick-and-dirty is sometimes good enough wink

#25 Re: Delphi » Delphi new generation VCL » 2011-03-09 18:37:34

I tried MSEgui some months ago and I wasn't very impressed by the whole usability. But maybe it's because I just didn't go very deeply into it. As I see, the project is updated quite often, so I'll have to give it another try.

#26 Re: PDF Engine » Wrong character width » 2011-03-04 15:00:58

If you find of a better solution, that's fine!

=> the author is always right wink

no - I am really happy that we solved the 2 issues! have a nice weekend!

#27 Re: PDF Engine » Wrong character width » 2011-03-03 22:56:50

I downloaded your code and everything is working fine. IMHO it is not necessary to have the 3 new parameters defined at 2 places, but the most important is that it's working wink

I downloaded Acrobat Reader X and checked the new generated pdfs and they are valid.
+ I am sure that you worked much longer than 2 hours to give us SynPDF, so I am glad that I could help you a little. The only thing is, that changing 3 characters of code makes such a difference big_smile

#28 Delphi » Delphi new generation VCL » 2011-03-03 22:47:59

reddwarf
Replies: 15

In the last month, there was made a very important step in Delphi's development. I am talking about www.ksdev.com - I was about to buy the VGScene units because I wanted to write programs for all platforms - but Codegear was quicker. They did not only buy the components, they bought all intelectual property from ksdev.com. So the dream of using open-source Lazarus with profesional (and relatively cheap) widgets was all gone. The only hope I have is that Codegear will bring Delphi further at last - to my mind there has been more false and strange development since Delphi 7 than real improvement. What a shame that Codegear introduced an out-of-box unicode-ready Delphi 10 years after Microsoft has fully supported it in Windows 2000... When everybody who needed unicode uses TntComponents, which makes porting old code to Delphi 2009+ a nightmare...

I hope that at some point Lazarus will be a real competitor to Delphi. But to my mind it was not a good decision to write the LCL. Some lightweight, easy and complete widgetset would be much better. LCL is just going to be very complicated for custom component writing. Maybe fpGUI at some point...

PS: everybody who wants to see all features of VGScene, use this link: http://www.ksdev.com/vgscene/index.html - the author hasn't deleted the main page yet.

#29 Re: PDF Engine » Wrong character width » 2011-03-03 01:27:53

this is a code example where I need UseSetTextJustification := False;

procedure TForm1.BtnTestFontClick(Sender: TObject);
var
  xRect: TRect;
  I: Integer;
const
  Text: WideString = 'Ondrej, RedDwarf:';
begin
  with TPdfDocumentGDI.Create do
  try
    KerningHScaleBottomLimit := 100;
    KerningHScaleTopLimit := 100;
    UseSetTextJustification := False;//try to use True

    AddPage;
    with VCLCanvas do begin
      Font.Name := 'Tahoma';//'Times New Roman';
      Font.Style := [fsBold, fsUnderline];
      Pen.Color := $AAAAAA;
      Img1.Canvas.Font.Assign(Font);
      Img1.Canvas.Pen.Assign(Pen);
      Img1.Canvas.Brush.Style := bsClear;

      for I := 5 to 15 do begin
        Font.Size := I;

        xRect := Rect(0, 0, TextWidthXP(Text), TextHeightXP(Text));
        OffsetRect(xRect, 100, 100+(I-5)*30);
        Rectangle(xRect);
        Windows.ExtTextOutW(Handle, xRect.Left, xRect.Top, ETO_CLIPPED,
          @xRect, PWideChar(Text), Length(Text), nil);

        //just to check with an image canvas
        Img1.Canvas.Font.Assign(Font);
        Img1.Canvas.Rectangle(xRect);
        Windows.ExtTextOutW(Img1.Canvas.Handle, xRect.Left, xRect.Top, ETO_CLIPPED,
          @xRect, PWideChar(Text), Length(Text), nil);
      end;
    end;
    SaveToFile('TestVcl.pdf');
  finally
    Free;
  end;
end;

#30 Re: PDF Engine » Wrong character width » 2011-03-02 22:23:43

Hello Arnauld,

thanks for checking my suggestions.

          Canvas.MoveToI(Left-1, Top+1);
          Canvas.LineToI(Left-1, Bottom+1);
          Canvas.LineToI(Right-1, Bottom+1);
          Canvas.LineToI(Right-1, Top+1);

this is fine - I just did not look at that precisely.

SetWordSpace() problem:
I am sure that you know more background information than me and therefore I suppose that your code is better. So keep it as you want. I will test it and perhaps I'll come with some clear example where I'd like to have another behaviour. Until then, keep your code.
To be honest, I don't use align=justify (since standard delphi doesn't like it either) so I did not realise this purpose.

I'd like to make a suggestion regarding the SetHorizontalScaling hysteresis:
I think you could add a property to TPdfDocument that would define the range the programmer prefers. then you could use something like:

        if (hscale<Canvas.FDoc.IgnoreHScaleBottomLimit) or (hscale>Canvas.FDoc.IgnoreHScaleTopLimit) then
          Canvas.SetHorizontalScaling(hscale) else
          hscale := 100;

I think it is the best solution to offer each programmer the possibility to change the range.

+ Opening PDF Files: I have always used Adobe Acrobat 9 Pro and I am sorry to say that I have observed the problem with "asking to save document at close" since the very first version of SynPDF I used. Additionally, Adobe Acrobat shows a message that the file has to be repaired when opening a SynPDF generated file (but only for 0-2 seconds and then it automatically closes). I haven't tested other versions of Acrobat since I've bought just 9 Pro.
To be honest, I have no idea what Acrobat doesn't like about your PDF source. Everything I know about PDF is what I have been able to learn from your code... I tried the Adobe Preflight but without success...

As a second program I use SumatraPDF which is a tiny freeware pdf viewer (http://blog.kowalczyk.info/software/sum … eader.html) and it has absolutely no problem viewing your PDFs.

BEST
Ondrej

+ yesss delphi rocks wink

#31 Re: PDF Engine » Wrong character width » 2011-03-02 16:13:09

Arnauld, it is clear, that you are the great guru here. Your starting point (2) was the right solution! I played with it a little bit and came to a result that satisfies me completely. Particularly I made these additional changes to your code so that the characters are printed exactly 1:1:
A) I changed the HorizontalScaling property to SINGLE for more precision
B) I used the HorizontalScaling for all fonts, not only for: if (hscale<99) or (hscale>106)

with these 2 small changes, the string positions are EXACT. Thanks for help! (I send you the changes by email, but there are not many wink)

#32 Re: PDF Engine » Wrong character width » 2011-03-02 10:34:59

I also had the idea to draw each character separately, but as you said, the resulting file is just toooo big when using this method for all texts although the characters are placed (nearly) exactly.
something like this:

  procedure PDFExactTextOut(aCanvas: TCanvas; aText: String; aRect: TRect);
  var I, xX: Integer;
    xS: String;
  begin
    for I := 1 to Length(aText) do begin
      xX := aCanvas.TextWidth(Copy(aText, 1, I-1));
      xS := Copy(aText, I, 1);
      Windows.ExtTextOut(aCanvas.Handle, aRect.Left+xX, aRect.Top, ETO_CLIPPED,
        @aRect, PChar(xS), 1, nil);
    end;
  end;

do you think is there any way how to get the string length without kerning?
I tried to compute the width of each character and to sum them, but it did not help.
The problem can be observed also when using underline/strikeout font:

procedure TForm1.BtnTestUnderlineClick(Sender: TObject);
const
  Text: String = 'RERERERE:';
begin
  with TPdfDocumentGDI.Create do
  try
    AddPage;
    //UseUniScribe := true; //uniscribe does not change anything about the problem
    with VCLCanvas do begin
      Font.Name := 'Tahoma';
      Font.Size := 8;
      Font.Style := [fsBold, fsUnderline];

      TextOut(100, 100, Text);
    end;
    SaveToFile('TestUnderline.pdf');
  finally
    Free;
  end;
end;

I don't need kerning. But what would be great is to get the exact string width as the string is shown in the pdf - if it is possible. I am sorry to say that I cannot solve this problem by myself.

#33 PDF Engine » Wrong character width » 2011-02-28 12:01:42

reddwarf
Replies: 13

Hi Arnaud,

I am facing a similar problem as Pavel in his "Problems with Courier New font" thread.

The thing is, that for example the Tahoma bold 8pt font is not drawn exactly 1:1 as on normal window canvas. Particularly the character width is wrong. This results in displaced words when exporting RTF or words being clipped when using ExtTextOutW with ETO_CLIPPED. The problem can be observed when importing the PDF into Adobe InDesign (I tested CS5), in which the characters are shown twice and are overlapping.

Look at this code. With SynPdf v1.12 the last ":" is cut off from the rectangle (8pt font):

procedure TForm1.BtnTestFontClick(Sender: TObject);
var
  xRect: TRect;
const
  Text: WideString = 'RERERERE:';
begin
  with TPdfDocumentGDI.Create do
  try
    AddPage;
    UseUniScribe := true; //uniscribe does not change anything about the problem
    with VCLCanvas do begin
      Font.Name := 'Tahoma';
      Font.Size := 8;
      Font.Style := [fsBold];
      Pen.Color := $AAAAAA;

      xRect := Rect(0, 0, TextWidth(Text), TextHeight(Text));
      OffsetRect(xRect, 100, 100);

      Rectangle(xRect);

      Windows.ExtTextOutW(Handle, xRect.Left, xRect.Top, ETO_CLIPPED,
        @xRect, PWideChar(Text), Length(Text), nil);

      Font.Size := 12;

      xRect := Rect(0, 0, TextWidth(Text), TextHeight(Text));
      OffsetRect(xRect, 100, 200);

      Rectangle(xRect);

      Windows.ExtTextOutW(Handle, xRect.Left, xRect.Top, ETO_CLIPPED,
        @xRect, PWideChar(Text), Length(Text), nil);

    end;
    SaveToFile('TestVcl.pdf');
  finally
    Free;
  end;
end;

Thanks for great PDF library!

#34 Re: PDF Engine » How to select printer tray source from pdf file (multiples page types) » 2010-09-18 20:08:33

this is an interesting post. some time ago I had a similar problem. but I used normal printer begindoc, canvas, endoc etc. I wanted to print the first page of a document as landscape and the second page as portrait and I did not find the way how to do it in a single print job. the only possibility I found was to split the document into 2 print jobs. this solution is, of course, not ideal because e.g. if you select the "adobe pdf" printer you get 2 files and you have to join them manually afterwards.

do you know if/how it is possible to change paper settings during the printer job when using standard printing procedures in delphi? to my mind, it must be possible because for example adobe reader can handle it...

#35 Re: PDF Engine » how to open a pdf file and append some content? » 2010-09-05 12:49:26

I doubt it's easily possible with SynPDF now, you have to write a PDF parser based on SynPDF and append the page manually...

#36 Re: PDF Engine » Print Preview and PDF generation from any TRichEdit component content » 2010-08-04 07:30:06

yes, you are right. your code is easier, but does the thing a little bit differently. therefore someone may find it useful wink and now i remember where i took inspiration - in the original delphi source code big_smile

#37 Re: PDF Engine » Print Preview and PDF generation from any TRichEdit component content » 2010-08-03 19:55:44

ok, let me be a little bit useful again... maybe i should have posted this code earlier... i have found some code on the internet how to paint rich text on canvas. but it was buggy so i modified it heavily. i also added more functionality to that. the best one is the calculation of actual height of the printed rtf text [you miss wink]. it is possible to paint rich text on more pages (always use the result value for the next "FromChar" parameter).

info for ab: i did test neither your new code, nor your rtf text drawing. sorry, no time, a lot of work sad so my example was compiled with your 1.8.0? version [maybe 1 month old wink]... but it works pretty fine for me.

there are some other functions you can just omit if you don't need them.

+ it works with TTntRichEdit, if you wish the normal TRichEdit, just rename everything...

ENJOY:

unit PrintRichText:

unit PrintRichText;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, TntComCtrls, TntControls, TntSysUtils;

type
  TPrintRichEdit = class helper for TTntRichEdit
  private
    function GetRawRTF: String;
    procedure SetRawRTF(const Value: String);
  public
    function DrawTo(ACanvas: TCanvas; aRect: TRect;
      DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint;

    //WARNING MyFindText COUNTS FROM 0!!!! if found at start return value = 0 !!!
    function MyFindText(aStr: WideString; FromPosition: Integer = 1): Integer;
    function GetLineBreakStyle: TTntTextLineBreakStyle;
    function GetTextLenW: Integer;
    procedure MyDelete(aPos, aLength: Integer);

    procedure ReplaceText(const FromStr, ToStr: WideString);
    procedure ProcessIfStatement(const IfStatement: WideString; const aBoolean: Boolean); overload;
    procedure ProcessIfStatement(const IfStatement, ElseStatement, EndIfStatement: WideString; const aBoolean: Boolean); overload;

    property RawRTF: String read GetRawRTF write SetRawRTF;
  end;

  TTntRichEditAccessProtected = class(TTntRichEdit)
  public
    function LineBreakStyle: TTntTextLineBreakStyle; reintroduce;
  end;

function DefPrintRTFToCanvas(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
  FromChar: Integer = 0; ToChar: Integer = -1): Longint;
function DefPrintRTFToCanvasTransparent(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
  Rop: Cardinal; FromChar: Integer = 0; ToChar: Integer = -1): Longint;
function DefCalculateRTFHeight(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
  DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint;

function PrintRTFToCanvas(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
  DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint; overload;
function PrintRTFToCanvas(aRichRawText: String; aCanvas: TCanvas; aRect: TRect;
  DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint; overload;
function CalculateRTFHeight(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
  DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint; overload;
function CalculateRTFHeight(aRichRawText: String; aCanvas: TCanvas; aRect: TRect;
  DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint; overload;

procedure SetRawRTF(RE: TTntRichEdit; RawRTF: String);
function GetRawRTF(RE: TTntRichEdit): String;

function ReplaceRTFText(aRichRawText: String; aFromText, aToText: WideString): String;

function DrawRichEdit: TTntRichEdit;

implementation

uses RichEdit, Types, WideStrUtils, Math;

var
  XDrawRichEdit: TTntRichEdit = nil;

function DrawRichEdit: TTntRichEdit;
begin
  if Application.MainForm = nil then begin
    Result := nil;
    exit;
  end;

  if not Assigned(XDrawRichEdit) then begin
    XDrawRichEdit := TTntRichEdit.Create(Application.MainForm);
    XDrawRichEdit.Visible := False;
    XDrawRichEdit.Parent := Application.MainForm;
  end;
  Result := XDrawRichEdit;
end;

function GetRawRTF(RE: TTntRichEdit): String;
var
   strStream: TStringStream;
begin
   strStream := TStringStream.Create('') ;
   try
     RE.PlainText := False;
     RE.Lines.SaveToStream(strStream) ;
     Result := strStream.DataString;
   finally
     strStream.Free
   end;
end;

procedure SetRawRTF(RE: TTntRichEdit; RawRTF: String);
var
   strStream: TStringStream;
begin
   strStream := TStringStream.Create(RawRTF) ;
   try
     RE.PlainText := False;
     RE.Lines.LoadFromStream(strStream) ;
   finally
     strStream.Free
   end;
end;

function PrintRTFToCanvas(aRichRawText: String; aCanvas: TCanvas; aRect: TRect;
  DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint;
var
  RE: TTntRichEdit;
begin
  RE := TTntRichEdit.Create(nil);
  with RE do
  try
    Visible := false;
    Parent := Application.MainForm;

    RE.RawRTF := aRichRawText;

    Result := PrintRTFToCanvas(RE, aCanvas, aRect, DPI, FromChar, ToChar);
  finally
    RE.Free;
  end;
end;

function DefCalculateRTFHeight(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
  DPI: Integer; FromChar: Integer; ToChar: Integer): Integer;
var
  MTF: TMetafile;
  MTFCanvas: TMetafileCanvas;
  Range: TFormatRange;
  rZoom: Single;
  SaveMapMode, LogX, LogY: Integer;
  SaveViewPort, SaveWindowExt: tagSize;
begin
  if Trim(aRichEdit.Text) = '' then begin
    Result := 0;
    exit;
  end;

  MTF := TMetafile.Create;
  MTFCanvas := TMetafileCanvas.Create(MTF, 0);
  try
    MTFCanvas.Font.Assign(aCanvas.Font);
    MTF.Width := aRect.Right-aRect.Left;
    MTF.Height := aRect.Bottom-aRect.Top;
    if DPI = 0 then
      DPI := ACanvas.Font.PixelsPerInch;
    LogX := GetDeviceCaps(MTFCanvas.Handle, LOGPIXELSX);
    LogY := GetDeviceCaps(MTFCanvas.Handle, LOGPIXELSY);

    rZoom := DPI/MTFCanvas.Font.PixelsPerInch;

    FillChar(Range, SizeOf(TFormatRange), 0);
    Range.hdc        := MTFCanvas.Handle;
    Range.hdcTarget  := MTFCanvas.Handle;
    Range.rc.left    := 0;
    Range.rc.top     := 0;
    Range.rc.right   := (aRect.Right-aRect.Left) * 1440 div DPI;
    Range.rc.Bottom  := (aRect.Bottom-aRect.Top) * 1440 div DPI;
    {Range.rc.left    := aRect.Left * 1440 div LogX;
    Range.rc.top     := aRect.Top * 1440 div LogY;
    Range.rc.right   := aRect.Right * 1440 div LogX;
    Range.rc.Bottom  := aRect.Bottom * 1440 div LogY;}
    if not((FromChar = 0) and (ToChar = 0)) then begin
      Range.chrg.cpMax := ToChar;
      Range.chrg.cpMin := FromChar;
    end else begin
      Range.chrg.cpMax := -1;
      Range.chrg.cpMin := 0;
    end;
    Range.rcPage := Range.rc;

    with MTFCanvas do begin
      SaveMapMode := GetMapMode(Handle);
      GetWindowExtEx(Handle, SaveWindowExt);
      GetViewportExtEx(Handle, SaveViewPort);
    end;
    try
      with MTFCanvas do begin
        SetMapMode(Handle, MM_TEXT);//MM_ANISOTROPIC MM_TEXT
        SetWindowExtEx(Handle, LogX, LogY, nil);
        SetViewportExtEx(Handle, Round(LogX * rZoom), Round(LogY * rZoom), nil);
      end;

      SendMessage(ARichedit.Handle, EM_FORMATRANGE, 0, Longint(@Range));
      SendMessage(ARichEdit.handle, EM_FORMATRANGE, 0,0);

      Result := Round(range.rc.bottom * DPI / 1440);
    finally
      with MTFCanvas do begin
        SetMapMode(Handle, SaveMapMode);
        SetWindowExtEx(Handle, SaveWindowExt.cx, SaveWindowExt.cy, nil);
        SetViewportExtEx(Handle, SaveViewPort.cx, SaveViewPort.cy, nil);
      end;
    end;
  finally
    MTFCanvas.Free;
    MTF.Free;
  end;
end;

function DefPrintRTFToCanvasTransparent(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
  Rop: Cardinal; FromChar: Integer = 0; ToChar: Integer = -1): Longint;
var B: TBitmap;
begin
  B := TBitmap.Create;
  try
    B.Canvas.Font.Assign(aCanvas.Font);
    B.Width := aRect.Right-aRect.Left;
    B.Height := aRect.Bottom-aRect.Top;
    B.Canvas.Brush.Color := clWhite;
    B.Canvas.FillRect(Rect(0, 0, B.Width, B.Height));

    Result := DefPrintRTFToCanvas(aRichEdit, B.Canvas, Rect(0, 0, B.Width, B.Height), FromChar, ToChar);

    {B.TransparentColor := clWhite;  DOES NOT REALLY FUNCTION BECAUSE OF ANTIALIASING
    B.TransparentMode := tmFixed;
    B.Transparent := True;
    aCanvas.Draw(aRect.Left, aRect.Top, B);}
    BitBlt(aCanvas.Handle, aRect.Left, aRect.Top, B.Width, B.Height,
      B.Canvas.Handle, 0, 0, Rop);
  finally
    B.Free;
  end;
end;

function DefPrintRTFToCanvas(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
  FromChar: Integer = 0; ToChar: Integer = -1): Longint; overload;
var
  Range: TFormatRange;
  SaveMapMode, LogX, LogY: Integer;
  //SaveViewPort, SaveWindowExt: tagSize;
  //B: TBitmap;
begin
  //B := TBitmap.Create;
  try
    //B.Canvas.Font.Assign(aCanvas.Font);
    //B.Width := aRect.Right-aRect.Left;
    //B.Height := aRect.Bottom-aRect.Top;
    //B.Canvas.Brush.Color := clWhite;
    //B.Canvas.FillRect(aRect);
    LogX := GetDeviceCaps(ACanvas.Handle, LOGPIXELSX);
    LogY := GetDeviceCaps(ACanvas.Handle, LOGPIXELSY);

    FillChar(Range, SizeOf(TFormatRange), 0);
    Range.hdc        := ACanvas.Handle;
    Range.hdcTarget  := ACanvas.Handle;
    {Range.rc.left    := 0;
    Range.rc.top     := 0;
    Range.rc.right   := B.Width * 1440 div DPI;
    Range.rc.Bottom  := B.Height * 1440 div DPI;}
    Range.rc.left    := aRect.Left * 1440 div LogX;
    Range.rc.top     := aRect.Top * 1440 div LogY;
    Range.rc.right   := aRect.Right * 1440 div LogX;
    Range.rc.Bottom  := aRect.Bottom * 1440 div LogY;
    if not((FromChar = 0) and (ToChar = 0)) then begin
      Range.chrg.cpMax := ToChar;
      Range.chrg.cpMin := FromChar;
    end else begin
      Range.chrg.cpMax := -1;
      Range.chrg.cpMin := 0;
    end;

    with ACanvas do begin
      SaveMapMode := GetMapMode(Handle);
      //GetWindowExtEx(Handle, SaveWindowExt);
      //GetViewportExtEx(Handle, SaveViewPort);
    end;
    try
      with ACanvas do begin
        SetMapMode(Handle, MM_TEXT);//MM_ANISOTROPIC
        //SetWindowExtEx(Handle, DPI, DPI, nil);
        //SetViewportExtEx(Handle, Round(DPI * rZoom), Round(DPI * rZoom), nil);
      end;
      {ARichedit.SelStart :=40;
      ARichedit.SelLength := 15;
      aRichEdit.SelAttributes.Color := clRed;}
      Result := SendMessage(ARichedit.Handle, EM_FORMATRANGE, 1, Longint(@Range));
      SendMessage(ARichEdit.handle, EM_FORMATRANGE, 0,0);

      //aCanvas.Draw(aRect.Left, aRect.Top, B);
      {BitBlt(aCanvas.Handle, aRect.Left, aRect.Top, B.Width, B.Height,
        B.Canvas.Handle, 0, 0, srcAND);}
    finally
      with ACanvas do begin
        SetMapMode(Handle, SaveMapMode);
        //SetWindowExtEx(Handle, SaveWindowExt.cx, SaveWindowExt.cy, nil);
        //SetViewportExtEx(Handle, SaveViewPort.cx, SaveViewPort.cy, nil);
      end;
    end;
  finally
    //B.Free;
  end;
end;

{function PrintRTFToCanvas(ARichEdit: TCustomRichEdit; ACanvas: TCanvas; aRect: TRect;
  DPI, FromChar, ToChar: Integer): Longint;
var
  LogX: Integer;
begin
  LogX := GetDeviceCaps(ACanvas.Handle, LOGPIXELSX);
  if DPI = 0 then
    DPI := ACanvas.Font.PixelsPerInch;
  if DPI <> LogX then
    result := DefPrintRTFToCanvasWithZoom(ARichEdit, ACanvas, aRect, DPI, FromChar, ToChar)
  else
    result := DefPrintRTFToCanvas(aRichEdit, aCanvas, aRect, FromChar, ToChar);
end;}

function CalculateRTFHeight(aRichRawText: String; aCanvas: TCanvas; aRect: TRect;
  DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint;
begin
  DrawRichEdit.RawRTF := aRichRawText;
  Result := CalculateRTFHeight(DrawRichEdit, aCanvas, aRect, DPI, FromChar, ToChar);
end;

function CalculateRTFHeight(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
  DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint;
{var
  LogX: Integer;
  MTF: TMetafile;
  MTFCanvas: TMetafileCanvas;
  aZoom: Single;}
begin
  Result := DefCalculateRTFHeight(aRichEdit, aCanvas, Rect(0, 0, Round((aRect.Right-aRect.Left)), High(SmallInt)), DPI);

  {LogX := GetDeviceCaps(aCanvas.Handle, LOGPIXELSX);
  if DPI = 0 then
    DPI := LogX;

  MTF := TMetafile.Create;
  try
    MTFCanvas := TMetafileCanvas.Create(MTF, 0);
    with MTFCanvas do
    try
      LogX := GetDeviceCaps(Handle, LOGPIXELSX);

      aZoom := DPI / LogX;
      if aZoom <= 0 then
        aZoom := 1;

      Result := Round(DefCalculateRTFHeight(aRichEdit, MTFCanvas, Rect(0, 0, Round((aRect.Right-aRect.Left) / aZoom), High(SmallInt)), 0) * aZoom);
      //Result := Round(DefCalculateRTFHeight(aRichEdit, MTFCanvas, aRect, 0) * aZoom);
    finally
      Free;
    end;
  finally
    MTF.Free;
  end;}
end;

function PrintRTFToCanvas(ARichEdit: TCustomRichEdit; ACanvas: TCanvas; aRect: TRect;
  DPI, FromChar, ToChar: Integer): Longint;
var
  LogX: Integer;
  MTF: TMetafile;
  MTFCanvas: TMetafileCanvas;
  aZoom: Single;
begin
  LogX := GetDeviceCaps(aCanvas.Handle, LOGPIXELSX);
  if DPI = 0 then
    DPI := LogX;

  if DPI = LogX then begin
    result := DefPrintRTFToCanvas(aRichEdit, aCanvas, aRect, FromChar, ToChar);{}
  end else begin
    MTF := TMetafile.Create;
    try
      with TMetafileCanvas.Create(MTF, 0) do
      try
        LogX := GetDeviceCaps(Handle, LOGPIXELSX);
      finally
        Free;
      end;

      aZoom := DPI / LogX;
      if aZoom <= 0 then
        aZoom := 1;

      MTF.Width := Round((aRect.Right-aRect.Left) / aZoom);
      //ShowMessage(IntToStr((aRect.Right-aRect.Left))+':'+IntToStr(MTF.Width));
      MTF.Height := Round((aRect.Bottom-aRect.Top) / aZoom);

      MTFCanvas := TMetafileCanvas.Create(MTF, 0);
      with MTFCanvas do
      try
        Result := DefPrintRTFToCanvas(aRichEdit, MTFCanvas, Rect(0, 0, MTF.Width, MTF.Height), FromChar, ToChar);
      finally
        MTFCanvas.Free;
      end;

      //MTF.Width := Round((aRect.Right-aRect.Left) / 1);
      //ShowMessage(IntToStr((aRect.Right-aRect.Left))+':'+IntToStr(MTF.Width));
      //MTF.Height := Round((aRect.Bottom-aRect.Top) / 1);

      ACanvas.StretchDraw(aRect, MTF);
      //ACanvas.Draw(0, 0, MTF);
    finally
      MTF.Free;
    end;{}
  end;
  
  {LogX := GetDeviceCaps(ACanvas.Handle, LOGPIXELSX);
  if DPI = 0 then
    DPI := ACanvas.Font.PixelsPerInch;
  if DPI <> LogX then
    result := DefPrintRTFToCanvasWithZoom(ARichEdit, ACanvas, aRect, DPI, FromChar, ToChar)
  else
    result := DefPrintRTFToCanvas(aRichEdit, aCanvas, aRect, FromChar, ToChar);{}
end;


{ TPrintRichEdit }

function TPrintRichEdit.DrawTo(ACanvas: TCanvas; aRect: TRect; DPI, FromChar,
  ToChar: Integer): Longint;
begin
  Result := PrintRTFToCanvas(Self, ACanvas, aRect, DPI, FromChar, ToChar);
end;

function TPrintRichEdit.GetRawRTF: String;
begin
  Result := PrintRichText.GetRawRTF(Self);

  Result := TrimRight(WideReplaceText(Result, #0, '')); 
end;

function TPrintRichEdit.GetTextLenW: Integer;
begin
  //Result := TntAdjustLineBreaksLength(Text, GetLineBreakStyle);
  Result := TntAdjustLineBreaksLength(TntControl_GetText(Self), GetLineBreakStyle);
end;

function TPrintRichEdit.GetLineBreakStyle: TTntTextLineBreakStyle;
begin
  Result := TTntRichEditAccessProtected(Self).LineBreakStyle;
end;

procedure TPrintRichEdit.MyDelete(aPos, aLength: Integer);
begin
  SelStart := aPos-1;
  SelLength := aLength;
  SelText := '';
end;

function TPrintRichEdit.MyFindText(aStr: WideString;
  FromPosition: Integer): Integer;
begin
  Result := FindText(aStr, FromPosition-1, Length(Text)-FromPosition+1, [])+1;
end;

procedure TPrintRichEdit.ProcessIfStatement(const IfStatement, ElseStatement,
  EndIfStatement: WideString; const aBoolean: Boolean);
var pos1, pos2, pos3: Integer;
  I: Integer;
begin
  I := 0;
  pos1 := MyFindText(IfStatement);
  while (pos1 <> 0) and (I < 20) do begin
    MyDelete(pos1, Length(IfStatement));

    pos2 := MyFindText(ElseStatement, pos1);
    pos3 := MyFindText(EndIfStatement, pos1);
    pos2 := Min(pos2, pos3);
    if (pos2 = 0) then
      pos2 := pos3;
    if aBoolean then begin
      MyDelete(pos2, pos3-pos2+Length(EndIfStatement));
    end else begin
      MyDelete(pos1, pos2-pos1);
      if (MyFindText(ElseStatement, pos1) = pos1) then
        MyDelete(pos1, Length(ElseStatement));
      pos1 := MyFindText(EndIfStatement, pos1);
      MyDelete(pos1, Length(EndIfStatement));
    end;
    pos1 := MyFindText(IfStatement);
    Inc(I);
  end;
end;

procedure TPrintRichEdit.ProcessIfStatement(const IfStatement: WideString;
  const aBoolean: Boolean);
var ElseStatement, EndIfStatement: WideString;
begin
  if Length(IfStatement) > 0 then begin
    ElseStatement := IfStatement[1] + 'ELSE' + IfStatement[Length(IfStatement)];
    EndIfStatement := IfStatement[1] + 'ENDIF' + IfStatement[Length(IfStatement)];

    ProcessIfStatement(IfStatement, ElseStatement, EndIfStatement, aBoolean);
  end;
end;

procedure TPrintRichEdit.ReplaceText(const FromStr, ToStr: WideString);
var X: Integer;
begin
  X := 1;
  X := MyFindText(FromStr, X);
  while X <> 0 do begin
    SelStart := X-1;
    SelLength := Length(FromStr);
    SelText := ToStr;
    X := MyFindText(FromStr, X + Length(ToStr));
  end;
end;

procedure TPrintRichEdit.SetRawRTF(const Value: String);
begin
  PrintRichText.SetRawRTF(Self, Value);
end;

function ReplaceRTFText(aRichRawText: String; aFromText, aToText: WideString): String;
var
  RE: TTntRichEdit;
begin
  RE := TTntRichEdit.Create(nil);
  with RE do
  try
    Visible := false;
    Parent := Application.MainForm;

    RawRTF := aRichRawText;

    ReplaceText(aFromText, aToText);

    Result := RawRTF;
  finally
    RE.Free;
  end;
end;



{ TTntRichEditAccessProtected }

function TTntRichEditAccessProtected.LineBreakStyle: TTntTextLineBreakStyle;
begin
  Result := inherited LineBreakStyle;
end;

end.

how to use that:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, TntComCtrls;

type
  TForm1 = class(TForm)
    RE1: TTntRichEdit;
    BtnSynopseRTFTest: TButton;
    BtnMorePagesTest: TButton;
    procedure BtnSynopseRTFTestClick(Sender: TObject);
    procedure BtnMorePagesTestClick(Sender: TObject);
  private
    { Private declarations }
  protected
    procedure DoCreate; override;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses SynPDF, PrintRichText, TntSysUtils;

procedure TForm1.BtnMorePagesTestClick(Sender: TObject);
var
  xPDF: TPdfDocumentGDI;
  xRect: TRect;
  xMF: TMetafile;
  xMFC: TMetaFileCanvas;
  xHeight, xLastChar, xMaxChar, I: Integer;

begin
  xPDF := TPdfDocumentGDI.Create;
  xMF := TMetafile.Create;
  try
    xPDF.AddPage;

    xLastChar := -1;
    //xMaxChar := RE1.GetTextLen;//USE FOR NORMAL RichEdit
    xMaxChar := RE1.GetTextLenW;
    I := 0;
    repeat
      xRect := Rect(100, 100+I*200, 300, 190++I*200);
      xLastChar := PrintRTFToCanvas(RE1, xPDF.VCLCanvas, xRect, 0, xLastChar);
      with xPDF.VCLCanvas do begin
        Brush.Style := bsClear;
        Pen.Width := 1;
        Pen.Color := clBlack;
        Rectangle(xRect);
      end;
      Inc(I);
    until (xLastChar >= xMaxChar) or (xLastChar = -1);
    
    xPDF.SaveToFile('a.pdf');
  finally
    xPDF.Free;
    xMF.Free;
  end;
end;

procedure TForm1.BtnSynopseRTFTestClick(Sender: TObject);
var
  xPDF: TPdfDocumentGDI;
  xRect: TRect;
  xMF: TMetafile;
  xMFC: TMetaFileCanvas;
  xHeight, xLastChar: Integer;

begin
  xPDF := TPdfDocumentGDI.Create;
  xMF := TMetafile.Create;
  try
    xPDF.AddPage;

    xRect := Rect(100, 100, 300, 1000);
    xHeight := CalculateRTFHeight(RE1, xPDF.VCLCanvas, xRect);
    xRect.Bottom := xRect.Top + xHeight;
    PrintRTFToCanvas(RE1, xPDF.VCLCanvas, xRect);
    with xPDF.VCLCanvas do begin
      Brush.Style := bsClear;
      Pen.Width := 1;
      Pen.Color := clBlack;
      Rectangle(xRect);
    end;

    xPDF.SaveToFile('a.pdf');
  finally
    xPDF.Free;
    xMF.Free;
  end;
end;

procedure TForm1.DoCreate;
var xStr: TStrings;
  I: Integer;
begin
  inherited;

  xStr := TStringList.Create;
  try
    if FileExists('a.rtf') then
      xStr.LoadFromFile('a.rtf');
    RE1.RawRTF := xStr.Text;
  finally
    xStr.Free;
  end;
end;

end.

EDIT: one example more

#38 Re: PDF Engine » Synopse PDF engine 1.8 » 2010-06-29 00:58:58

I worked on your PDF library and corrected some other issues (all concerning VCLCanvas):

  • Error when drawing multiple images

  • Automatical JPEG compression for graphics

  • UNDERLINE + STRIKEOUT support (also in RICH TEXT and rotated text !)

  • Because some developers may prefer to use the default JPEG unit in Delphi I added the directive USE_SYNGDIPLUS that can switch between GDIPLUS / JPEG

  • PenWidth changed to Single -> better precision (f.e. for underlined text)

  • ... maybe some other minor corrections

I don't see where I could save an attachment, therefore I send you the whole SynPdf.pas unit in an email. All my code changes (I hope that I haven't forgotten something) are marked with ONDREJ comment.

#39 Re: PDF Engine » Synopse PDF engine 1.8 » 2010-06-28 15:54:01

Thanks for the great PDF Library. I found 2 issues with the VCLCanvas functions. Please check my code:

procedure TForm1.Btn1Click(Sender: TObject);
var
  xPDF: TPdfDocumentGDI;
  xRect: TRect;
  xMF: TMetafile;
  xMFC: TMetaFileCanvas;

  procedure RotTextWithPoint(aC: TCanvas; aText: String; aAngle, aX, aY: Integer);
  var
    xLF: TLogFont;
    xF: TFont;
  begin
    xF := TFont.Create;
    try
      xF.Assign(aC.Font);
      GetObject(xF.Handle, SizeOf(xLF), @xLF);
      xLF.lfEscapement  := aAngle*10;
      xLF.lfOrientation := aAngle*10;
      SetBkMode(Handle, TRANSPARENT);
      xF.Handle := CreateFontIndirect(xLF);
      aC.Font.Assign(xF);
    finally
      xF.Free;
    end;
    aC.TextOut(aX, aY, aText);
    aC.MoveTo(aX-2, aY);
    aC.LineTo(aX+2,aY);
    aC.MoveTo(aX,aY-2);
    aC.LineTo(aX,aY+2);
  end;
  
begin
  xPDF := TPdfDocumentGDI.Create;
  xMF := TMetafile.Create;
  try
    xMF.Width := 500;
    xMF.Height := 500;
    
    xMFC := TMetafileCanvas.Create(xMF, 0);
    with xMFC do
    try
      Pen.Color := clBlue;
      Brush.Style := bsClear;
      Rectangle(0, 0, ClipRect.Right-1, ClipRect.Bottom-1);
      Pen.Color := clRed;
      xRect := Rect(100, 20, 150, 40);
      Rectangle(xRect);
      
      TextRect(xRect, xRect.Left, xRect.Top, 'long text is not clipped');

      RotTextWithPoint(xMFC, 'rotated text is misplaced', 90, 100, 200);
      RotTextWithPoint(xMFC, 'rotated text is misplaced', 180, 250, 200);
      RotTextWithPoint(xMFC, 'rotated text is misplaced', 270, 300, 200);
      RotTextWithPoint(xMFC, 'rotated text is misplaced', 45, 350, 200);
    finally
      Free;
    end;

    xMF.SaveToFile('a.emf');

    xPDF.AddPage;
    with xPDF.VCLCanvas do begin
      Draw(0, 0, xMF);
    end;

    xPDF.SaveToFile('a.pdf');
  finally
    xPDF.Free;
    xMF.Free;
  end;
end;

1. Rotated text is misplaced - there is a wrong sign in SynPDF.pas on line 6556. (procedure TPdfEnum.TextOut(var R: TEMRExtTextOut);)

Here is the corrected code:

(...)
    if font.spec.angle<>0 then begin
      a := font.spec.angle*(PI/180);
      acos := cos(a);
      asin := sin(a);
      with Canvas do
        SetTextMatrix(acos, asin, -asin, acos,
          I2X(R.emrtext.ptlReference.X+Round(W*acos-H*asin)),
          I2Y(R.emrtext.ptlReference.Y-Round(H*acos-W*asin))); // <- minus sign REDDWARF
    end else
(...)

2. Text is not cropped when using TextRect - unfortunately I did not find the solution on my own. I need this feature for drawing tables (text is clipped in a cell).

EDIT: Maybe I found a solution. This is the code:

procedure TPdfEnum.TextOut(var R: TEMRExtTextOut);
var W,H: integer;
    DX: PIntegerArray; // not handled during drawing yet
    ASize: single;
    tmp: array of WideChar; // R.emrtext is not #0 terminated -> use tmp[]
    a, acos, asin: single;
begin
  with DC[nDC] do begin
    SetLength(tmp,R.emrtext.nChars+1); // faster than WideString for our purpose
    move(pointer(PtrUInt(@R)+R.emrtext.offString)^,tmp[0],R.emrtext.nChars*2);
    // guess the font size
    if font.LogFont.lfHeight<0 then
      ASize := -font.LogFont.lfHeight*Canvas.fFactorY else
      ASize := font.spec.cell*Canvas.fFactorY;
    // ensure this font is selected (very fast if was already selected)
    Canvas.SetFont(Canvas.FDoc.FDC,font.LogFont,ASize);
    // calculate coordinates
    if R.emrtext.offDx=0 then begin
       if R.emrtext.fOptions and ETO_GLYPH_INDEX<>0 then
         W := 0 else
         W := Round(Canvas.UnicodeTextWidth(pointer(tmp))/Canvas.fFactorX);
    end else  begin
      DX := pointer(cardinal(@R)+R.emrtext.offDx);
      W := DXTextWidth(DX,R.emrText.nChars);
    end;
    if font.Align and TA_CENTER=TA_CENTER then
      W := W shr 1 else
    if font.Align and TA_RIGHT=0 then
      W := 0;
    if font.Align and TA_BASELINE<>0 then
      H := 0 else
    if font.Align and TA_BOTTOM<>0 then
      H := font.spec.descent else
      H := -font.spec.ascent;
    // draw background (if any)
    if (R.emrtext.fOptions and ETO_OPAQUE<>0) and not brush.null and
       (font.spec.angle=0) then begin
      // don't handle BkMode, since global to the page, but only specific text
      // don't handle rotation here, since should not be used much
      NormalizeRect(R.rclBounds);
      FillRectangle(R.rclBounds);
    end;
    // draw text
    FillColor := font.color;
{$ifdef USE_UNISCRIBE}
    Canvas.RightToLeftText := R.emrtext.fOptions and ETO_RTLREADING<>0;
{$endif}

    //REDDWARF BEGIN CLIPPING
    with Canvas do begin
      GSave;
      with R.emrtext.rcl do begin
        MoveTo(I2X(Left), I2Y(Top));
        LineTo(I2X(Left), I2Y(Bottom-1));
        LineTo(I2X(Right-1), I2Y(Bottom-1));
        LineTo(I2X(Right-1), I2Y(Top));
      end;
      ClosePath;
      Clip;
      NewPath;
    end;
    //REDDWARF END CLIPPING

    Canvas.BeginText;
    if font.spec.angle<>0 then begin
      a := font.spec.angle*(PI/180);
      acos := cos(a);
      asin := sin(a);
      with Canvas do
        SetTextMatrix(acos, asin, -asin, acos,
          I2X(R.emrtext.ptlReference.X+Round(W*acos-H*asin)),
          I2Y(R.emrtext.ptlReference.Y-Round(H*acos-W*asin))); // <- minus REDDWARF
    end else
      Canvas.MoveTextPoint(
        Canvas.I2X(R.emrtext.ptlReference.X-W),
        Canvas.I2Y(R.emrtext.ptlReference.Y-H));
    if R.emrtext.fOptions and ETO_GLYPH_INDEX<>0 then
      Canvas.ShowGlyph(pointer(tmp),R.emrtext.nChars) else
      Canvas.ShowText(pointer(tmp));
    Canvas.EndText;

    Canvas.GRestore;//REDDWARF CLIPPING RESTORE
  end;
end;

Board footer

Powered by FluxBB