Recent

Author Topic: [SOLVED] Embed a font to use without installing  (Read 27677 times)

fatmonk

  • Full Member
  • ***
  • Posts: 225
[SOLVED] Embed a font to use without installing
« on: May 29, 2013, 06:16:58 pm »
According to http://www.lazarus.freepascal.org/index.php?topic=14276.0 this is easy to achieve in Windows, but a lot of searching has turned up next to nothing.

What I'd like to do is include a specific font in my .exe for that .exe to use. I don't want to install the font (the .exe is standalone and doesn't need installing) and I don't want to save it onto the users' machine.

So far the only hint I have is to include the font as a resource in the project but I have no idea how to get the application to use that font.

Can anyone give me any clues?

Thanks,

FM
« Last Edit: June 04, 2013, 01:49:42 pm by fatmonk »

taazz

  • Hero Member
  • *****
  • Posts: 5363
Re: Embed a font to use without installing
« Reply #1 on: May 29, 2013, 07:24:37 pm »
Code: [Select]
const
    MM_MAX_NUMAXES = 16;
type
  PDesignVector = ^TDesignVector;
  TDesignVector = packed record
    dvReserved: DWORD;
    dvNumAxes: DWORD;
    dvValues: array[0..MM_MAX_NUMAXES-1] of Longint;
  end;

function AddFontMemResourceEx(p1: Pointer; p2: DWORD; p3: PDesignVector; p4: LPDWORD): THandle; external 'gdi32.dll' name 'AddFontMemResourceEx'; stdcall;
function RemoveFontMemResourceEx(p1: THandle): BOOL; external 'gdi32.dll' name 'RemoveFontMemResourceEx'; stdcall;

procedure LoadFontFromRes(FontName: PWideChar);
var
  ResHandle: HRSRC;
  ResSize, NbFontAdded: Cardinal;
  ResAddr: HGLOBAL;
begin
  ResHandle := FindResource(system.HINSTANCE, FontName, RT_FONT);
  if ResHandle = 0 then
    RaiseLastOSError;
  ResAddr := LoadResource(system.HINSTANCE, ResHandle);
  if ResAddr = 0 then
    RaiseLastOSError;
  ResSize := SizeOfResource(DllHandle, ResHandle);
  if ResSize = 0 then
    RaiseLastOSError;
  if 0 = AddFontMemResourceEx(Pointer(ResAddr), ResSize, nil, @NbFontAdded) then
    RaiseLastOSError;
end;


Assuming you know how to include a Font in your resources correctly the above code should load the font from your resources just make sure that when you do not need it anymore to call the RemoveFontMemResourceEx a good place to do that is the main forms constructor / destructor.

Keep in mind the above are typed directly in the forums editor and I do not know if it will compile or not.

Just make sure the code is Windows only
« Last Edit: May 29, 2013, 07:28:05 pm by taazz »
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

eny

  • Hero Member
  • *****
  • Posts: 1588
Re: Embed a font to use without installing
« Reply #2 on: May 29, 2013, 07:30:19 pm »
You might need to signal that the font is now available for use via
Code: [Select]
  SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
(at least that's what I had to do in my font view program...)

<<edit>>
1001.. mm.. sounds like a fairytale
All posts based on: Win10 (Win64); Lazarus 1.8.0 'stable' (#56594 win64) unless specified otherwise...

fatmonk

  • Full Member
  • ***
  • Posts: 225
Re: Embed a font to use without installing
« Reply #3 on: May 30, 2013, 10:42:53 am »
Thanks both... it looks like I'm going to need a bit more help on this, though, I'm afraid.

taazz> You say 'assuming you know how to include a font in your resources correctly'... that's debatable! I have added the font file to the project via the Project Inspector - my guess is that that was probably assuming the task to be far simple than it really is. Is there any way you could explain how I do add the font as a resource properly and also explain a bit about the code sample you posted so that I can understand how to use it before I just go copying and pasting.

eny> Once I get the other bit added and compiling I'll add your suggestion as well - what's the bit about "1001" being a fairytale (sorry, I'm having a day of being stupid today).

-FM

taazz

  • Hero Member
  • *****
  • Posts: 5363
Re: Embed a font to use without installing
« Reply #4 on: May 30, 2013, 11:31:32 am »
I think that the code is self explained but here it goes.

Having a resource is like having a simple array of data of variable size and type. As you can understand so far a resource is identified by an ID. This ID is created algorithmically by the name which must be unique in the application or dll resource table. findResource translates a resource name to its id, loadresource loads the resource in to memory sizeofresource calculates the data size of the resource and the final call AddFontMemRes will add the font to the windows installed font table. Every call is checked if it was executed succesfuly and if it is not then raiselastOSError is called to create an exception with the error number and message reported by that call to the OS making sure that you get a meaningful message instead of silently failing, this is the standard procedure when calling api functions.

By calling the sendmessage(....) that envy told you, you inform windows that a new font has been loaded and at the same time windows send a message to all running applications to refresh their copy of font list as needed. I think that the refresh is taken care internally for you.
You can also send the message to your application only instead of the HWND_BROADCAST (application.MainFormHandle if I remember correctly or something along those lines)

Now about the resource you need to create an RC file and link it in your application using {$R myFont.rc} this will compile the RC to a binary Res and include it in the exe's resource table. How you create the RC I do not remember by heart, it has been more than 5 years the last time I did something along those lines. Of course you can use any resource editor and create a binary RES file instead of an RC and use that in the {$R } directive.

Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

fatmonk

  • Full Member
  • ***
  • Posts: 225
Re: Embed a font to use without installing
« Reply #5 on: May 30, 2013, 04:53:30 pm »
Thanks taazz,

I'm new to pascal so I'm sure your code was very self-explanatory, but just consider me ignorant on most things. My C++ Builder experience was years ago as well so I'm trying to get myself back up to speed while handling different ways of doing things.

I've made some progress as follows.

- Created an lrs file with lazres.
- Added LResources to the uses clause.
- Added an initialization clause just before the end. at the end of the Unit1.pas file with {$I myResourceFile.lrs}.

That all seems to compile OK so I assume that the resource is being included in the exe (when I compile without the {$I clause the exe created is 19kB smaller - though the resource file is 59kB on disk).

I've then added your code but am not sure I've put things in the correct place.

- The const clause I have added after the uses clause but before the type clause in Unit1.pas.
- The type declarations I have added to the end; of the TFor1 class declaration in the existing type clause in Unit1.pas.
- The two  function declarations I've added right after that.
- The procedure I've added after my other procedures and before the new initialization clause that's including my resource.

When I tried to compile I got an error that seemed to imply that an end; was needed to match the record of TDesignVector so I added that and the compile gets further than that line now.

I'm now getting another two errors though:

Code: [Select]
unit1.pas(41,87) Error: Identifier not found "LPDWORD"
unit1.pas(41,149) Error: Procedure directive "STDCALL" has conflicts with other directives
unit1.pas(41,149) Fatal: There were 2 errors compiling module, stopping

Looking back at my explanation it's probably worth me pasting that whole definition bit from the top of my Unit1.pas:

Code: [Select]
unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
  StdCtrls, Unit2, LResources;

const
    MM_MAX_NUMAXES = 16;   // font embed

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Image1: TImage;
    Label1: TLabel;

    procedure CloseDialog(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure showImage(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;

  PDesignVector = ^TDesignVector;       // font embed
  TDesignVector = packed record         // font embed
      dvReserved: DWORD;                  // font embed
      dvNumAxes: DWORD;                   // font embed
      dvValues: array[0..MM_MAX_NUMAXES-1] of Longint;     // font embed
  end;

  function AddFontMemResourceEx(p1: Pointer; p2: DWORD; p3: PDesignVector; p4: LPDWORD): THandle; external 'gdi32.dll' name 'AddFontMemResourceEx'; stdcall;                // font embed
  function RemoveFontMemResourceEx(p1: THandle): BOOL; external 'gdi32.dll' name 'RemoveFontMemResourceEx'; stdcall;                                                        // font embed


var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 } 
and then my procedures etc follow.

I hope that's not too much of a bombardment.

Oh, and the stupid question... how do I specify the font and resource to load/use? I assume I make it available using LoadFontFromRes('myResourceFile') or is it by font name as in LoadFontFromRes('myFontName')? And do I need to then set that for the controls that use it or will it use the font of the correct name was specified at design time?

Finally, stupid question 2: How do I know the parameter to pass to RemoveFontMemResourceEx?

(I'm really feeling the newbie in me with this one - but all good learning).

Thanks,

FM





eny

  • Hero Member
  • *****
  • Posts: 1588
Re: Embed a font to use without installing
« Reply #6 on: May 30, 2013, 08:57:48 pm »
All posts based on: Win10 (Win64); Lazarus 1.8.0 'stable' (#56594 win64) unless specified otherwise...

Cyrax

  • Hero Member
  • *****
  • Posts: 760
Re: Embed a font to use without installing
« Reply #7 on: May 31, 2013, 08:02:42 am »
...

I highly doubt that Lazarus resource format is correct way to include embed font. You should use FPC/Windows native resource format as taazz have explained in his/her posts.

See this link http://msdn.microsoft.com/en-us/library/windows/desktop/ee264311(v=vs.85).aspx for more info.

BigChimp

  • Hero Member
  • *****
  • Posts: 5740
  • Add to the wiki - it's free ;)
    • FPCUp, PaperTiger scanning and other open source projects
Want quicker answers to your questions? Read http://wiki.lazarus.freepascal.org/Lazarus_Faq#What_is_the_correct_way_to_ask_questions_in_the_forum.3F

Open source including papertiger OCR/PDF scanning:
https://bitbucket.org/reiniero

Lazarus trunk+FPC trunk x86, Windows x64 unless otherwise specified

taazz

  • Hero Member
  • *****
  • Posts: 5363
Re: Embed a font to use without installing
« Reply #9 on: May 31, 2013, 11:47:55 am »
Well given all the responces so far you should have enough information by now to make corrections to your resources and how to include them in your application. In any case lResources is code based resources and they are not compatible with the "file" resources that a windows executable has. You need to create windows resource and link them in your application that is done using 2 methods.
1) create an RC file and link it using the {$R} directive. An RC file can be seen as the source file of a binary resource aka .res file. The FPC tool set will detect and compile those to binary resources aka *.res file.
2) create a .RES file and link it in using the {$R} directive. Just use any Resource editor in the internet that can handle .res files and add your font resource to a new file.

As for the second question I see that my procedure does not provide the complete picture so here is a revised version of it to.
Code: [Select]
function LoadFontFromRes(FontName: PWideChar):THandle;
var
  ResHandle: HRSRC;
  ResSize, NbFontAdded: Cardinal;
  ResAddr: HGLOBAL;
begin
  ResHandle := FindResource(system.HINSTANCE, FontName, RT_FONT);
  if ResHandle = 0 then
    RaiseLastOSError;
  ResAddr := LoadResource(system.HINSTANCE, ResHandle);
  if ResAddr = 0 then
    RaiseLastOSError;
  ResSize := SizeOfResource(system.HINSTANCE, ResHandle);
  if ResSize = 0 then
    RaiseLastOSError;
  Result = AddFontMemResourceEx(Pointer(ResAddr), ResSize, nil, @NbFontAdded)
  if Result = 0 then
    RaiseLastOSError;
end;
Save the result of the function to a variable and use that variable to pass to the RemoveFontMemResourceEx call.

Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

fatmonk

  • Full Member
  • ***
  • Posts: 225
Re: Embed a font to use without installing
« Reply #10 on: May 31, 2013, 02:56:13 pm »
I'm slowly getting there... possibly.

- Have created a resource file (.rc) from a template on MSDN that points to my ttf file. (I didn't realise it was just a text file!)
- I've changed the {$I.... in the initialize clause at the end of Unit1.pas to a {$R... statement now pointing at the new .rc file rather than the .lrs file.
- Have updated the LoadFont... function to taazz's new version.

I was still getting the error:
Code: [Select]
unit1.pas(41,87) Error: Identifier not found "LPDWORD"
but adding Windows to the uses clause for Unit1.pas seems to have cured that - is that the correct fix?

I still, however, have the error:
Code: [Select]
unit1.pas(41,149) Error: Procedure directive "STDCALL" has conflicts with other directives
so I can't compile at the moment.

Then I need to figure out how to actually assign the font to the controls that I want to use it on the TForm.

As you can see I'm really struggling with this one as it's completely new territory for me.

Thanks,

FM

taazz

  • Hero Member
  • *****
  • Posts: 5363
Re: Embed a font to use without installing
« Reply #11 on: May 31, 2013, 03:22:44 pm »
I'm slowly getting there... possibly.

- Have created a resource file (.rc) from a template on MSDN that points to my ttf file. (I didn't realise it was just a text file!)
- I've changed the {$I.... in the initialize clause at the end of Unit1.pas to a {$R... statement now pointing at the new .rc file rather than the .lrs file.
- Have updated the LoadFont... function to taazz's new version.

I was still getting the error:
Code: [Select]
unit1.pas(41,87) Error: Identifier not found "LPDWORD"

yes using the unit windows is required.

but adding Windows to the uses clause for Unit1.pas seems to have cured that - is that the correct fix?

I still, however, have the error:
Code: [Select]
unit1.pas(41,149) Error: Procedure directive "STDCALL" has conflicts with other directives
so I can't compile at the moment.

Then I need to figure out how to actually assign the font to the controls that I want to use it on the TForm.

As you can see I'm really struggling with this one as it's completely new territory for me.

Thanks,

FM

Oh must have been half asleep when writing the method definitions you need to put stdcall before everything else something alone the lines of
Code: [Select]
function AddFontMemResourceEx(p1: Pointer; p2: DWORD; p3: PDesignVector; p4: LPDWORD): THandle; stdcall external 'gdi32.dll' name 'AddFontMemResourceEx';
function RemoveFontMemResourceEx(p1: THandle): BOOL; stdcall external 'gdi32.dll' name 'RemoveFontMemResourceEx';
that should be enough.

To use the font in your controls you need to first send the message that eny talked about after the call to the loadFontFromRes call and let the application process the messages eg.
Code: [Select]
var
  vFnt : THandle;
begin
  vFnt := LoadFontFromRes('MyResFontName');
  SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
  Application.ProcessMessages;
end;
That should refresh your application to be able to recognise the font name after that setting the Control.Font.Name to the font's name will be the only thing you need to use it. Keep in mind that the font's name is not the resource name you defined in the RC file to make things simple just install the font in your development machine and select it in a control that supports fonts the name you see on the object inspector is the name you must set in order to use it.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

fatmonk

  • Full Member
  • ***
  • Posts: 225
Re: Embed a font to use without installing
« Reply #12 on: June 03, 2013, 05:11:01 pm »
Thanks all,

It took a bit of fiddling, but I believe I'm there... I have the font displaying in the application on a box where that font is not installed which is exactly what I needed.

(Now I just have to pick over taazz's code so that I can fully understand it - the only way I'll learn).

Thanks again, it's very much appreciated,

FM

Sait™

  • New member
  • *
  • Posts: 7
Re: Embed a font to use without installing
« Reply #13 on: August 03, 2016, 01:45:56 pm »
Code: [Select]
const
    MM_MAX_NUMAXES = 16;
type
  PDesignVector = ^TDesignVector;
  TDesignVector = packed record
    dvReserved: DWORD;
    dvNumAxes: DWORD;
    dvValues: array[0..MM_MAX_NUMAXES-1] of Longint;
  end;

function AddFontMemResourceEx(p1: Pointer; p2: DWORD; p3: PDesignVector; p4: LPDWORD): THandle; external 'gdi32.dll' name 'AddFontMemResourceEx'; stdcall;
function RemoveFontMemResourceEx(p1: THandle): BOOL; external 'gdi32.dll' name 'RemoveFontMemResourceEx'; stdcall;

procedure LoadFontFromRes(FontName: PWideChar);
var
  ResHandle: HRSRC;
  ResSize, NbFontAdded: Cardinal;
  ResAddr: HGLOBAL;
begin
  ResHandle := FindResource(system.HINSTANCE, FontName, RT_FONT);
  if ResHandle = 0 then
    RaiseLastOSError;
  ResAddr := LoadResource(system.HINSTANCE, ResHandle);
  if ResAddr = 0 then
    RaiseLastOSError;
  ResSize := SizeOfResource(DllHandle, ResHandle);
  if ResSize = 0 then
    RaiseLastOSError;
  if 0 = AddFontMemResourceEx(Pointer(ResAddr), ResSize, nil, @NbFontAdded) then
    RaiseLastOSError;
end;


Can I load the font from raw file instead of compling font to resource data?

RAW

  • Hero Member
  • *****
  • Posts: 794
Re: [SOLVED] Embed a font to use without installing
« Reply #14 on: October 07, 2016, 11:12:01 pm »
Quote
Can I load the font from raw file instead of compling font to resource data?
raw file ???


I guess this is the easy way without RES and stress...  :P

Code: Pascal  [Select]
  1. Unit Unit1;
  2.  {$mode objfpc}{$H+}
  3.  
  4. Interface
  5.  USES
  6.   Windows, Classes, SysUtils, Forms, Controls;
  7.  
  8.  TYPE
  9.   TForm1 = Class(TForm)
  10.    Procedure FormCreate (Sender: TObject);
  11.    Procedure FormClose  (Sender: TObject;  Var CloseAction: TCloseAction);
  12.   End;
  13.  
  14.  CONST
  15.   MM_MAX_NUMAXES =  16;
  16.   FR_PRIVATE     = $10;
  17.   FR_NOT_ENUM    = $20;
  18.  
  19.  TYPE
  20.   PDesignVector = ^TDesignVector;
  21.   TDesignVector = Packed Record
  22.    dvReserved: DWORD;
  23.    dvNumAxes : DWORD;
  24.    dvValues  : Array[0..MM_MAX_NUMAXES-1] Of LongInt;
  25.   End;
  26.  
  27.  VAR
  28.   Form1: TForm1;
  29.  
  30.  
  31.  Function AddFontResourceEx    (Dir : PAnsiChar;
  32.                                 Flag: Cardinal;
  33.                                 PDV : PDesignVector): Int64; StdCall;
  34.                                 External 'GDI32.dll' Name 'AddFontResourceExA';
  35.  
  36.  Function RemoveFontResourceEx (Dir : PAnsiChar;
  37.                                 Flag: Cardinal;
  38.                                 PDV : PDesignVector): Int64; StdCall;
  39.                                 External 'GDI32.dll' Name 'RemoveFontResourceExA';
  40.  
  41.  
  42. Implementation
  43.  {$R *.lfm}
  44.  
  45.  
  46. Procedure LoadFonts;
  47.   Var
  48.    AppPath: String;
  49.  Begin
  50.   AppPath:= ExtractFilePath(Application.ExeName);
  51.  
  52.    If FileExists(AppPath+'FONTS\MONO.ttf')
  53.    Then
  54.     If AddFontResourceEx(PAnsiChar(AppPath+'FONTS\MONO.ttf'), FR_Private, Nil) <> 0
  55.     Then SendMessage(Form1.Handle, WM_FONTCHANGE, 0, 0);
  56.  End;
  57.  
  58.  
  59. Procedure RemoveFonts;
  60.   Var
  61.    AppPath: String;
  62.  Begin
  63.   AppPath:= ExtractFilePath(Application.ExeName);
  64.  
  65.    If FileExists(AppPath+'FONTS\MONO.ttf')
  66.    Then
  67.     If RemoveFontResourceEx(PAnsiChar(AppPath+'FONTS\MONO.ttf'), FR_Private, Nil) <> 0
  68.     Then SendMessage(Form1.Handle, WM_FONTCHANGE, 0, 0);
  69.  End;
  70.  
  71.  
  72. Procedure TForm1.FormCreate(Sender: TObject);
  73.  Begin
  74.   LoadFonts;
  75.  End;
  76.  
  77.  
  78. Procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
  79.  Begin
  80.   RemoveFonts;
  81.  End;
  82.  
  83.  
  84. End.
  85.  
Windows 7 Pro (x64 Sp1) And Windows XP Pro (x86 Sp3) - LAZARUS 2.0.4 FPC 3.0.4 - TRUNK 2.1.0 FPC 3.3.1