Merge pull request #7476 from ahocevar/reset-measurements

Reset text measurements when available fonts change
This commit is contained in:
Andreas Hocevar
2017-11-16 13:43:04 +01:00
committed by GitHub
3 changed files with 129 additions and 96 deletions
+78 -14
View File
@@ -3,6 +3,7 @@ goog.provide('ol.render.canvas');
goog.require('ol.css'); goog.require('ol.css');
goog.require('ol.dom'); goog.require('ol.dom');
goog.require('ol.obj');
goog.require('ol.structs.LRUCache'); goog.require('ol.structs.LRUCache');
goog.require('ol.transform'); goog.require('ol.transform');
@@ -98,28 +99,39 @@ ol.render.canvas.labelCache = new ol.structs.LRUCache();
/** /**
* @type {!Object.<string, (number)>} * @type {!Object.<string, number>}
*/ */
ol.render.canvas.checkedFonts_ = {}; ol.render.canvas.checkedFonts_ = {};
/**
* @type {CanvasRenderingContext2D}
*/
ol.render.canvas.measureContext_ = null;
/**
* @type {!Object.<string, number>}
*/
ol.render.canvas.textHeights_ = {};
/** /**
* Clears the label cache when a font becomes available. * Clears the label cache when a font becomes available.
* @param {string} fontSpec CSS font spec. * @param {string} fontSpec CSS font spec.
*/ */
ol.render.canvas.checkFont = (function() { ol.render.canvas.checkFont = (function() {
var retries = 60;
var checked = ol.render.canvas.checkedFonts_; var checked = ol.render.canvas.checkedFonts_;
var labelCache = ol.render.canvas.labelCache; var labelCache = ol.render.canvas.labelCache;
var font = '32px monospace'; var font = '32px monospace';
var text = 'wmytzilWMYTZIL@#/&?$%10'; var text = 'wmytzilWMYTZIL@#/&?$%10';
var context, interval, referenceWidth; var interval, referenceWidth;
function isAvailable(fontFamily) { function isAvailable(fontFamily) {
if (!context) { var context = ol.render.canvas.getMeasureContext();
context = ol.dom.createCanvasContext2D(1, 1); context.font = font;
context.font = font; referenceWidth = context.measureText(text).width;
referenceWidth = context.measureText(text).width;
}
var available = true; var available = true;
if (fontFamily != 'monospace') { if (fontFamily != 'monospace') {
context.font = '32px ' + fontFamily + ',monospace'; context.font = '32px ' + fontFamily + ',monospace';
@@ -128,10 +140,6 @@ ol.render.canvas.checkFont = (function() {
// fallback was used instead of the font we wanted, so the font is not // fallback was used instead of the font we wanted, so the font is not
// available. // available.
available = width != referenceWidth; available = width != referenceWidth;
// Setting the font back to a different one works around an issue in
// Safari where subsequent `context.font` assignments with the same font
// will not re-attempt to use a font that is currently loading.
context.font = font;
} }
return available; return available;
} }
@@ -139,9 +147,12 @@ ol.render.canvas.checkFont = (function() {
function check() { function check() {
var done = true; var done = true;
for (var font in checked) { for (var font in checked) {
if (checked[font] < 60) { if (checked[font] < retries) {
if (isAvailable(font)) { if (isAvailable(font)) {
checked[font] = 60; checked[font] = retries;
ol.obj.clear(ol.render.canvas.textHeights_);
// Make sure that loaded fonts are picked up by Safari
ol.render.canvas.measureContext_ = null;
labelCache.clear(); labelCache.clear();
} else { } else {
++checked[font]; ++checked[font];
@@ -163,7 +174,7 @@ ol.render.canvas.checkFont = (function() {
for (var i = 0, ii = fontFamilies.length; i < ii; ++i) { for (var i = 0, ii = fontFamilies.length; i < ii; ++i) {
var fontFamily = fontFamilies[i]; var fontFamily = fontFamilies[i];
if (!(fontFamily in checked)) { if (!(fontFamily in checked)) {
checked[fontFamily] = 60; checked[fontFamily] = retries;
if (!isAvailable(fontFamily)) { if (!isAvailable(fontFamily)) {
checked[fontFamily] = 0; checked[fontFamily] = 0;
if (interval === undefined) { if (interval === undefined) {
@@ -176,6 +187,59 @@ ol.render.canvas.checkFont = (function() {
})(); })();
/**
* @return {CanvasRenderingContext2D} Measure context.
*/
ol.render.canvas.getMeasureContext = function() {
var context = ol.render.canvas.measureContext_;
if (!context) {
context = ol.render.canvas.measureContext_ = ol.dom.createCanvasContext2D(1, 1);
}
return context;
};
/**
* @param {string} font Font to use for measuring.
* @return {ol.Size} Measurement.
*/
ol.render.canvas.measureTextHeight = (function() {
var span;
var heights = ol.render.canvas.textHeights_;
return function(font) {
var height = heights[font];
if (height == undefined) {
if (!span) {
span = document.createElement('span');
span.textContent = 'M';
span.style.margin = span.style.padding = '0 !important';
span.style.position = 'absolute !important';
span.style.left = '-99999px !important';
}
span.style.font = font;
document.body.appendChild(span);
height = heights[font] = span.offsetHeight;
document.body.removeChild(span);
}
return height;
};
})();
/**
* @param {string} font Font.
* @param {string} text Text.
* @return {number} Width.
*/
ol.render.canvas.measureTextWidth = function(font, text) {
var measureContext = ol.render.canvas.getMeasureContext();
if (font != measureContext.font) {
measureContext.font = font;
}
return measureContext.measureText(text).width;
};
/** /**
* @param {CanvasRenderingContext2D} context Context. * @param {CanvasRenderingContext2D} context Context.
* @param {number} rotation Rotation. * @param {number} rotation Rotation.
+4 -51
View File
@@ -118,53 +118,6 @@ ol.render.canvas.TextReplay = function(
ol.inherits(ol.render.canvas.TextReplay, ol.render.canvas.Replay); ol.inherits(ol.render.canvas.TextReplay, ol.render.canvas.Replay);
/**
* @param {string} font Font to use for measuring.
* @return {ol.Size} Measurement.
*/
ol.render.canvas.TextReplay.measureTextHeight = (function() {
var span;
var heights = {};
return function(font) {
var height = heights[font];
if (height == undefined) {
if (!span) {
span = document.createElement('span');
span.textContent = 'M';
span.style.margin = span.style.padding = '0 !important';
span.style.position = 'absolute !important';
span.style.left = '-99999px !important';
}
span.style.font = font;
document.body.appendChild(span);
height = heights[font] = span.offsetHeight;
document.body.removeChild(span);
}
return height;
};
})();
/**
* @param {string} font Font.
* @param {string} text Text.
* @return {number} Width.
*/
ol.render.canvas.TextReplay.measureTextWidth = (function() {
var measureContext;
var currentFont;
return function(font, text) {
if (!measureContext) {
measureContext = ol.dom.createCanvasContext2D(1, 1);
}
if (font != currentFont) {
currentFont = measureContext.font = font;
}
return measureContext.measureText(text).width;
};
})();
/** /**
* @param {string} font Font to use for measuring. * @param {string} font Font to use for measuring.
* @param {Array.<string>} lines Lines to measure. * @param {Array.<string>} lines Lines to measure.
@@ -177,7 +130,7 @@ ol.render.canvas.TextReplay.measureTextWidths = function(font, lines, widths) {
var width = 0; var width = 0;
var currentWidth, i; var currentWidth, i;
for (i = 0; i < numLines; ++i) { for (i = 0; i < numLines; ++i) {
currentWidth = ol.render.canvas.TextReplay.measureTextWidth(font, lines[i]); currentWidth = ol.render.canvas.measureTextWidth(font, lines[i]);
width = Math.max(width, currentWidth); width = Math.max(width, currentWidth);
widths.push(currentWidth); widths.push(currentWidth);
} }
@@ -325,7 +278,7 @@ ol.render.canvas.TextReplay.prototype.getImage = function(text, fill, stroke) {
var numLines = lines.length; var numLines = lines.length;
var widths = []; var widths = [];
var width = ol.render.canvas.TextReplay.measureTextWidths(textState.font, lines, widths); var width = ol.render.canvas.TextReplay.measureTextWidths(textState.font, lines, widths);
var lineHeight = ol.render.canvas.TextReplay.measureTextHeight(textState.font); var lineHeight = ol.render.canvas.measureTextHeight(textState.font);
var height = lineHeight * numLines; var height = lineHeight * numLines;
var renderWidth = (width + strokeWidth); var renderWidth = (width + strokeWidth);
var context = ol.dom.createCanvasContext2D( var context = ol.dom.createCanvasContext2D(
@@ -433,7 +386,7 @@ ol.render.canvas.TextReplay.prototype.drawChars_ = function(begin, end, declutte
function(text) { function(text) {
var width = widths[text]; var width = widths[text];
if (!width) { if (!width) {
width = widths[text] = ol.render.canvas.TextReplay.measureTextWidth(font, text); width = widths[text] = ol.render.canvas.measureTextWidth(font, text);
} }
return width * textScale * pixelRatio; return width * textScale * pixelRatio;
}, },
@@ -445,7 +398,7 @@ ol.render.canvas.TextReplay.prototype.drawChars_ = function(begin, end, declutte
function(text) { function(text) {
var width = widths[text]; var width = widths[text];
if (!width) { if (!width) {
width = widths[text] = ol.render.canvas.TextReplay.measureTextWidth(font, text); width = widths[text] = ol.render.canvas.measureTextWidth(font, text);
} }
return width * textScale; return width * textScale;
}, },
+47 -31
View File
@@ -12,56 +12,72 @@ describe('ol.render.canvas', function() {
describe('ol.render.canvas.checkFont()', function() { describe('ol.render.canvas.checkFont()', function() {
beforeEach(function() {
ol.obj.clear(ol.render.canvas.checkedFonts_);
ol.render.canvas.getMeasureContext();
ol.render.canvas.measureTextHeight('12px sans-serif');
});
var checkFont = ol.render.canvas.checkFont; var checkFont = ol.render.canvas.checkFont;
var retries = 60;
it('does not clear the label cache for unavailable fonts', function(done) { it('does not clear label cache and measurements for unavailable fonts', function(done) {
ol.obj.clear(ol.render.canvas.checkedFonts_); this.timeout(3000);
var spy = sinon.spy(); var spy = sinon.spy();
ol.events.listen(ol.render.canvas.labelCache, 'clear', spy); ol.events.listen(ol.render.canvas.labelCache, 'clear', spy);
var interval = setInterval(function() {
if (ol.render.canvas.checkedFonts_['foo'] == retries && ol.render.canvas.checkedFonts_['sans-serif'] == retries) {
clearInterval(interval);
ol.events.unlisten(ol.render.canvas.labelCache, 'clear', spy);
expect(spy.callCount).to.be(0);
expect(ol.render.canvas.measureContext_).to.not.be(null);
expect(ol.render.canvas.textHeights_).to.not.eql({});
done();
}
}, 32);
checkFont('12px foo,sans-serif'); checkFont('12px foo,sans-serif');
setTimeout(function() {
ol.events.unlisten(ol.render.canvas.labelCache, 'clear', spy);
expect(spy.callCount).to.be(0);
done();
}, 1600);
}); });
it('does not clear the label cache for available fonts', function(done) { it('does not clear label cache and measurements for available fonts', function(done) {
ol.obj.clear(ol.render.canvas.checkedFonts_);
var spy = sinon.spy(); var spy = sinon.spy();
ol.events.listen(ol.render.canvas.labelCache, 'clear', spy); ol.events.listen(ol.render.canvas.labelCache, 'clear', spy);
var interval = setInterval(function() {
if (ol.render.canvas.checkedFonts_['sans-serif'] == retries) {
clearInterval(interval);
ol.events.unlisten(ol.render.canvas.labelCache, 'clear', spy);
expect(spy.callCount).to.be(0);
expect(ol.render.canvas.measureContext_).to.not.be(null);
expect(ol.render.canvas.textHeights_).to.not.eql({});
done();
}
}, 32);
checkFont('12px sans-serif'); checkFont('12px sans-serif');
setTimeout(function() {
ol.events.unlisten(ol.render.canvas.labelCache, 'clear', spy);
expect(spy.callCount).to.be(0);
done();
}, 800);
}); });
it('does not clear the label cache for the \'monospace\' font', function(done) { it('does not clear label cache and measurements for the \'monospace\' font', function(done) {
ol.obj.clear(ol.render.canvas.checkedFonts_);
var spy = sinon.spy(); var spy = sinon.spy();
ol.events.listen(ol.render.canvas.labelCache, 'clear', spy); ol.events.listen(ol.render.canvas.labelCache, 'clear', spy);
var interval = setInterval(function() {
if (ol.render.canvas.checkedFonts_['monospace'] == retries) {
clearInterval(interval);
ol.events.unlisten(ol.render.canvas.labelCache, 'clear', spy);
expect(spy.callCount).to.be(0);
expect(ol.render.canvas.measureContext_).to.not.be(null);
expect(ol.render.canvas.textHeights_).to.not.eql({});
done();
}
}, 32);
checkFont('12px monospace'); checkFont('12px monospace');
setTimeout(function() {
ol.events.unlisten(ol.render.canvas.labelCache, 'clear', spy);
expect(spy.callCount).to.be(0);
done();
}, 800);
}); });
it('clears the label cache for fonts that become available', function(done) { it('clears label cache and measurements for fonts that become available', function(done) {
ol.obj.clear(ol.render.canvas.checkedFonts_);
head.appendChild(font); head.appendChild(font);
var spy = sinon.spy(); ol.events.listen(ol.render.canvas.labelCache, 'clear', function() {
ol.events.listen(ol.render.canvas.labelCache, 'clear', spy); expect(ol.render.canvas.measureContext_).to.be(null);
checkFont('12px Abel'); expect(ol.render.canvas.textHeights_).to.eql({});
setTimeout(function() {
ol.events.unlisten(ol.render.canvas.labelCache, 'clear', spy);
head.removeChild(font);
expect(spy.callCount).to.be(1);
done(); done();
}, 1600); });
checkFont('12px Abel');
}); });
}); });