Commit a2dd0645 authored by David Schnur's avatar David Schnur

Break text styles into their own cache tier.

Previously the cache was divided only by layer, with entries keyed on a
string built from the text and style.  Now the style has its own tier in
the cache, i.e. layers > styles > text > info.

This introduces some complexity, since the nested for loops are ugly,
but at the same time we avoid having to create the cache-key strings.
More importantly it solves the problem of uniqueness that exists when we
try to join strings that may contain arbitrary text.  It also allows a
further optimization in the canvas plugin, which can now set text style
and color just once per distinct style, instead of with every string.
parent 77e50b17
...@@ -61,69 +61,80 @@ browser, but needs to redraw with canvas text when exporting as an image. ...@@ -61,69 +61,80 @@ browser, but needs to redraw with canvas text when exporting as an image.
for (var layerKey in cache) { for (var layerKey in cache) {
if (hasOwnProperty.call(cache, layerKey)) { if (hasOwnProperty.call(cache, layerKey)) {
var layerCache = cache[layerKey]; var layerCache = cache[layerKey];
for (var styleKey in layerCache) {
for (var key in layerCache) { if (hasOwnProperty.call(layerCache, styleKey)) {
if (hasOwnProperty.call(layerCache, key)) { var styleCache = layerCache[styleKey],
updateStyles = true;
var info = layerCache[key]; for (var key in styleCache) {
if (hasOwnProperty.call(styleCache, key)) {
if (!info.active) {
delete cache[key]; var info = styleCache[key];
continue;
} if (!info.active) {
delete styleCache[key];
var x = info.x, continue;
y = info.y, }
lines = info.lines,
halign = info.halign; var x = info.x,
y = info.y,
context.fillStyle = info.font.color; lines = info.lines,
context.font = info.font.definition; halign = info.halign;
// TODO: Comments in Ole's implementation indicate that // Since every element at this level of the cache have the
// some browsers differ in their interpretation of 'top'; // same font and fill styles, we can just change them once
// so far I don't see this, but it requires more testing. // using the values from the first element.
// We'll stick with top until this can be verified.
if (updateStyles) {
// Original comment was: context.fillStyle = info.font.color;
// Top alignment would be more natural, but browsers can context.font = info.font.definition;
// differ a pixel or two in where they consider the top to updateStyles = false;
// be, so instead we middle align to minimize variation }
// between browsers and compensate when calculating the
// coordinates. // TODO: Comments in Ole's implementation indicate that
// some browsers differ in their interpretation of 'top';
context.textBaseline = "top"; // so far I don't see this, but it requires more testing.
// We'll stick with top until this can be verified.
for (var i = 0; i < lines.length; ++i) {
// Original comment was:
var line = lines[i], // Top alignment would be more natural, but browsers can
linex = x; // differ a pixel or two in where they consider the top to
// be, so instead we middle align to minimize variation
// Apply horizontal alignment per-line // between browsers and compensate when calculating the
// coordinates.
if (halign == "center") {
linex -= line.width / 2; context.textBaseline = "top";
} else if (halign == "right") {
linex -= line.width; for (var i = 0; i < lines.length; ++i) {
}
var line = lines[i],
// FIXME: LEGACY BROWSER FIX linex = x;
// AFFECTS: Opera < 12.00
// Apply horizontal alignment per-line
// Round the coordinates, since Opera otherwise
// switches to uglier (probably non-hinted) rendering. if (halign == "center") {
// Also offset the y coordinate, since Opera is off linex -= line.width / 2;
// pretty consistently compared to the other browsers. } else if (halign == "right") {
linex -= line.width;
if (!!(window.opera && window.opera.version().split(".")[0] < 12)) { }
linex = Math.floor(linex);
y = Math.ceil(y - 2); // FIXME: LEGACY BROWSER FIX
// AFFECTS: Opera < 12.00
// Round the coordinates, since Opera otherwise
// switches to uglier (probably non-hinted) rendering.
// Also offset the y coordinate, since Opera is off
// pretty consistently compared to the other browsers.
if (!!(window.opera && window.opera.version().split(".")[0] < 12)) {
linex = Math.floor(linex);
y = Math.ceil(y - 2);
}
context.fillText(line.text, linex, y);
y += line.height;
}
} }
context.fillText(line.text, linex, y);
y += line.height;
} }
} }
} }
...@@ -160,7 +171,7 @@ browser, but needs to redraw with canvas text when exporting as an image. ...@@ -160,7 +171,7 @@ browser, but needs to redraw with canvas text when exporting as an image.
return getTextInfo.call(this, layer, text, font, angle); return getTextInfo.call(this, layer, text, font, angle);
} }
var textStyle, cache, cacheKey, info; var textStyle, layerCache, styleCache, info;
// Cast the value to a string, in case we were given a number // Cast the value to a string, in case we were given a number
...@@ -174,21 +185,21 @@ browser, but needs to redraw with canvas text when exporting as an image. ...@@ -174,21 +185,21 @@ browser, but needs to redraw with canvas text when exporting as an image.
textStyle = font; textStyle = font;
} }
// Retrieve (or create) the cache for the text's layer // Retrieve (or create) the cache for the text's layer and styles
cache = this._textCache[layer]; layerCache = this._textCache[layer];
if (cache == null) { if (layerCache == null) {
cache = this._textCache[layer] = {}; layerCache = this._textCache[layer] = {};
} }
// The text + style + angle uniquely identify the text's dimensions styleCache = layerCache[textStyle];
// and content; we'll use them to build the entry's text cache key.
// NOTE: We don't support rotated text yet, so the angle is unused.
cacheKey = textStyle + "|" + text; if (styleCache == null) {
styleCache = layerCache[textStyle] = {};
}
info = cache[cacheKey]; info = styleCache[text];
if (info == null) { if (info == null) {
...@@ -224,7 +235,7 @@ browser, but needs to redraw with canvas text when exporting as an image. ...@@ -224,7 +235,7 @@ browser, but needs to redraw with canvas text when exporting as an image.
// Create a new info object, initializing the dimensions to // Create a new info object, initializing the dimensions to
// zero so we can count them up line-by-line. // zero so we can count them up line-by-line.
info = cache[cacheKey] = { info = styleCache[text] = {
x: null, x: null,
y: null, y: null,
width: 0, width: 0,
......
...@@ -180,20 +180,23 @@ Licensed under the MIT license. ...@@ -180,20 +180,23 @@ Licensed under the MIT license.
layer.hide(); layer.hide();
for (var key in layerCache) { for (var styleKey in layerCache) {
if (hasOwnProperty.call(layerCache, key)) { if (hasOwnProperty.call(layerCache, styleKey)) {
var styleCache = layerCache[styleKey];
var info = layerCache[key]; for (var key in styleCache) {
if (hasOwnProperty.call(styleCache, key)) {
if (info.active) { var info = styleCache[key];
if (!info.rendered) { if (info.active) {
layer.append(info.element); if (!info.rendered) {
info.rendered = true; layer.append(info.element);
} info.rendered = true;
} else { }
delete layerCache[key]; } else {
if (info.rendered) { delete styleCache[key];
info.element.detach(); if (info.rendered) {
info.element.detach();
}
}
} }
} }
} }
...@@ -258,7 +261,7 @@ Licensed under the MIT license. ...@@ -258,7 +261,7 @@ Licensed under the MIT license.
Canvas.prototype.getTextInfo = function(layer, text, font, angle) { Canvas.prototype.getTextInfo = function(layer, text, font, angle) {
var textStyle, cache, cacheKey, info; var textStyle, layerCache, styleCache, info;
// Cast the value to a string, in case we were given a number or such // Cast the value to a string, in case we were given a number or such
...@@ -272,21 +275,21 @@ Licensed under the MIT license. ...@@ -272,21 +275,21 @@ Licensed under the MIT license.
textStyle = font; textStyle = font;
} }
// Retrieve (or create) the cache for the text's layer // Retrieve (or create) the cache for the text's layer and styles
cache = this._textCache[layer]; layerCache = this._textCache[layer];
if (cache == null) { if (layerCache == null) {
cache = this._textCache[layer] = {}; layerCache = this._textCache[layer] = {};
} }
// The text + style + angle uniquely identify the text's dimensions and styleCache = layerCache[textStyle];
// content; we'll use them to build this entry's text cache key.
// NOTE: We don't support rotated text yet, so the angle is unused.
cacheKey = textStyle + "|" + text; if (styleCache == null) {
styleCache = layerCache[textStyle] = {};
}
info = cache[cacheKey]; info = styleCache[text];
// If we can't find a matching element in our cache, create a new one // If we can't find a matching element in our cache, create a new one
...@@ -308,7 +311,7 @@ Licensed under the MIT license. ...@@ -308,7 +311,7 @@ Licensed under the MIT license.
element.addClass(font); element.addClass(font);
} }
info = cache[cacheKey] = { info = styleCache[text] = {
active: false, active: false,
rendered: false, rendered: false,
element: element, element: element,
...@@ -387,11 +390,16 @@ Licensed under the MIT license. ...@@ -387,11 +390,16 @@ Licensed under the MIT license.
Canvas.prototype.removeText = function(layer, text, font, angle) { Canvas.prototype.removeText = function(layer, text, font, angle) {
if (text == null) { if (text == null) {
var cache = this._textCache[layer]; var layerCache = this._textCache[layer];
if (cache != null) { if (layerCache != null) {
for (var key in cache) { for (var styleKey in layerCache) {
if (hasOwnProperty.call(cache, key)) { if (hasOwnProperty.call(layerCache, styleKey)) {
cache[key].active = false; var styleCache = layerCache[styleKey]
for (var key in styleCache) {
if (hasOwnProperty.call(styleCache, key)) {
styleCache[key].active = false;
}
}
} }
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment