Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions draftlogs/8106_fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Refresh text measurements cached during web font loading [[#8106](https://github.com/plotly/plotly.js/pull/8106)]
11 changes: 10 additions & 1 deletion src/components/drawing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1662,7 +1662,16 @@ drawing.bBox = function (node, inTester, hash) {
}

// cache this bbox
if (hash) drawing.savedBBoxes[hash] = bb;
if (hash) {
const cache = drawing.savedBBoxes;
cache[hash] = bb;
if (document.fonts && document.fonts.status === 'loading') {
// Reuse fallback measurements during the draw, but not after the fonts settle.
document.fonts.ready.then(() => {
if (cache[hash] === bb) delete cache[hash];
});
}
}
savedBBoxesCount++;

return Lib.extendFlat({}, bb);
Expand Down
37 changes: 37 additions & 0 deletions test/jasmine/tests/drawing_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,43 @@ describe('Drawing', function() {
describe('bBox', function() {
afterEach(destroyGraphDiv);

it('remeasures cached fallback text after fonts load outside a plot redraw', async () => {
Drawing.makeTester();
const stable = Drawing.tester.append('text').text('Stable text')
.call(Drawing.font, { family: 'Arial', size: 20 })
.call(svgTextUtils.convertToTspans).node();
const stableMeasure = spyOn(stable, 'getBoundingClientRect').and.callThrough();
Drawing.bBox(stable, true);

const family = `PartialRedrawFont${Date.now()}`;
const face = new FontFace(family,
'url(/base/node_modules/@plotly/mathjax-v3/es5/output/chtml/fonts/woff-v2/MathJax_Typewriter-Regular.woff)');
document.fonts.add(face);
const text = Drawing.tester.append('text').text('WWWWWWWW iiiiiiii')
.call(Drawing.font, { family: `${family}, Arial`, size: 20 })
.call(svgTextUtils.convertToTspans).node();
const measure = spyOn(text, 'getBoundingClientRect').and.callThrough();
try {
const fallback = Drawing.bBox(text, true);
expect(face.status).toBe('loading');
expect(Drawing.bBox(text, true)).toEqual(fallback);
expect(measure).toHaveBeenCalledTimes(1);
await face.loaded;
await document.fonts.ready;
const loaded = Drawing.bBox(text, true);
expect(loaded.width).not.toBe(fallback.width);
expect(measure).toHaveBeenCalledTimes(2);
expect(Drawing.bBox(text, true)).toEqual(loaded);
expect(measure).toHaveBeenCalledTimes(2);
Drawing.bBox(stable, true);
expect(stableMeasure).toHaveBeenCalledTimes(1);
} finally {
document.fonts.delete(face);
text.remove();
stable.remove();
}
});

function assertBBox(actual, expected) {
[
'height', 'top', 'bottom',
Expand Down
35 changes: 35 additions & 0 deletions test/jasmine/tests/treemap_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,41 @@ describe('Test treemap clicks:', function () {

afterEach(destroyGraphDiv);

it('remeasures text first drawn by a partial zoom after its font loads', async () => {
const family = `TreemapPartialFont${Date.now()}`;
const face = new FontFace(family,
'url(/base/node_modules/@plotly/mathjax-v3/es5/output/chtml/fonts/woff-v2/MathJax_Typewriter-Regular.woff)');
document.fonts.add(face);
const label = 'WWWWWWWW iiiiiiii WWWWWWWW iiiiiiii';
const zoom = level => Plotly.animate(gd, { data: [{ level }], traces: [0] }, {
transition: { duration: 0 }, frame: { duration: 0, redraw: false }
});
const transforms = () => Array.from(gd.querySelectorAll(SLICES_TEXT_SELECTOR))
.filter(node => node.style.fontFamily.includes(family)).map(node => node.getAttribute('transform'));
try {
await Plotly.newPlot(gd, [{
type: 'treemap', ids: ['root', 'A', 'B', 'a1', 'a2', 'b1'],
labels: ['root', 'A', 'B', label, label, label],
parents: ['', 'root', 'root', 'A', 'A', 'B'], values: [0, 0, 0, 30, 20, 25],
level: 'B', textfont: { family: ['Arial', 'Arial', 'Arial', family, family, 'Arial'] }
}], { width: 700, height: 500 });
expect(face.status).toBe('unloaded');
await zoom('root');
await face.loaded;
await document.fonts.ready;
await zoom('B');
await zoom('root');
const loaded = transforms();
expect(loaded.length).toBe(2);
Drawing.savedBBoxes = {};
await zoom('B');
await zoom('root');
expect(transforms()).toEqual(loaded, 'the next partial zoom matches a fresh measurement with the loaded font');
} finally {
document.fonts.delete(face);
}
});

function setupListeners(opts) {
opts = opts || {};

Expand Down
Loading