diff --git a/examples/vector-labels.html b/examples/vector-labels.html
index 872bbb8213..69c6acf50f 100644
--- a/examples/vector-labels.html
+++ b/examples/vector-labels.html
@@ -6,7 +6,8 @@ docs: >
This example showcases a number of options that can be set on text styles.
When "Text/Wrap" is chosen (for example for the line features), the label is
wrapped by inserting the character `\n`, which will create a multi-line
- label.
+ label. The "Open Sans" web font will be loaded on demand, to show dynamic font
+ loading.
tags: "geojson, vector, openstreetmap, label"
---
@@ -69,8 +70,8 @@ tags: "geojson, vector, openstreetmap, label"
@@ -178,8 +179,8 @@ tags: "geojson, vector, openstreetmap, label"
@@ -287,8 +288,8 @@ tags: "geojson, vector, openstreetmap, label"
diff --git a/examples/vector-labels.js b/examples/vector-labels.js
index c11bf40825..75aded09a9 100644
--- a/examples/vector-labels.js
+++ b/examples/vector-labels.js
@@ -11,6 +11,7 @@ goog.require('ol.style.Stroke');
goog.require('ol.style.Style');
goog.require('ol.style.Text');
+var openSansAdded = false;
var myDom = {
points: {
@@ -96,6 +97,13 @@ var createTextStyle = function(feature, resolution, dom) {
var maxAngle = dom.maxangle ? parseFloat(dom.maxangle.value) : undefined;
var exceedLength = dom.exceedlength ? (dom.exceedlength.value == 'true') : undefined;
var rotation = parseFloat(dom.rotation.value);
+ if (dom.font.value == '\'Open Sans\'' && !openSansAdded) {
+ var openSans = document.createElement('link');
+ openSans.href = 'https://fonts.googleapis.com/css?family=Open+Sans';
+ openSans.rel = 'stylesheet';
+ document.getElementsByTagName('head')[0].appendChild(openSans);
+ openSansAdded = true;
+ }
var font = weight + ' ' + size + ' ' + dom.font.value;
var fillColor = dom.color.value;
var outlineColor = dom.outline.value;
diff --git a/src/ol/css.js b/src/ol/css.js
index 915893ea34..599bdacc4f 100644
--- a/src/ol/css.js
+++ b/src/ol/css.js
@@ -43,3 +43,30 @@ ol.css.CLASS_UNSUPPORTED = 'ol-unsupported';
* @type {string}
*/
ol.css.CLASS_CONTROL = 'ol-control';
+
+
+/**
+ * Get the list of font families from a font spec. Note that this doesn't work
+ * for font families that have commas in them.
+ * @param {string} The CSS font property.
+ * @return {Object.} The font families (or null if the input spec is invalid).
+ */
+ol.css.getFontFamilies = (function() {
+ var style;
+ var cache = {};
+ return function(font) {
+ if (!style) {
+ style = document.createElement('div').style;
+ }
+ if (!(font in cache)) {
+ style.font = font;
+ var family = style.fontFamily;
+ style.font = '';
+ if (!family) {
+ return null;
+ }
+ cache[font] = family.split(/,\s?/);
+ }
+ return cache[font];
+ };
+})();
diff --git a/src/ol/events/eventtype.js b/src/ol/events/eventtype.js
index daec939d69..a7d11a0541 100644
--- a/src/ol/events/eventtype.js
+++ b/src/ol/events/eventtype.js
@@ -12,6 +12,7 @@ ol.events.EventType = {
*/
CHANGE: 'change',
+ CLEAR: 'clear',
CLICK: 'click',
DBLCLICK: 'dblclick',
DRAGENTER: 'dragenter',
diff --git a/src/ol/render/canvas.js b/src/ol/render/canvas.js
index d2ca4d8764..d4b23ca122 100644
--- a/src/ol/render/canvas.js
+++ b/src/ol/render/canvas.js
@@ -1,6 +1,9 @@
goog.provide('ol.render.canvas');
+goog.require('ol.css');
+goog.require('ol.dom');
+goog.require('ol.structs.LRUCache');
goog.require('ol.transform');
@@ -81,6 +84,88 @@ ol.render.canvas.defaultTextBaseline = 'middle';
ol.render.canvas.defaultLineWidth = 1;
+/**
+ * @type {ol.structs.LRUCache.}
+ */
+ol.render.canvas.labelCache = new ol.structs.LRUCache();
+
+
+/**
+ * @type {!Object.}
+ */
+ol.render.canvas.checkedFonts_ = {};
+
+
+/**
+ * Clears the label cache when a font becomes available.
+ * @param {string} fontSpec CSS font spec.
+ */
+ol.render.canvas.checkFont = (function() {
+ var checked = ol.render.canvas.checkedFonts_;
+ var labelCache = ol.render.canvas.labelCache;
+ var font = '32px monospace';
+ var text = 'wmytzilWMYTZIL@#/&?$%10';
+ var context, referenceWidth;
+
+ function isAvailable(fontFamily) {
+ if (!context) {
+ context = ol.dom.createCanvasContext2D(1, 1);
+ context.font = font;
+ referenceWidth = context.measureText(text).width;
+ }
+ var available = true;
+ if (fontFamily != 'monospace') {
+ context.font = '32px ' + fontFamily + ',monospace';
+ var width = context.measureText(text).width;
+ // If width and referenceWidth are the same, then the 'monospace'
+ // fallback was used instead of the font we wanted, so the font is not
+ // available.
+ 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;
+ }
+
+ function check() {
+ var done = true;
+ for (var font in checked) {
+ if (checked[font] < 60) {
+ if (isAvailable(font)) {
+ checked[font] = 60;
+ labelCache.clear();
+ } else {
+ ++checked[font];
+ done = false;
+ }
+ }
+ }
+ if (!done) {
+ window.setTimeout(check, 32);
+ }
+ }
+
+ return function(fontSpec) {
+ var fontFamilies = ol.css.getFontFamilies(fontSpec);
+ if (!fontFamilies) {
+ return;
+ }
+ for (var i = 0, ii = fontFamilies.length; i < ii; ++i) {
+ var fontFamily = fontFamilies[i];
+ if (!(fontFamily in checked)) {
+ checked[fontFamily] = 60;
+ if (!isAvailable(fontFamily)) {
+ checked[fontFamily] = 0;
+ window.setTimeout(check, 25);
+ }
+ }
+ }
+ };
+})();
+
+
/**
* @param {CanvasRenderingContext2D} context Context.
* @param {number} rotation Rotation.
diff --git a/src/ol/render/canvas/replay.js b/src/ol/render/canvas/replay.js
index 6d214409b0..15075c158e 100644
--- a/src/ol/render/canvas/replay.js
+++ b/src/ol/render/canvas/replay.js
@@ -596,7 +596,7 @@ ol.render.canvas.Replay.prototype.replay_ = function(
chars = /** @type {string} */ (part[4]);
label = /** @type {ol.render.canvas.TextReplay} */ (this).getImage(chars, false, true);
anchorX = /** @type {number} */ (part[2]) + strokeWidth;
- anchorY = baseline * label.height + (0.5 - baseline) * strokeWidth - offsetY;
+ anchorY = baseline * label.height + (0.5 - baseline) * 2 * strokeWidth - offsetY;
this.replayImage_(context,
/** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label,
anchorX, anchorY, declutterGroup, label.height, 1, 0, 0,
diff --git a/src/ol/render/canvas/textreplay.js b/src/ol/render/canvas/textreplay.js
index 349e01152c..c4a158d241 100644
--- a/src/ol/render/canvas/textreplay.js
+++ b/src/ol/render/canvas/textreplay.js
@@ -11,7 +11,6 @@ goog.require('ol.render.canvas');
goog.require('ol.render.canvas.Instruction');
goog.require('ol.render.canvas.Replay');
goog.require('ol.render.replay');
-goog.require('ol.structs.LRUCache');
goog.require('ol.style.TextPlacement');
@@ -121,21 +120,10 @@ ol.render.canvas.TextReplay = function(
*/
this.widths_ = {};
- while (ol.render.canvas.TextReplay.labelCache_.canExpireCache()) {
- ol.render.canvas.TextReplay.labelCache_.pop();
- }
-
};
ol.inherits(ol.render.canvas.TextReplay, ol.render.canvas.Replay);
-/**
- * @private
- * @type {ol.structs.LRUCache.}
- */
-ol.render.canvas.TextReplay.labelCache_ = new ol.structs.LRUCache();
-
-
/**
* @param {string} font Font to use for measuring.
* @return {ol.Size} Measurement.
@@ -324,7 +312,8 @@ ol.render.canvas.TextReplay.prototype.getImage = function(text, fill, stroke) {
var label;
var key = (stroke ? this.strokeKey_ : '') + this.textKey_ + text + (fill ? this.fillKey_ : '');
- if (!ol.render.canvas.TextReplay.labelCache_.containsKey(key)) {
+ var labelCache = ol.render.canvas.labelCache;
+ if (!labelCache.containsKey(key)) {
var strokeState = this.textStrokeState_;
var fillState = this.textFillState_;
var textState = this.textState_;
@@ -344,7 +333,7 @@ ol.render.canvas.TextReplay.prototype.getImage = function(text, fill, stroke) {
Math.ceil(renderWidth * scale),
Math.ceil((height + strokeWidth) * scale));
label = context.canvas;
- ol.render.canvas.TextReplay.labelCache_.set(key, label);
+ labelCache.pruneAndSet(key, label);
if (scale != 1) {
context.scale(scale, scale);
}
@@ -379,7 +368,7 @@ ol.render.canvas.TextReplay.prototype.getImage = function(text, fill, stroke) {
}
}
}
- return ol.render.canvas.TextReplay.labelCache_.get(key);
+ return labelCache.get(key);
};
@@ -537,6 +526,7 @@ ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle, declutt
if (!textState) {
textState = this.textState_ = /** @type {ol.CanvasTextState} */ ({});
}
+ ol.render.canvas.checkFont(font);
textState.exceedLength = textStyle.getExceedLength();
textState.font = font;
textState.maxAngle = textStyle.getMaxAngle();
diff --git a/src/ol/renderer/canvas/vectorlayer.js b/src/ol/renderer/canvas/vectorlayer.js
index dc25ba9dac..ca702e15cd 100644
--- a/src/ol/renderer/canvas/vectorlayer.js
+++ b/src/ol/renderer/canvas/vectorlayer.js
@@ -4,6 +4,8 @@ goog.require('ol');
goog.require('ol.LayerType');
goog.require('ol.ViewHint');
goog.require('ol.dom');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
goog.require('ol.ext.rbush');
goog.require('ol.extent');
goog.require('ol.render.EventType');
@@ -73,6 +75,8 @@ ol.renderer.canvas.VectorLayer = function(vectorLayer) {
*/
this.context_ = ol.dom.createCanvasContext2D();
+ ol.events.listen(ol.render.canvas.labelCache, ol.events.EventType.CLEAR, this.handleFontsChanged_, this);
+
};
ol.inherits(ol.renderer.canvas.VectorLayer, ol.renderer.canvas.Layer);
@@ -99,6 +103,15 @@ ol.renderer.canvas.VectorLayer['create'] = function(mapRenderer, layer) {
};
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.VectorLayer.prototype.disposeInternal = function() {
+ ol.events.unlisten(ol.render.canvas.labelCache, ol.events.EventType.CLEAR, this.handleFontsChanged_, this);
+ ol.renderer.canvas.Layer.prototype.disposeInternal.call(this);
+};
+
+
/**
* @inheritDoc
*/
@@ -259,6 +272,17 @@ ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtCoordinate = function(c
};
+/**
+ * @param {ol.events.Event} event Event.
+ */
+ol.renderer.canvas.VectorLayer.prototype.handleFontsChanged_ = function(event) {
+ var layer = this.getLayer();
+ if (layer.getVisible() && this.replayGroup_) {
+ layer.changed();
+ }
+};
+
+
/**
* Handle changes in image style state.
* @param {ol.events.Event} event Image style change event.
@@ -410,7 +434,7 @@ ol.renderer.canvas.VectorLayer.prototype.renderFeature = function(feature, resol
loading = ol.renderer.vector.renderFeature(
replayGroup, feature, styles,
ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
- this.handleStyleImageChange_, this) || loading;
+ this.handleStyleImageChange_, this);
}
return loading;
};
diff --git a/src/ol/renderer/canvas/vectortilelayer.js b/src/ol/renderer/canvas/vectortilelayer.js
index cdf2186d18..3f5e6fc1ff 100644
--- a/src/ol/renderer/canvas/vectortilelayer.js
+++ b/src/ol/renderer/canvas/vectortilelayer.js
@@ -4,6 +4,8 @@ goog.require('ol');
goog.require('ol.LayerType');
goog.require('ol.TileState');
goog.require('ol.dom');
+goog.require('ol.events');
+goog.require('ol.events.EventType');
goog.require('ol.ext.rbush');
goog.require('ol.extent');
goog.require('ol.layer.VectorTileRenderType');
@@ -61,6 +63,9 @@ ol.renderer.canvas.VectorTileLayer = function(layer) {
// Use lower resolution for pure vector rendering. Closest resolution otherwise.
this.zDirection =
layer.getRenderMode() == ol.layer.VectorTileRenderType.VECTOR ? 1 : 0;
+
+ ol.events.listen(ol.render.canvas.labelCache, ol.events.EventType.CLEAR, this.handleFontsChanged_, this);
+
};
ol.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.TileLayer);
@@ -109,6 +114,15 @@ ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS = {
};
+/**
+ * @inheritDoc
+ */
+ol.renderer.canvas.VectorTileLayer.prototype.disposeInternal = function() {
+ ol.events.unlisten(ol.render.canvas.labelCache, ol.events.EventType.CLEAR, this.handleFontsChanged_, this);
+ ol.renderer.canvas.TileLayer.prototype.disposeInternal.call(this);
+};
+
+
/**
* @inheritDoc
*/
@@ -334,6 +348,17 @@ ol.renderer.canvas.VectorTileLayer.prototype.getReplayTransform_ = function(tile
};
+/**
+ * @param {ol.events.Event} event Event.
+ */
+ol.renderer.canvas.VectorTileLayer.prototype.handleFontsChanged_ = function(event) {
+ var layer = this.getLayer();
+ if (layer.getVisible() && this.renderedLayerRevision_ !== undefined) {
+ layer.changed();
+ }
+};
+
+
/**
* Handle changes in image style state.
* @param {ol.events.Event} event Image style change event.
@@ -443,7 +468,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.renderFeature = function(feature, s
} else {
loading = ol.renderer.vector.renderFeature(
replayGroup, feature, styles, squaredTolerance,
- this.handleStyleImageChange_, this) || loading;
+ this.handleStyleImageChange_, this);
}
return loading;
};
diff --git a/src/ol/renderer/vector.js b/src/ol/renderer/vector.js
index 13cd33f827..8fdf23a455 100644
--- a/src/ol/renderer/vector.js
+++ b/src/ol/renderer/vector.js
@@ -94,6 +94,7 @@ ol.renderer.vector.renderFeature = function(
}
ol.renderer.vector.renderFeature_(replayGroup, feature, style,
squaredTolerance);
+
return loading;
};
diff --git a/src/ol/structs/lrucache.js b/src/ol/structs/lrucache.js
index 046e8f5adf..2743854de6 100644
--- a/src/ol/structs/lrucache.js
+++ b/src/ol/structs/lrucache.js
@@ -1,6 +1,9 @@
goog.provide('ol.structs.LRUCache');
+goog.require('ol');
goog.require('ol.asserts');
+goog.require('ol.events.EventTarget');
+goog.require('ol.events.EventType');
/**
@@ -8,12 +11,16 @@ goog.require('ol.asserts');
* Object's properties (e.g. 'hasOwnProperty' is not allowed as a key). Expiring
* items from the cache is the responsibility of the user.
* @constructor
+ * @extends {ol.events.EventTarget}
+ * @fires ol.events.Event
* @struct
* @template T
* @param {number=} opt_highWaterMark High water mark.
*/
ol.structs.LRUCache = function(opt_highWaterMark) {
+ ol.events.EventTarget.call(this);
+
/**
* @type {number}
*/
@@ -45,6 +52,8 @@ ol.structs.LRUCache = function(opt_highWaterMark) {
};
+ol.inherits(ol.structs.LRUCache, ol.events.EventTarget);
+
/**
* @return {boolean} Can expire cache.
@@ -62,6 +71,7 @@ ol.structs.LRUCache.prototype.clear = function() {
this.entries_ = {};
this.oldest_ = null;
this.newest_ = null;
+ this.dispatchEvent(ol.events.EventType.CLEAR);
};
@@ -255,3 +265,15 @@ ol.structs.LRUCache.prototype.set = function(key, value) {
this.entries_[key] = entry;
++this.count_;
};
+
+
+/**
+ * @param {string} key Key.
+ * @param {T} value Value.
+ */
+ol.structs.LRUCache.prototype.pruneAndSet = function(key, value) {
+ while (this.canExpireCache()) {
+ this.pop();
+ }
+ this.set(key, value);
+};
diff --git a/test/spec/ol/css.test.js b/test/spec/ol/css.test.js
new file mode 100644
index 0000000000..cf5e40f18b
--- /dev/null
+++ b/test/spec/ol/css.test.js
@@ -0,0 +1,47 @@
+goog.require('ol.css');
+
+describe('ol.css', function() {
+
+ describe('getFontFamilies()', function() {
+ var cases = [{
+ font: '2em "Open Sans"',
+ families: ['"Open Sans"']
+ }, {
+ font: '2em \'Open Sans\'',
+ families: ['"Open Sans"']
+ }, {
+ font: '2em "Open Sans", sans-serif',
+ families: ['"Open Sans"', 'sans-serif']
+ }, {
+ font: 'italic small-caps bolder 16px/3 cursive',
+ families: ['cursive']
+ }, {
+ font: 'garbage 2px input',
+ families: null
+ }, {
+ font: '100% fantasy',
+ families: ['fantasy']
+ }];
+
+ cases.forEach(function(c, i) {
+ it('works for ' + c.font, function() {
+ var families = ol.css.getFontFamilies(c.font);
+ if (c.families === null) {
+ expect(families).to.be(null);
+ return;
+ }
+ families.forEach(function(family, j) {
+ // Safari uses single quotes for font families, so we have to do extra work
+ if (family.charAt(0) === '\'') {
+ // we wouldn't want to do this in the lib since it doesn't properly escape quotes
+ // but we know that our test cases don't include quotes in font names
+ families[j] = '"' + family.slice(1, -1) + '"';
+ }
+ });
+ expect(families).to.eql(c.families);
+ });
+ });
+
+ });
+
+});
diff --git a/test/spec/ol/render/canvas/index.test.js b/test/spec/ol/render/canvas/index.test.js
index 1321dcb1d3..2e04220d57 100644
--- a/test/spec/ol/render/canvas/index.test.js
+++ b/test/spec/ol/render/canvas/index.test.js
@@ -1,10 +1,72 @@
-
-
+goog.require('ol.events');
+goog.require('ol.obj');
goog.require('ol.render.canvas');
describe('ol.render.canvas', function() {
+ var font = document.createElement('link');
+ font.href = 'https://fonts.googleapis.com/css?family=Inconsolata';
+ font.rel = 'stylesheet';
+ var head = document.getElementsByTagName('head')[0];
+
+ describe('ol.render.canvas.checkFont()', function() {
+
+ var checkFont = ol.render.canvas.checkFont;
+
+ it('does not clear the label cache for unavailable fonts', function(done) {
+ ol.obj.clear(ol.render.canvas.checkedFonts_);
+ var spy = sinon.spy();
+ ol.events.listen(ol.render.canvas.labelCache, 'clear', spy);
+ 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) {
+ ol.obj.clear(ol.render.canvas.checkedFonts_);
+ var spy = sinon.spy();
+ ol.events.listen(ol.render.canvas.labelCache, 'clear', spy);
+ 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) {
+ ol.obj.clear(ol.render.canvas.checkedFonts_);
+ var spy = sinon.spy();
+ ol.events.listen(ol.render.canvas.labelCache, 'clear', spy);
+ 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) {
+ ol.obj.clear(ol.render.canvas.checkedFonts_);
+ head.appendChild(font);
+ var spy = sinon.spy();
+ ol.events.listen(ol.render.canvas.labelCache, 'clear', spy);
+ checkFont('12px Inconsolata');
+ setTimeout(function() {
+ ol.events.unlisten(ol.render.canvas.labelCache, 'clear', spy);
+ head.removeChild(font);
+ expect(spy.callCount).to.be(1);
+ done();
+ }, 1600);
+ });
+
+ });
+
+
describe('rotateAtOffset', function() {
it('rotates a canvas at an offset point', function() {
var context = {
diff --git a/test/spec/ol/renderer/canvas/vectorlayer.test.js b/test/spec/ol/renderer/canvas/vectorlayer.test.js
index 1f66feaa77..1a2c88a162 100644
--- a/test/spec/ol/renderer/canvas/vectorlayer.test.js
+++ b/test/spec/ol/renderer/canvas/vectorlayer.test.js
@@ -7,7 +7,9 @@ goog.require('ol.View');
goog.require('ol.extent');
goog.require('ol.geom.Point');
goog.require('ol.layer.Vector');
+goog.require('ol.obj');
goog.require('ol.proj');
+goog.require('ol.render.canvas');
goog.require('ol.renderer.canvas.VectorLayer');
goog.require('ol.source.Vector');
goog.require('ol.style.Style');
@@ -18,6 +20,24 @@ describe('ol.renderer.canvas.VectorLayer', function() {
describe('constructor', function() {
+ var head = document.getElementsByTagName('head')[0];
+ var font = document.createElement('link');
+ font.href = 'https://fonts.googleapis.com/css?family=Droid+Sans';
+ font.rel = 'stylesheet';
+
+ var target;
+
+ beforeEach(function() {
+ target = document.createElement('div');
+ target.style.width = '256px';
+ target.style.height = '256px';
+ document.body.appendChild(target);
+ });
+
+ afterEach(function() {
+ document.body.removeChild(target);
+ });
+
it('creates a new instance', function() {
var layer = new ol.layer.Vector({
source: new ol.source.Vector()
@@ -66,6 +86,101 @@ describe('ol.renderer.canvas.VectorLayer', function() {
document.body.removeChild(target);
});
+ it('does not re-render for unavailable fonts', function(done) {
+ ol.obj.clear(ol.render.canvas.checkedFonts_);
+ var map = new ol.Map({
+ view: new ol.View({
+ center: [0, 0],
+ zoom: 0
+ }),
+ target: target
+ });
+ var layerStyle = new ol.style.Style({
+ text: new ol.style.Text({
+ text: 'layer',
+ font: '12px "Unavailable Font",sans-serif'
+ })
+ });
+
+ var feature = new ol.Feature(new ol.geom.Point([0, 0]));
+ var layer = new ol.layer.Vector({
+ source: new ol.source.Vector({
+ features: [feature]
+ }),
+ style: layerStyle
+ });
+ map.addLayer(layer);
+ var revision = layer.getRevision();
+ setTimeout(function() {
+ expect(layer.getRevision()).to.be(revision);
+ done();
+ }, 800);
+ });
+
+ it('does not re-render for available fonts', function(done) {
+ ol.obj.clear(ol.render.canvas.checkedFonts_);
+ var map = new ol.Map({
+ view: new ol.View({
+ center: [0, 0],
+ zoom: 0
+ }),
+ target: target
+ });
+ var layerStyle = new ol.style.Style({
+ text: new ol.style.Text({
+ text: 'layer',
+ font: '12px sans-serif'
+ })
+ });
+
+ var feature = new ol.Feature(new ol.geom.Point([0, 0]));
+ var layer = new ol.layer.Vector({
+ source: new ol.source.Vector({
+ features: [feature]
+ }),
+ style: layerStyle
+ });
+ map.addLayer(layer);
+ var revision = layer.getRevision();
+ setTimeout(function() {
+ expect(layer.getRevision()).to.be(revision);
+ done();
+ }, 800);
+ });
+
+ it('re-renders for fonts that become available', function(done) {
+ ol.obj.clear(ol.render.canvas.checkedFonts_);
+ head.appendChild(font);
+ var map = new ol.Map({
+ view: new ol.View({
+ center: [0, 0],
+ zoom: 0
+ }),
+ target: target
+ });
+ var layerStyle = new ol.style.Style({
+ text: new ol.style.Text({
+ text: 'layer',
+ font: '12px "Droid Sans",sans-serif'
+ })
+ });
+
+ var feature = new ol.Feature(new ol.geom.Point([0, 0]));
+ var layer = new ol.layer.Vector({
+ source: new ol.source.Vector({
+ features: [feature]
+ }),
+ style: layerStyle
+ });
+ map.addLayer(layer);
+ var revision = layer.getRevision();
+ setTimeout(function() {
+ expect(layer.getRevision()).to.be(revision + 1);
+ head.removeChild(font);
+ done();
+ }, 1600);
+ });
+
});
describe('#forEachFeatureAtCoordinate', function() {
diff --git a/test/spec/ol/renderer/canvas/vectortilelayer.test.js b/test/spec/ol/renderer/canvas/vectortilelayer.test.js
index a1e24ca89c..fcb87f6a9e 100644
--- a/test/spec/ol/renderer/canvas/vectortilelayer.test.js
+++ b/test/spec/ol/renderer/canvas/vectortilelayer.test.js
@@ -1,6 +1,7 @@
goog.require('ol');
+goog.require('ol.obj');
goog.require('ol.Feature');
goog.require('ol.Map');
goog.require('ol.TileState');
@@ -13,6 +14,7 @@ goog.require('ol.geom.Point');
goog.require('ol.layer.VectorTile');
goog.require('ol.proj');
goog.require('ol.proj.Projection');
+goog.require('ol.render.canvas');
goog.require('ol.render.Feature');
goog.require('ol.renderer.canvas.VectorTileLayer');
goog.require('ol.source.VectorTile');
@@ -25,7 +27,12 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
describe('constructor', function() {
- var map, layer, source, feature1, feature2, feature3, target, tileCallback;
+ var head = document.getElementsByTagName('head')[0];
+ var font = document.createElement('link');
+ font.href = 'https://fonts.googleapis.com/css?family=Dancing+Script';
+ font.rel = 'stylesheet';
+
+ var map, layer, layerStyle, source, feature1, feature2, feature3, target, tileCallback;
beforeEach(function() {
tileCallback = function() {};
@@ -40,7 +47,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
}),
target: target
});
- var layerStyle = [new ol.style.Style({
+ layerStyle = [new ol.style.Style({
text: new ol.style.Text({
text: 'layer'
})
@@ -147,6 +154,44 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
spy.restore();
});
+ it('does not re-render for unavailable fonts', function(done) {
+ map.renderSync();
+ ol.obj.clear(ol.render.canvas.checkedFonts_);
+ layerStyle[0].getText().setFont('12px "Unavailable font",sans-serif');
+ layer.changed();
+ var revision = layer.getRevision();
+ setTimeout(function() {
+ expect(layer.getRevision()).to.be(revision);
+ done();
+ }, 800);
+ });
+
+ it('does not re-render for available fonts', function(done) {
+ map.renderSync();
+ ol.obj.clear(ol.render.canvas.checkedFonts_);
+ layerStyle[0].getText().setFont('12px sans-serif');
+ layer.changed();
+ var revision = layer.getRevision();
+ setTimeout(function() {
+ expect(layer.getRevision()).to.be(revision);
+ done();
+ }, 800);
+ });
+
+ it('re-renders for fonts that become available', function(done) {
+ map.renderSync();
+ ol.obj.clear(ol.render.canvas.checkedFonts_);
+ head.appendChild(font);
+ layerStyle[0].getText().setFont('12px "Dancing Script",sans-serif');
+ layer.changed();
+ var revision = layer.getRevision();
+ setTimeout(function() {
+ head.removeChild(font);
+ expect(layer.getRevision()).to.be(revision + 1);
+ done();
+ }, 1600);
+ });
+
it('transforms geometries when tile and view projection are different', function() {
var tile;
tileCallback = function(t) {