Jump to content
Carlo Barazzetta

Looking for Icon Fonts support in Delphi for High-DPI and Themed app?

Recommended Posts

IconFontsImageList components by @Carlo Barazzetta could be the answer: you can explorer the complete wiki to see how it works.

 

Summary of library:

  • An IconFontsImageCollection component that inherits from Delphi's CustomImageCollection and is compatible with VirtualImageList
  • A IconFontsVirtualImageList, to use with Delphi version older than 10.3
  • A rendering engine of Icon-fonts using GDI+ (from Delphi XE4)
  • A complete backward compatibility with older Delphi versions (from Delphi 7)
  • A useful Collection and Component editor, with support for Category of Icons
  • A custom CharMap viewer, to easily select icons contained in any Font
  • Support for changing the Color based on the active VCL Style. 
  • High performance of drawing engine
  • Support for FMX (also for mobile platforms)
  • It's free and open-source

 

Icons based on Fonts are a good alternative to bitmaps because they need only the Font installed in the system to obtain thousands of images (like the "Material Design Font Desktop.ttf" font: https://github.com/Templarian/MaterialDesign-Font). The icons scales perfectly, so, you don't need to multiple resolutions of your images to match the DPI of the monitors and multiple colors for Theme used. 

The Collection of Icons can be rendered by a single Font/Color defined at collection level, or by different Fonts/Color defined at Icon level, so you can mix different icons from different Fonts in a single collection.

 

The library is quite stable, but any contribution is welcome!

 

ImageEditor.jpg

  • Like 5
  • Thanks 1

Share this post


Link to post

Are you sure that the font has to be installed?

I'm loading a custom font from the resources and it's working fine:

 

    // Load custom Fonts before creating any forms
    ResStream := TResourceStream.Create(hInstance, 'MONTSERRAT_MEDIUM', RT_RCDATA);
    try
      AddFontMemResourceEx(ResStream.Memory, ResStream.Size, nil, @FontsCount);
    finally
      ResStream.Free;
    end;

 

Edited by Attila Kovacs

Share this post


Link to post
12 hours ago, Attila Kovacs said:

Are you sure that the font has to be installed?

I'm loading a custom font from the resources and it's working fine:

 


    // Load custom Fonts before creating any forms
    ResStream := TResourceStream.Create(hInstance, 'MONTSERRAT_MEDIUM', RT_RCDATA);
    try
      AddFontMemResourceEx(ResStream.Memory, ResStream.Size, nil, @FontsCount);
    finally
      ResStream.Free;
    end;

 

Yes, to develop the app the font must be installed on the system in order to use it in the IDE.
Deploying the app you can do as you explain: I have similar code in the demo, which responds to the OnFontMissing event raised by the component if Font is not installed:

 

  //The "material desktop font is not installed into system: load and install now from disk
  LFontFileName := ExtractFilePath(Application.ExeName)+'..\Fonts\Material Design Icons Desktop.ttf';
  if FileExists(LFontFileName) then
  begin
    {$IFNDEF D2010+}
    AddFontResource(PChar(LFontFileName));
    {$ELSE}
    AddFontResource(PWideChar(LFontFileName));
    {$ENDIF}
    SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
  end

 

  • Like 1

Share this post


Link to post
3 hours ago, Carlo Barazzetta said:

Yes, to develop the app the font must be installed on the system in order to use it in the IDE.
Deploying the app you can do as you explain:

If you wanted to avoid the requirement of having the fonts installed globally in Windows you could write a design package with the fonts linked in as resources. The package, when loaded by the IDE, could load the fonts from the resources (using the code shown by @Attila Kovacs) and call Screen.ResetFonts to signal the IDE to reinitialize its font list . After that the fonts would be available to the IDE.

Share this post


Link to post
3 hours ago, Ondrej Kelle said:

If you wanted to avoid the requirement of having the fonts installed globally in Windows you could write a design package with the fonts linked in as resources. The package, when loaded by the IDE, could load the fonts from the resources (using the code shown by @Attila Kovacs) and call Screen.ResetFonts to signal the IDE to reinitialize its font list . After that the fonts would be available to the IDE.

Indeed:

From https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-addfontmemresourceex

Quote

 

This function allows an application to get a font that is embedded in a document or a webpage. A font that is added by AddFontMemResourceEx is always private to the process that made the call and is not enumerable.

A memory image can contain more than one font. When this function succeeds, pcFonts is a pointer to a DWORD whose value is the number of fonts added to the system as a result of this call. For example, this number could be 2 for the vertical and horizontal faces of an Asian font.

When the function succeeds, the caller of this function can free the memory pointed to by pbFont because the system has made its own copy of the memory. To remove the fonts that were installed, call RemoveFontMemResourceEx. However, when the process goes away, the system will unload the fonts even if the process did not call RemoveFontMemResource.

 

 

Edited by pyscripter

Share this post


Link to post


Actually the components do not install any fonts on purpose, to avoid thinking they only works with a specific font ... The demo uses a sample font, and also shows how to load it on-the-fly if it is not installed on the system.
The downside is that when you open the demo you don't see the icons right away in the IDE and this could make you think that the component is not working properly...
The other aspect is that any font loaded as a resource in the IDE affects the possibility of installing a new version on the same font in the system...
I think that letting the developer choose to install the fonts he needs to use in his applications to use them with the components is the right and least invasive choice, don't you agree?

Share this post


Link to post

The transition to "Virtual" is now complete (released ver.2.3).
Demos for 10.3 and 10.4 now uses native TVirtualImageList + TIconFontsImageCollection, and the TIconFontImage component can also be linked to VirtualImageList and IconFontsVirtualImageList (not only to IconFontsImageList).

I'm searching for users and contributors to the project, expecially for older Delphi versions (because the components are compatible from Delphi 7 to current): it's very hard to me to maintain backward compatibility without help.
From Delphi XE4 they uses GDI+ to render icons. 

Share this post


Link to post

Is it just me, or even the demo application does not import the font file properly when started for the first time?

 

image.thumb.png.7ee843c7cdfa6bffe6b48152c9d1fc9c.png

 

If the font is installed on the system (manually or by AddFontResource due to the first run) everything appears correctly.

 

I tried Screen.ResetFonts and ImageList.RecreateBitmaps after the SendMessage, without success. Dumping everything on the disk with .SaveToPngFiles also saves the rectangles only.

 

I'm using the latest snapshot from Git directly.

Share this post


Link to post

When starts, the demo looks for the font installed into Windows: if not try loading from disk in this event of the datamodule:

procedure TdmImages.IconFontsImageCollectionFontMissing(
  const AFontName: TFontName);
var
  LFontFileName: string;
begin
  inherited;
  //The "material desktop font is not installed into system: load and install now from disk
  LFontFileName := ExtractFilePath(ParamStr(0))+'..\Fonts\Material Design Icons Desktop.ttf';
  if FileExists(LFontFileName) then
  begin
    {$IFNDEF D2010+}
    AddFontResource(PChar(LFontFileName));
    {$ELSE}
    AddFontResource(PWideChar(LFontFileName));
    {$ENDIF}
    SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
  end
  else
  begin
    //If the font file is not available
    raise Exception.CreateFmt('Warning: "%s" font is not present in your system!'+sLineBreak+
      'Please download at https://materialdesignicons.com and install it, because this demo is based on this font.',
        [AFontName]);
  end;
end;

If the call to AddFontResource fails on your machine I don't known why...

please use the issue section of the project to continue discussion...
https://github.com/EtheaDev/IconFontsImageList/issues

Another reason could be that the font is installed only as resource of the IDE: I reccommend to install it manually in the developer machine as system font.

bye
Carlo

Share this post


Link to post

After several attempts we found these problems:
1) after calling AddFontResource and notified in windows that a font has been added calling SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0); the application hangs for several seconds waiting for all processes to respond to the message... 😞

