diff --git a/NOTICE.txt b/NOTICE.txt index 4ede43610ca7b..1694193892e16 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -295,7 +295,7 @@ MIT License http://www.opensource.org/licenses/mit-license --- This product includes code that is adapted from mapbox-gl-js, which is available under a "BSD-3-Clause" license. -https://github.com/mapbox/mapbox-gl-js/blob/master/src/util/image.js +https://github.com/mapbox/mapbox-gl-js/blob/v1.13.2/src/util/image.js Copyright (c) 2016, Mapbox diff --git a/package.json b/package.json index bd38699b6966a..9f7a2482875a3 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,6 @@ "@elastic/ems-client": "8.0.0", "@elastic/eui": "41.0.0", "@elastic/filesaver": "1.1.2", - "@elastic/maki": "6.3.0", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.1", "@elastic/react-search-ui": "^1.6.0", @@ -196,8 +195,10 @@ "archiver": "^5.2.0", "axios": "^0.21.1", "base64-js": "^1.3.1", + "bitmap-sdf": "^1.0.3", "brace": "0.11.1", "broadcast-channel": "^4.7.0", + "canvg": "^3.0.9", "chalk": "^4.1.0", "cheerio": "^1.0.0-rc.10", "chokidar": "^3.4.3", diff --git a/x-pack/plugins/maps/public/classes/layers/ems_vector_tile_layer/ems_vector_tile_layer.tsx b/x-pack/plugins/maps/public/classes/layers/ems_vector_tile_layer/ems_vector_tile_layer.tsx index 8f7471f255a5d..c6d594617c448 100644 --- a/x-pack/plugins/maps/public/classes/layers/ems_vector_tile_layer/ems_vector_tile_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/ems_vector_tile_layer/ems_vector_tile_layer.tsx @@ -7,15 +7,13 @@ import type { Map as MbMap, Layer as MbLayer, Style as MbStyle } from '@kbn/mapbox-gl'; import _ from 'lodash'; +// @ts-expect-error +import { RGBAImage } from './image_utils'; import { AbstractLayer } from '../layer'; import { SOURCE_DATA_REQUEST_ID, LAYER_TYPE, LAYER_STYLE_TYPE } from '../../../../common/constants'; import { LayerDescriptor } from '../../../../common/descriptor_types'; import { DataRequest } from '../../util/data_request'; import { isRetina } from '../../../util'; -import { - addSpriteSheetToMapFromImageData, - loadSpriteSheetImageData, -} from '../../../connected_components/mb_map/utils'; import { DataRequestContext } from '../../../actions'; import { EMSTMSSource } from '../../sources/ems_tms_source'; import { TileStyle } from '../../styles/tile/tile_style'; @@ -118,7 +116,7 @@ export class EmsVectorTileLayer extends AbstractLayer { startLoading(SOURCE_DATA_REQUEST_ID, requestToken, nextMeta); const styleAndSprites = await this.getSource().getVectorStyleSheetAndSpriteMeta(isRetina()); const spriteSheetImageData = styleAndSprites.spriteMeta - ? await loadSpriteSheetImageData(styleAndSprites.spriteMeta.png) + ? await this._loadSpriteSheetImageData(styleAndSprites.spriteMeta.png) : undefined; const data = { ...styleAndSprites, @@ -210,6 +208,60 @@ export class EmsVectorTileLayer extends AbstractLayer { }); } + _getImageData(img: HTMLImageElement) { + const canvas = window.document.createElement('canvas'); + const context = canvas.getContext('2d'); + if (!context) { + throw new Error('failed to create canvas 2d context'); + } + canvas.width = img.width; + canvas.height = img.height; + context.drawImage(img, 0, 0, img.width, img.height); + return context.getImageData(0, 0, img.width, img.height); + } + + _isCrossOriginUrl(url: string) { + const a = window.document.createElement('a'); + a.href = url; + return ( + a.protocol !== window.document.location.protocol || + a.host !== window.document.location.host || + a.port !== window.document.location.port + ); + } + + _loadSpriteSheetImageData(imgUrl: string): Promise { + return new Promise((resolve, reject) => { + const image = new Image(); + if (this._isCrossOriginUrl(imgUrl)) { + image.crossOrigin = 'Anonymous'; + } + image.onload = (event) => { + resolve(this._getImageData(image)); + }; + image.onerror = (e) => { + reject(e); + }; + image.src = imgUrl; + }); + } + + _addSpriteSheetToMapFromImageData(json: EmsSpriteSheet, imgData: ImageData, mbMap: MbMap) { + for (const imageId in json) { + if (!(json.hasOwnProperty(imageId) && !mbMap.hasImage(imageId))) { + continue; + } + const { width, height, x, y, sdf, pixelRatio } = json[imageId]; + if (typeof width !== 'number' || typeof height !== 'number') { + continue; + } + + const data = new RGBAImage({ width, height }); + RGBAImage.copy(imgData, data, { x, y }, { x: 0, y: 0 }, { width, height }); + mbMap.addImage(imageId, data, { pixelRatio, sdf }); + } + } + syncLayerWithMB(mbMap: MbMap) { const vectorStyle = this._getVectorStyle(); if (!vectorStyle) { @@ -252,7 +304,7 @@ export class EmsVectorTileLayer extends AbstractLayer { if (!imageData) { return; } - addSpriteSheetToMapFromImageData(newJson, imageData, mbMap); + this._addSpriteSheetToMapFromImageData(newJson, imageData, mbMap); // sync layers const layers = vectorStyle.layers ? vectorStyle.layers : []; diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/image_utils.js b/x-pack/plugins/maps/public/classes/layers/ems_vector_tile_layer/image_utils.js similarity index 98% rename from x-pack/plugins/maps/public/connected_components/mb_map/image_utils.js rename to x-pack/plugins/maps/public/classes/layers/ems_vector_tile_layer/image_utils.js index 3b19b474d699b..b907bea3cbad7 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/image_utils.js +++ b/x-pack/plugins/maps/public/classes/layers/ems_vector_tile_layer/image_utils.js @@ -8,7 +8,7 @@ /* @notice * This product includes code that is adapted from mapbox-gl-js, which is * available under a "BSD-3-Clause" license. - * https://github.com/mapbox/mapbox-gl-js/blob/master/src/util/image.js + * https://github.com/mapbox/mapbox-gl-js/blob/v1.13.2/src/util/image.js * * Copyright (c) 2016, Mapbox * diff --git a/x-pack/plugins/maps/public/classes/styles/vector/maki_icons.ts b/x-pack/plugins/maps/public/classes/styles/vector/maki_icons.ts new file mode 100644 index 0000000000000..3a5e78fc2ea73 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/maki_icons.ts @@ -0,0 +1,729 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const MAKI_ICONS = { + aerialway: { + label: 'Aerialway', + svg: '\n\n \n', + }, + airfield: { + label: 'Airfield', + svg: '\n\n \n', + }, + airport: { + label: 'Airport', + svg: '\n\n \n', + }, + 'alcohol-shop': { + label: 'Alcohol shop', + svg: '\n\n \n', + }, + 'american-football': { + label: 'American football', + svg: '\n\n \n', + }, + 'amusement-park': { + label: 'Amusement park', + svg: '\n\n \n', + }, + aquarium: { + label: 'Aquarium', + svg: '\n\n \n', + }, + 'arrow-es': { + label: 'Arrow', + svg: '\n\n \n', + }, + 'art-gallery': { + label: 'Art gallery', + svg: '\n\n \n', + }, + attraction: { + label: 'Attraction', + svg: '\n\n \n', + }, + bakery: { + label: 'Bakery', + svg: '\n\n \n \n', + }, + bank: { + label: 'Bank', + svg: '\n\n \n', + }, + bar: { + label: 'Bar', + svg: '\n\n \n', + }, + barrier: { + label: 'Barrier', + svg: '\n\n \n', + }, + baseball: { + label: 'Baseball', + svg: '\n\n \n', + }, + basketball: { + label: 'Basketball', + svg: '\n\n \n', + }, + bbq: { + label: 'BBQ', + svg: '\n\n \n', + }, + beach: { + label: 'Beach', + svg: '\n\n \n', + }, + beer: { + label: 'Beer', + svg: '\n\n \n', + }, + bicycle: { + label: 'Bicycle', + svg: '\n\n \n', + }, + 'bicycle-share': { + label: 'Bicycle share', + svg: '\n\n \n', + }, + 'blood-bank': { + label: 'Blood bank', + svg: '\n\n \n', + }, + 'boat-es': { + label: 'Boat', + svg: '\n\n \n', + }, + 'bowling-alley': { + label: 'Bowling alley', + svg: '\n\n \n', + }, + bridge: { + label: 'Bridge', + svg: '\n\n \n', + }, + building: { + label: 'Building', + svg: '\n\n \n', + }, + 'building-alt1': { + label: 'Building 2', + svg: '\n\n \n', + }, + bus: { + label: 'Bus', + svg: '\n\n \n', + }, + cafe: { + label: 'Cafe', + svg: '\n\n \n', + }, + campsite: { + label: 'Campsite', + svg: '\n\n \n', + }, + car: { + label: 'Car', + svg: '\n\n \n', + }, + 'car-top-es': { + label: 'Car 2', + svg: '\n\n \n', + }, + 'car-rental': { + label: 'Car rental', + svg: '\n\n \n \n \n \n', + }, + 'car-repair': { + label: 'Car repair', + svg: '\n\n \n \n \n \n', + }, + casino: { + label: 'Casino', + svg: '\n\n \n', + }, + castle: { + label: 'Castle', + svg: '\n\n \n', + }, + cemetery: { + label: 'Cemetery', + svg: '\n\n \n', + }, + 'charging-station': { + label: 'Charging station', + svg: '\n\n \n', + }, + cinema: { + label: 'Cinema', + svg: '\n\n \n', + }, + circle: { + label: 'Circle', + svg: '\n\n \n', + }, + 'circle-stroked': { + label: 'Circle stroked', + svg: '\n\n \n', + }, + city: { + label: 'City', + svg: '\n\n \n', + }, + 'clothing-store': { + label: 'Clothing store', + svg: '\n\n \n', + }, + college: { + label: 'College', + svg: '\n\n \n', + }, + commercial: { + label: 'Commercial', + svg: '\n\n \n', + }, + 'communications-tower': { + label: 'Communications tower', + svg: '\n\n \n \n \n', + }, + confectionery: { + label: 'Confectionery', + svg: '\n\n \n \n \n', + }, + convenience: { + label: 'Convenience', + svg: '\n\n \n \n \n', + }, + cricket: { + label: 'Cricket', + svg: '\n\n \n', + }, + cross: { + label: 'Cross', + svg: '\n\n \n', + }, + dam: { + label: 'Dam', + svg: '\n\n \n', + }, + danger: { + label: 'Danger', + svg: '\n\n \n', + }, + defibrillator: { + label: 'Defibrillator', + svg: '\n\n \n', + }, + dentist: { + label: 'Dentist', + svg: '\n\n \n', + }, + doctor: { + label: 'Doctor', + svg: '\n\n \n', + }, + 'dog-park': { + label: 'Dog park', + svg: '\n\n \n \n \n', + }, + 'drinking-water': { + label: 'Drinking water', + svg: '\n\n \n \n', + }, + embassy: { + label: 'Embassy', + svg: '\n\n \n', + }, + 'emergency-phone': { + label: 'Emergency phone', + svg: '\n\n \n', + }, + entrance: { + label: 'Entrance', + svg: '\n\n \n \n', + }, + 'entrance-alt1': { + label: 'Entrance 2', + svg: '\n\n \n', + }, + farm: { + label: 'Farm', + svg: '\n\n \n', + }, + 'fast-food': { + label: 'Fast food', + svg: '\n\n \n', + }, + fence: { + label: 'Fence', + svg: '\n\n \n', + }, + ferry: { + label: 'Ferry', + svg: '\n\n \n', + }, + 'fire-station': { + label: 'Fire station', + svg: '\n\n \n', + }, + 'fitness-centre': { + label: 'Fitness centre', + svg: '\n\n \n', + }, + florist: { + label: 'Florist', + svg: '\n\n \n', + }, + fuel: { + label: 'Fuel', + svg: '\n\n \n', + }, + furniture: { + label: 'Furniture', + svg: '\n\n \n \n \n \n', + }, + gaming: { + label: 'Gaming', + svg: '\n\n \n', + }, + garden: { + label: 'Garden', + svg: '\n\n \n', + }, + 'garden-centre': { + label: 'Garden centre', + svg: '\n\n \n', + }, + gift: { + label: 'Gift', + svg: '\n\n \n', + }, + globe: { + label: 'Globe', + svg: '\n\n \n \n \n \n \n \n \n \n', + }, + golf: { + label: 'Golf', + svg: '\n\n \n', + }, + grocery: { + label: 'Grocery', + svg: '\n\n \n \n \n', + }, + hairdresser: { + label: 'Hairdresser', + svg: '\n\n \n', + }, + harbor: { + label: 'Harbor', + svg: '\n\n \n', + }, + hardware: { + label: 'Hardware', + svg: '\n\n \n', + }, + heart: { + label: 'Heart', + svg: '\n\n \n', + }, + heliport: { + label: 'Heliport', + svg: '\n\n \n', + }, + home: { + label: 'Home', + svg: '\n\n \n', + }, + 'horse-riding': { + label: 'Horse riding', + svg: '\n\n \n', + }, + hospital: { + label: 'Hospital', + svg: '\n\n \n', + }, + 'ice-cream': { + label: 'Ice cream', + svg: '\n\n \n \n', + }, + industry: { + label: 'Industry', + svg: '\n\n \n', + }, + information: { + label: 'Information', + svg: '\n\n \n', + }, + 'jewelry-store': { + label: 'Jewelry store', + svg: '\n\n \n', + }, + karaoke: { + label: 'Karaoke', + svg: '\n\n \n \n \n \n', + }, + landmark: { + label: 'Landmark', + svg: '\n\n \n', + }, + landuse: { + label: 'Landuse', + svg: '\n\n \n', + }, + laundry: { + label: 'Laundry', + svg: '\n\n \n', + }, + library: { + label: 'Library', + svg: '\n\n \n', + }, + lighthouse: { + label: 'Lighthouse', + svg: '\n\n \n', + }, + lodging: { + label: 'Lodging', + svg: '\n\n \n', + }, + logging: { + label: 'Logging', + svg: '\n\n \n', + }, + marker: { + label: 'Marker', + svg: '\n\n \n', + }, + 'marker-stroked': { + label: 'Marker stroked', + svg: '\n\n \n', + }, + 'mobile-phone': { + label: 'Mobile phone', + svg: '\n\n \n', + }, + monument: { + label: 'Monument', + svg: '\n\n \n', + }, + mountain: { + label: 'Mountain', + svg: '\n\n \n', + }, + museum: { + label: 'Museum', + svg: '\n\n \n', + }, + music: { + label: 'Music', + svg: '\n\n \n', + }, + natural: { + label: 'Natural', + svg: '\n\n \n', + }, + 'oil-rig-es': { + label: 'Oil rig', + svg: '\n\n \n', + }, + optician: { + label: 'Optician', + svg: '\n\n \n', + }, + paint: { + label: 'Paint', + svg: '\n\n \n', + }, + park: { + label: 'Park', + svg: '\n\n \n', + }, + 'park-alt1': { + label: 'Park 2', + svg: '\n\n \n', + }, + parking: { + label: 'Parking', + svg: '\n\n \n', + }, + 'parking-garage': { + label: 'Parking garage', + svg: '\n\n \n', + }, + pharmacy: { + label: 'Pharmacy', + svg: '\n\n \n', + }, + 'picnic-site': { + label: 'Picnic site', + svg: '\n\n \n', + }, + pitch: { + label: 'Pitch', + svg: '\n\n \n', + }, + 'place-of-worship': { + label: 'Place of worship', + svg: '\n\n \n', + }, + playground: { + label: 'Playground', + svg: '\n\n \n', + }, + police: { + label: 'Police', + svg: '\n\n \n', + }, + post: { + label: 'Post', + svg: '\n\n \n', + }, + prison: { + label: 'Prison', + svg: '\n\n \n', + }, + rail: { + label: 'Rail', + svg: '\n\n \n', + }, + 'rail-light': { + label: 'Rail light', + svg: '\n\n \n', + }, + 'rail-metro': { + label: 'Rail metro', + svg: '\n\n \n', + }, + 'ranger-station': { + label: 'Ranger station', + svg: '\n\n \n', + }, + recycling: { + label: 'Recycling', + svg: '\n\n \n', + }, + 'religious-buddhist': { + label: 'Religious buddhist', + svg: '\n\n \n', + }, + 'religious-christian': { + label: 'Religious christian', + svg: '\n\n \n', + }, + 'religious-jewish': { + label: 'Religious jewish', + svg: '\n\n \n', + }, + 'religious-muslim': { + label: 'Religious muslim', + svg: '\n\n \n', + }, + 'residential-community': { + label: 'Residential community', + svg: '\n\n \n', + }, + restaurant: { + label: 'Restaurant', + svg: '\n\n \n', + }, + 'restaurant-noodle': { + label: 'Restaurant noodle', + svg: '\n\n \n \n \n', + }, + 'restaurant-pizza': { + label: 'Restaurant pizza', + svg: '\n\n \n \n \n', + }, + 'restaurant-seafood': { + label: 'Restaurant seafood', + svg: '\n\n \n \n \n', + }, + roadblock: { + label: 'Roadblock', + svg: '\n\n \n', + }, + rocket: { + label: 'Rocket', + svg: '\n\n \n', + }, + school: { + label: 'School', + svg: '\n\n \n', + }, + scooter: { + label: 'Scooter', + svg: '\n\n \n', + }, + shelter: { + label: 'Shelter', + svg: '\n\n \n', + }, + shoe: { + label: 'Shoe', + svg: '\n\n \n \n \n \n', + }, + shop: { + label: 'Shop', + svg: '\n\n \n', + }, + skateboard: { + label: 'Skateboard', + svg: '\n\n \n', + }, + skiing: { + label: 'Skiing', + svg: '\n\n \n', + }, + slaughterhouse: { + label: 'Slaughterhouse', + svg: '\n\n \n', + }, + slipway: { + label: 'Slipway', + svg: '\n\n \n \n \n', + }, + snowmobile: { + label: 'Snowmobile', + svg: '\n\n \n', + }, + soccer: { + label: 'Soccer', + svg: '\n\n \n', + }, + square: { + label: 'Square', + svg: '\n\n \n', + }, + 'square-stroked': { + label: 'Square stroked', + svg: '\n\n \n', + }, + stadium: { + label: 'Stadium', + svg: '\n\n \n', + }, + star: { + label: 'Star', + svg: '\n\n \n', + }, + 'star-stroked': { + label: 'Star stroked', + svg: '\n\n \n', + }, + suitcase: { + label: 'Suitcase', + svg: '\n\n \n', + }, + sushi: { + label: 'Sushi', + svg: '\n\n \n', + }, + swimming: { + label: 'Swimming', + svg: '\n\n \n', + }, + 'table-tennis': { + label: 'Table tennis', + svg: '\n\n \n', + }, + teahouse: { + label: 'Teahouse', + svg: '\n\n \n', + }, + telephone: { + label: 'Telephone', + svg: '\n\n \n', + }, + tennis: { + label: 'Tennis', + svg: '\n\n \n', + }, + theatre: { + label: 'Theatre', + svg: '\n\n \n', + }, + toilet: { + label: 'Toilet', + svg: '\n\n \n', + }, + town: { + label: 'Town', + svg: '\n\n \n', + }, + 'town-hall': { + label: 'Town hall', + svg: '\n\n \n', + }, + triangle: { + label: 'Triangle', + svg: '\n\n \n', + }, + 'triangle-stroked': { + label: 'Triangle stroked', + svg: '\n\n \n', + }, + veterinary: { + label: 'Veterinary', + svg: '\n\n \n \n \n \n \n', + }, + viewpoint: { + label: 'Viewpoint', + svg: '\n\n \n', + }, + village: { + label: 'Village', + svg: '\n\n \n', + }, + volcano: { + label: 'Volcano', + svg: '\n\n \n', + }, + volleyball: { + label: 'Volleyball', + svg: '\n\n \n', + }, + warehouse: { + label: 'Warehouse', + svg: '\n\n \n', + }, + 'waste-basket': { + label: 'Waste basket', + svg: '\n\n \n', + }, + watch: { + label: 'Watch', + svg: '\n\n \n \n', + }, + water: { + label: 'Water', + svg: '\n\n \n', + }, + waterfall: { + label: 'Waterfall', + svg: '\n\n \n', + }, + watermill: { + label: 'Watermill', + svg: '\n\n \n', + }, + wetland: { + label: 'Wetland', + svg: '\n\n \n', + }, + wheelchair: { + label: 'Wheelchair', + svg: '\n\n \n', + }, + windmill: { + label: 'Windmill', + svg: '\n\n \n', + }, + zoo: { + label: 'Zoo', + svg: '\n\n \n', + }, +}; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx index 08ad93c5b8cb7..b3e5293d6860f 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx @@ -73,14 +73,14 @@ describe('get mapbox icon-image expression (via internal _getMbIconImageExpressi const iconStyle = makeProperty({ iconPaletteId: 'filledShapes', }); - expect(iconStyle._getMbIconImageExpression(15)).toEqual([ + expect(iconStyle._getMbIconImageExpression()).toEqual([ 'match', ['to-string', ['get', 'foobar']], 'US', - 'circle-15', + 'circle', 'CN', - 'marker-15', - 'square-15', + 'marker', + 'square', ]); }); @@ -92,12 +92,12 @@ describe('get mapbox icon-image expression (via internal _getMbIconImageExpressi { stop: 'MX', icon: 'marker' }, ], }); - expect(iconStyle._getMbIconImageExpression(15)).toEqual([ + expect(iconStyle._getMbIconImageExpression()).toEqual([ 'match', ['to-string', ['get', 'foobar']], 'MX', - 'marker-15', - 'circle-15', + 'marker', + 'circle', ]); }); }); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.tsx index b5d5e90efa45f..77510f9c82d0b 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.tsx @@ -10,8 +10,11 @@ import React from 'react'; import { EuiTextColor } from '@elastic/eui'; import type { Map as MbMap } from '@kbn/mapbox-gl'; import { DynamicStyleProperty } from './dynamic_style_property'; -// @ts-expect-error -import { getIconPalette, getMakiIconId, getMakiSymbolAnchor } from '../symbol_utils'; +import { + getIconPalette, + getMakiSymbolAnchor, + // @ts-expect-error +} from '../symbol_utils'; import { BreakedLegend } from '../components/legend/breaked_legend'; import { getOtherCategoryLabel, assignCategoriesToPalette } from '../style_util'; import { LegendProps } from './style_property'; @@ -31,13 +34,9 @@ export class DynamicIconProperty extends DynamicStyleProperty { mbStops.push(`${stop}`); - mbStops.push(getMakiIconId(style, iconPixelSize)); + mbStops.push(style); }); if (fallbackSymbolId) { - mbStops.push(getMakiIconId(fallbackSymbolId, iconPixelSize)); // last item is fallback style for anything that does not match provided stops + mbStops.push(fallbackSymbolId); // last item is fallback style for anything that does not match provided stops } return ['match', ['to-string', ['get', this.getMbFieldName()]], ...mbStops]; } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx index e76e9e936faec..5ea99e64e8626 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx @@ -11,9 +11,7 @@ import { DynamicStyleProperty } from './dynamic_style_property'; import { OrdinalLegend } from '../components/legend/ordinal_legend'; import { makeMbClampedNumberExpression } from '../style_util'; import { - HALF_LARGE_MAKI_ICON_SIZE, - LARGE_MAKI_ICON_SIZE, - SMALL_MAKI_ICON_SIZE, + HALF_MAKI_ICON_SIZE, // @ts-expect-error } from '../symbol_utils'; import { FieldFormatter, MB_LOOKUP_FUNCTION, VECTOR_STYLES } from '../../../../../common/constants'; @@ -55,16 +53,9 @@ export class DynamicSizeProperty extends DynamicStyleProperty= HALF_LARGE_MAKI_ICON_SIZE - ? LARGE_MAKI_ICON_SIZE - : SMALL_MAKI_ICON_SIZE; - } - syncIconSizeWithMb(symbolLayerId: string, mbMap: MbMap) { const rangeFieldMeta = this.getRangeFieldMeta(); if (this._isSizeDynamicConfigComplete() && rangeFieldMeta) { - const halfIconPixels = this.getIconPixelSize() / 2; const targetName = this.getMbFieldName(); // Using property state instead of feature-state because layout properties do not support feature-state mbMap.setLayoutProperty(symbolLayerId, 'icon-size', [ @@ -78,9 +69,9 @@ export class DynamicSizeProperty extends DynamicStyleProperty { - syncIconWithMb(symbolLayerId: string, mbMap: MbMap, iconPixelSize: number) { + syncIconWithMb(symbolLayerId: string, mbMap: MbMap) { const symbolId = this._options.value; mbMap.setLayoutProperty(symbolLayerId, 'icon-anchor', getMakiSymbolAnchor(symbolId)); - mbMap.setLayoutProperty(symbolLayerId, 'icon-image', getMakiIconId(symbolId, iconPixelSize)); + mbMap.setLayoutProperty(symbolLayerId, 'icon-image', symbolId); } } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/static_size_property.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/static_size_property.ts index de71d07aa7167..771e0f8f33a0c 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/static_size_property.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/static_size_property.ts @@ -9,9 +9,7 @@ import type { Map as MbMap } from '@kbn/mapbox-gl'; import { StaticStyleProperty } from './static_style_property'; import { VECTOR_STYLES } from '../../../../../common/constants'; import { - HALF_LARGE_MAKI_ICON_SIZE, - LARGE_MAKI_ICON_SIZE, - SMALL_MAKI_ICON_SIZE, + HALF_MAKI_ICON_SIZE, // @ts-expect-error } from '../symbol_utils'; import { SizeStaticOptions } from '../../../../../common/descriptor_types'; @@ -29,15 +27,8 @@ export class StaticSizeProperty extends StaticStyleProperty { mbMap.setPaintProperty(mbLayerId, 'icon-halo-width', this._options.size); } - getIconPixelSize() { - return this._options.size >= HALF_LARGE_MAKI_ICON_SIZE - ? LARGE_MAKI_ICON_SIZE - : SMALL_MAKI_ICON_SIZE; - } - syncIconSizeWithMb(symbolLayerId: string, mbMap: MbMap) { - const halfIconPixels = this.getIconPixelSize() / 2; - mbMap.setLayoutProperty(symbolLayerId, 'icon-size', this._options.size / halfIconPixels); + mbMap.setLayoutProperty(symbolLayerId, 'icon-size', this._options.size / HALF_MAKI_ICON_SIZE); } syncCircleStrokeWidthWithMb(mbLayerId: string, mbMap: MbMap, hasNoRadius: boolean) { diff --git a/x-pack/plugins/maps/public/classes/styles/vector/symbol_utils.js b/x-pack/plugins/maps/public/classes/styles/vector/symbol_utils.js index 30cc93d65722b..07ac77dc0cb78 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/symbol_utils.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/symbol_utils.js @@ -6,46 +6,76 @@ */ import React from 'react'; -import maki from '@elastic/maki'; import xml2js from 'xml2js'; +import { Canvg } from 'canvg'; +import calcSDF from 'bitmap-sdf'; import { parseXmlString } from '../../../../common/parse_xml_string'; import { SymbolIcon } from './components/legend/symbol_icon'; import { getIsDarkMode } from '../../../kibana_services'; +import { MAKI_ICONS } from './maki_icons'; -export const LARGE_MAKI_ICON_SIZE = 15; -const LARGE_MAKI_ICON_SIZE_AS_STRING = LARGE_MAKI_ICON_SIZE.toString(); -export const SMALL_MAKI_ICON_SIZE = 11; -export const HALF_LARGE_MAKI_ICON_SIZE = Math.ceil(LARGE_MAKI_ICON_SIZE); +const MAKI_ICON_SIZE = 16; +export const HALF_MAKI_ICON_SIZE = MAKI_ICON_SIZE / 2; -export const SYMBOLS = {}; -maki.svgArray.forEach((svgString) => { - const ID_FRAG = 'id="'; - const index = svgString.indexOf(ID_FRAG); - if (index !== -1) { - const idStartIndex = index + ID_FRAG.length; - const idEndIndex = svgString.substring(idStartIndex).indexOf('"') + idStartIndex; - const fullSymbolId = svgString.substring(idStartIndex, idEndIndex); - const symbolId = fullSymbolId.substring(0, fullSymbolId.length - 3); // remove '-15' or '-11' from id - const symbolSize = fullSymbolId.substring(fullSymbolId.length - 2); // grab last 2 chars from id - // only show large icons, small/large icon selection will based on configured size style - if (symbolSize === LARGE_MAKI_ICON_SIZE_AS_STRING) { - SYMBOLS[symbolId] = svgString; - } - } -}); - -export const SYMBOL_OPTIONS = Object.keys(SYMBOLS).map((symbolId) => { +export const SYMBOL_OPTIONS = Object.keys(MAKI_ICONS).map((symbolId) => { return { value: symbolId, label: symbolId, }; }); +/** + * Converts a SVG icon to a monochrome image using a signed distance function. + * + * @param {string} svgString - SVG icon as string + * @param {number} [cutoff=0.25] - balance between SDF inside 1 and outside 0 of glyph + * @param {number} [radius=0.25] - size of SDF around the cutoff as percent of output icon size + * @return {ImageData} Monochrome image that can be added to a MapLibre map + */ +export async function createSdfIcon(svgString, cutoff = 0.25, radius = 0.25) { + const buffer = 3; + const size = MAKI_ICON_SIZE + buffer * 4; + const svgCanvas = document.createElement('canvas'); + svgCanvas.width = size; + svgCanvas.height = size; + const svgCtx = svgCanvas.getContext('2d'); + const v = Canvg.fromString(svgCtx, svgString, { + ignoreDimensions: true, + offsetX: buffer / 2, + offsetY: buffer / 2, + }); + v.resize(size - buffer, size - buffer); + await v.render(); + + const distances = calcSDF(svgCtx, { + channel: 3, + cutoff, + radius: radius * size, + }); + + const canvas = document.createElement('canvas'); + canvas.width = size; + canvas.height = size; + const ctx = canvas.getContext('2d'); + + const imageData = ctx.createImageData(size, size); + for (let i = 0; i < size; i++) { + for (let j = 0; j < size; j++) { + imageData.data[j * size * 4 + i * 4 + 0] = 0; + imageData.data[j * size * 4 + i * 4 + 1] = 0; + imageData.data[j * size * 4 + i * 4 + 2] = 0; + imageData.data[j * size * 4 + i * 4 + 3] = distances[j * size + i] * 255; + } + } + return imageData; +} + export function getMakiSymbolSvg(symbolId) { - if (!SYMBOLS[symbolId]) { + const svg = MAKI_ICONS?.[symbolId]?.svg; + if (!svg) { throw new Error(`Unable to find symbol: ${symbolId}`); } - return SYMBOLS[symbolId]; + return svg; } export function getMakiSymbolAnchor(symbolId) { @@ -59,12 +89,6 @@ export function getMakiSymbolAnchor(symbolId) { } } -// Style descriptor stores symbolId, for example 'aircraft' -// Icons are registered in Mapbox with full maki ids, for example 'aircraft-11' -export function getMakiIconId(symbolId, iconPixelSize) { - return `${symbolId}-${iconPixelSize}`; -} - export function buildSrcUrl(svgString) { const domUrl = window.URL || window.webkitURL || window; const svg = new Blob([svgString], { type: 'image/svg+xml' }); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx index 5afd05366ab1d..a4ea62cb63970 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx @@ -899,11 +899,7 @@ export class VectorStyle implements IVectorStyle { mbMap.setPaintProperty(symbolLayerId, 'icon-opacity', alpha); mbMap.setLayoutProperty(symbolLayerId, 'icon-allow-overlap', true); - this._iconStyleProperty.syncIconWithMb( - symbolLayerId, - mbMap, - this._iconSizeStyleProperty.getIconPixelSize() - ); + this._iconStyleProperty.syncIconWithMb(symbolLayerId, mbMap); // icon-color is only supported on SDF icons. this._fillColorStyleProperty.syncIconColorWithMb(symbolLayerId, mbMap); this._lineColorStyleProperty.syncHaloBorderColorWithMb(symbolLayerId, mbMap); diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx index c262eaa9d1527..7646b6033a2f5 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx @@ -7,10 +7,6 @@ import _ from 'lodash'; import React, { Component } from 'react'; -// @ts-expect-error -import { spritesheet } from '@elastic/maki'; -import sprites1 from '@elastic/maki/dist/sprite@1.png'; -import sprites2 from '@elastic/maki/dist/sprite@2.png'; import { Adapters } from 'src/plugins/inspector/public'; import { Filter } from 'src/plugins/data/public'; import { Action, ActionExecutionContext } from 'src/plugins/ui_actions/public'; @@ -33,20 +29,18 @@ import { Timeslice, } from '../../../common/descriptor_types'; import { DECIMAL_DEGREES_PRECISION, RawValue, ZOOM_PRECISION } from '../../../common/constants'; -import { getGlyphUrl, isRetina } from '../../util'; +import { getGlyphUrl } from '../../util'; import { syncLayerOrder } from './sort_layers'; -import { - addSpriteSheetToMapFromImageData, - getTileMetaFeatures, - loadSpriteSheetImageData, - removeOrphanedSourcesAndLayers, -} from './utils'; +import { getTileMetaFeatures, removeOrphanedSourcesAndLayers } from './utils'; import { ResizeChecker } from '../../../../../../src/plugins/kibana_utils/public'; import { RenderToolTipContent } from '../../classes/tooltips/tooltip_property'; import { TileStatusTracker } from './tile_status_tracker'; import { DrawFeatureControl } from './draw_control/draw_feature_control'; import type { MapExtentState } from '../../reducers/map/types'; +// @ts-expect-error +import { createSdfIcon } from '../../classes/styles/vector/symbol_utils'; +import { MAKI_ICONS } from '../../classes/styles/vector/maki_icons'; export interface Props { isMapReady: boolean; @@ -290,11 +284,17 @@ export class MbMap extends Component { } async _loadMakiSprites(mbMap: MapboxMap) { - const spritesUrl = isRetina() ? sprites2 : sprites1; - const json = isRetina() ? spritesheet[2] : spritesheet[1]; - const spritesData = await loadSpriteSheetImageData(spritesUrl); if (this._isMounted) { - addSpriteSheetToMapFromImageData(json, spritesData, mbMap); + const pixelRatio = Math.floor(window.devicePixelRatio); + for (const [symbolId, { svg }] of Object.entries(MAKI_ICONS)) { + if (!mbMap.hasImage(symbolId)) { + const imageData = await createSdfIcon(svg, 0.25, 0.25); + mbMap.addImage(symbolId, imageData, { + pixelRatio, + sdf: true, + }); + } + } } } diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/utils.ts b/x-pack/plugins/maps/public/connected_components/mb_map/utils.ts index f5de99d04c01c..a79c1a1f71b76 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/utils.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/utils.ts @@ -7,11 +7,8 @@ import type { Map as MbMap } from '@kbn/mapbox-gl'; import { TileMetaFeature } from '../../../common/descriptor_types'; -// @ts-expect-error -import { RGBAImage } from './image_utils'; import { isGlDrawLayer } from './sort_layers'; import { ILayer } from '../../classes/layers/layer'; -import { EmsSpriteSheet } from '../../classes/layers/ems_vector_tile_layer/ems_vector_tile_layer'; import { ES_MVT_META_LAYER_NAME } from '../../classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer'; export function removeOrphanedSourcesAndLayers( @@ -64,64 +61,6 @@ export function removeOrphanedSourcesAndLayers( mbSourcesToRemove.forEach((mbSourceId) => mbMap.removeSource(mbSourceId)); } -function getImageData(img: HTMLImageElement) { - const canvas = window.document.createElement('canvas'); - const context = canvas.getContext('2d'); - if (!context) { - throw new Error('failed to create canvas 2d context'); - } - canvas.width = img.width; - canvas.height = img.height; - context.drawImage(img, 0, 0, img.width, img.height); - return context.getImageData(0, 0, img.width, img.height); -} - -function isCrossOriginUrl(url: string) { - const a = window.document.createElement('a'); - a.href = url; - return ( - a.protocol !== window.document.location.protocol || - a.host !== window.document.location.host || - a.port !== window.document.location.port - ); -} - -export async function loadSpriteSheetImageData(imgUrl: string): Promise { - return new Promise((resolve, reject) => { - const image = new Image(); - if (isCrossOriginUrl(imgUrl)) { - image.crossOrigin = 'Anonymous'; - } - image.onload = (event) => { - resolve(getImageData(image)); - }; - image.onerror = (e) => { - reject(e); - }; - image.src = imgUrl; - }); -} - -export function addSpriteSheetToMapFromImageData( - json: EmsSpriteSheet, - imgData: ImageData, - mbMap: MbMap -) { - for (const imageId in json) { - if (!(json.hasOwnProperty(imageId) && !mbMap.hasImage(imageId))) { - continue; - } - const { width, height, x, y, sdf, pixelRatio } = json[imageId]; - if (typeof width !== 'number' || typeof height !== 'number') { - continue; - } - - const data = new RGBAImage({ width, height }); - RGBAImage.copy(imgData, data, { x, y }, { x: 0, y: 0 }, { width, height }); - mbMap.addImage(imageId, data, { pixelRatio, sdf }); - } -} - export function getTileMetaFeatures(mbMap: MbMap, mbSourceId: string): TileMetaFeature[] { // querySourceFeatures can return duplicated features when features cross tile boundaries. // Tile meta will never have duplicated features since by there nature, tile meta is a feature contained within a single tile diff --git a/yarn.lock b/yarn.lock index e838d0ec297a3..aa6f6d90ea07e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1676,11 +1676,6 @@ through2 "^2.0.0" update-notifier "^0.5.0" -"@elastic/maki@6.3.0": - version "6.3.0" - resolved "https://registry.yarnpkg.com/@elastic/maki/-/maki-6.3.0.tgz#09780650f1510554bef9121b9db86ce297f021f1" - integrity sha512-a2U2DaemIJaW+3nL/sN/+JScdrkoggoGHLDtRPurk2Axnpa9O9QHekmMXLO7eLK1brDpYcplqGE6hwFaMvRRUg== - "@elastic/node-crypto@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@elastic/node-crypto/-/node-crypto-1.1.1.tgz#619b70322c9cce4a7ee5fbf8f678b1baa7f06095" @@ -6225,6 +6220,11 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== +"@types/raf@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@types/raf/-/raf-3.4.0.tgz#2b72cbd55405e071f1c4d29992638e022b20acc2" + integrity sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw== + "@types/range-parser@*": version "1.2.4" resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" @@ -8551,6 +8551,13 @@ binary-search@^1.3.3: resolved "https://registry.yarnpkg.com/binary-search/-/binary-search-1.3.6.tgz#e32426016a0c5092f0f3598836a1c7da3560565c" integrity sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA== +bitmap-sdf@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/bitmap-sdf/-/bitmap-sdf-1.0.3.tgz#c99913e5729357a6fd350de34158180c013880b2" + integrity sha512-ojYySSvWTx21cbgntR942zgEgqj38wHctN64vr4vYRFf3GKVmI23YlA94meWGkFslidwLwGCsMy2laJ3g/94Sg== + dependencies: + clamp "^1.0.1" + bl@^4.0.1, bl@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" @@ -9333,6 +9340,20 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001097, caniuse-lite@^1.0.30001109, can resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001280.tgz#066a506046ba4be34cde5f74a08db7a396718fb7" integrity sha512-kFXwYvHe5rix25uwueBxC569o53J6TpnGu0BEEn+6Lhl2vsnAumRFWEBhDft1fwyo6m1r4i+RqA4+163FpeFcA== +canvg@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/canvg/-/canvg-3.0.9.tgz#9ba095f158b94b97ca2c9c1c40785b11dc08df6d" + integrity sha512-rDXcnRPuz4QHoCilMeoTxql+fvGqNAxp+qV/KHD8rOiJSAfVjFclbdUNHD2Uqfthr+VMg17bD2bVuk6F07oLGw== + dependencies: + "@babel/runtime" "^7.12.5" + "@types/raf" "^3.4.0" + core-js "^3.8.3" + raf "^3.4.1" + regenerator-runtime "^0.13.7" + rgbcolor "^1.0.1" + stackblur-canvas "^2.0.0" + svg-pathdata "^6.0.3" + capture-exit@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" @@ -9606,6 +9627,11 @@ cjs-module-lexer@^0.6.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== +clamp@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/clamp/-/clamp-1.0.1.tgz#66a0e64011816e37196828fdc8c8c147312c8634" + integrity sha1-ZqDmQBGBbjcZaCj9yMjBRzEshjQ= + class-utils@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.5.tgz#17e793103750f9627b2176ea34cfd1b565903c80" @@ -10401,7 +10427,7 @@ core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.9: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== -core-js@^3.0.4, core-js@^3.19.1, core-js@^3.6.5, core-js@^3.8.2: +core-js@^3.0.4, core-js@^3.19.1, core-js@^3.6.5, core-js@^3.8.2, core-js@^3.8.3: version "3.19.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.19.1.tgz#f6f173cae23e73a7d88fa23b6e9da329276c6641" integrity sha512-Tnc7E9iKd/b/ff7GFbhwPVzJzPztGrChB8X8GLqoYGdEOG8IpLnK1xPyo3ZoO3HsK6TodJS58VGPOxA+hLHQMg== @@ -24680,6 +24706,11 @@ rgba-regex@^1.0.0: resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= +rgbcolor@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rgbcolor/-/rgbcolor-1.0.1.tgz#d6505ecdb304a6595da26fa4b43307306775945d" + integrity sha1-1lBezbMEplldom+ktDMHMGd1lF0= + right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" @@ -25907,6 +25938,11 @@ stack-utils@^2.0.3: escape-string-regexp "^2.0.0" source-map-support "^0.5.20" +stackblur-canvas@^2.0.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz#aa87bbed1560fdcd3138fff344fc6a1c413ebac4" + integrity sha512-EeNzTVfj+1In7aSLPKDD03F/ly4RxEuF/EX0YcOG0cKoPXs+SLZxDawQbexQDBzwROs4VKLWTOaZQlZkGBFEIQ== + stackframe@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-0.3.1.tgz#33aa84f1177a5548c8935533cbfeb3420975f5a4" @@ -26664,6 +26700,11 @@ sver-compat@^1.5.0: es6-iterator "^2.0.1" es6-symbol "^3.1.1" +svg-pathdata@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/svg-pathdata/-/svg-pathdata-6.0.3.tgz#80b0e0283b652ccbafb69ad4f8f73e8d3fbf2cac" + integrity sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw== + svg-tags@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"