It is currently November 28th, 2022, 4:08 pm

Fonts

Report bugs with the Rainmeter application and suggest features.
User avatar
jsmorley
Developer
Posts: 22551
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Fonts

Post by jsmorley »

Matt,

What I have in mind is something a few of us have talked about in the past but never got round to. It is to allow skin authors to include a font with their skins, and have Rainmeter use it without the user having to "install" it to Windows\Fonts.

We would like to have another folder at the same level as Rainmeter.exe (normally C:\Program Files\Rainmeter) called \Fonts

This folder should be resolved and added to the list of #VARNAME# built-in variables we have now like #SKINSPATH# It could be #FONTSPATH#

In that you can store all fonts you get from various skins downloaded from DevArt etc.

Then we would need a new statement in the [Rainmeter] section of a skin. I was thinking LocalFont=xxxxx

The author could either leave the font in the skin folder and just put LocalFont=Veranda.ttf for instance, or have the user put the font in the common \Fonts folder under Rainmeter and use LocalFont=#FONTSPATH#Veranda.ttf (they will need to specify .ttf .otf etc. as the function(s) I am looking at will need a full file name.

So from a skin standpoint you would have:

[Rainmeter]
LocalFont=Veranda.ttf

[Variables]
FontName=Veranda

(It should be noted that the filename.ttf of a font and the "internal" name needed for FontName= may not be the same. This is the author's responsibility)

Now the C++

http://msdn.microsoft.com/en-us/library/dd183327%28VS.85%29.aspx

So you call it more or less like this:

Code: Select all

CString szFontFile = "Veranda.ttf";

int nResults = AddFontResourceEx(
	m_szFontFile, // font file name
	FR_PRIVATE,             // font characteristics
	NULL);
And when Rainmeter unloads the skin you need to run:

Code: Select all

BOOL b = RemoveFontResourceEx(
	m_szFontFile,  // name of font file
	FR_PRIVATE,            // font characteristics
	NULL            // Reserved.
	);
To remove it from memory.

I don't think we want or need to "broadcast" the font to other Windows apps.

There was a suggestion that we just autoload all fonts found in \Fonts when Rainmeter starts and clear them when it ends. This has some charm as it makes it a lot simpler for the skin author, and would be an easier job of coding in the C++ as you wouldn't have to parse the .ini and all that. I would prefer doing it the way I suggested, as I keep like 2,000 fonts "uninstalled" on my system to use in Photoshop with NexusFonts (which does read the whole folder and broadcast the fonts to all Windows apps) but I would hate to have Rainmeter using all that memory to load a ton of fonts you are not using, since Rainmeter is running all the time on most machines. However, I could be talked into the easier way. Let the perfect not be the enemy of the good...

Let me know what you think, if you have any questions or suggestions. I am just not good enough at C++ to be confident I can do this myself without some help, and would love to work with you on this.

P.S. There is an interesting discussion of this at http://www.codeguru.com/cpp/g-m/gdi/gdi/article.php/c16019/
MattKing
Developer
Posts: 98
Joined: August 6th, 2009, 3:03 pm

Re: Fonts

Post by MattKing »

This looks doable.

As a student in video game programming I can't very well let Rainmeter load resources it doesn't need! So I will definitely try to only load the fonts that are needed.

So you're just gonna want to load fonts in the [Rainmeter] section? That would mean we'd need like LocalFont1, LocalFont2. I was thinking you may want to do it in the StringMeters section, you can specify the LocalFont you want(the file) and then the font variant (IE its real font name, FontFace). Why I think this is easier is that I only have to change the StringMeter class leaving everything else the same, also I can a keep a static variable to check if a font is loaded or not.

Code: Select all

[SomeStringMeter]
Meter=STRING
LocalFont=Verdana.ttf
FontFace=Verdana
To be fair I don't know much about how GDI loads fonts I'll have to look into it to see how it really works.

Problems I dont know about:
  • If the font is already one of the fonts installed does it throw an error of some sort when I try to add it?
  • I thought there was more, I'm sure I'll think of more problems.

EDIT:

I've got it displaying fonts that aren't installed, now to read them in from the ini file.

EDIT 2:

I've got it reading the font location (The full path location as of now) and it is displaying the font.
User avatar
jsmorley
Developer
Posts: 22551
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: Fonts

Post by jsmorley »

MattKing wrote:This looks doable.

As a student in video game programming I can't very well let Rainmeter load resources it doesn't need! So I will definitely try to only load the fonts that are needed.

So you're just gonna want to load fonts in the [Rainmeter] section? That would mean we'd need like LocalFont1, LocalFont2. I was thinking you may want to do it in the StringMeters section, you can specify the LocalFont you want(the file) and then the font variant (IE its real font name, FontFace). Why I think this is easier is that I only have to change the StringMeter class leaving everything else the same, also I can a keep a static variable to check if a font is loaded or not.

Code: Select all

[SomeStringMeter]
Meter=STRING
LocalFont=Verdana.ttf
FontFace=Verdana
To be fair I don't know much about how GDI loads fonts I'll have to look into it to see how it really works.

Problems I dont know about:
  • If the font is already one of the fonts installed does it throw an error of some sort when I try to add it?
  • I thought there was more, I'm sure I'll think of more problems.

EDIT:

I've got it displaying fonts that aren't installed, now to read them in from the ini file.
Excellent!

* My thoughts on "when" to load them were as follows:

1) It would be nice to load them when Rainmeter itself starts up and remove them when Rainmeter closes, but that would mean you would be almost forced to load all fonts in \Fonts as you wouldn't know which were being used. So as clean as that approach is, it probably isn't the best.

2) If you load them dynamically as part of the meter itself, you have to avoid loading (or at least checking to be sure they are loaded) every Update= cycle. Could be a fair amount of programming and / or a cpu load.

3) If you load them as a part of [Rainmeter] they would only be read and loaded on the initial load of the skin and on a full refresh. (hmm. we need to look at "refresh" and make sure we are unloading fonts before reloading them, or build the routine in such a way that reloading them doesn't throw an error or cause a build up of memory usage.)

That was my thinking behind using [Rainmeter] as the best place to put the LocalFont= However, I don't see anything about the second and third approaches (in [Rainmeter] or in [MeterName]) that is a show stopper. You will be the best judge as you dig into it.

* As to multiple fonts. That we need to think through. Obviously we want to support it. I wonder if the best isn't

LocalFont=Veranda|Arial|Tahoma
or
LocalFont=Veranda,Arial,Tahoma

So you don't need to run some kind of recursive search for Font1= Font2= where you have to parse the string looking for a number at the end. Obviously this also is a selling point for putting the routine in [MeterName] rather than [Rainmeter] since there by the nature of things can only be one font per text meter and it solves the "multiple fonts" issue quite nicely.

* I think that article I posted touches on what happens when you try to load a local font that is already installed. I think it defaults to the font you are asking it to load, but we need to research that to be sure we don't get an error as you say.
User avatar
jsmorley
Developer
Posts: 22551
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: Fonts

Post by jsmorley »

EDIT:

I've got it displaying fonts that aren't installed, now to read them in from the ini file.

EDIT 2:

I've got it reading the font location (The full path location as of now) and it is displaying the font.



I am so jealous of you at this point I just hate your guts.... ;-)
User avatar
jsmorley
Developer
Posts: 22551
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: Fonts

Post by jsmorley »

One thing you might look at / test is how best to handle "unloading" the fonts. From my reading of the functions on MSDN, I think it "might" unload them automatically when Rainmeter ends if you load them using the "FR_PRIVATE" attribute of "Font Characteristics". If so, you won't have to come up with code to determine what fonts are loaded and unload them as each skin is unloaded.
MattKing
Developer
Posts: 98
Joined: August 6th, 2009, 3:03 pm

Re: Fonts

Post by MattKing »

jsmorley wrote: Excellent!

* My thoughts on "when" to load them were as follows:

1) It would be nice to load them when Rainmeter itself starts up and remove them when Rainmeter closes, but that would mean you would be almost forced to load all fonts in \Fonts as you wouldn't know which were being used. So as clean as that approach is, it probably isn't the best.

