maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
361 lines (323 loc) • 11.1 kB
text/typescript
import {type PositionedIcon, type Box, type Shaping, applyTextFit, shapeIcon, fitIconToText} from './shaping';
import {ImagePosition} from '../render/image_atlas';
import {StyleImage, TextFit} from '../style/style_image';
describe('applyTextFit', () => {
describe('applyTextFitHorizontal', () => {
// This set of tests against applyTextFit starts with a 100x20 image with a 5,5,95,15 content box
// that has been scaled to 4x4... resulting in a 14x14 image.
const left = 0;
const top = 0;
const right = 14;
const bottom = 14;
const rectangle = {x: 0, y: 0, w: 100, h: 20};
const content: [number, number, number, number] = [5, 5, 95, 15];
test('applyTextFit: not specified', async () => {
// No change should happen
const styleImage: StyleImage = {
pixelRatio: 1,
version: 1,
sdf: false,
content,
data: undefined!};
const shapedIcon: PositionedIcon = {
left,
top,
right,
bottom,
image: new ImagePosition(rectangle, styleImage),
};
const result: Box = applyTextFit(shapedIcon);
expect(result).toEqual({x1: 0, y1: 0, x2: 14, y2: 14});
});
test('applyTextFit: stretchOrShrink', async () => {
// No change should happen
const styleImage: StyleImage = {
pixelRatio: 1,
version: 1,
sdf: false,
content,
textFitWidth: TextFit.stretchOrShrink,
textFitHeight: TextFit.stretchOrShrink,
data: undefined!};
const shapedIcon: PositionedIcon = {
left,
top,
right,
bottom,
image: new ImagePosition(rectangle, styleImage),
};
const result: Box = applyTextFit(shapedIcon);
expect(result).toEqual({x1: 0, y1: 0, x2: 14, y2: 14});
});
test('applyTextFit: stretchOnly, proportional', async () => {
// Since textFitWidth is stretchOnly, it should be returned to
// the aspect ratio of the content rectangle (9:1) aspect ratio so 126x14.
const styleImage: StyleImage = {
pixelRatio: 1,
version: 1,
sdf: false,
content,
textFitWidth: TextFit.stretchOnly,
textFitHeight: TextFit.proportional,
data: undefined!};
const shapedIcon: PositionedIcon = {
left,
top,
right,
bottom,
image: new ImagePosition(rectangle, styleImage),
};
const result: Box = applyTextFit(shapedIcon);
expect(result).toEqual({x1: 0, y1: 0, x2: 126, y2: 14});
});
});
describe('applyTextFitVertical', () => {
// This set of tests against applyTextFit starts with a 20x100 image with a 5,5,15,95 content box
// that has been scaled to 4x4... resulting in a 14x14 image.
const left = 0;
const top = 0;
const right = 14;
const bottom = 14;
const rectangle = {x: 0, y: 0, w: 20, h: 100};
const content: [number, number, number, number] = [5, 5, 15, 95];
test('applyTextFit: proportional, stretchOnly', async () => {
// Since the rectangle is wider than tall, when it matches based on width (because that is proportional),
// then the height will stretch to match the content so we also get a 14x14 image.
const styleImage: StyleImage = {
pixelRatio: 1,
version: 1,
sdf: false,
content,
textFitWidth: TextFit.proportional,
textFitHeight: TextFit.stretchOnly,
data: undefined!};
const shapedIcon: PositionedIcon = {
left,
top,
right,
bottom,
image: new ImagePosition(rectangle, styleImage),
};
const result: Box = applyTextFit(shapedIcon);
expect(result).toEqual({x1: 0, y1: 0, x2: 14, y2: 126});
});
});
});
describe('shapeIcon', () => {
const imagePosition = new ImagePosition({x: 0, y: 0, w: 22, h: 22}, {pixelRatio: 1, version: 1} as StyleImage);
const image = Object.freeze({
content: undefined,
stretchX: undefined,
stretchY: undefined,
paddedRect: Object.freeze({x: 0, y: 0, w: 22, h: 22}),
pixelRatio: 1,
version: 1
});
test('text-anchor: center', () => {
expect(shapeIcon(imagePosition, [0, 0], 'center')).toEqual({
top: -10,
bottom: 10,
left: -10,
right: 10,
image
});
expect(shapeIcon(imagePosition, [4, 7], 'center')).toEqual({
top: -3,
bottom: 17,
left: -6,
right: 14,
image
});
});
test('text-anchor: left', () => {
expect(shapeIcon(imagePosition, [0, 0], 'left')).toEqual({
top: -10,
bottom: 10,
left: 0,
right: 20,
image
});
expect(shapeIcon(imagePosition, [4, 7], 'left')).toEqual({
top: -3,
bottom: 17,
left: 4,
right: 24,
image
});
});
test('text-anchor: bottom-right', () => {
expect(shapeIcon(imagePosition, [0, 0], 'bottom-right')).toEqual({
top: -20,
bottom: 0,
left: -20,
right: 0,
image
});
expect(shapeIcon(imagePosition, [4, 7], 'bottom-right')).toEqual({
top: -13,
bottom: 7,
left: -16,
right: 4,
image
});
});
});
describe('fitIconToText', () => {
const glyphSize = 24;
const shapedIcon = Object.freeze({
top: -10,
bottom: 10,
left: -10,
right: 10,
collisionPadding: undefined,
image: Object.freeze({
pixelRatio: 1,
displaySize: [20, 20],
paddedRect: Object.freeze({x: 0, y: 0, w: 22, h: 22})
})
}) as PositionedIcon;
const shapedText = Object.freeze({
top: -10,
bottom: 30,
left: -60,
right: 20
}) as Shaping;
test('icon-text-fit: width', () => {
expect(
fitIconToText(shapedIcon, shapedText, 'width', [0, 0, 0, 0], [0, 0], 24 / glyphSize)
).toEqual({
image: shapedIcon.image,
collisionPadding: undefined,
top: 0,
right: 20,
bottom: 20,
left: -60
});
expect(
fitIconToText(shapedIcon, shapedText, 'width', [0, 0, 0, 0], [3, 7], 24 / glyphSize)
).toEqual({
image: shapedIcon.image,
collisionPadding: undefined,
top: 7,
right: 23,
bottom: 27,
left: -57
});
expect(
fitIconToText(shapedIcon, shapedText, 'width', [0, 0, 0, 0], [0, 0], 12 / glyphSize)
).toEqual({
image: shapedIcon.image,
collisionPadding: undefined,
top: -5,
right: 10,
bottom: 15,
left: -30
});
// Ignores padding for top/bottom, since the icon is only stretched to the text's width but not height
expect(
fitIconToText(shapedIcon, shapedText, 'width', [5, 10, 5, 10], [0, 0], 12 / glyphSize)
).toEqual({
image: shapedIcon.image,
collisionPadding: undefined,
top: -5,
right: 20,
bottom: 15,
left: -40
});
});
test('icon-text-fit: height', () => {
expect(
fitIconToText(shapedIcon, shapedText, 'height', [0, 0, 0, 0], [0, 0], 24 / glyphSize)
).toEqual({
image: shapedIcon.image,
collisionPadding: undefined,
top: -10,
right: -10,
bottom: 30,
left: -30
});
expect(
fitIconToText(shapedIcon, shapedText, 'height', [0, 0, 0, 0], [3, 7], 24 / glyphSize)
).toEqual({
image: shapedIcon.image,
collisionPadding: undefined,
top: -3,
right: -7,
bottom: 37,
left: -27
});
expect(
fitIconToText(shapedIcon, shapedText, 'height', [0, 0, 0, 0], [0, 0], 12 / glyphSize)
).toEqual({
image: shapedIcon.image,
collisionPadding: undefined,
top: -5,
right: 0,
bottom: 15,
left: -20
});
// Ignores padding for left/right, since the icon is only stretched to the text's height but not width
expect(
fitIconToText(shapedIcon, shapedText, 'height', [5, 10, 5, 10], [0, 0], 12 / glyphSize)
).toEqual({
image: shapedIcon.image,
collisionPadding: undefined,
top: -10,
right: 0,
bottom: 20,
left: -20
});
});
test('icon-text-fit: both', () => {
expect(
fitIconToText(shapedIcon, shapedText, 'both', [0, 0, 0, 0], [0, 0], 24 / glyphSize)
).toEqual({
image: shapedIcon.image,
collisionPadding: undefined,
top: -10,
right: 20,
bottom: 30,
left: -60
});
expect(
fitIconToText(shapedIcon, shapedText, 'both', [0, 0, 0, 0], [3, 7], 24 / glyphSize)
).toEqual({
image: shapedIcon.image,
collisionPadding: undefined,
top: -3,
right: 23,
bottom: 37,
left: -57
});
expect(
fitIconToText(shapedIcon, shapedText, 'both', [0, 0, 0, 0], [0, 0], 12 / glyphSize)
).toEqual({
image: shapedIcon.image,
collisionPadding: undefined,
top: -5,
right: 10,
bottom: 15,
left: -30
});
expect(
fitIconToText(shapedIcon, shapedText, 'both', [5, 10, 5, 10], [0, 0], 12 / glyphSize)
).toEqual({
image: shapedIcon.image,
collisionPadding: undefined,
top: -10,
right: 20,
bottom: 20,
left: -40
});
expect(
fitIconToText(shapedIcon, shapedText, 'both', [0, 5, 10, 15], [0, 0], 12 / glyphSize)
).toEqual({
image: shapedIcon.image,
collisionPadding: undefined,
top: -5,
right: 15,
bottom: 25,
left: -45
});
});
});