Skip to content

fix: Recalculate bounding boxes after fonts load - #8094

Open
camdecoster wants to merge 6 commits into
mainfrom
cam/8089/replot-after-font-load
Open

camdecoster wants to merge 6 commits into
mainfrom
cam/8089/replot-after-font-load

Conversation

@camdecoster

@camdecoster camdecoster commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Description

Recalculate bounding boxes after fonts load to ensure they are sized properly.

Closes #8089.

Changes

  • Track which fonts aren't loaded yet and redraw when they settle
  • Clean up tracking token during purge
  • Add tests
  • Update internal type
  • Add utility for parsing font families

Screenshots

Before After
image image

Testing

  • Be on main
  • Open plotly devtools and paste the following into the browser devtools console:
    // A new family and URL on every run, so the plot always draws before the font arrives
    const family = `JetBrainsMono${Date.now()}`;
    const face = new FontFace(
        family,
        `url(https://cdn.jsdelivr.net/npm/@fontsource/[email protected]/files/jetbrains-mono-latin-400-normal.woff2?v=${Math.random()})`
    );
    document.fonts.add(face);
    
    const gd = Tabs.fresh();
    Plotly.newPlot(
        gd,
        [
            { type: 'bar', x: ['NO', 'DE', 'CA'], y: [30, 12, 10], name: 'Gold: a test of sizes' },
            { type: 'bar', x: ['NO', 'DE', 'CA'], y: [8, 10, 5], name: 'Silver: a second test of sizes' }
        ],
        {
            font: { family, size: 13 },
            legend: { borderwidth: 1, x: 0.02, y: 0.98 },
            width: 500,
            height: 300,
            annotations: [
                {
                    x: 1.02, y: 0, xref: 'paper', yref: 'paper', xanchor: 'left', yanchor: 'bottom',
                    text: 'Source: official list', textangle: -90, showarrow: false,
                    bgcolor: 'rgba(0,0,0,0.15)', borderwidth: 1
                }
            ]
        }
    );
  • Note that the text boxes have the incorrect sizes
  • Switch to this branch
  • Run the snippet again
  • Note that the text boxes have the correct sizes

Notes

  • The first draw is synchronous, so plots with no specified fonts shouldn't see any change with this update
  • Plots with fonts specified that are drawn before the fonts load will be redrawn after those fonts load
    • The redraw costs about as much as the first draw
    • It happens once per font load
    • Here are some performance numbers from Chrome running locally:
      Plots First draw Redraws after the font loads
      1 small bar chart 18 ms 21 ms
      20 small bar charts (3 runs) 112-139 ms 94-185 ms
      1 plot with a 50,000-point SVG scatter 596 ms 593 ms
      20 plots with a 5,000-point SVG scatter each 1.6 s 1.3 s
    • The redraws run synchronously in one task.
  • Plots with fonts specified that are drawn after the fonts load will be drawn once with the correct sizes
  • Purged plots don't get redrawn
  • The redraw fires plotly_beforeplot and plotly_afterplot again
  • To skip the extra draw, wait for the font before the first draw:
    await document.fonts.load('12px "Open Sans"');
    await Plotly.newPlot(gd, data, layout);
  • document.fonts.ready does not work for this, because ready does not start the load of a font that no text uses yet
  • A WebKit bug prevents use of the loadingdone event on the document fonts
  • This fix does not cover text drawn on a canvas, such as scattergl text and gl3d labels. A plot redraws only when an SVG element in the plot uses the loaded family.
  • This fix is related to Axis labels and tick labels in 3D plots do not use fonts loaded from remote server #7413, but it doesn't actually fix that issue. gl-axes3d caches each label mesh by font family for the life of the page, so gl3d labels keep the fallback font after the redraw.

@camdecoster
camdecoster marked this pull request as ready for review September 30, 2026 23:17
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.

[BUG]: Text extents are not recomputed after web font loading

1 participant