I’m building a plugin, and now I’ve gotten the 'Font' is deprecated: Use the constructor that takes a FontOptions argument warning. My naive solution was to, in every place in my LookAndFeel, replace
return Font (tfDemiBold);
with
return Font (FontOptions (tfDemiBold));
however, this seems to have made my fonts much larger. Was the previous sizing just 14.0?
This is likely because the old Font constructor use TypefaceMetricsKind::legacy, but the new FontOptions defaults to TypefaceMetricsKind::portable. The different metrics kinds can cause font sizes to be interpreted differently.
The ‘legacy’ metrics are compatible with JUCE 7 font sizing, but may produce inconsistent results across platforms. That is, the same font at font size 14 may render at different sizes on macOS, Windows, and Linux. The new ‘portable’ metrics should produce the same results on all platforms.
The main reason is that Font() and Font (FontOptions()) produce different results. If you construct anything using Font() that font will have the metrics kind set to legacy by default (this is so upgrading to JUCE 8 isn’t a breaking change). If however, you use FontOptions() then by default the metrics kind is portable.
It’s completely reasonable that in some cases a user might want to ignore the deprecation warnings until such a time that upgrading to using FontOptions makes sense.
Just ported to JUCE 8 for one of my properties and the lack of a non-deprecated default constructor was one of the most annoying things. Let me suggest you reconsider. Especially in the common case of declare a variable and then assign it with a properly constructed font, it makes for some messy code.
For instance, here’s some code which is now really hard to make work with deprecation warnings on as errors
The fact that to use either of those idioms I need to either allow any software anywhere to use a deprecated API or do something horribly gross really seems like it’s wrong. (The rest of the constructors are fine of course. Sometimes APIs change).
I mean look at this code I just committed to our codebase
So I guess the long version is that “until such a time the upgrading to using FontOptions make sense” means code like this will always be clumsy. I don’t think my code is improved by that FT macro, and i don’t think it is improved by using some tempalte trickery to make an array just because the no-arg constructor is using a legacy approach.
But if you don’t reconsider, lets look at the doc for juce::Font.
/** Creates a basic sans-serif font at a default height.
You should use one of the other constructors for creating a font that you're planning
on drawing with - this constructor is here to help initialise objects before changing
the font's settings later.
*/
[[deprecated ("Use the constructor that takes a FontOptions argument")]]
Font();
with the current deprecation settings, what is the appropriate web to initialize objects before changing the font’s settings later if you compile with deprecation warnings as errors? Is it really the things I showed above?
Sorry that you feel this was a difficult process, unfortunately the problem with not deprecating the Font constructor is that inevitably users (especially new users) will go on using those constructors when they really shouldn’t (because depending on the font the size of the font might be different on different platforms). There are numerous examples on the forum of users hitting these problems and we hear them directly from users we engage with outside the forums too. It becomes even more of a problem with unicode support and fallback fonts where text may contain a mix of fonts!
The advantage of the deprecation warning is that you can switch off the deprecation warnings using a compiler flag and your code should behave exactly as it did in JUCE 7. We realise this isn’t ideal but we hope this encourages users to switch to the new portable metrics.
Another alternative option was that we change the behaviour of Font constructors but this would’ve meant without warning the size of your fonts might change on one or more platforms, this change could be subtle, or non-existent on the platform you are working from!
I don’t think there were any easy options but we went with an option that
Doesn’t change the behaviour of existing code (but unfortunately does require disabling deprecation warnings if you have warnings as errors enabled)
Encourages best practices by default (The legacy metrics kind should be considered bad practice and that’s what the old font constructors essentially do)
Still allows users to recreate the JUCE 7 behaviour without warnings (This is achieved using the TypefaceMetricsKind)
It’s also worth highlighting that simply changing from Font to FontOptions without reviewing the sizes of your fonts on different platforms is probably the wrong thing to do. I do wonder if we should revisit the warning message to see how we can help communicate this.
Would it work for you if you changed it to
std::array<juce::FontOptions, N> myFonts;
If you really need the typeface loaded a simple wrapper type might help? For the simplest case this might work?
Note that a Font can be implicitly constructed using a FontOptions type so you should be able to swap Font for FontOptions in most cases. This means myDrawFont could also be a FontOptions rather than a Font.
If you need to support multiple versions of JUCE and you’re not concerned about the potential size differences between JUCE 7/8 then maybe this could work?
I admit this is slightly different and may mean you’re constructing Fonts more often than you need to but it would be good to know if this is actually a problem in practice?
If however you do need to make sure JUCE 7/8 also matches in Font sizes/metrics then just swapping Font for FontOptions probably isn’t the right answer. So creating your own font type that does the right thing based on the JUCE version might be the best option, for example something like…
struct Font : juce::Font
{
#if JUCE_MAJOR_VERSION >= 8
Font()
: juce::Font (juce::FontOptions().withMetricsKind (juce::TypefaceMetricsKind::legacy))
{}
Font (float fontHeight, int styleFlags = plain)
: juce::Font (juce::FontOptions (fontHeight, styleFlags).withMetricsKind (juce::TypefaceMetricsKind::legacy))
{}
Font (const juce::String& typefaceName, float fontHeight, int styleFlags)
: juce::Font (juce::FontOptions (typefaceName, fontHeight, styleFlags).withMetricsKind (juce::TypefaceMetricsKind::legacy))
{}
Font (const juce::String& typefaceName, const juce::String& typefaceStyle, float fontHeight)
: juce::Font (juce::FontOptions (typefaceName, typefaceStyle).withMetricsKind (juce::TypefaceMetricsKind::legacy))
{}
Font (const Typeface::Ptr& typeface)
: juce::Font (juce::FontOptions (typeface).withMetricsKind (juce::TypefaceMetricsKind::legacy))
{}
#else
using juce::Font::Font;
#endif
}
The above is untested but I think this (or something like it) should work as a drop in replacement for juce::Font that behaves exactly the same for JUCE 7 and 8. Once JUCE 7 support is no longer required and you’ve reviewed all your fonts you could switch to the portable metrics like so…
struct Font : juce::Font
{
Font()
: juce::Font (juce::FontOptions())
{}
Font (float fontHeight, int styleFlags = plain)
: juce::Font (juce::FontOptions (fontHeight, styleFlags)))
{}
Font (const juce::String& typefaceName, float fontHeight, int styleFlags)
: juce::Font (juce::FontOptions (typefaceName, fontHeight, styleFlags))
{}
Font (const juce::String& typefaceName, const juce::String& typefaceStyle, float fontHeight)
: juce::Font (juce::FontOptions (typefaceName, typefaceStyle))
{}
Font (const Typeface::Ptr& typeface)
: juce::Font (juce::FontOptions (typeface))
{}
}
I realise this isn’t ideal but hopefully it keeps the changes minimal and consistent across the codebase for you.
If something like this works for you maybe we could consider adding the type as a convenience class for making porting easier for those that just want a drop in replacement for Font and are not bothered by the potential size differences. However, it would be very similar to using FontOptions in place of Font. I guess we could also consider a LegacyFont type as a drop in replacement that behaves the same as JUCE 7 for those that at least care that there is no change in behaviour.
Please let us know how you get on and if there are changes we can make to JUCE that help we will consider them but I’m not sure removing the deprecation is the right change to make.
So “difficult”. I ported to juce 8 in 90 minutes so it didn’t exactly kill me. The hardest part was we used to build on Ubuntu 18 so I had to update our docker images to 20. And the reason we did it was mostly better font rendering in windows. And I appreciate the answer here. Plus now I get emojis again which is cool.
And the problem with turning off deprecation warnings is it sweeps your whole project (or you used really targeted pragma which is even worse code splattage) so if I want to avoid deprecation in one of my non juce deps I lose that safety.
It really seems like your recommendation amounts to “never use font as a member or a value at rest since font options auto casts” and I should just internalize that juce font doesn’t only have deprecated constructors but is just generally not what you want to use anymore for any member at rest? Maybe update that comment in the juce font constructor I highlighted then since it seems rather wrong now!
Although defining font options as font in namespace juce for juce 7 does give me the heebie jeebies!
The = for options for member though is what I was trying to avoid indeed and it’s exactly what my ft macro does.
Finally yes this is only a problem for variables which are late assigned of type font. The rest of our port was pretty easy (but we are also using a typeface where we had fixed up the font metrics so juce font and juce font options did basically the same thing) and the ui looks good.
Agreed it’s not ideal but I’m not aware of a way for us to make warnings more granular than that without adding more preprocessor definitions which we would prefer to avoid.
It’s not ideal that you can’t construct a default Font as easily but if we didn’t deprecate it then we were also in the problem case that some users, maybe only in some cases, could have accidentally resulted in fonts constructed with the legacy metrics.
I take on board your point about the comment, that should probably be changed.
It’s not ideal but it’s a very simple line of code and as we’re not likely to update JUCE 7 there is a very low chance of it ever causing an issue.
If it’s a matter of avoiding placing stuff in the juce namespace you could probably do this?..
#if JUCE_MAJOR_VERSION < 8
using Font = juce::Font;
#else
using Font = juce::FontOptions;
#endif
Then use your Font in place of juce::Font in most cases.
That said I’m intrigued by the need to support JUCE 7 and JUCE 8, do you have some shared code used by multiple projects on different versions? or used by third parties that might need to support older versions? I think it would be fair to say that we probably overlooked the use case where there might be code that needs to support both JUCE 7 and JUCE 8 at the same time and I don’t recall anything coming up while the juce8 preview branch was available.
Glad the rest of the migration was simple enough though.
Do you think it would be useful if we had some types you could use to replace juce::Font such as juce::PortableFont that would default construct correctly without warning? The downside is that type wouldn’t be available in juce7 so I’m not sure it would’ve helped in your case anyway?
Yeah we have a component library we use in a few products and I don’t want to force them to all big bang to juce 8 on one day.
Also tbh I wanted to be able to back down to juce 7 if one of our users found a bug in next couple of weeks.
I don’t quite understand how the no arg constructor would lead to bad metrics if you later assigned to it but I also don’t know your users as well as you do! Is it because someone would do a g.setFont({}) basically?
Anyway now I know the answer (containers should contain font options not fonts) I think the only feedback I have is indeed expand the comment for the no arg constructor so I could have not written this forum post
Thanks for the responses and for all the work on juce
Right. that makes sense. I don’t use fonts that way but indeed that would be unexpected compared to juce::Font f(juce::FontOptions()) as the first line.
Oh one other thing. The idea to subclass font into myfont is hindered by juce::Font being final. Just in case other folks stumble across that suggestion