Merge pull request #7476 from ahocevar/reset-measurements
Reset text measurements when available fonts change
This commit is contained in:
+78
-14
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user