Skip to content

Load font faces from cached bytes instead of re-reading the file per pixel size - #235

Open
handrok wants to merge 1 commit into
musescore:mainfrom
handrok:fonts_cache_dtb
Open

Load font faces from cached bytes instead of re-reading the file per pixel size#235
handrok wants to merge 1 commit into
musescore:mainfrom
handrok:fonts_cache_dtb

Conversation

@handrok

@handrok handrok commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

FontsEngine is not used in Musescore, so I tested in standalone application.
After last PR #231 profiling showed clearLoadedFaces() taking ~20% of total score-open time.
FontsEngine's face cache is keyed by (family, type, pixelSize, isSymbolMode), so every distinct pixel size used in a document creates a brand-new IFontFace, which re-opens and re-reads the same font file from disk again. FT_New_Memory_Face itself is fast; the actual cost is (a) the repeated disk reads and (b) destroying all those per-pixel-size ByteArray buffers later in clearLoadedFaces().

So FontsDatabase now caches the font file's bytes once per path (fontData()), and IFontFace::load() takes those bytes directly instead of a path, so a new pixel size no longer triggers a new disk read.

The reading of font file was changing from code

io::File file(path);
if (!file.open(io::IODevice::ReadOnly)) {
  return false;
}
m_data->fontData = file.readAll();

to

ByteArray data;
io::File::readFile(path, data)

because sequence of file.open file.readAll() malloc memory twice. For 5mb otf file it is a problem.

@handrok handrok self-assigned this Aug 19, 2026
@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@handrok, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 19 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4530da26-aee4-4c87-a71f-2c1e080a2aa6

📥 Commits

Reviewing files that changed from the base of the PR and between 1bd5f29 and 4e96c39.

📒 Files selected for processing (10)
  • framework/draw/internal/fontfacedu.cpp
  • framework/draw/internal/fontfacedu.h
  • framework/draw/internal/fontfaceft.cpp
  • framework/draw/internal/fontfaceft.h
  • framework/draw/internal/fontfacext.cpp
  • framework/draw/internal/fontfacext.h
  • framework/draw/internal/fontsengine.cpp
  • framework/draw/internal/fontsengine.h
  • framework/draw/internal/ifontface.h
  • framework/draw/tests/fontfacext_tests.cpp

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9f204ce0-6869-4bf1-abac-6000a6fa0753

📥 Commits

Reviewing files that changed from the base of the PR and between d614792 and 1bd5f29.

📒 Files selected for processing (7)
  • framework/draw/internal/fontfacext.cpp
  • framework/draw/internal/fontsdatabase.cpp
  • framework/draw/internal/fontsdatabase.h
  • framework/draw/internal/fontsengine.cpp
  • framework/draw/internal/fontsengine.h
  • framework/draw/internal/ifontsdatabase.h
  • framework/draw/tests/fontfacext_tests.cpp

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Font loading now uses in-memory ByteArray data instead of filesystem paths. FontsDatabase caches font bytes and reports FTX font metadata. FontsEngine retrieves the data, selects a backend, and loads the face from bytes. FreeType validates and stores the data. FontFaceXT retains the data in a buffer for ZipReader. Tests now load font files into byte arrays before creating faces.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the motivation and implementation, but it omits the issue reference and leaves all required checklist items unchecked. Add the issue reference and complete each applicable checklist item, including CLA, testing, coding rules, and unit-test confirmation.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: loading font faces from cached bytes instead of rereading files for each pixel size.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread framework/draw/internal/fontfacext.cpp Outdated
Comment thread framework/draw/types/fontstypes.h
Comment thread framework/draw/internal/ifontface.h Outdated
Comment thread framework/draw/internal/fontsengine.cpp Outdated

for (IFontFace* face : m_loadedFaces) {
if (face->key().dataKey == dataKey && face->key().type == type && face->key().pixelSize == loadedPixelSize
if (face->key().dataKey == actualDataKey && face->key().type == type && face->key().pixelSize == loadedPixelSize

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not necessarily, thoughts:

  • We have requireDataKey and actualDataKey
  • When creating FontFace, we pass both keys to it (actualDataKey as a property of FontData)
  • i.e. we don't need to call actualFont, actualDataKey already has it, we just need to save it there and add a getter
  • actualFont call File::exists inside - this is not good

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants