Skip to content

Commit

Permalink
colocate icons with text when icon-text-fit is set
Browse files Browse the repository at this point in the history
  • Loading branch information
kkaefer committed Sep 13, 2019
1 parent a7c8dff commit cb7c306
Show file tree
Hide file tree
Showing 23 changed files with 1,975 additions and 49 deletions.
48 changes: 10 additions & 38 deletions src/symbol/quads.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,44 +58,16 @@ export function getIconQuads(anchor: Anchor,
// on one edge in some cases.
const border = 1;

const size = layout.get('text-size').evaluate(feature, {}) / 24;
const stretchX = layout.get('icon-text-fit') === 'width' || layout.get('icon-text-fit') === 'both';
const stretchY = layout.get('icon-text-fit') === 'height' || layout.get('icon-text-fit') === 'both';

let left, right;
if (shapedText && stretchX) {
// Stretched horizontally
const padL = layout.get('icon-text-fit-padding')[3];
const padR = layout.get('icon-text-fit-padding')[1];
const textLeft = shapedText.left * size;
const textRight = shapedText.right * size;
const textWidth = textRight - textLeft;
// Expand the box to respect the 1 pixel border in the atlas image.
const expandX = (textWidth * image.paddedRect.w / (image.paddedRect.w - 2 * border) - textWidth) / 2;
left = textLeft - expandX - padL;
right = textRight + expandX + padR;
} else {
// Normal icon size mode
left = shapedIcon.left - border / image.pixelRatio;
right = shapedIcon.right + border / image.pixelRatio;
}

let top, bottom;
if (shapedText && stretchY) {
// Stretched vertically
const padT = layout.get('icon-text-fit-padding')[0];
const padB = layout.get('icon-text-fit-padding')[2];
const textTop = shapedText.top * size;
const textBottom = shapedText.bottom * size;
const textHeight = textBottom - textTop;
// Expand the box to respect the 1 pixel border in the atlas image.
const expandY = (textHeight * image.paddedRect.h / (image.paddedRect.h - 2 * border) - textHeight) / 2;
top = textTop - expandY - padT;
bottom = textBottom + expandY + padB;
} else {
top = shapedIcon.top - border / image.pixelRatio;
bottom = shapedIcon.bottom + border / image.pixelRatio;
}
// Expand the box to respect the 1 pixel border in the atlas image.
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);
Expand Down
70 changes: 61 additions & 9 deletions src/symbol/shaping.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import ONE_EM from './one_em';

import type {StyleGlyph} from '../style/style_glyph';
import type {ImagePosition} from '../render/image_atlas';
import type SymbolStyleLayer from '../style/style_layer/symbol_style_layer';
import Formatted from '../style-spec/expression/types/formatted';
import type {Feature} from '../style-spec/expression';

