Commit 5846116
Cache synthetic/nearest glyph typefaces under the requested family name (#21993)
* Cache synthetic/nearest glyph typefaces under the requested family name
FontCollectionBase.TryGetGlyphTypeface only cached the resolved typeface under the
requested family name when synthesis FAILED. When TryCreateSyntheticGlyphTypeface
succeeds it registers the synthetic only under the source font's own family names,
never under the name the caller asked for.
A request arriving through a different name therefore never hits the cache and
re-enters synthesis on every call. Synthesis goes through IPlatformTypeface.TryGetStream,
which reads the entire font file into memory and hands it to SKTypeface.FromStream,
which copies it again natively - so every resolution costs one retained copy of the
whole font file.
Two common shapes reach this path:
- a platform alias, i.e. a family the font manager resolves but that is absent from
GetInstalledFontFamilyNames(). Android declares several in /system/etc/fonts.xml
(arial, helvetica, tahoma, verdana, times, courier);
- a "Family Style" composite decomposed by Typeface.Normalize (e.g. "Arial Black"
-> family arial + FontWeight.Black), which makes the requested key differ from the
key the platform returns.
Windows/DirectWrite is structurally immune: every resolvable family is also an installed
family there, so the case-insensitive family-name search finds the synthetic (registered
under its real name) on the second call and the missing entry repairs itself after one
copy. With a platform alias nothing repairs it.
Measured on Android 13 / arm64: a text widget set to "Arial Black" grew the native heap
by +1187 MB over ~14 redraws (chunks of Roboto-Regular.ttf, identified by byte
comparison), until the platform memory guard killed the process at 2.6 GB.
Adds a regression test with an IFontManagerImpl that models a platform alias: without
the fix the second resolution returns a different instance (re-synthesis), with it the
cached one.
* Make the regression test platform-independent
Back the alias with an embedded test font instead of an installed Arial, and
turn the test into a plain [Fact] so it runs on every platform.
The defect being covered lives in FontCollectionBase and is platform-agnostic,
so gating the test behind [Win32Fact] left it unexercised on the Linux and macOS
legs of CI. Relying on an installed system font also made the test dependent on
the build environment.
The fake font manager now always resolves the alias to the regular face of an
embedded font, whatever weight is asked for - which is what a platform alias
actually does - so no system font is involved at all.
* Make the regression test fail against unfixed code
Name the backing font explicitly instead of taking whichever asset
GetAssets enumerates first (currently AdobeBlank2VF.ttf, a blank
variable font), matching the style used elsewhere for embedded test
fonts.
Assert that the first resolution really is a synthesised bold. Without
it, a backing font that cannot be emboldened makes TryCreateSyntheticGlyphTypeface
fail, the old else branch caches the nearest match, and the whole test
passes against unfixed code.
Verified both ways: FontCollectionTests is 4/4 with the fix, and
Should_Cache_Synthetic_Match_Under_Requested_Family_Name is the single
failure without it.
---------
Co-authored-by: ronnycohen <19652995+ronnycohen@users.noreply.github.com>
Co-authored-by: Benedikt Stebner <Gillibald@users.noreply.github.com>1 parent b709c58 commit 5846116
2 files changed
Lines changed: 130 additions & 5 deletions
File tree
- src/Avalonia.Base/Media/Fonts
- tests/Avalonia.Skia.UnitTests/Media
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
852 | 852 | | |
853 | 853 | | |
854 | 854 | | |
855 | | - | |
856 | | - | |
857 | | - | |
858 | | - | |
859 | | - | |
| 855 | + | |
| 856 | + | |
| 857 | + | |
| 858 | + | |
| 859 | + | |
| 860 | + | |
| 861 | + | |
| 862 | + | |
| 863 | + | |
860 | 864 | | |
861 | 865 | | |
862 | 866 | | |
| |||
Lines changed: 121 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
148 | 149 | | |
149 | 150 | | |
150 | 151 | | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
151 | 272 | | |
152 | 273 | | |
0 commit comments