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