const WritingMode = {
horizontal: 1,
Expand Down Expand Up @@ -569,13 +571,63 @@ export type PositionedIcon = {
right: number
};

function shapeIcon(image: ImagePosition, iconOffset: [number, number], iconAnchor: SymbolAnchor): PositionedIcon {
const {horizontalAlign, verticalAlign} = getAnchorAlignment(iconAnchor);
const dx = iconOffset[0];
const dy = iconOffset[1];
const x1 = dx - image.displaySize[0] * horizontalAlign;
const x2 = x1 + image.displaySize[0];
const y1 = dy - image.displaySize[1] * verticalAlign;
const y2 = y1 + image.displaySize[1];
return {image, top: y1, bottom: y2, left: x1, right: x2};
function shapeIcon(image: ImagePosition,
layout: $PropertyType<SymbolStyleLayer, 'layout'>,
shapedText: Shaping | null,
feature: Feature) : PositionedIcon {
const textFit = layout.get('icon-text-fit');
const stretchWidth : boolean = textFit === 'width' || textFit === 'both';
const stretchHeight : boolean = textFit === 'height' || textFit === 'both';

let top, right, bottom, left;
if (shapedText && (stretchWidth || stretchHeight)) {
// We don't respect the icon-anchor, because icon-text-fit is set. Instead, the icon will be
// centered on the text, then stretched in the given dimensions.
const size = layout.get('text-size').evaluate(feature, {}) / 24;
const padding = layout.get('icon-text-fit-padding');

const textLeft = shapedText.left * size;
const textRight = shapedText.right * size;
if (stretchWidth) {
// Stretched horizontally to the text width
left = textLeft - padding[3];
right = textRight + padding[1];
} else {
// Centered on the text
left = (textLeft + textRight - image.displaySize[0]) / 2;
right = left + image.displaySize[0];
}

const textTop = shapedText.top * size;
const textBottom = shapedText.bottom * size;
if (stretchHeight) {
// Stretched vertically to the text height
top = textTop - padding[0];
bottom = textBottom + padding[2];
} else {
// Centered on the text
top = (textTop + textBottom - image.displaySize[1]) / 2;
bottom = top + image.displaySize[1];
}
} else {
// No icon-text-fit. We're going to respect the icon-anchor alignment.
const iconAnchor = layout.get('icon-anchor').evaluate(feature, {});
const { horizontalAlign, verticalAlign } = getAnchorAlignment(iconAnchor);

left = -image.displaySize[0] * horizontalAlign;
right = left + image.displaySize[0];
top = -image.displaySize[1] * verticalAlign;
bottom = top + image.displaySize[1];
}

// Finally, apply the icon offset in all cases.
const [offsetX, offsetY] = layout.get('icon-offset').evaluate(feature, {});

return {
image,
top: top + offsetY,
right: right + offsetX,
bottom: bottom + offsetY,
left: left + offsetX
};
}
5 changes: 3 additions & 2 deletions src/symbol/symbol_layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,9 @@ export function performSymbolLayout(bucket: SymbolBucket,
if (image) {
shapedIcon = shapeIcon(
imagePositions[feature.icon],
layout.get('icon-offset').evaluate(feature, {}),
layout.get('icon-anchor').evaluate(feature, {}));
layout,
getDefaultHorizontalShaping(shapedTextOrientations.horizontal),
feature);
if (bucket.sdfIcons === undefined) {
bucket.sdfIcons = image.sdf;
} else if (bucket.sdfIcons !== image.sdf) {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
{
"version": 8,
"metadata": {
"test": {
"collisionDebug": true,
"width": 200,
"height": 150
}
},
"sources": {
"geojson": {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": { "anchor": "center" },
"geometry": { "type": "Point", "coordinates": [ 0, 0 ] }
}, {
"type": "Feature",
"properties": { "anchor": "left" },
"geometry": { "type": "Point", "coordinates": [ 30, 0 ] }
}, {
"type": "Feature",
"properties": { "anchor": "top-left" },
"geometry": { "type": "Point", "coordinates": [ 20, -15 ] }
}, {
"type": "Feature",
"properties": { "anchor": "top" },
"geometry": { "type": "Point", "coordinates": [ 0, -25 ] }
}, {
"type": "Feature",
"properties": { "anchor": "top-right" },
"geometry": { "type": "Point", "coordinates": [ -20, -15 ] }
}, {
"type": "Feature",
"properties": { "anchor": "right" },
"geometry": { "type": "Point", "coordinates": [ -30, 0 ] }
}, {
"type": "Feature",
"properties": { "anchor": "bottom-left" },
"geometry": { "type": "Point", "coordinates": [ 20, 15 ] }
}, {
"type": "Feature",
"properties": { "anchor": "bottom" },
"geometry": { "type": "Point", "coordinates": [ 0, 25 ] }
}, {
"type": "Feature",
"properties": { "anchor": "bottom-right" },
"geometry": { "type": "Point", "coordinates": [ -20, 15 ] }
}]
}
}
},
"sprite": "local://sprites/icon-text-fit",
"glyphs": "local://glyphs/{fontstack}/{range}.pbf",
"layers": [
{
"id": "anchor-center",
"type": "symbol",
"source": "geojson",
"filter": ["==", "anchor", "center"],
"layout": {
"text-field": "Ügt",
"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": "small-box",
"icon-text-fit": "both",
"icon-allow-overlap": true,
"icon-ignore-placement": true,
"icon-anchor": "top-left"
}
},
{
"id": "anchor-left",
"type": "symbol",
"source": "geojson",
"filter": ["==", "anchor", "left"],
"layout": {
"text-field": "Ügt",
"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": "small-box",
"icon-text-fit": "both",
"icon-allow-overlap": true,
"icon-ignore-placement": true,
"icon-anchor": "top-left"
}
},
{
"id": "anchor-top-left",
"type": "symbol",
"source": "geojson",
"filter": ["==", "anchor", "top-left"],
"layout": {
"text-field": "Ügt",
"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": "small-box",
"icon-text-fit": "both",
"icon-allow-overlap": true,
"icon-ignore-placement": true,
"icon-anchor": "top-left"
}
},
{
"id": "anchor-top",
"type": "symbol",
"source": "geojson",
"filter": ["==", "anchor", "top"],
"layout": {
"text-field": "Ügt",
"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": "small-box",
"icon-text-fit": "both",
"icon-allow-overlap": true,
"icon-ignore-placement": true,
"icon-anchor": "top-left"
}
},
{
"id": "anchor-top-right",
"type": "symbol",
"source": "geojson",
"filter": ["==", "anchor", "top-right"],
"layout": {
"text-field": "Ügt",
"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": "small-box",
"icon-text-fit": "both",
"icon-allow-overlap": true,
"icon-ignore-placement": true,
"icon-anchor": "top-left"
}
},
{
"id": "anchor-right",
"type": "symbol",
"source": "geojson",
"filter": ["==", "anchor", "right"],
"layout": {
"text-field": "Ügt",
"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": "small-box",
"icon-text-fit": "both",
"icon-allow-overlap": true,
"icon-ignore-placement": true,
"icon-anchor": "top-left"
}
},
{
"id": "anchor-bottom-left",
"type": "symbol",
"source": "geojson",
"filter": ["==", "anchor", "bottom-left"],
"layout": {
"text-field": "Ügt",
"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": "small-box",
"icon-text-fit": "both",
"icon-allow-overlap": true,
"icon-ignore-placement": true,
"icon-anchor": "top-left"
}
},
{
"id": "anchor-bottom",
"type": "symbol",
"source": "geojson",
"filter": ["==", "anchor", "bottom"],
"layout": {
"text-field": "Ügt",
"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": "small-box",
"icon-text-fit": "both",
"icon-allow-overlap": true,
"icon-ignore-placement": true,
"icon-anchor": "top-left"
}
},
{
"id": "anchor-bottom-right",
"type": "symbol",
"source": "geojson",
"filter": ["==", "anchor", "bottom-right"],
"layout": {
"text-field": "Ügt",
"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": "small-box",
"icon-text-fit": "both",
"icon-allow-overlap": true,
"icon-ignore-placement": true,
"icon-anchor": "top-left"
}
},
{
"id": "anchors",
"type": "circle",
"source": "geojson",
"paint": {
"circle-radius": 2,
"circle-color": "green",
"circle-stroke-color": "white",
"circle-stroke-width": 1
}
}
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit cb7c306

Please sign in to comment.