Check if fonts are available and redraw when label cache was cleared

This commit is contained in:
Andreas Hocevar
2017-10-20 00:02:20 +02:00
parent dea8a340a6
commit 7f865b8520
10 changed files with 376 additions and 21 deletions

View File

@@ -12,6 +12,7 @@ ol.events.EventType = {
*/
CHANGE: 'change',
CLEAR: 'clear',
CLICK: 'click',
DBLCLICK: 'dblclick',
DRAGENTER: 'dragenter',

View File

@@ -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,73 @@ ol.render.canvas.defaultTextBaseline = 'middle';
ol.render.canvas.defaultLineWidth = 1;
/**
* @type {ol.structs.LRUCache.<HTMLCanvasElement>}
*/
ol.render.canvas.labelCache = new ol.structs.LRUCache();
/**
* @type {!Object.<string, boolean>}
*/
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 text = 'wmytzilWMYTZIL@#/&?$%10';
var context, referenceWidth;
function isAvailable(fontFamily) {
if (!context) {
context = ol.dom.createCanvasContext2D();
context.font = '32px monospace';
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;
}
return available;
}
return function(fontSpec) {
var fontFamilies = ol.css.getFontFamilies(fontSpec);
if (!fontFamilies) {
return;
}
fontFamilies.forEach(function(fontFamily) {
if (!checked[fontFamily]) {
checked[fontFamily] = true;
if (!isAvailable(fontFamily)) {
var callCount = 0;
var interval = window.setInterval(function() {
++callCount;
var available = isAvailable(fontFamily);
if (available || callCount >= 60) {
window.clearInterval(interval);
if (available) {
labelCache.clear();
}
}
}, 25);
}
}
});
};
})();
/**
* @param {CanvasRenderingContext2D} context Context.
* @param {number} rotation Rotation.

View File

@@ -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.<HTMLCanvasElement>}
*/
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();

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -94,6 +94,7 @@ ol.renderer.vector.renderFeature = function(
}
ol.renderer.vector.renderFeature_(replayGroup, feature, style,
squaredTolerance);
return loading;
};

View File

@@ -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);
};