Skip to content

Commit 95a4cf5

Browse files
authored
fix collisions for stretchable images with a content area (#9041) (#9044)
1 parent 5255736 commit 95a4cf5

File tree

5 files changed

+193
-2
lines changed

5 files changed

+193
-2
lines changed

src/symbol/collision_feature.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ class CollisionFeature {
4343
let x1 = shaped.left * boxScale - padding;
4444
let x2 = shaped.right * boxScale + padding;
4545

46+
const collisionPadding = shaped.collisionPadding;
47+
if (collisionPadding) {
48+
x1 -= collisionPadding[0] * boxScale;
49+
y1 -= collisionPadding[1] * boxScale;
50+
x2 += collisionPadding[2] * boxScale;
51+
y2 += collisionPadding[3] * boxScale;
52+
}
53+
4654
this.boxStartIndex = collisionBoxArray.length;
4755

4856
if (alignLine) {

src/symbol/shaping.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,8 @@ export type PositionedIcon = {
745745
top: number,
746746
bottom: number,
747747
left: number,
748-
right: number
748+
right: number,
749+
collisionPadding?: [number, number, number, number]
749750
};
750751

751752
function shapeIcon(image: ImagePosition, iconOffset: [number, number], iconAnchor: SymbolAnchor): PositionedIcon {
@@ -769,6 +770,18 @@ function fitIconToText(shapedIcon: PositionedIcon, shapedText: Shaping,
769770

770771
const image = shapedIcon.image;
771772

773+
let collisionPadding;
774+
if (image.content) {
775+
const content = image.content;
776+
const pixelRatio = image.pixelRatio || 1;
777+
collisionPadding = [
778+
content[0] / pixelRatio,
779+
content[1] / pixelRatio,
780+
image.displaySize[0] - content[2] / pixelRatio,
781+
image.displaySize[1] - content[3] / pixelRatio
782+
];
783+
}
784+
772785
// We don't respect the icon-anchor, because icon-text-fit is set. Instead,
773786
// the icon will be centered on the text, then stretched in the given
774787
// dimensions.
@@ -799,5 +812,5 @@ function fitIconToText(shapedIcon: PositionedIcon, shapedText: Shaping,
799812
bottom = top + image.displaySize[1];
800813
}
801814

802-
return {image, top, right, bottom, left};
815+
return {image, top, right, bottom, left, collisionPadding};
803816
}
7.6 KB
Loading
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
{
2+
"version": 8,
3+
"metadata": {
4+
"test": {
5+
"width": 200,
6+
"height": 150
7+
}
8+
},
9+
"sources": {
10+
"geojson": {
11+
"type": "geojson",
12+
"data": "local://geojson/anchors.json"
13+
}
14+
},
15+
"sprite": "local://sprites/stretch",
16+
"glyphs": "local://glyphs/{fontstack}/{range}.pbf",
17+
"layers": [
18+
{
19+
"id": "anchor-center",
20+
"type": "symbol",
21+
"source": "geojson",
22+
"filter": ["==", "anchor", "center"],
23+
"layout": {
24+
"text-field": "ABC",
25+
"text-size": 20,
26+
"text-anchor": "center",
27+
"text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
28+
"icon-image": "nine-part-content",
29+
"icon-text-fit": "both"
30+
}
31+
},
32+
{
33+
"id": "anchor-left",
34+
"type": "symbol",
35+
"source": "geojson",
36+
"filter": ["==", "anchor", "left"],
37+
"layout": {
38+
"text-field": "ABC",
39+
"text-size": 20,
40+
"text-anchor": "left",
41+
"text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
42+
"icon-image": "nine-part-content",
43+
"icon-text-fit": "both"
44+
}
45+
},
46+
{
47+
"id": "anchor-top-left",
48+
"type": "symbol",
49+
"source": "geojson",
50+
"filter": ["==", "anchor", "top-left"],
51+
"layout": {
52+
"text-field": "ABC",
53+
"text-size": 20,
54+
"text-anchor": "top-left",
55+
"text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
56+
"icon-image": "nine-part-content",
57+
"icon-text-fit": "both"
58+
}
59+
},
60+
{
61+
"id": "anchor-top",
62+
"type": "symbol",
63+
"source": "geojson",
64+
"filter": ["==", "anchor", "top"],
65+
"layout": {
66+
"text-field": "ABC",
67+
"text-size": 20,
68+
"text-anchor": "top",
69+
"text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
70+
"icon-image": "nine-part-content",
71+
"icon-text-fit": "both"
72+
}
73+
},
74+
{
75+
"id": "anchor-top-right",
76+
"type": "symbol",
77+
"source": "geojson",
78+
"filter": ["==", "anchor", "top-right"],
79+
"layout": {
80+
"text-field": "ABC",
81+
"text-size": 20,
82+
"text-anchor": "top-right",
83+
"text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
84+
"icon-image": "nine-part-content",
85+
"icon-text-fit": "both"
86+
}
87+
},
88+
{
89+
"id": "anchor-right",
90+
"type": "symbol",
91+
"source": "geojson",
92+
"filter": ["==", "anchor", "right"],
93+
"layout": {
94+
"text-field": "ABC",
95+
"text-size": 20,
96+
"text-anchor": "right",
97+
"text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
98+
"icon-image": "nine-part-content",
99+
"icon-text-fit": "both"
100+
}
101+
},
102+
{
103+
"id": "anchor-bottom-left",
104+
"type": "symbol",
105+
"source": "geojson",
106+
"filter": ["==", "anchor", "bottom-left"],
107+
"layout": {
108+
"text-field": "ABC",
109+
"text-size": 20,
110+
"text-anchor": "bottom-left",
111+
"text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
112+
"icon-image": "nine-part-content",
113+
"icon-text-fit": "both"
114+
}
115+
},
116+
{
117+
"id": "anchor-bottom",
118+
"type": "symbol",
119+
"source": "geojson",
120+
"filter": ["==", "anchor", "bottom"],
121+
"layout": {
122+
"text-field": "ABC",
123+
"text-size": 20,
124+
"text-anchor": "bottom",
125+
"text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
126+
"icon-image": "nine-part-content",
127+
"icon-text-fit": "both"
128+
}
129+
},
130+
{
131+
"id": "anchor-bottom-right",
132+
"type": "symbol",
133+
"source": "geojson",
134+
"filter": ["==", "anchor", "bottom-right"],
135+
"layout": {
136+
"text-field": "ABC",
137+
"text-size": 20,
138+
"text-anchor": "bottom-right",
139+
"text-font": [ "Open Sans Semibold", "Arial Unicode MS Bold" ],
140+
"icon-image": "nine-part-content",
141+
"icon-text-fit": "both"
142+
}
143+
},
144+
{
145+
"id": "anchors",
146+
"type": "circle",
147+
"source": "geojson",
148+
"paint": {
149+
"circle-radius": 2,
150+
"circle-color": "green",
151+
"circle-stroke-color": "white",
152+
"circle-stroke-width": 1
153+
}
154+
}
155+
]
156+
}

test/unit/symbol/shaping.test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ test('fitIconToText', (t) => {
216216
bottom: 10,
217217
left: -10,
218218
right: 10,
219+
collisionPadding: undefined,
219220
image: Object.freeze({
220221
pixelRatio: 1,
221222
displaySize: [ 20, 20 ],
@@ -233,6 +234,7 @@ test('fitIconToText', (t) => {
233234
t.test('icon-text-fit: width', (t) => {
234235
t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'width', [0, 0, 0, 0], [0, 0], 24 / glyphSize), {
235236
image: shapedIcon.image,
237+
collisionPadding: undefined,
236238
top: 0,
237239
right: 20,
238240
bottom: 20,
@@ -241,6 +243,7 @@ test('fitIconToText', (t) => {
241243

242244
t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'width', [0, 0, 0, 0], [3, 7], 24 / glyphSize), {
243245
image: shapedIcon.image,
246+
collisionPadding: undefined,
244247
top: 7,
245248
right: 23,
246249
bottom: 27,
@@ -249,6 +252,7 @@ test('fitIconToText', (t) => {
249252

250253
t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'width', [0, 0, 0, 0], [0, 0], 12 / glyphSize), {
251254
image: shapedIcon.image,
255+
collisionPadding: undefined,
252256
top: -5,
253257
right: 10,
254258
bottom: 15,
@@ -258,6 +262,7 @@ test('fitIconToText', (t) => {
258262
// Ignores padding for top/bottom, since the icon is only stretched to the text's width but not height
259263
t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'width', [ 5, 10, 5, 10 ], [0, 0], 12 / glyphSize), {
260264
image: shapedIcon.image,
265+
collisionPadding: undefined,
261266
top: -5,
262267
right: 20,
263268
bottom: 15,
@@ -270,6 +275,7 @@ test('fitIconToText', (t) => {
270275
t.test('icon-text-fit: height', (t) => {
271276
t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'height', [0, 0, 0, 0], [0, 0], 24 / glyphSize), {
272277
image: shapedIcon.image,
278+
collisionPadding: undefined,
273279
top: -10,
274280
right: -10,
275281
bottom: 30,
@@ -278,6 +284,7 @@ test('fitIconToText', (t) => {
278284

279285
t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'height', [0, 0, 0, 0], [3, 7], 24 / glyphSize), {
280286
image: shapedIcon.image,
287+
collisionPadding: undefined,
281288
top: -3,
282289
right: -7,
283290
bottom: 37,
@@ -286,6 +293,7 @@ test('fitIconToText', (t) => {
286293

287294
t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'height', [0, 0, 0, 0], [0, 0], 12 / glyphSize), {
288295
image: shapedIcon.image,
296+
collisionPadding: undefined,
289297
top: -5,
290298
right: 0,
291299
bottom: 15,
@@ -295,6 +303,7 @@ test('fitIconToText', (t) => {
295303
// Ignores padding for left/right, since the icon is only stretched to the text's height but not width
296304
t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'height', [ 5, 10, 5, 10 ], [0, 0], 12 / glyphSize), {
297305
image: shapedIcon.image,
306+
collisionPadding: undefined,
298307
top: -10,
299308
right: 0,
300309
bottom: 20,
@@ -307,6 +316,7 @@ test('fitIconToText', (t) => {
307316
t.test('icon-text-fit: both', (t) => {
308317
t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'both', [0, 0, 0, 0], [0, 0], 24 / glyphSize), {
309318
image: shapedIcon.image,
319+
collisionPadding: undefined,
310320
top: -10,
311321
right: 20,
312322
bottom: 30,
@@ -315,6 +325,7 @@ test('fitIconToText', (t) => {
315325

316326
t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'both', [0, 0, 0, 0], [3, 7], 24 / glyphSize), {
317327
image: shapedIcon.image,
328+
collisionPadding: undefined,
318329
top: -3,
319330
right: 23,
320331
bottom: 37,
@@ -323,6 +334,7 @@ test('fitIconToText', (t) => {
323334

324335
t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'both', [0, 0, 0, 0], [0, 0], 12 / glyphSize), {
325336
image: shapedIcon.image,
337+
collisionPadding: undefined,
326338
top: -5,
327339
right: 10,
328340
bottom: 15,
@@ -331,6 +343,7 @@ test('fitIconToText', (t) => {
331343

332344
t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'both', [ 5, 10, 5, 10 ], [0, 0], 12 / glyphSize), {
333345
image: shapedIcon.image,
346+
collisionPadding: undefined,
334347
top: -10,
335348
right: 20,
336349
bottom: 20,
@@ -339,6 +352,7 @@ test('fitIconToText', (t) => {
339352

340353
t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'both', [ 0, 5, 10, 15 ], [0, 0], 12 / glyphSize), {
341354
image: shapedIcon.image,
355+
collisionPadding: undefined,
342356
top: -5,
343357
right: 15,
344358
bottom: 25,

0 commit comments

Comments
 (0)