Using PostMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0); instead of SendMessage does not hang.

https://social.msdn.microsoft.com/Forums/en-US/6900f74f-6ece-47da-88fc-f9c8bcd40206/sendmessage-api-slow?forum=wpf


2) In any case the imagelist does not draw icons correctly when it is using GDI+: only by adding a sleep (500) you get the correct drawing of the icons.
It seems that calling AddFontResource does not immediately update the list of fonts available to GDI+
For now the solution is to "wait" 500 millisecond at startup application after adding font loaded from disk:

    AddFontResource(PWideChar(FFontFileName));
    PostMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
    //Wait for Font available on GDI+ collection for drawing...
    Sleep(500);

But is not an elegant solution... Any idea?

 

Another article with similar problem:

https://www.vbforums.com/showthread.php?883637-GDI-problems-with-GdipCreateFontFamilyFromName

 

 

Edited by Carlo Barazzetta

Share this post


Link to post
2 hours ago, Carlo Barazzetta said:

After several attempts we found these problems

Maybe this is a clue that you're using fonts for something that they were not intended for...

Share this post


Link to post
11 minutes ago, Anders Melander said:

Maybe this is a clue that you're using fonts for something that they were not intended for...

You mean a font is not meant to be installed runtime or not to be drawn with GDI+?

Share this post


Link to post
1 hour ago, Anders Melander said:

Maybe this is a clue that you're using fonts for something that they were not intended for...

I didn't understand what you mean...
I am using "fonts of icons" to draw bitmaps in an ImageCollection / ImageList, to get scaled images for any DPI... I am using GDI + for best performance and transparency support ...
If the font is already installed in the system it works fine, the problem is only when deploying an application without installing the font during setup: only in this case the application must load and add the font at startup, before use it to draw bitmaps, but I don't know why I need to add a sleep 😞

Share this post


Link to post

Have you checked if your app receives any notification after the installation of the font?
A sleep seems so random... what if the system is REALLY busy so that the sleep is too short?

Share this post


Link to post
5 minutes ago, Lars Fosdal said:

Have you checked if your app receives any notification after the installation of the font?

There is no automatic notification as far as I'm aware, but the app receives the WM_FONTCHANGE message which was broadcasted. In the discussion on GitHub I mentioned that if I put a label on the form, set the font name to the same as in the ListView and paste some characters it shows up correctly without PostMessage / Sleep. This makes me believe that the installation is successful, only GDI+ needs some time before it can access a freshly installed font (or implementation is not correct). Unfortunately I have zero experience with any graphic-related stuff so can not confirm / debug :(

10 minutes ago, Lars Fosdal said:

A sleep seems so random... what if the system is REALLY busy so that the sleep is too short?

This is why it's not even viable as a workaround imo.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×