2) If you load them dynamically as part of the meter itself, you have to avoid loading (or at least checking to be sure they are loaded) every Update= cycle. Could be a fair amount of programming and / or a cpu load.

3) If you load them as a part of [Rainmeter] they would only be read and loaded on the initial load of the skin and on a full refresh. (hmm. we need to look at "refresh" and make sure we are unloading fonts before reloading them, or build the routine in such a way that reloading them doesn't throw an error or cause a build up of memory usage.)

That was my thinking behind using [Rainmeter] as the best place to put the LocalFont= However, I don't see anything about the second and third approaches (in [Rainmeter] or in [MeterName]) that is a show stopper. You will be the best judge as you dig into it.

* As to multiple fonts. That we need to think through. Obviously we want to support it. I wonder if the best isn't

LocalFont=Veranda|Arial|Tahoma
or
LocalFont=Veranda,Arial,Tahoma

So you don't need to run some kind of recursive search for Font1= Font2= where you have to parse the string looking for a number at the end.

* I think that article I posted touches on what happens when you try to load a local font that is already installed. I think it defaults to the font you are asking it to load, but we need to research that to be sure we don't get an error as you say.
If I use the second set of methods the article provides I get to create a collection of fonts per config (or per meter depending on how it ends up really). When the config is deleted the collection of fonts is deleted.

As far as I can tell from the Refresh method in MeterWindow.cpp it deletes all the measures in the window when you do a refresh. This means it will delete the collection of fonts added to the meter when refresh (really the collection would be 1 font, but it would be really convenient, unless its a big memory issues I don't foresee any problems).

As for the checking every update cycle, the way the string meter class works is it doesn't reload the font every update, it just keeps the same font it had when it was initialized. I think I saw Rainy post on that before, I'm fairly certain I'm right about that. I believe it would really destroy the cpu load if you were able to update the font often.

As for the paths right now it just checks if the file exists in \fonts and if it doesn't it checks if its a full path, and then if that doesn't work it uses a default font.

EDIT:

I'm thinking putting it in the [Rainmeter] section is the best idea, less fonts loading per config.
Now I just gotta find where that happens, probably in MeterWindow. Then I'd have to pass the PrivateFontCollection to each MeterString.

Seems Meters have a reference to their window, I could just do it the same way I'm doing it now and use a single PrivateFontCollection in the meter's window instead of per meter.


Doable, may be annoying :)
User avatar
jsmorley
Developer
Posts: 22551
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: Fonts

Post by jsmorley »

MattKing wrote:
If I use the second set of methods the article provides I get to create a collection of fonts per config (or per meter depending on how it ends up really). When the config is deleted the collection of fonts is deleted.

As far as I can tell from the Refresh method in MeterWindow.cpp it deletes all the measures in the window when you do a refresh. This means it will delete the collection of fonts added to the meter when refresh (really the collection would be 1 font, but it would be really convenient, unless its a big memory issues I don't foresee any problems).

As for the checking every update cycle, the way the string meter class works is it doesn't reload the font every update, it just keeps the same font it had when it was initialized. I think I saw Rainy post on that before, I'm fairly certain I'm right about that. I believe it would really destroy the cpu load if you were able to update the font often.

As for the paths right now it just checks if the file exists in \fonts and if it doesn't it checks if its a full path, and then if that doesn't work it uses a default font.
Sounds good to me...

Are you specifically deleting the fonts using RemoveFontResourceEx or was I correct that if they are "private" you don't need to?

As to the path, we can always add #FONTSPATH# later on. As long as it will accept normal Windows "path" constructs like the full path, relative paths like .\skinfont\Veranda.ttf and is ok with just Veranda.ttf if it is in the same folder as the .ini that is good enough for now.
MattKing
Developer
Posts: 98
Joined: August 6th, 2009, 3:03 pm

Re: Fonts

Post by MattKing »

Ok so I'm finished now (mostly, except for pathing)

The way it works is that you use [Rainmeter] section for local fonts:
LocalFont=
LocalFont2=
LocalFont##=

and then in the MeterString:

LocalFontFace="FontFace"

if you supply a FontFace it is ignored if LocalFontFace is supplied. If the fontface you supply isn't in the localfonts you've supplied it searches the installed fonts, and then if that doesn't work it checks if you put in a FontFace, and then if that doesn't work it defaults to Arial.

More to come, going home right now.
User avatar
jsmorley
Developer
Posts: 22551
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: Fonts

Post by jsmorley »

MattKing wrote:Ok so I'm finished now (mostly, except for pathing)

The way it works is that you use [Rainmeter] section for local fonts:
LocalFont=
LocalFont2=
LocalFont##=

and then in the MeterString:

LocalFontFace="FontFace"

if you supply a FontFace it is ignored if LocalFontFace is supplied. If the fontface you supply isn't in the localfonts you've supplied it searches the installed fonts, and then if that doesn't work it checks if you put in a FontFace, and then if that doesn't work it defaults to Arial.

More to come, going home right now.
I was hoping we would not need the separate new entry in the meter. On the surface I still think it might be unneeded. Once a font is "installed" using the C++ function, it should be as visible to Rainmeter as any regular font in Windows\Fonts. So once you load Veranda.ttf using the routine, FontFace=Veranda should in theory be treated and seen exactly the same as FontFace=Arial. Now it is possible that the way Rainmeter is using fonts is different than other Windows applications, but I assumed this would work the way NexusFonts works. It reads the font files in a directory, loads them with AddFontResourceEx, broadcasts them to all running Windows apps, and they are just there when you use any text tool, for example in Photoshop, even if Photoshop was already running when you started NexusFonts. I have tested this with a bunch of Windows apps, including some that are far less well written or robust as Photoshop, and it always works that way.

Not the end of the world, and if the way Rainmeter uses GDI to handle fonts means we need this new statement in the meter or it won't work, then so be it. However it adds some complexity that I was hoping to avoid.

I wonder if the problem is that we are not "broadcasting" the loaded font(s) to Windows apps? Could it be that doing that would make Rainmeter treat it like any other font? I didn't think we would need to, as from what I read it seemed to indicate that when loaded "Private" without the "broadcast" the font is automatically available to the loading application but just not other applications.

If all else fails, what if you reversed the process. if we assume that Rainmeter CAN'T just see the loaded font just like any installed font for some reason, what if you left just FontFace=Veranda, then if Rainmeter is unable to "create the font" intercept the normal error routine that kicks in today and pops up a dialog and instead have it call your routines that look for it in the "loaded collection" or fall finally to Arial...
MattKing
Developer
Posts: 98
Joined: August 6th, 2009, 3:03 pm

Re: Fonts

Post by MattKing »

jsmorley wrote: I was hoping we would not need the separate new entry in the meter. On the surface I still think it might be unneeded. Once a font is "installed" using the C++ function, it should be as visible to Rainmeter as any regular font in Windows\Fonts. So once you load Veranda.ttf using the routine, FontFace=Veranda should in theory be treated and seen exactly the same as FontFace=Arial. Now it is possible that the way Rainmeter is using fonts is different than other Windows applications, but I assumed this would work the way NexusFonts works. It reads the font files in a directory, loads them with AddFontResourceEx, broadcasts them to all running Windows apps, and they are just there when you use any text tool, for example in Photoshop, even if Photoshop was already running when you started NexusFonts.

Not the end of the world, and if the way Rainmeter uses GDI to handle fonts means we need this new statement in the meter or it won't work, then so be it. However it adds some complexity that I was hoping to avoid.

I wonder if the problem is that we are not "broadcasting" the loaded font(s) to Windows apps? Could it be that doing that would make Rainmeter treat it like any other font? I didn't think we would need to, as from what I read it seemed to indicate that when loaded "Private" without the "broadcast" the font is automatically available to the loading application but just not other applications.

If all else fails, what if you reversed the process. if we assume that Rainmeter CAN'T just see the loaded font just like any installed font for some reason, what if you left just FontFace=Veranda, then if Rainmeter is unable to "create the font" intercept the normal error routine that kicks in today and pops up a dialog and instead have it call your routines that look for it in the "loaded collection" or fall finally to Arial...
I was just being lazy, I can just use FontFace and not LocalFont. :P