Basic support for text symbolizers
This adds basic label rendering for points and polygons to the canvas renderer, and a text symbolizer to the style package.
This commit is contained in:
@@ -4,13 +4,17 @@ goog.require('ol.RendererHint');
|
||||
goog.require('ol.View2D');
|
||||
goog.require('ol.control.defaults');
|
||||
goog.require('ol.filter.Filter');
|
||||
goog.require('ol.filter.Geometry');
|
||||
goog.require('ol.geom.GeometryType');
|
||||
goog.require('ol.layer.Vector');
|
||||
goog.require('ol.parser.GeoJSON');
|
||||
goog.require('ol.proj');
|
||||
goog.require('ol.source.Vector');
|
||||
goog.require('ol.style.Line');
|
||||
goog.require('ol.style.Rule');
|
||||
goog.require('ol.style.Shape');
|
||||
goog.require('ol.style.Style');
|
||||
goog.require('ol.style.Text');
|
||||
|
||||
|
||||
var style = new ol.style.Style({rules: [
|
||||
@@ -42,6 +46,21 @@ var style = new ol.style.Style({rules: [
|
||||
opacity: 1
|
||||
})
|
||||
]
|
||||
}),
|
||||
new ol.style.Rule({
|
||||
filter: new ol.filter.Geometry(ol.geom.GeometryType.POINT),
|
||||
symbolizers: [
|
||||
new ol.style.Shape({
|
||||
size: 40,
|
||||
fillColor: '#013'
|
||||
}),
|
||||
new ol.style.Text({
|
||||
color: '#bada55',
|
||||
name: new ol.Expression('label'),
|
||||
fontFamily: 'Calibri,sans-serif',
|
||||
fontSize: 14
|
||||
})
|
||||
]
|
||||
})
|
||||
]});
|
||||
|
||||
@@ -114,6 +133,42 @@ vector.parseFeatures({
|
||||
'type': 'LineString',
|
||||
'coordinates': [[10000000, -10000000], [-10000000, -10000000]]
|
||||
}
|
||||
}, {
|
||||
'type': 'Feature',
|
||||
'properties': {
|
||||
'label': 'South'
|
||||
},
|
||||
'geometry': {
|
||||
'type': 'Point',
|
||||
'coordinates': [0, -6000000]
|
||||
}
|
||||
}, {
|
||||
'type': 'Feature',
|
||||
'properties': {
|
||||
'label': 'West'
|
||||
},
|
||||
'geometry': {
|
||||
'type': 'Point',
|
||||
'coordinates': [-6000000, 0]
|
||||
}
|
||||
}, {
|
||||
'type': 'Feature',
|
||||
'properties': {
|
||||
'label': 'North'
|
||||
},
|
||||
'geometry': {
|
||||
'type': 'Point',
|
||||
'coordinates': [0, 6000000]
|
||||
}
|
||||
}, {
|
||||
'type': 'Feature',
|
||||
'properties': {
|
||||
'label': 'East'
|
||||
},
|
||||
'geometry': {
|
||||
'type': 'Point',
|
||||
'coordinates': [6000000, 0]
|
||||
}
|
||||
}]
|
||||
}, new ol.parser.GeoJSON(), ol.proj.get('EPSG:3857'));
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
<div class="span4">
|
||||
<h4 id="title">Vector layer example</h4>
|
||||
<p id="shortdesc">Example of a countries vector layer with country information on hover.</p>
|
||||
<p id="shortdesc">Example of a countries vector layer with country information on hover and country labels at higher zoom levels.</p>
|
||||
<div id="docs">
|
||||
<p>See the <a href="vector-layer.js" target="_blank">vector-layer.js source</a> to see how this is done.</p>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
goog.require('ol.Expression');
|
||||
goog.require('ol.Map');
|
||||
goog.require('ol.RendererHint');
|
||||
goog.require('ol.View2D');
|
||||
goog.require('ol.filter.Filter');
|
||||
goog.require('ol.layer.TileLayer');
|
||||
goog.require('ol.layer.Vector');
|
||||
goog.require('ol.parser.GeoJSON');
|
||||
@@ -10,6 +12,7 @@ goog.require('ol.source.Vector');
|
||||
goog.require('ol.style.Polygon');
|
||||
goog.require('ol.style.Rule');
|
||||
goog.require('ol.style.Style');
|
||||
goog.require('ol.style.Text');
|
||||
|
||||
|
||||
var raster = new ol.layer.TileLayer({
|
||||
@@ -27,6 +30,19 @@ var vector = new ol.layer.Vector({
|
||||
strokeColor: '#bada55'
|
||||
})
|
||||
]
|
||||
}),
|
||||
new ol.style.Rule({
|
||||
filter: new ol.filter.Filter(function() {
|
||||
return map.getView().getResolution() < 5000;
|
||||
}),
|
||||
symbolizers: [
|
||||
new ol.style.Text({
|
||||
color: '#bada55',
|
||||
name: new ol.Expression('name'),
|
||||
fontFamily: 'Calibri,sans-serif',
|
||||
fontSize: 12
|
||||
})
|
||||
]
|
||||
})
|
||||
]}),
|
||||
transformFeatureInfo: function(features) {
|
||||
|
||||
@@ -573,6 +573,15 @@
|
||||
* @property {Array.<ol.style.Rule>} rules Rules.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ol.style.TextOptions
|
||||
* @property {string|ol.Expression|undefined} color Color.
|
||||
* @property {string|ol.Expression|undefined} fontFamily Font family.
|
||||
* @property {number|ol.Expression|undefined} fontSize Font size in pixels.
|
||||
* @property {string|ol.Expression} name Name (i.e. text content) of the label.
|
||||
* @property {number|ol.Expression|undefined} opacity Opacity (0-1).
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ol.tilegrid.TileGridOptions
|
||||
* @property {number|undefined} minZoom Minimum zoom.
|
||||
|
||||
@@ -11,6 +11,7 @@ goog.require('ol.proj');
|
||||
goog.require('ol.source.Vector');
|
||||
goog.require('ol.structs.RTree');
|
||||
goog.require('ol.style.Style');
|
||||
goog.require('ol.style.TextLiteral');
|
||||
|
||||
|
||||
|
||||
@@ -276,14 +277,17 @@ ol.layer.Vector.prototype.getPolygonVertices = function() {
|
||||
|
||||
/**
|
||||
* @param {Object.<string, ol.Feature>} features Features.
|
||||
* @return {Array.<Array>} symbolizers for features.
|
||||
* @return {Array.<Array>} symbolizers for features. Each array in this array
|
||||
* contains 3 items: an array of features, the symbolizer literal, and
|
||||
* an array with optional additional data for each feature.
|
||||
*/
|
||||
ol.layer.Vector.prototype.groupFeaturesBySymbolizerLiteral =
|
||||
function(features) {
|
||||
var uniqueLiterals = {},
|
||||
featuresBySymbolizer = [],
|
||||
style = this.style_,
|
||||
i, j, l, feature, literals, numLiterals, literal, uniqueLiteral, key;
|
||||
i, j, l, feature, literals, numLiterals, literal, uniqueLiteral, key,
|
||||
item;
|
||||
for (i in features) {
|
||||
feature = features[i];
|
||||
literals = feature.getSymbolizerLiterals();
|
||||
@@ -305,9 +309,17 @@ ol.layer.Vector.prototype.groupFeaturesBySymbolizerLiteral =
|
||||
key = goog.getUid(literal);
|
||||
if (!goog.object.containsKey(uniqueLiterals, key)) {
|
||||
uniqueLiterals[key] = featuresBySymbolizer.length;
|
||||
featuresBySymbolizer.push([[], literal]);
|
||||
featuresBySymbolizer.push([
|
||||
/** @type {Array.<ol.Feature>} */ ([]),
|
||||
/** @type {ol.style.SymbolizerLiteral} */ (literal),
|
||||
/** @type {Array} */ ([])
|
||||
]);
|
||||
}
|
||||
item = featuresBySymbolizer[uniqueLiterals[key]];
|
||||
item[0].push(feature);
|
||||
if (literal instanceof ol.style.TextLiteral) {
|
||||
item[2].push(literals[j].name);
|
||||
}
|
||||
featuresBySymbolizer[uniqueLiterals[key]][0].push(feature);
|
||||
}
|
||||
}
|
||||
return featuresBySymbolizer;
|
||||
|
||||
@@ -446,7 +446,7 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
|
||||
group = groups[j];
|
||||
deferred = sketchCanvasRenderer.renderFeaturesByGeometryType(
|
||||
/** @type {ol.geom.GeometryType} */ (type),
|
||||
group[0], group[1]);
|
||||
group[0], group[1], group[2]);
|
||||
if (deferred) {
|
||||
break renderByGeometryType;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.vec.Mat4');
|
||||
goog.require('ol.Feature');
|
||||
goog.require('ol.extent');
|
||||
goog.require('ol.geom.AbstractCollection');
|
||||
goog.require('ol.geom.Geometry');
|
||||
goog.require('ol.geom.GeometryType');
|
||||
goog.require('ol.geom.LineString');
|
||||
@@ -23,6 +25,7 @@ goog.require('ol.style.PolygonLiteral');
|
||||
goog.require('ol.style.ShapeLiteral');
|
||||
goog.require('ol.style.ShapeType');
|
||||
goog.require('ol.style.SymbolizerLiteral');
|
||||
goog.require('ol.style.TextLiteral');
|
||||
|
||||
|
||||
|
||||
@@ -100,35 +103,40 @@ ol.renderer.canvas.VectorRenderer.prototype.getMaxSymbolSize = function() {
|
||||
* @param {ol.geom.GeometryType} type Geometry type.
|
||||
* @param {Array.<ol.Feature>} features Array of features.
|
||||
* @param {ol.style.SymbolizerLiteral} symbolizer Symbolizer.
|
||||
* @param {Array} data Additional data.
|
||||
* @return {boolean} true if deferred, false if rendered.
|
||||
*/
|
||||
ol.renderer.canvas.VectorRenderer.prototype.renderFeaturesByGeometryType =
|
||||
function(type, features, symbolizer) {
|
||||
function(type, features, symbolizer, data) {
|
||||
var deferred = false;
|
||||
switch (type) {
|
||||
case ol.geom.GeometryType.POINT:
|
||||
case ol.geom.GeometryType.MULTIPOINT:
|
||||
goog.asserts.assert(symbolizer instanceof ol.style.PointLiteral,
|
||||
'Expected point symbolizer: ' + symbolizer);
|
||||
deferred = this.renderPointFeatures_(
|
||||
features, /** @type {ol.style.PointLiteral} */ (symbolizer));
|
||||
break;
|
||||
case ol.geom.GeometryType.LINESTRING:
|
||||
case ol.geom.GeometryType.MULTILINESTRING:
|
||||
goog.asserts.assert(symbolizer instanceof ol.style.LineLiteral,
|
||||
'Expected line symbolizer: ' + symbolizer);
|
||||
this.renderLineStringFeatures_(
|
||||
features, /** @type {ol.style.LineLiteral} */ (symbolizer));
|
||||
break;
|
||||
case ol.geom.GeometryType.POLYGON:
|
||||
case ol.geom.GeometryType.MULTIPOLYGON:
|
||||
goog.asserts.assert(symbolizer instanceof ol.style.PolygonLiteral,
|
||||
'Expected polygon symbolizer: ' + symbolizer);
|
||||
this.renderPolygonFeatures_(
|
||||
features, /** @type {ol.style.PolygonLiteral} */ (symbolizer));
|
||||
break;
|
||||
default:
|
||||
throw new Error('Rendering not implemented for geometry type: ' + type);
|
||||
if (!(symbolizer instanceof ol.style.TextLiteral)) {
|
||||
switch (type) {
|
||||
case ol.geom.GeometryType.POINT:
|
||||
case ol.geom.GeometryType.MULTIPOINT:
|
||||
goog.asserts.assert(symbolizer instanceof ol.style.PointLiteral,
|
||||
'Expected point symbolizer: ' + symbolizer);
|
||||
deferred = this.renderPointFeatures_(
|
||||
features, /** @type {ol.style.PointLiteral} */ (symbolizer));
|
||||
break;
|
||||
case ol.geom.GeometryType.LINESTRING:
|
||||
case ol.geom.GeometryType.MULTILINESTRING:
|
||||
goog.asserts.assert(symbolizer instanceof ol.style.LineLiteral,
|
||||
'Expected line symbolizer: ' + symbolizer);
|
||||
this.renderLineStringFeatures_(
|
||||
features, /** @type {ol.style.LineLiteral} */ (symbolizer));
|
||||
break;
|
||||
case ol.geom.GeometryType.POLYGON:
|
||||
case ol.geom.GeometryType.MULTIPOLYGON:
|
||||
goog.asserts.assert(symbolizer instanceof ol.style.PolygonLiteral,
|
||||
'Expected polygon symbolizer: ' + symbolizer);
|
||||
this.renderPolygonFeatures_(
|
||||
features, /** @type {ol.style.PolygonLiteral} */ (symbolizer));
|
||||
break;
|
||||
default:
|
||||
throw new Error('Rendering not implemented for geometry type: ' + type);
|
||||
}
|
||||
} else {
|
||||
this.renderLabels_(features, symbolizer, data);
|
||||
}
|
||||
return deferred;
|
||||
};
|
||||
@@ -255,6 +263,52 @@ ol.renderer.canvas.VectorRenderer.prototype.renderPointFeatures_ =
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {Array.<ol.Feature>} features Array of features.
|
||||
* @param {ol.style.TextLiteral} text Text symbolizer.
|
||||
* @param {Array} names Label text for each feature.
|
||||
* @private
|
||||
*/
|
||||
ol.renderer.canvas.VectorRenderer.prototype.renderLabels_ =
|
||||
function(features, text, names) {
|
||||
var context = this.context_,
|
||||
fontArray = [],
|
||||
color = text.color,
|
||||
vecs, vec;
|
||||
|
||||
context.save();
|
||||
context.scale(1, -1);
|
||||
|
||||
if (color) {
|
||||
context.fillStyle = color;
|
||||
}
|
||||
if (goog.isDef(text.fontSize)) {
|
||||
fontArray.push((text.fontSize * this.inverseScale_) + 'px');
|
||||
}
|
||||
if (goog.isDef(text.fontFamily)) {
|
||||
fontArray.push(text.fontFamily);
|
||||
}
|
||||
if (fontArray.length) {
|
||||
context.font = fontArray.join(' ');
|
||||
}
|
||||
context.globalAlpha = text.opacity;
|
||||
context.textAlign = 'center';
|
||||
context.textBaseline = 'middle';
|
||||
|
||||
for (var i = 0, ii = features.length; i < ii; ++i) {
|
||||
vecs = ol.renderer.canvas.VectorRenderer.getLabelVectors(
|
||||
features[i].getGeometry());
|
||||
for (var j = 0, jj = vecs.length; j < jj; ++j) {
|
||||
vec = vecs[j];
|
||||
goog.vec.Mat4.multVec3(this.transform_, vec, vec);
|
||||
context.fillText(names[i], vec[0], vec[1]);
|
||||
}
|
||||
}
|
||||
|
||||
context.restore();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {Array.<ol.Feature>} features Array of polygon features.
|
||||
* @param {ol.style.PolygonLiteral} symbolizer Polygon symbolizer.
|
||||
@@ -384,6 +438,35 @@ ol.renderer.canvas.VectorRenderer.renderCircle_ = function(circle) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.geom.Geometry} geometry Geometry.
|
||||
* @return {Array.<goog.vec.Vec3.AnyType>} Renderable geometry vectors.
|
||||
*/
|
||||
ol.renderer.canvas.VectorRenderer.getLabelVectors = function(geometry) {
|
||||
if (geometry instanceof ol.geom.AbstractCollection) {
|
||||
var components = geometry.components;
|
||||
var numComponents = components.length;
|
||||
var result = [];
|
||||
for (var i = 0; i < numComponents; ++i) {
|
||||
result.push.apply(result,
|
||||
ol.renderer.canvas.VectorRenderer.getLabelVectors(components[i]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
var type = geometry.getType();
|
||||
if (type == ol.geom.GeometryType.POINT) {
|
||||
return [[geometry.get(0), geometry.get(1), 0]];
|
||||
}
|
||||
if (type == ol.geom.GeometryType.POLYGON) {
|
||||
// TODO Better label placement
|
||||
var coordinates = ol.extent.getCenter(geometry.getBounds());
|
||||
return [[coordinates[0], coordinates[1], 0]];
|
||||
}
|
||||
throw new Error('Label rendering not implemented for geometry type: ' +
|
||||
type);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.style.ShapeLiteral} shape Shape symbolizer.
|
||||
* @return {!HTMLCanvasElement} Canvas element.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
@exportClass ol.style.Rule ol.style.RuleOptions
|
||||
@exportClass ol.style.Shape ol.style.ShapeOptions
|
||||
@exportClass ol.style.Style ol.style.StyleOptions
|
||||
@exportClass ol.style.Text ol.style.TextOptions
|
||||
@exportSymbol ol.style.IconType
|
||||
@exportSymbol ol.style.ShapeType
|
||||
@exportProperty ol.style.ShapeType.CIRCLE
|
||||
|
||||
171
src/ol/style/text.js
Normal file
171
src/ol/style/text.js
Normal file
@@ -0,0 +1,171 @@
|
||||
goog.provide('ol.style.Text');
|
||||
goog.provide('ol.style.TextLiteral');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('ol.Expression');
|
||||
goog.require('ol.ExpressionLiteral');
|
||||
goog.require('ol.style.Symbolizer');
|
||||
goog.require('ol.style.SymbolizerLiteral');
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {{color: (string|undefined),
|
||||
* fontFamily: (string|undefined),
|
||||
* fontSize: number,
|
||||
* name: string,
|
||||
* opacity: number}}
|
||||
*/
|
||||
ol.style.TextLiteralOptions;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends {ol.style.SymbolizerLiteral}
|
||||
* @param {ol.style.TextLiteralOptions} options Text literal options.
|
||||
*/
|
||||
ol.style.TextLiteral = function(options) {
|
||||
|
||||
/** @type {string|undefined} */
|
||||
this.color = options.color;
|
||||
if (goog.isDef(options.color)) {
|
||||
goog.asserts.assertString(options.color, 'color must be a string');
|
||||
}
|
||||
|
||||
/** @type {string|undefined} */
|
||||
this.fontFamily = options.fontFamily;
|
||||
if (goog.isDef(options.fontFamily)) {
|
||||
goog.asserts.assertString(options.fontFamily,
|
||||
'fontFamily must be a string');
|
||||
}
|
||||
|
||||
goog.asserts.assertNumber(options.fontSize, 'fontSize must be a number');
|
||||
/** @type {number} */
|
||||
this.fontSize = options.fontSize;
|
||||
|
||||
goog.asserts.assertString(options.name, 'name must be a string');
|
||||
/** @type {string} */
|
||||
this.name = options.name;
|
||||
|
||||
goog.asserts.assertNumber(options.opacity, 'opacity must be a number');
|
||||
/** @type {number} */
|
||||
this.opacity = options.opacity;
|
||||
|
||||
};
|
||||
goog.inherits(ol.style.TextLiteral, ol.style.SymbolizerLiteral);
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
ol.style.TextLiteral.prototype.equals = function(textLiteral) {
|
||||
return this.color == textLiteral.color &&
|
||||
this.fontFamily == textLiteral.fontFamily &&
|
||||
this.fontSize == textLiteral.fontSize &&
|
||||
this.opacity == textLiteral.opacity;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends {ol.style.Symbolizer}
|
||||
* @param {ol.style.TextOptions} options Text options.
|
||||
*/
|
||||
ol.style.Text = function(options) {
|
||||
|
||||
/**
|
||||
* @type {ol.Expression}
|
||||
* @private
|
||||
*/
|
||||
this.color_ = !goog.isDef(options.color) ?
|
||||
null :
|
||||
(options.color instanceof ol.Expression) ?
|
||||
options.color : new ol.ExpressionLiteral(options.color);
|
||||
|
||||
/**
|
||||
* @type {ol.Expression}
|
||||
* @private
|
||||
*/
|
||||
this.fontFamily_ = !goog.isDefAndNotNull(options.fontFamily) ?
|
||||
null :
|
||||
(options.fontFamily instanceof ol.Expression) ?
|
||||
options.fontFamily : new ol.ExpressionLiteral(options.fontFamily);
|
||||
|
||||
/**
|
||||
* @type {ol.Expression}
|
||||
* @private
|
||||
*/
|
||||
this.fontSize_ = !goog.isDefAndNotNull(options.fontSize) ?
|
||||
null :
|
||||
(options.fontSize instanceof ol.Expression) ?
|
||||
options.fontSize : new ol.ExpressionLiteral(options.fontSize);
|
||||
|
||||
/**
|
||||
* @type {ol.Expression}
|
||||
* @private
|
||||
*/
|
||||
this.name_ = (options.name instanceof ol.Expression) ?
|
||||
options.name : new ol.ExpressionLiteral(options.name);
|
||||
|
||||
/**
|
||||
* @type {ol.Expression}
|
||||
* @private
|
||||
*/
|
||||
this.opacity_ = !goog.isDef(options.opacity) ?
|
||||
new ol.ExpressionLiteral(ol.style.TextDefaults.opacity) :
|
||||
(options.opacity instanceof ol.Expression) ?
|
||||
options.opacity : new ol.ExpressionLiteral(options.opacity);
|
||||
|
||||
};
|
||||
goog.inherits(ol.style.Text, ol.style.Symbolizer);
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return {ol.style.TextLiteral} Literal text symbolizer.
|
||||
*/
|
||||
ol.style.Text.prototype.createLiteral = function(opt_feature) {
|
||||
var attrs,
|
||||
feature = opt_feature;
|
||||
if (goog.isDef(feature)) {
|
||||
attrs = feature.getAttributes();
|
||||
}
|
||||
|
||||
var color = goog.isNull(this.color_) ?
|
||||
undefined :
|
||||
/** @type {string} */ (this.color_.evaluate(feature, attrs));
|
||||
goog.asserts.assert(!goog.isDef(color) || goog.isString(color));
|
||||
|
||||
var fontFamily = goog.isNull(this.fontFamily_) ?
|
||||
undefined :
|
||||
/** @type {string} */ (this.fontFamily_.evaluate(feature, attrs));
|
||||
goog.asserts.assert(!goog.isDef(fontFamily) || goog.isString(fontFamily));
|
||||
|
||||
var fontSize = this.fontSize_.evaluate(feature, attrs);
|
||||
goog.asserts.assertNumber(fontSize, 'fontSize must be a number');
|
||||
|
||||
var name = this.name_.evaluate(feature, attrs);
|
||||
goog.asserts.assertString(name, 'name must be a string');
|
||||
|
||||
var opacity = this.opacity_.evaluate(feature, attrs);
|
||||
goog.asserts.assertNumber(opacity, 'opacity must be a number');
|
||||
|
||||
return new ol.style.TextLiteral({
|
||||
color: color,
|
||||
fontFamily: fontFamily,
|
||||
fontSize: fontSize,
|
||||
name: name,
|
||||
opacity: opacity
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @type {ol.style.TextLiteral}
|
||||
*/
|
||||
ol.style.TextDefaults = new ol.style.TextLiteral({
|
||||
fontSize: 10,
|
||||
name: '',
|
||||
opacity: 1
|
||||
});
|
||||
Reference in New Issue
Block a user