Support text shadow and stroke.
MODIFY
examples/annotations/index.jade
examples/annotations/index.jade
@@ -102,6 +102,7 @@ block append mainContent
option(value='round') Round
option(value='miter-clip') Miter-Clip
.col-md-6
+ //- label
.form-group(title='The label defaults to the annotation name')
label(for='edit-label') Label
input#edit-label(option='label')
@@ -110,15 +111,22 @@ block append mainContent
select#edit-showLabel(option='showLabel', format='boolean', optiontype='option')
option(value='true') Yes
option(value='false') No
- .form-group(annotation-types='all')
- label(for='edit-color') Label Color
- input#edit-color(option='color', format='color', optiontype='label')
- .form-group(annotation-types='all')
- label(for='edit-textOpacity') Label Opacity
- input#edit-textOpacity(option='textOpacity', format='opacity', optiontype='label')
.form-group(annotation-types='all', title='This is of the form [italic|oblique] [small-caps] [bold|bolder|lighter|<weight>] [<stretch>] <size>[/<line height>] <family>')
label(for='edit-font') Font
input#edit-font(option='font', optiontype='label')
+ .form-group(annotation-types='all', title='This applies to both the filled text and the stroke')
+ label(for='edit-textOpacity') Label Opacity
+ input#edit-textOpacity(option='textOpacity', format='opacity', optiontype='label')
+ .form-group(annotation-types='all', title='The color of the filled text. Use an rgba() form to specify opacity.')
+ label(for='edit-color') Fill Color
+ input#edit-color(option='color', format='color', optiontype='label')
+ .form-group(annotation-types='all', title='The color of a stroke around the text. If used with Fill Color, this adds a perimeter half the Stroke Width outside of the text. Use an rgba() form to specify opacity.')
+ label(for='edit-textStrokeColor') Stroke Color
+ input#edit-textStrokeColor(option='textStrokeColor', format='textStrokeColor', optiontype='label')
+ .form-group(annotation-types='all')
+ label(for='edit-textStrokeWidth') Stroke Width
+ input#edit-textStrokeWidth(option='textStrokeWidth', format='numberOrBlank', optiontype='label')
+ //- positioning
.form-group(annotation-types='all', title='Horizontal alignment')
label(for='edit-textAlign') Horizontal Align.
select#edit-textAlign(option='textAlign', format='text', optiontype='label')
@@ -150,6 +158,21 @@ block append mainContent
.form-group(annotation-types='all', title='This is the x, y offset of the label in pixels from its default position before rotation and scale. Example: "5, -4"')
label(for='edit-offset') Offset
input#edit-offset(option='offset', format='coordinate2', optiontype='label')
+ //- shadow options
+ .form-group(annotation-types='all')
+ label(for='edit-shadowColor') Shadow Color
+ input#edit-shadowColor(option='shadowColor', format='shadowColor', optiontype='label')
+ .form-group(annotation-types='all')
+ label(for='edit-shadowBlur') Shadow Blur
+ input#edit-shadowBlur(option='shadowBlur', format='numberOrBlank', optiontype='label')
+ .form-group(annotation-types='all', title='This is the x, y shadowOffset of the shadow in pixels from its default position before rotation. Example: "5, -4"')
+ label(for='edit-shadowOffset') Shadow Offset
+ input#edit-shadowOffset(option='shadowOffset', format='coordinate2', optiontype='label')
+ .form-group(annotation-types='all', title='Enable to rotate the shadow absed on the label\'s rotation.')
+ label(for='edit-shadowRotate') Rotate Shadow
+ select#edit-shadowRotate(option='shadowRotate', format='boolean', optiontype='label')
+ option(value='false') No
+ option(value='true') Yes
.form-group
#edit-validation-error
.modal-footer

MODIFY
examples/annotations/main.js
examples/annotations/main.js
@@ -393,9 +393,6 @@ $(function () {
break;
}
switch (key) {
- case 'offset':
- //DWM::
- break;
case 'textScaled':
if (['true', 'on', 'yes'].indexOf(value.trim().toLowerCase()) >= 0) {
value = map.zoom();

MODIFY
src/annotationLayer.js
src/annotationLayer.js
@@ -88,8 +88,9 @@ var annotationLayer = function (args) {
dataType: ['visible', 'rotateWithMap', 'scaleWithMap'].indexOf(key) >= 0 ? 'boolean' : (
['scale'].indexOf(key) >= 0 ? 'booleanOrNumber' : (
['rotation'].indexOf(key) >= 0 ? 'angle' : (
- ['offset'].indexOf(key) >= 0 ? 'coordinate2' :
- 'text'))),
+ ['offset', 'shadowOffset'].indexOf(key) >= 0 ? 'coordinate2' : (
+ ['shadowBlur, strokeWidth'].indexOf(key) >= 0 ? 'numberOrBlank' :
+ 'text')))),
keys: [
key,
'label' + key.charAt(0).toUpperCase() + key.slice(1),
@@ -685,6 +686,15 @@ var annotationLayer = function (args) {
return;
}
break;
+ case 'numberOrBlank':
+ if (value === '') {
+ break;
+ }
+ value = +value;
+ if (!util.isNonNullFinite(value)) {
+ return;
+ }
+ break;
case 'opacity':
if (value === undefined || value === null || value === '') {
return;

MODIFY
src/canvas/textFeature.js
src/canvas/textFeature.js
@@ -3,6 +3,7 @@ var registerFeature = require('../registry').registerFeature;
var textFeature = require('../textFeature');
var util = require('../util');
var mat3 = require('gl-mat3');
+var vec3 = require('gl-vec3');
/**
* Create a new instance of class canvas.textFeature.
@@ -92,7 +93,7 @@ var canvas_textFeature = function (arg) {
textFunc = m_this.style.get('text'),
mapRotation = map.rotation(),
mapZoom = map.zoom(),
- fontFromSubValues, text, pos, val,
+ fontFromSubValues, text, pos, visible, color, blur, stroke, width,
rotation, rotateWithMap, scale, offset,
transform, lastTransform = util.mat3AsArray();
@@ -105,17 +106,18 @@ var canvas_textFeature = function (arg) {
/* Clear the canvas property buffer */
m_this._canvasProperty();
data.forEach(function (d, i) {
- val = m_this.style.get('visible')(d, i);
- if (!val && val !== undefined) {
+ visible = m_this.style.get('visible')(d, i);
+ if (!visible && visible !== undefined) {
return;
}
- val = util.convertColorAndOpacity(
+ color = util.convertColorAndOpacity(
m_this.style.get('color')(d, i), m_this.style.get('textOpacity')(d, i));
- if (val.a === 0) {
+ stroke = util.convertColorAndOpacity(
+ m_this.style.get('textStrokeColor')(d, i), m_this.style.get('textOpacity')(d, i), {r: 0, g: 0, b: 0, a: 0});
+ if (color.a === 0 && stroke.a === 0) {
return;
}
- m_this._canvasProperty(context2d, 'globalAlpha', val.a);
- m_this._canvasProperty(context2d, 'fillStyle', util.convertColorToHex(val));
+ m_this._canvasProperty(context2d, 'fillStyle', util.convertColorToRGBA(color));
// TODO: get the position position without transform. If it is outside
// of the map to an extent that there is no chance of text showing,
// skip further processing.
@@ -154,17 +156,37 @@ var canvas_textFeature = function (arg) {
context2d.setTransform(transform[0], transform[1], transform[3], transform[4], transform[6], transform[7]);
mat3.copy(lastTransform, transform);
}
-
- // TODO: fetch and use other properties:
- // 'shadowColor', 'shadowOffset', 'shadowBlur', 'shadowRotate',
- // 'shadowOpacity'
- /*
- ctx.shadowColor = "black";
- ctx.shadowOffsetX = 5;
- ctx.shadowOffsetY = 5;
- ctx.shadowBlur = 7;
- */
-
+ /* shadow */
+ color = util.convertColorAndOpacity(
+ m_this.style.get('shadowColor')(d, i), undefined, {r: 0, g: 0, b: 0, a: 0});
+ if (color.a) {
+ offset = m_this.style.get('shadowOffset')(d, i);
+ blur = m_this.style.get('shadowBlur')(d, i);
+ }
+ if (color.a && ((offset && (offset.x || offset.y)) || blur)) {
+ m_this._canvasProperty(context2d, 'shadowColor', util.convertColorToRGBA(color));
+ if (offset && (rotation || rotateWithMap) && m_this.style.get('shadowRotate')(d, i)) {
+ transform = [+offset.x, +offset.y, 0];
+ vec3.rotateZ(transform, transform, [0, 0, 0],
+ rotation + (rotateWithMap ? mapRotation : 0));
+ offset = {x: transform[0], y: transform[1]};
+ }
+ m_this._canvasProperty(context2d, 'shadowOffsetX', offset && offset.x ? +offset.x : 0);
+ m_this._canvasProperty(context2d, 'shadowOffsetY', offset && offset.y ? +offset.y : 0);
+ m_this._canvasProperty(context2d, 'shadowBlur', blur || 0);
+ } else {
+ m_this._canvasProperty(context2d, 'shadowColor', 'rgba(0,0,0,0)');
+ }
+ /* draw the text */
+ if (stroke.a) {
+ width = m_this.style.get('textStrokeWidth')(d, i);
+ if (isFinite(width) && width > 0) {
+ m_this._canvasProperty(context2d, 'strokeStyle', util.convertColorToRGBA(stroke));
+ m_this._canvasProperty(context2d, 'lineWidth', width);
+ context2d.strokeText(text, pos.x, pos.y);
+ m_this._canvasProperty(context2d, 'shadowColor', 'rgba(0,0,0,0)');
+ }
+ }
context2d.fillText(text, pos.x, pos.y);
});
m_this._canvasProperty(context2d, 'globalAlpha', 1);

MODIFY
src/textFeature.js
src/textFeature.js
@@ -64,6 +64,10 @@ var feature = require('./feature');
* @property {boolean|function} [style.shadowRotate=false] If truthy, rotate
* the shadow offset based on the text rotation (the `shadowOffset` is
* the offset if the text has a 0 rotation).
+ * @property {geo.geoColor|function} [style.textStrokeColor='transparent'] Text
+ * stroke color. May include opacity.
+ * @property {geo.geoColor|function} [style.textStrokeWidth=0] Text stroke
+ * width in pixels.
*/
/**
@@ -181,7 +185,7 @@ textFeature.usedStyles = [
'fontSize', 'lineHeight', 'fontFamily', 'textAlign', 'textBaseline',
'direction', 'color', 'textOpacity', 'rotation', 'rotateWithMap',
'textScaled', 'offset', 'shadowColor', 'shadowOpacity', 'shadowOffset',
- 'shadowBlur', 'shadowRotate'
+ 'shadowBlur', 'shadowRotate', 'textStrokeColor', 'textStrokeWidth'
];
/**

MODIFY
src/util/index.js
src/util/index.js
@@ -301,9 +301,9 @@ var util = module.exports = {
r: isFinite(color.r) && color.r >= 0 ? (color.r <= 1 ? +color.r : 1) : 0,
g: isFinite(color.g) && color.g >= 0 ? (color.g <= 1 ? +color.g : 1) : 0,
b: isFinite(color.b) && color.b >= 0 ? (color.b <= 1 ? +color.b : 1) : 0,
- a: isFinite(color.a) && color.a >= 0 ? (color.a <= 1 ? +color.a : 1) : 1
+ a: util.isNonNullFinite(color.a) && color.a >= 0 && color.a < 1 ? +color.a : 1
};
- if (isFinite(opacity) && opacity < 1) {
+ if (util.isNonNullFinite(opacity) && opacity < 1) {
color.a = opacity <= 0 ? 0 : color.a * opacity;
}
return color;

Support text shadow and stroke.