diff --git a/debug/stretchable.html b/debug/stretchable.html
new file mode 100644
index 00000000000..fb1cbf8d124
--- /dev/null
+++ b/debug/stretchable.html
@@ -0,0 +1,129 @@
+
+
+
+ Mapbox GL JS debug page
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/data/array_types.js b/src/data/array_types.js
index ffc8bf7f62c..a62f059d8a3 100644
--- a/src/data/array_types.js
+++ b/src/data/array_types.js
@@ -189,10 +189,11 @@ register('StructArrayLayout8ui16', StructArrayLayout8ui16);
* Implementation of the StructArray layout:
* [0]: Int16[4]
* [8]: Uint16[4]
+ * [16]: Int16[4]
*
* @private
*/
-class StructArrayLayout4i4ui16 extends StructArray {
+class StructArrayLayout4i4ui4i24 extends StructArray {
uint8: Uint8Array;
int16: Int16Array;
uint16: Uint16Array;
@@ -203,14 +204,14 @@ class StructArrayLayout4i4ui16 extends StructArray {
this.uint16 = new Uint16Array(this.arrayBuffer);
}
- emplaceBack(v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number) {
+ emplaceBack(v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number, v8: number, v9: number, v10: number, v11: number) {
const i = this.length;
this.resize(i + 1);
- return this.emplace(i, v0, v1, v2, v3, v4, v5, v6, v7);
+ return this.emplace(i, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11);
}
- emplace(i: number, v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number) {
- const o2 = i * 8;
+ emplace(i: number, v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number, v8: number, v9: number, v10: number, v11: number) {
+ const o2 = i * 12;
this.int16[o2 + 0] = v0;
this.int16[o2 + 1] = v1;
this.int16[o2 + 2] = v2;
@@ -219,12 +220,16 @@ class StructArrayLayout4i4ui16 extends StructArray {
this.uint16[o2 + 5] = v5;
this.uint16[o2 + 6] = v6;
this.uint16[o2 + 7] = v7;
+ this.int16[o2 + 8] = v8;
+ this.int16[o2 + 9] = v9;
+ this.int16[o2 + 10] = v10;
+ this.int16[o2 + 11] = v11;
return i;
}
}
-StructArrayLayout4i4ui16.prototype.bytesPerElement = 16;
-register('StructArrayLayout4i4ui16', StructArrayLayout4i4ui16);
+StructArrayLayout4i4ui4i24.prototype.bytesPerElement = 24;
+register('StructArrayLayout4i4ui4i24', StructArrayLayout4i4ui4i24);
/**
* Implementation of the StructArray layout:
@@ -1143,7 +1148,7 @@ export {
StructArrayLayout2i4i12,
StructArrayLayout2i4ub8,
StructArrayLayout8ui16,
- StructArrayLayout4i4ui16,
+ StructArrayLayout4i4ui4i24,
StructArrayLayout3f12,
StructArrayLayout1ul4,
StructArrayLayout6i1ul2ui2i24,
@@ -1167,7 +1172,7 @@ export {
StructArrayLayout2i4 as HeatmapLayoutArray,
StructArrayLayout2i4ub8 as LineLayoutArray,
StructArrayLayout8ui16 as PatternLayoutArray,
- StructArrayLayout4i4ui16 as SymbolLayoutArray,
+ StructArrayLayout4i4ui4i24 as SymbolLayoutArray,
StructArrayLayout3f12 as SymbolDynamicLayoutArray,
StructArrayLayout1ul4 as SymbolOpacityArray,
StructArrayLayout2i2i2i12 as CollisionBoxLayoutArray,
diff --git a/src/data/bucket/symbol_attributes.js b/src/data/bucket/symbol_attributes.js
index bc8bb06e0bf..014d922e595 100644
--- a/src/data/bucket/symbol_attributes.js
+++ b/src/data/bucket/symbol_attributes.js
@@ -4,8 +4,9 @@ import {createLayout} from '../../util/struct_array';
export const symbolLayoutAttributes = createLayout([
{name: 'a_pos_offset', components: 4, type: 'Int16'},
- {name: 'a_data', components: 4, type: 'Uint16'}
-]);
+ {name: 'a_data', components: 4, type: 'Uint16'},
+ {name: 'a_pixeloffset', components: 4, type: 'Int16'}
+], 4);
export const dynamicLayoutAttributes = createLayout([
{name: 'a_projected_pos', components: 3, type: 'Float32'}
diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js
index 925e8e70499..2b7b751389e 100644
--- a/src/data/bucket/symbol_bucket.js
+++ b/src/data/bucket/symbol_bucket.js
@@ -102,7 +102,7 @@ const shaderOpacityAttributes = [
{name: 'a_fade_opacity', components: 1, type: 'Uint8', offset: 0}
];
-function addVertex(array, anchorX, anchorY, ox, oy, tx, ty, sizeVertex, isSDF: boolean) {
+function addVertex(array, anchorX, anchorY, ox, oy, tx, ty, sizeVertex, isSDF: boolean, pixelOffsetX, pixelOffsetY, minFontScaleX, minFontScaleY) {
const aSizeX = sizeVertex ? Math.min(MAX_PACKED_SIZE, Math.round(sizeVertex[0])) : 0;
const aSizeY = sizeVertex ? Math.min(MAX_PACKED_SIZE, Math.round(sizeVertex[1])) : 0;
array.emplaceBack(
@@ -116,7 +116,11 @@ function addVertex(array, anchorX, anchorY, ox, oy, tx, ty, sizeVertex, isSDF: b
tx, // x coordinate of symbol on glyph atlas texture
ty, // y coordinate of symbol on glyph atlas texture
(aSizeX << 1) + (isSDF ? 1 : 0),
- aSizeY
+ aSizeY,
+ pixelOffsetX * 16,
+ pixelOffsetY * 16,
+ minFontScaleX * 256,
+ minFontScaleY * 256
);
}
@@ -593,15 +597,19 @@ class SymbolBucket implements Bucket {
tr = symbol.tr,
bl = symbol.bl,
br = symbol.br,
- tex = symbol.tex;
+ tex = symbol.tex,
+ pixelOffsetTL = symbol.pixelOffsetTL,
+ pixelOffsetBR = symbol.pixelOffsetBR,
+ mfsx = symbol.minFontScaleX,
+ mfsy = symbol.minFontScaleY;
const index = segment.vertexLength;
const y = symbol.glyphOffset[1];
- addVertex(layoutVertexArray, labelAnchor.x, labelAnchor.y, tl.x, y + tl.y, tex.x, tex.y, sizeVertex, symbol.isSDF);
- addVertex(layoutVertexArray, labelAnchor.x, labelAnchor.y, tr.x, y + tr.y, tex.x + tex.w, tex.y, sizeVertex, symbol.isSDF);
- addVertex(layoutVertexArray, labelAnchor.x, labelAnchor.y, bl.x, y + bl.y, tex.x, tex.y + tex.h, sizeVertex, symbol.isSDF);
- addVertex(layoutVertexArray, labelAnchor.x, labelAnchor.y, br.x, y + br.y, tex.x + tex.w, tex.y + tex.h, sizeVertex, symbol.isSDF);
+ addVertex(layoutVertexArray, labelAnchor.x, labelAnchor.y, tl.x, y + tl.y, tex.x, tex.y, sizeVertex, symbol.isSDF, pixelOffsetTL.x, pixelOffsetTL.y, mfsx, mfsy);
+ addVertex(layoutVertexArray, labelAnchor.x, labelAnchor.y, tr.x, y + tr.y, tex.x + tex.w, tex.y, sizeVertex, symbol.isSDF, pixelOffsetBR.x, pixelOffsetTL.y, mfsx, mfsy);
+ addVertex(layoutVertexArray, labelAnchor.x, labelAnchor.y, bl.x, y + bl.y, tex.x, tex.y + tex.h, sizeVertex, symbol.isSDF, pixelOffsetTL.x, pixelOffsetBR.y, mfsx, mfsy);
+ addVertex(layoutVertexArray, labelAnchor.x, labelAnchor.y, br.x, y + br.y, tex.x + tex.w, tex.y + tex.h, sizeVertex, symbol.isSDF, pixelOffsetBR.x, pixelOffsetBR.y, mfsx, mfsy);
addDynamicAttributes(dynamicLayoutVertexArray, labelAnchor, angle);
diff --git a/src/render/image_atlas.js b/src/render/image_atlas.js
index ebf78823be9..e3083183ac5 100644
--- a/src/render/image_atlas.js
+++ b/src/render/image_atlas.js
@@ -22,10 +22,16 @@ export class ImagePosition {
paddedRect: Rect;
pixelRatio: number;
version: number;
+ stretchY: Array> | void;
+ stretchX: Array> | void;
+ content: Array | void;
- constructor(paddedRect: Rect, {pixelRatio, version}: StyleImage) {
+ constructor(paddedRect: Rect, {pixelRatio, version, stretchX, stretchY, content}: StyleImage) {
this.paddedRect = paddedRect;
this.pixelRatio = pixelRatio;
+ this.stretchX = stretchX;
+ this.stretchY = stretchY;
+ this.content = content;
this.version = version;
}
diff --git a/src/render/image_manager.js b/src/render/image_manager.js
index e3b53bfa911..40b9666595a 100644
--- a/src/render/image_manager.js
+++ b/src/render/image_manager.js
@@ -150,6 +150,9 @@ class ImageManager extends Evented {
pixelRatio: image.pixelRatio,
sdf: image.sdf,
version: image.version,
+ stretchX: image.stretchX,
+ stretchY: image.stretchY,
+ content: image.content,
hasRenderCallback: Boolean(image.userImage && image.userImage.render)
};
} else {
diff --git a/src/shaders/symbol_icon.vertex.glsl b/src/shaders/symbol_icon.vertex.glsl
index 056ce24e4d2..16ab111429c 100644
--- a/src/shaders/symbol_icon.vertex.glsl
+++ b/src/shaders/symbol_icon.vertex.glsl
@@ -2,6 +2,7 @@ const float PI = 3.141592653589793;
attribute vec4 a_pos_offset;
attribute vec4 a_data;
+attribute vec4 a_pixeloffset;
attribute vec3 a_projected_pos;
attribute float a_fade_opacity;
@@ -39,6 +40,9 @@ void main() {
vec2 a_size = a_data.zw;
float a_size_min = floor(a_size[0] * 0.5);
+ vec2 a_pxoffset = a_pixeloffset.xy;
+ vec2 a_minFontScale = a_pixeloffset.zw / 256.0;
+
highp float segment_angle = -a_projected_pos[2];
float size;
@@ -81,7 +85,7 @@ void main() {
mat2 rotation_matrix = mat2(angle_cos, -1.0 * angle_sin, angle_sin, angle_cos);
vec4 projected_pos = u_label_plane_matrix * vec4(a_projected_pos.xy, 0.0, 1.0);
- gl_Position = u_coord_matrix * vec4(projected_pos.xy / projected_pos.w + rotation_matrix * (a_offset / 32.0 * fontScale), 0.0, 1.0);
+ gl_Position = u_coord_matrix * vec4(projected_pos.xy / projected_pos.w + rotation_matrix * (a_offset / 32.0 * max(a_minFontScale, fontScale) + a_pxoffset / 16.0), 0.0, 1.0);
v_tex = a_tex / u_texsize;
vec2 fade_opacity = unpack_opacity(a_fade_opacity);
diff --git a/src/shaders/symbol_sdf.vertex.glsl b/src/shaders/symbol_sdf.vertex.glsl
index 6c9ef11f833..71ccf3c81d7 100644
--- a/src/shaders/symbol_sdf.vertex.glsl
+++ b/src/shaders/symbol_sdf.vertex.glsl
@@ -2,6 +2,7 @@ const float PI = 3.141592653589793;
attribute vec4 a_pos_offset;
attribute vec4 a_data;
+attribute vec4 a_pixeloffset;
attribute vec3 a_projected_pos;
attribute float a_fade_opacity;
@@ -51,6 +52,8 @@ void main() {
vec2 a_size = a_data.zw;
float a_size_min = floor(a_size[0] * 0.5);
+ vec2 a_pxoffset = a_pixeloffset.xy;
+
highp float segment_angle = -a_projected_pos[2];
float size;
@@ -100,7 +103,7 @@ void main() {
mat2 rotation_matrix = mat2(angle_cos, -1.0 * angle_sin, angle_sin, angle_cos);
vec4 projected_pos = u_label_plane_matrix * vec4(a_projected_pos.xy, 0.0, 1.0);
- gl_Position = u_coord_matrix * vec4(projected_pos.xy / projected_pos.w + rotation_matrix * (a_offset / 32.0 * fontScale), 0.0, 1.0);
+ gl_Position = u_coord_matrix * vec4(projected_pos.xy / projected_pos.w + rotation_matrix * (a_offset / 32.0 * fontScale + a_pxoffset), 0.0, 1.0);
float gamma_scale = gl_Position.w;
vec2 fade_opacity = unpack_opacity(a_fade_opacity);
diff --git a/src/style/load_sprite.js b/src/style/load_sprite.js
index 11f5eb2e25e..6e8a65443fb 100644
--- a/src/style/load_sprite.js
+++ b/src/style/load_sprite.js
@@ -42,10 +42,10 @@ export default function(baseURL: string,
const result = {};
for (const id in json) {
- const {width, height, x, y, sdf, pixelRatio} = json[id];
+ const {width, height, x, y, sdf, pixelRatio, stretchX, stretchY, content} = json[id];
const data = new RGBAImage({width, height});
RGBAImage.copy(imageData, data, {x, y}, {x: 0, y: 0}, {width, height});
- result[id] = {data, pixelRatio, sdf};
+ result[id] = {data, pixelRatio, sdf, stretchX, stretchY, content};
}
callback(null, result);
diff --git a/src/style/style_image.js b/src/style/style_image.js
index fdb62515301..77d1d3303a1 100644
--- a/src/style/style_image.js
+++ b/src/style/style_image.js
@@ -8,6 +8,9 @@ export type StyleImage = {
data: RGBAImage,
pixelRatio: number,
sdf: boolean,
+ stretchX: any,
+ stretchY: any,
+ content: any,
version: number,
hasRenderCallback?: boolean,
userImage?: StyleImageInterface
diff --git a/src/symbol/quads.js b/src/symbol/quads.js
index c30ea9a931f..2c3d6423167 100644
--- a/src/symbol/quads.js
+++ b/src/symbol/quads.js
@@ -37,12 +37,21 @@ export type SymbolQuad = {
w: number,
h: number
},
+ pixelOffsetTL: Point,
+ pixelOffsetBR: Point,
writingMode: any | void,
glyphOffset: [number, number],
sectionIndex: number,
- isSDF: boolean
+ isSDF: boolean,
+ minFontScaleX: number,
+ minFontScaleY: number
};
+// If you have a 10px icon that isn't perfectly aligned to the pixel grid it will cover 11 actual
+// pixels. The quad needs to be padded to account for this, otherwise they'll look slightly clipped
+// on one edge in some cases.
+const border = IMAGE_PADDING;
+
/**
* Create the quads used for rendering an icon.
* @private
@@ -51,46 +60,158 @@ export function getIconQuads(
shapedIcon: PositionedIcon,
iconRotate: number,
isSDFIcon: boolean): Array {
- const image = shapedIcon.image;
+ const quads = [];
- // If you have a 10px icon that isn't perfectly aligned to the pixel grid it will cover 11 actual
- // pixels. The quad needs to be padded to account for this, otherwise they'll look slightly clipped
- // on one edge in some cases.
- const border = IMAGE_PADDING;
+ const image = shapedIcon.image;
+ const pixelRatio = image.pixelRatio;
+ const imageWidth = image.paddedRect.w - 2 * border;
+ const imageHeight = image.paddedRect.h - 2 * border;
- // Expand the box to respect the 1 pixel border in the atlas image. We're using `image.paddedRect - border`
- // instead of image.displaySize because we only pad with one pixel for retina images as well, and the
- // displaySize uses the logical dimensions, not the physical pixel dimensions.
const iconWidth = shapedIcon.right - shapedIcon.left;
- const expandX = (iconWidth * image.paddedRect.w / (image.paddedRect.w - 2 * border) - iconWidth) / 2;
- const left = shapedIcon.left - expandX;
- const right = shapedIcon.right + expandX;
-
const iconHeight = shapedIcon.bottom - shapedIcon.top;
- const expandY = (iconHeight * image.paddedRect.h / (image.paddedRect.h - 2 * border) - iconHeight) / 2;
- const top = shapedIcon.top - expandY;
- const bottom = shapedIcon.bottom + expandY;
-
- const tl = new Point(left, top);
- const tr = new Point(right, top);
- const br = new Point(right, bottom);
- const bl = new Point(left, bottom);
-
- const angle = iconRotate * Math.PI / 180;
-
- if (angle) {
- const sin = Math.sin(angle),
- cos = Math.cos(angle),
- matrix = [cos, -sin, sin, cos];
-
- tl._matMult(matrix);
- tr._matMult(matrix);
- bl._matMult(matrix);
- br._matMult(matrix);
+
+ const stretchX = image.stretchX || [[0, imageWidth]];
+ const stretchY = image.stretchY || [[0, imageHeight]];
+
+ const reduceRanges = (sum, range) => sum + range[1] - range[0];
+ const stretchWidth = stretchX.reduce(reduceRanges, 0);
+ const stretchHeight = stretchY.reduce(reduceRanges, 0);
+ const fixedWidth = imageWidth - stretchWidth;
+ const fixedHeight = imageHeight - stretchHeight;
+
+ let stretchOffsetX = 0;
+ let stretchContentWidth = stretchWidth;
+ let stretchOffsetY = 0;
+ let stretchContentHeight = stretchHeight;
+ let fixedOffsetX = 0;
+ let fixedContentWidth = fixedWidth;
+ let fixedOffsetY = 0;
+ let fixedContentHeight = fixedHeight;
+
+ if (image.content) {
+ const content = image.content;
+ stretchOffsetX = sumWithinRange(stretchX, 0, content[0]);
+ stretchOffsetY = sumWithinRange(stretchY, 0, content[1]);
+ stretchContentWidth = sumWithinRange(stretchX, content[0], content[2]);
+ stretchContentHeight = sumWithinRange(stretchY, content[1], content[3]);
+ fixedOffsetX = content[0] - stretchOffsetX;
+ fixedOffsetY = content[1] - stretchOffsetY;
+ fixedContentWidth = content[2] - content[0] - stretchContentWidth;
+ fixedContentHeight = content[3] - content[1] - stretchContentHeight;
+ }
+
+ const makeBox = (left, top, right, bottom) => {
+
+ const leftEm = getEmOffset(left.stretch - stretchOffsetX, stretchContentWidth, iconWidth, shapedIcon.left);
+ const leftPx = getPxOffset(left.fixed - fixedOffsetX, fixedContentWidth, left.stretch, stretchWidth);
+
+ const topEm = getEmOffset(top.stretch - stretchOffsetY, stretchContentHeight, iconHeight, shapedIcon.top);
+ const topPx = getPxOffset(top.fixed - fixedOffsetY, fixedContentHeight, top.stretch, stretchHeight);
+
+ const rightEm = getEmOffset(right.stretch - stretchOffsetX, stretchContentWidth, iconWidth, shapedIcon.left);
+ const rightPx = getPxOffset(right.fixed - fixedOffsetX, fixedContentWidth, right.stretch, stretchWidth);
+
+ const bottomEm = getEmOffset(bottom.stretch - stretchOffsetY, stretchContentHeight, iconHeight, shapedIcon.top);
+ const bottomPx = getPxOffset(bottom.fixed - fixedOffsetY, fixedContentHeight, bottom.stretch, stretchHeight);
+
+ const tl = new Point(leftEm, topEm);
+ const tr = new Point(rightEm, topEm);
+ const br = new Point(rightEm, bottomEm);
+ const bl = new Point(leftEm, bottomEm);
+ const pixelOffsetTL = new Point(leftPx / pixelRatio, topPx / pixelRatio);
+ const pixelOffsetBR = new Point(rightPx / pixelRatio, bottomPx / pixelRatio);
+
+ const angle = iconRotate * Math.PI / 180;
+
+ if (angle) {
+ const sin = Math.sin(angle),
+ cos = Math.cos(angle),
+ matrix = [cos, -sin, sin, cos];
+
+ tl._matMult(matrix);
+ tr._matMult(matrix);
+ bl._matMult(matrix);
+ br._matMult(matrix);
+ }
+
+ const x1 = left.stretch + left.fixed;
+ const x2 = right.stretch + right.fixed;
+ const y1 = top.stretch + top.fixed;
+ const y2 = bottom.stretch + bottom.fixed;
+
+ const subRect = {
+ x: image.paddedRect.x + border + x1,
+ y: image.paddedRect.y + border + y1,
+ w: x2 - x1,
+ h: y2 - y1
+ };
+
+ const minFontScaleX = fixedContentWidth / pixelRatio / iconWidth;
+ const minFontScaleY = fixedContentHeight / pixelRatio / iconHeight;
+
+ // Icon quad is padded, so texture coordinates also need to be padded.
+ return {tl, tr, bl, br, tex: subRect, writingMode: undefined, glyphOffset: [0, 0], sectionIndex: 0, pixelOffsetTL, pixelOffsetBR, minFontScaleX, minFontScaleY, isSDF: isSDFIcon};
+ };
+
+ if (!image.stretchX && !image.stretchY) {
+ quads.push(makeBox(
+ {fixed: 0, stretch: -1},
+ {fixed: 0, stretch: -1},
+ {fixed: 0, stretch: imageWidth + 1},
+ {fixed: 0, stretch: imageHeight + 1}));
+ } else {
+ const xCuts = stretchZonesToCuts(stretchX, fixedWidth, stretchWidth);
+ const yCuts = stretchZonesToCuts(stretchY, fixedHeight, stretchHeight);
+
+ for (let xi = 0; xi < xCuts.length - 1; xi++) {
+ const x1 = xCuts[xi];
+ const x2 = xCuts[xi + 1];
+ for (let yi = 0; yi < yCuts.length - 1; yi++) {
+ const y1 = yCuts[yi];
+ const y2 = yCuts[yi + 1];
+ quads.push(makeBox(x1, y1, x2, y2));
+ }
+ }
+ }
+
+ return quads;
+}
+
+function sumWithinRange(ranges, min, max) {
+ let sum = 0;
+ for (const range of ranges) {
+ sum += Math.max(min, Math.min(max, range[1])) - Math.max(min, Math.min(max, range[0]));
+ }
+ return sum;
+}
+
+function stretchZonesToCuts(stretchZones, fixedSize, stretchSize) {
+ const cuts = [{fixed: -border, stretch: 0}];
+
+ for (const [c1, c2] of stretchZones) {
+ const last = cuts[cuts.length - 1];
+ cuts.push({
+ fixed: c1 - last.stretch,
+ stretch: last.stretch
+ });
+ cuts.push({
+ fixed: c1 - last.stretch,
+ stretch: last.stretch + (c2 - c1)
+ });
}
+ cuts.push({
+ fixed: fixedSize + border,
+ stretch: stretchSize
+ });
+ return cuts;
+}
+
+function getEmOffset(stretchOffset, stretchSize, iconSize, iconOffset) {
+ return stretchOffset / stretchSize * iconSize + iconOffset;
+}
- // Icon quad is padded, so texture coordinates also need to be padded.
- return [{tl, tr, bl, br, tex: image.paddedRect, writingMode: undefined, glyphOffset: [0, 0], sectionIndex: 0, isSDF: isSDFIcon}];
+function getPxOffset(fixedOffset, fixedSize, stretchOffset, stretchSize) {
+ return fixedOffset - fixedSize * stretchOffset / stretchSize;
}
/**
@@ -200,7 +321,11 @@ export function getGlyphQuads(anchor: Anchor,
br._matMult(matrix);
}
- quads.push({tl, tr, bl, br, tex: textureRect, writingMode: shaping.writingMode, glyphOffset, sectionIndex: positionedGlyph.sectionIndex, isSDF});
+ const pixelOffsetTL = new Point(0, 0);
+ const pixelOffsetBR = new Point(0, 0);
+ const minFontScaleX = 0;
+ const minFontScaleY = 0;
+ quads.push({tl, tr, bl, br, tex: textureRect, writingMode: shaping.writingMode, glyphOffset, sectionIndex: positionedGlyph.sectionIndex, isSDF, pixelOffsetTL, pixelOffsetBR, minFontScaleX, minFontScaleY});
}
}
diff --git a/src/ui/map.js b/src/ui/map.js
index 8bd87bb11da..ea4a8573e86 100755
--- a/src/ui/map.js
+++ b/src/ui/map.js
@@ -1409,13 +1409,13 @@ class Map extends Camera {
*/
addImage(id: string,
image: HTMLImageElement | ImageData | {width: number, height: number, data: Uint8Array | Uint8ClampedArray} | StyleImageInterface,
- {pixelRatio = 1, sdf = false}: {pixelRatio?: number, sdf?: boolean} = {}) {
+ {pixelRatio = 1, sdf = false, stretchX, stretchY, content}: {pixelRatio?: number, sdf?: boolean, stretchX?: Array>, stretchY?: Array>, content?: Array} = {}) {
const version = 0;
if (image instanceof HTMLImageElement) {
const {width, height, data} = browser.getImageData(image);
- this.style.addImage(id, {data: new RGBAImage({width, height}, data), pixelRatio, sdf, version});
+ this.style.addImage(id, {data: new RGBAImage({width, height}, data), pixelRatio, stretchX, stretchY, content, sdf, version});
} else if (image.width === undefined || image.height === undefined) {
return this.fire(new ErrorEvent(new Error(
'Invalid arguments to map.addImage(). The second argument must be an `HTMLImageElement`, `ImageData`, ' +
@@ -1427,6 +1427,9 @@ class Map extends Camera {
this.style.addImage(id, {
data: new RGBAImage({width, height}, new Uint8Array(data)),
pixelRatio,
+ stretchX,
+ stretchY,
+ content,
sdf,
version,
userImage
diff --git a/test/integration/render-tests/icon-text-fit/stretch-fifteen-part/expected.png b/test/integration/render-tests/icon-text-fit/stretch-fifteen-part/expected.png
new file mode 100644
index 00000000000..e9a97643789
Binary files /dev/null and b/test/integration/render-tests/icon-text-fit/stretch-fifteen-part/expected.png differ
diff --git a/test/integration/render-tests/icon-text-fit/stretch-fifteen-part/style.json b/test/integration/render-tests/icon-text-fit/stretch-fifteen-part/style.json
new file mode 100644
index 00000000000..f06d1490eeb
--- /dev/null
+++ b/test/integration/render-tests/icon-text-fit/stretch-fifteen-part/style.json
@@ -0,0 +1,192 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 200,
+ "height": 150
+ }
+ },
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": "local://geojson/anchors.json"
+ }
+ },
+ "sprite": "local://sprites/stretch",
+ "glyphs": "local://glyphs/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "anchor-center",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "center"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "center",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "fifteen-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "left"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "fifteen-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top-left"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "top-left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "fifteen-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "top",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "fifteen-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top-right"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "top-right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "fifteen-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "right"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "fifteen-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom-left"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "bottom-left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "fifteen-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "bottom",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "fifteen-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom-right"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "bottom-right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "fifteen-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchors",
+ "type": "circle",
+ "source": "geojson",
+ "paint": {
+ "circle-radius": 2,
+ "circle-color": "green",
+ "circle-stroke-color": "white",
+ "circle-stroke-width": 1
+ }
+ }
+ ]
+}
diff --git a/test/integration/render-tests/icon-text-fit/stretch-nine-part-@2x/expected.png b/test/integration/render-tests/icon-text-fit/stretch-nine-part-@2x/expected.png
new file mode 100644
index 00000000000..fe56e368c01
Binary files /dev/null and b/test/integration/render-tests/icon-text-fit/stretch-nine-part-@2x/expected.png differ
diff --git a/test/integration/render-tests/icon-text-fit/stretch-nine-part-@2x/style.json b/test/integration/render-tests/icon-text-fit/stretch-nine-part-@2x/style.json
new file mode 100644
index 00000000000..d7ebfbd8afc
--- /dev/null
+++ b/test/integration/render-tests/icon-text-fit/stretch-nine-part-@2x/style.json
@@ -0,0 +1,192 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 200,
+ "height": 150
+ }
+ },
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": "local://geojson/anchors.json"
+ }
+ },
+ "sprite": "local://sprites/stretch",
+ "glyphs": "local://glyphs/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "anchor-center",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "center"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "center",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-@2x",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "left"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-@2x",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top-left"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "top-left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-@2x",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "top",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-@2x",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top-right"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "top-right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-@2x",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "right"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-@2x",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom-left"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "bottom-left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-@2x",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "bottom",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-@2x",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom-right"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "bottom-right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-@2x",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchors",
+ "type": "circle",
+ "source": "geojson",
+ "paint": {
+ "circle-radius": 2,
+ "circle-color": "green",
+ "circle-stroke-color": "white",
+ "circle-stroke-width": 1
+ }
+ }
+ ]
+}
diff --git a/test/integration/render-tests/icon-text-fit/stretch-nine-part-content/expected.png b/test/integration/render-tests/icon-text-fit/stretch-nine-part-content/expected.png
new file mode 100644
index 00000000000..87b0ca1431c
Binary files /dev/null and b/test/integration/render-tests/icon-text-fit/stretch-nine-part-content/expected.png differ
diff --git a/test/integration/render-tests/icon-text-fit/stretch-nine-part-content/style.json b/test/integration/render-tests/icon-text-fit/stretch-nine-part-content/style.json
new file mode 100644
index 00000000000..f0babee1c58
--- /dev/null
+++ b/test/integration/render-tests/icon-text-fit/stretch-nine-part-content/style.json
@@ -0,0 +1,192 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 200,
+ "height": 150
+ }
+ },
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": "local://geojson/anchors.json"
+ }
+ },
+ "sprite": "local://sprites/stretch",
+ "glyphs": "local://glyphs/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "anchor-center",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "center"],
+ "layout": {
+ "text-field": "ABC",
+ "text-size": 20,
+ "text-anchor": "center",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-content",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "left"],
+ "layout": {
+ "text-field": "ABC",
+ "text-size": 20,
+ "text-anchor": "left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-content",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top-left"],
+ "layout": {
+ "text-field": "ABC",
+ "text-size": 20,
+ "text-anchor": "top-left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-content",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top"],
+ "layout": {
+ "text-field": "ABC",
+ "text-size": 20,
+ "text-anchor": "top",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-content",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top-right"],
+ "layout": {
+ "text-field": "ABC",
+ "text-size": 20,
+ "text-anchor": "top-right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-content",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "right"],
+ "layout": {
+ "text-field": "ABC",
+ "text-size": 20,
+ "text-anchor": "right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-content",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom-left"],
+ "layout": {
+ "text-field": "ABC",
+ "text-size": 20,
+ "text-anchor": "bottom-left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-content",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom"],
+ "layout": {
+ "text-field": "ABC",
+ "text-size": 20,
+ "text-anchor": "bottom",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-content",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom-right"],
+ "layout": {
+ "text-field": "ABC",
+ "text-size": 20,
+ "text-anchor": "bottom-right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part-content",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchors",
+ "type": "circle",
+ "source": "geojson",
+ "paint": {
+ "circle-radius": 2,
+ "circle-color": "green",
+ "circle-stroke-color": "white",
+ "circle-stroke-width": 1
+ }
+ }
+ ]
+}
diff --git a/test/integration/render-tests/icon-text-fit/stretch-nine-part/expected.png b/test/integration/render-tests/icon-text-fit/stretch-nine-part/expected.png
new file mode 100644
index 00000000000..8a035589456
Binary files /dev/null and b/test/integration/render-tests/icon-text-fit/stretch-nine-part/expected.png differ
diff --git a/test/integration/render-tests/icon-text-fit/stretch-nine-part/style.json b/test/integration/render-tests/icon-text-fit/stretch-nine-part/style.json
new file mode 100644
index 00000000000..547817396e9
--- /dev/null
+++ b/test/integration/render-tests/icon-text-fit/stretch-nine-part/style.json
@@ -0,0 +1,192 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 200,
+ "height": 150
+ }
+ },
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": "local://geojson/anchors.json"
+ }
+ },
+ "sprite": "local://sprites/stretch",
+ "glyphs": "local://glyphs/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "anchor-center",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "center"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "center",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "left"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top-left"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "top-left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "top",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top-right"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "top-right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "right"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom-left"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "bottom-left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "bottom",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom-right"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "bottom-right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "nine-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchors",
+ "type": "circle",
+ "source": "geojson",
+ "paint": {
+ "circle-radius": 2,
+ "circle-color": "green",
+ "circle-stroke-color": "white",
+ "circle-stroke-width": 1
+ }
+ }
+ ]
+}
diff --git a/test/integration/render-tests/icon-text-fit/stretch-three-part/expected.png b/test/integration/render-tests/icon-text-fit/stretch-three-part/expected.png
new file mode 100644
index 00000000000..eee29df9f9d
Binary files /dev/null and b/test/integration/render-tests/icon-text-fit/stretch-three-part/expected.png differ
diff --git a/test/integration/render-tests/icon-text-fit/stretch-three-part/style.json b/test/integration/render-tests/icon-text-fit/stretch-three-part/style.json
new file mode 100644
index 00000000000..30cebd99ce9
--- /dev/null
+++ b/test/integration/render-tests/icon-text-fit/stretch-three-part/style.json
@@ -0,0 +1,192 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 200,
+ "height": 150
+ }
+ },
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": "local://geojson/anchors.json"
+ }
+ },
+ "sprite": "local://sprites/stretch",
+ "glyphs": "local://glyphs/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "anchor-center",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "center"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "center",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "left"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top-left"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "top-left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "top",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top-right"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "top-right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "right"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom-left"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "bottom-left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "bottom",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom-right"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "bottom-right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchors",
+ "type": "circle",
+ "source": "geojson",
+ "paint": {
+ "circle-radius": 2,
+ "circle-color": "green",
+ "circle-stroke-color": "white",
+ "circle-stroke-width": 1
+ }
+ }
+ ]
+}
diff --git a/test/integration/render-tests/icon-text-fit/stretch-two-part/expected.png b/test/integration/render-tests/icon-text-fit/stretch-two-part/expected.png
new file mode 100644
index 00000000000..f218020df93
Binary files /dev/null and b/test/integration/render-tests/icon-text-fit/stretch-two-part/expected.png differ
diff --git a/test/integration/render-tests/icon-text-fit/stretch-two-part/style.json b/test/integration/render-tests/icon-text-fit/stretch-two-part/style.json
new file mode 100644
index 00000000000..5c45313726d
--- /dev/null
+++ b/test/integration/render-tests/icon-text-fit/stretch-two-part/style.json
@@ -0,0 +1,192 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 200,
+ "height": 150
+ }
+ },
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": "local://geojson/anchors.json"
+ }
+ },
+ "sprite": "local://sprites/stretch",
+ "glyphs": "local://glyphs/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "anchor-center",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "center"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "center",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "two-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "left"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "two-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top-left"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "top-left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "two-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "top",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "two-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top-right"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "top-right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "two-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "right"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "two-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom-left"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "bottom-left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "two-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "bottom",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "two-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom-right"],
+ "layout": {
+ "text-field": "ASDF",
+ "text-size": 20,
+ "text-anchor": "bottom-right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "two-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchors",
+ "type": "circle",
+ "source": "geojson",
+ "paint": {
+ "circle-radius": 2,
+ "circle-color": "green",
+ "circle-stroke-color": "white",
+ "circle-stroke-width": 1
+ }
+ }
+ ]
+}
diff --git a/test/integration/render-tests/icon-text-fit/stretch-underscale/expected.png b/test/integration/render-tests/icon-text-fit/stretch-underscale/expected.png
new file mode 100644
index 00000000000..f6e32fca763
Binary files /dev/null and b/test/integration/render-tests/icon-text-fit/stretch-underscale/expected.png differ
diff --git a/test/integration/render-tests/icon-text-fit/stretch-underscale/style.json b/test/integration/render-tests/icon-text-fit/stretch-underscale/style.json
new file mode 100644
index 00000000000..a658c727bc6
--- /dev/null
+++ b/test/integration/render-tests/icon-text-fit/stretch-underscale/style.json
@@ -0,0 +1,192 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 200,
+ "height": 150
+ }
+ },
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": "local://geojson/anchors.json"
+ }
+ },
+ "sprite": "local://sprites/stretch",
+ "glyphs": "local://glyphs/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "anchor-center",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "center"],
+ "layout": {
+ "text-field": "A",
+ "text-size": 20,
+ "text-anchor": "center",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "left"],
+ "layout": {
+ "text-field": "A",
+ "text-size": 20,
+ "text-anchor": "left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top-left"],
+ "layout": {
+ "text-field": "A",
+ "text-size": 20,
+ "text-anchor": "top-left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top"],
+ "layout": {
+ "text-field": "A",
+ "text-size": 20,
+ "text-anchor": "top",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-top-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "top-right"],
+ "layout": {
+ "text-field": "A",
+ "text-size": 20,
+ "text-anchor": "top-right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "right"],
+ "layout": {
+ "text-field": "A",
+ "text-size": 20,
+ "text-anchor": "right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom-left",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom-left"],
+ "layout": {
+ "text-field": "A",
+ "text-size": 20,
+ "text-anchor": "bottom-left",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom"],
+ "layout": {
+ "text-field": "A",
+ "text-size": 20,
+ "text-anchor": "bottom",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchor-bottom-right",
+ "type": "symbol",
+ "source": "geojson",
+ "filter": ["==", "anchor", "bottom-right"],
+ "layout": {
+ "text-field": "A",
+ "text-size": 20,
+ "text-anchor": "bottom-right",
+ "text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
+ "text-allow-overlap": true,
+ "text-ignore-placement": true,
+ "icon-image": "three-part",
+ "icon-text-fit": "both",
+ "icon-allow-overlap": true,
+ "icon-ignore-placement": true
+ }
+ },
+ {
+ "id": "anchors",
+ "type": "circle",
+ "source": "geojson",
+ "paint": {
+ "circle-radius": 2,
+ "circle-color": "green",
+ "circle-stroke-color": "white",
+ "circle-stroke-width": 1
+ }
+ }
+ ]
+}
diff --git a/test/integration/sprites/stretch.json b/test/integration/sprites/stretch.json
new file mode 100644
index 00000000000..35b03bec863
--- /dev/null
+++ b/test/integration/sprites/stretch.json
@@ -0,0 +1,61 @@
+{
+ "nine-part": {
+ "x": 0,
+ "y": 0,
+ "width": 40,
+ "height": 40,
+ "pixelRatio": 1,
+ "stretchX": [[10, 30]],
+ "stretchY": [[10, 30]],
+ "sdf": false
+ },
+ "nine-part-content": {
+ "x": 0,
+ "y": 0,
+ "width": 40,
+ "height": 40,
+ "pixelRatio": 2,
+ "stretchX": [[10, 30]],
+ "stretchY": [[10, 30]],
+ "sdf": false,
+ "content": [10, 10, 30, 30]
+ },
+ "fifteen-part": {
+ "x": 50,
+ "y": 0,
+ "width": 70,
+ "height": 40,
+ "pixelRatio": 1,
+ "stretchX": [[10, 30], [40, 60]],
+ "stretchY": [[10, 30]],
+ "sdf": false
+ },
+ "three-part": {
+ "x": 150,
+ "y": 0,
+ "width": 60,
+ "height": 10,
+ "pixelRatio": 1,
+ "stretchX": [[20, 40]],
+ "sdf": false
+ },
+ "two-part": {
+ "x": 250,
+ "y": 0,
+ "width": 30,
+ "height": 10,
+ "pixelRatio": 1,
+ "stretchX": [[10, 20]],
+ "sdf": false
+ },
+ "nine-part-@2x": {
+ "x": 300,
+ "y": 0,
+ "width": 80,
+ "height": 80,
+ "pixelRatio": 2,
+ "stretchX": [[20, 60]],
+ "stretchY": [[20, 60]],
+ "sdf": false
+ }
+}
diff --git a/test/integration/sprites/stretch.png b/test/integration/sprites/stretch.png
new file mode 100644
index 00000000000..36685591c28
Binary files /dev/null and b/test/integration/sprites/stretch.png differ
diff --git a/test/unit/symbol/quads.test.js b/test/unit/symbol/quads.test.js
index f97a7959fd6..3c3135e7b63 100644
--- a/test/unit/symbol/quads.test.js
+++ b/test/unit/symbol/quads.test.js
@@ -24,7 +24,17 @@ test('getIconQuads', (t) => {
writingMode: null,
glyphOffset: [0, 0],
isSDF: true,
- sectionIndex: 0
+ sectionIndex: 0,
+ minFontScaleX: 0,
+ minFontScaleY: 0,
+ pixelOffsetBR: {
+ x: 0,
+ y: 0
+ },
+ pixelOffsetTL: {
+ x: 0,
+ y: 0
+ }
}], 'icon-anchor: center');
t.deepEqual(getIconQuads({
@@ -42,7 +52,17 @@ test('getIconQuads', (t) => {
writingMode: null,
glyphOffset: [0, 0],
isSDF: false,
- sectionIndex: 0
+ sectionIndex: 0,
+ minFontScaleX: 0,
+ minFontScaleY: 0,
+ pixelOffsetBR: {
+ x: 0,
+ y: 0
+ },
+ pixelOffsetTL: {
+ x: 0,
+ y: 0
+ }
}], 'icon-anchor: center icon, icon-scale: 2');
t.deepEqual(getIconQuads({
@@ -60,7 +80,17 @@ test('getIconQuads', (t) => {
writingMode: null,
glyphOffset: [0, 0],
isSDF: false,
- sectionIndex: 0
+ sectionIndex: 0,
+ minFontScaleX: 0,
+ minFontScaleY: 0,
+ pixelOffsetBR: {
+ x: 0,
+ y: 0
+ },
+ pixelOffsetTL: {
+ x: 0,
+ y: 0
+ }
}], 'icon-anchor: top-right');
t.deepEqual(getIconQuads({
@@ -78,7 +108,17 @@ test('getIconQuads', (t) => {
writingMode: null,
glyphOffset: [0, 0],
isSDF: false,
- sectionIndex: 0
+ sectionIndex: 0,
+ minFontScaleX: 0,
+ minFontScaleY: 0,
+ pixelOffsetBR: {
+ x: 0,
+ y: 0
+ },
+ pixelOffsetTL: {
+ x: 0,
+ y: 0
+ }
}], 'icon-text-fit: both');
t.end();
@@ -100,7 +140,17 @@ test('getIconQuads', (t) => {
writingMode: null,
glyphOffset: [0, 0],
isSDF: false,
- sectionIndex: 0
+ sectionIndex: 0,
+ minFontScaleX: 0,
+ minFontScaleY: 0,
+ pixelOffsetBR: {
+ x: 0,
+ y: 0
+ },
+ pixelOffsetTL: {
+ x: 0,
+ y: 0
+ }
}]);
t.end();
});
diff --git a/test/unit/symbol/shaping.test.js b/test/unit/symbol/shaping.test.js
index 33278229af2..6dce3ef180e 100644
--- a/test/unit/symbol/shaping.test.js
+++ b/test/unit/symbol/shaping.test.js
@@ -141,6 +141,9 @@ test('shaping', (t) => {
test('shapeIcon', (t) => {
const imagePosition = new ImagePosition({x: 0, y: 0, w: 22, h: 22}, {pixelRatio: 1, version: 1});
const image = Object.freeze({
+ content: null,
+ stretchX: null,
+ stretchY: null,
paddedRect: Object.freeze({x: 0, y: 0, w: 22, h: 22}),
pixelRatio: 1,
version: 1