diff --git a/.gitignore b/.gitignore index b739568994de..ecc983f8e4ea 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ /contribs/gmf/build/ /contribs/gmf/examples/https.js /openlayers_src/ +/dist/ diff --git a/Makefile b/Makefile index c78010e24fc2..1d6edd76dd7c 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ NGEO_EXAMPLES_JS_FILES := $(NGEO_EXAMPLES_HTML_FILES:.html=.js) GMF_PARTIALS_FILES := $(shell find contribs/gmf/src/ -name *.html) GMF_JS_FILES := $(shell find contribs/gmf/src/ -type f -name '*.js') -GMF_ALL_SRC_FILES := $(shell find contribs/gmf/src/ -type f) $(shell find contribs/gmf/src/cursors/ -type f) $(NGEO_ALL_SRC_FILES) +GMF_ALL_SRC_FILES := $(shell find contribs/gmf/src/ -type f) $(NGEO_ALL_SRC_FILES) GMF_TEST_JS_FILES := $(shell find contribs/gmf/test/ -type f -name '*.js') GMF_EXAMPLES_HTML_FILES := $(shell ls -1 contribs/gmf/examples/*.html) GMF_EXAMPLES_JS_FILES := $(GMF_EXAMPLES_HTML_FILES:.html=.js) diff --git a/buildtools/generate-xml-from-tokens.js b/buildtools/generate-xml-from-tokens.js new file mode 100644 index 000000000000..4dcd0f62ad95 --- /dev/null +++ b/buildtools/generate-xml-from-tokens.js @@ -0,0 +1,98 @@ +// Initially get from https://github.com/tildeio/simple-html-tokenizer/blob/v0.1.1/lib/simple-html-tokenizer/generator.js + +const escape = (function() { + const test = /[&<>"'`]/; + const replace = /[&<>"'`]/g; + const map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\'': ''', + '`': '`' + }; + function escapeChar(char) { + return map[char]; + } + return function escape(string) { + if (!test.test(string)) { + return string; + } + return string.replace(replace, escapeChar); + }; +}()); + +function Generator() { + this.escape = escape; +} + +Generator.prototype = { + generate: function(tokens) { + let buffer = ''; + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + buffer += this[token.type](token); + } + return buffer; + }, + + escape: function(text) { + const unsafeCharsMap = this.unsafeCharsMap; + return text.replace(this.unsafeChars, function(char) { + return unsafeCharsMap[char] || char; + }); + }, + + StartTag: function(token) { + let out = '<'; + out += token.tagName; + + if (token.attributes.length) { + out += ' ' + this.Attributes(token.attributes); + } + + out += token.selfClosing ? '/>' : '>'; + + return out; + }, + + EndTag: function(token) { + return ''; + }, + + Chars: function(token) { + return this.escape(token.chars); + }, + + Comment: function(token) { + return ''; + }, + + Attributes: function(attributes) { + const out = []; + + for (let i = 0, l = attributes.length; i < l; i++) { + const attribute = attributes[i]; + + out.push(this.Attribute(attribute[0], attribute[1])); + } + + return out.join(' '); + }, + + Attribute: function(name, value) { + let attrString = name; + + if (value) { + value = this.escape(value); + attrString += '="' + value + '"'; + } + + return attrString; + } +}; + +module.exports = function(tokens) { + const generator = new Generator(); + return generator.generate(tokens); +}; diff --git a/buildtools/svg-viewbox-loader.js b/buildtools/svg-viewbox-loader.js index 577d23e4cc38..95634d8634e2 100644 --- a/buildtools/svg-viewbox-loader.js +++ b/buildtools/svg-viewbox-loader.js @@ -1,32 +1,79 @@ const simpleHTMLTokenizer = require('simple-html-tokenizer'); +const generate = require('./generate-xml-from-tokens.js'); module.exports = function(source) { this.cacheable(true); let tokens = simpleHTMLTokenizer.tokenize(source); tokens = tokens.map((tag) => { - let width = undefined; - let height = undefined; if (tag.type === 'StartTag' && tag.tagName === 'svg') { + let width = undefined; + let height = undefined; + let x = 0; + let y = 0; for (const attribute of tag.attributes) { if (attribute[0] === 'width') { - width = parseFloat(attribute[1]); + try { + width = parseFloat(attribute[1]); + } catch (e) { + console.warn('Unable to read width: ' + attribute[1]); + } } if (attribute[0] === 'height') { - height = parseFloat(attribute[1]); + try { + height = parseFloat(attribute[1]); + } catch (e) { + console.warn('Unable to read height: ' + attribute[1]); + } + } + if (attribute[0] === 'x') { + try { + x = parseFloat(attribute[1]); + } catch (e) { + console.warn('Unable to read x: ' + attribute[1]); + } + } + if (attribute[0] === 'y') { + try { + y = parseFloat(attribute[1]); + } catch (e) { + console.warn('Unable to read y: ' + attribute[1]); + } + } + if (attribute[0] === 'viewBox') { + try { + const attrs = attribute[1].split(' '); + x = parseFloat(attrs[0]); + y = parseFloat(attrs[1]); + width = parseFloat(attrs[2]); + height = parseFloat(attrs[3]); + } catch (e) { + console.warn('Unable to read viewbox: ' + attribute[1]); + } } } if (width !== undefined && height != undefined) { tag.attributes = tag.attributes.filter((attribute) => { - return attribute[0] !== 'width' && attribute[0] != 'height' - }) - tag.attributes.push(['viewBox', `0 0 ${width} ${height}`, true]); - tag.attributes.push(['height', '1em', true]); - tag.attributes.push(['width', `${width / height}em`, true]); + return attribute[0] !== 'width' && attribute[0] != 'height' && attribute[0] != 'viewBox'; + }); + if (x) { + tag.attributes.push(['x', x, true]); + } + if (y) { + tag.attributes.push(['y', y, true]); + } + if (this.resourceQuery.search(/inline/) >= 0) { + tag.attributes.push(['width', width, true]); + tag.attributes.push(['height', height, true]); + } else { + tag.attributes.push(['viewBox', `0 0 ${width} ${height}`, true]); + tag.attributes.push(['height', '1em', true]); + tag.attributes.push(['width', `${width / height}em`, true]); + } } } return tag; - }) + }); - return simpleHTMLTokenizer.generate(tokens) + return generate(tokens); }; diff --git a/buildtools/webpack.commons.js b/buildtools/webpack.commons.js index b399607f9462..a7d0ca142ee8 100644 --- a/buildtools/webpack.commons.js +++ b/buildtools/webpack.commons.js @@ -61,19 +61,6 @@ const htmlRule = { use: 'ejs-loader', }; -const svgRule = { - test: /\.svg$/, - use: [ - { - loader: 'svg-inline-loader', - options: { - removeSVGTagAttrs: false, - }, - }, - './buildtools/svg-viewbox-loader', - 'svgo-loader', - ] -}; function get_comp(firsts, lasts) { return (f1, f2) => { @@ -142,7 +129,6 @@ const config = function(hardSourceConfig, babelLoaderCacheDirectory) { cssRule, sassRule, htmlRule, - svgRule, ngeoRule, otherRule, ] diff --git a/buildtools/webpack.dev.js b/buildtools/webpack.dev.js index db241759887d..313e6f9a0a32 100644 --- a/buildtools/webpack.dev.js +++ b/buildtools/webpack.dev.js @@ -12,6 +12,33 @@ const resourcesRule = { } }; +const svgRule = { + test: /\.svg$/, + oneOf: [{ + resourceQuery: /url/, + use: [ + { + loader: 'file-loader', + options: { + name: '[name].[ext]' + }, + }, + 'svgo-loader', + ] + }, { + use: [ + { + loader: 'svg-inline-loader', + options: { + removeSVGTagAttrs: false, + }, + }, + './buildtools/svg-viewbox-loader', + 'svgo-loader', + ] + }] +}; + new webpack.LoaderOptionsPlugin({ debug: false }); @@ -19,12 +46,14 @@ new webpack.LoaderOptionsPlugin({ module.exports = { mode: 'development', + // devtool: 'eval', output: { filename: '[name].js' }, module: { rules: [ resourcesRule, + svgRule, ] }, }; diff --git a/buildtools/webpack.prod.js b/buildtools/webpack.prod.js index 7b192008dc34..ba3a5386c613 100644 --- a/buildtools/webpack.prod.js +++ b/buildtools/webpack.prod.js @@ -12,6 +12,44 @@ const resourcesRule = { } }; +const svgRule = { + test: /\.svg$/, + oneOf: [{ + resourceQuery: /url/, + use: [ + { + loader: 'file-loader', + options: { + name: '[name].[hash:6].[ext]' + }, + }, + 'svgo-loader', + ] + }, { + resourceQuery: /inline/, + use: [ + { + loader: 'svg-inline-loader', + options: { + removeSVGTagAttrs: false, + }, + }, + 'svgo-loader', + ] + }, { + use: [ + { + loader: 'svg-inline-loader', + options: { + removeSVGTagAttrs: false, + }, + }, + './buildtools/svg-viewbox-loader', + 'svgo-loader', + ] + }] +}; + module.exports = function(TerserPluginCache) { return { mode: 'production', @@ -24,6 +62,7 @@ module.exports = function(TerserPluginCache) { module: { rules: [ resourcesRule, + svgRule, ] }, optimization: { diff --git a/contribs/gmf/apps/desktop_alt/Controller.js b/contribs/gmf/apps/desktop_alt/Controller.js index b7cb4a1bf62a..1cb553c01b86 100644 --- a/contribs/gmf/apps/desktop_alt/Controller.js +++ b/contribs/gmf/apps/desktop_alt/Controller.js @@ -20,7 +20,11 @@ import ngeoRoutingModule from 'ngeo/routing/module.js'; import EPSG2056 from '@geoblocks/proj/src/EPSG_2056.js'; import EPSG21781 from '@geoblocks/proj/src/EPSG_21781.js'; import ngeoStatemanagerWfsPermalink from 'ngeo/statemanager/WfsPermalink.js'; -import {Circle, Fill, Stroke, Style} from 'ol/style'; +import Style from 'ol/style/Style.js'; +import Circle from 'ol/style/Circle.js'; +import Fill from 'ol/style/Fill.js'; +import Stroke from 'ol/style/Stroke.js'; +import Icon from 'ol/style/Icon.js'; import Raven from 'raven-js/src/raven.js'; import RavenPluginsAngular from 'raven-js/plugins/angular.js'; @@ -194,4 +198,18 @@ const module = angular.module('Appdesktop_alt', [ module.controller('AlternativeDesktopController', Controller); + +module.value('gmfPermalinkOptions', /** @type {import('gmf/permalink/Permalink.js').PermalinkOptions} */ ({ + crosshairStyle: [ + new Style({ + image: new Icon({ + src: 'data:image/svg+xml;base64,' + btoa(require('./image/crosshair.svg?inline')), + // Also working + // src: require('./image/crosshair.svg?url'), + imgSize: [22, 22], + }) + }) + ] +})); + export default module; diff --git a/contribs/gmf/apps/desktop_alt/image/crosshair.svg b/contribs/gmf/apps/desktop_alt/image/crosshair.svg new file mode 100644 index 000000000000..ad2c93477f28 --- /dev/null +++ b/contribs/gmf/apps/desktop_alt/image/crosshair.svg @@ -0,0 +1,17 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/contribs/gmf/apps/desktop_alt/image/logo.png b/contribs/gmf/apps/desktop_alt/image/logo.png deleted file mode 100644 index a07d43d5b7a8..000000000000 Binary files a/contribs/gmf/apps/desktop_alt/image/logo.png and /dev/null differ diff --git a/contribs/gmf/apps/desktop_alt/image/logo.svg b/contribs/gmf/apps/desktop_alt/image/logo.svg new file mode 100644 index 000000000000..e211bc9e7556 --- /dev/null +++ b/contribs/gmf/apps/desktop_alt/image/logo.svg @@ -0,0 +1,104 @@ + + + +image/svg+xml diff --git a/contribs/gmf/apps/desktop_alt/index.html.ejs b/contribs/gmf/apps/desktop_alt/index.html.ejs index 8aecd7a461b0..99745c51fcd7 100644 --- a/contribs/gmf/apps/desktop_alt/index.html.ejs +++ b/contribs/gmf/apps/desktop_alt/index.html.ejs @@ -17,7 +17,7 @@
diff --git a/examples/createfeature.js b/examples/createfeature.js index c6048fc9b44b..d7b6e6e421e2 100644 --- a/examples/createfeature.js +++ b/examples/createfeature.js @@ -36,9 +36,6 @@ const module = angular.module('app', [ */ function MainController(ngeoToolActivateMgr) { - /** - * @type {import("ol/Collection.js").default} - */ this.features = new olCollection(); /** diff --git a/examples/font.svg b/examples/font.svg new file mode 100644 index 000000000000..99c8723bdd1a --- /dev/null +++ b/examples/font.svg @@ -0,0 +1,80 @@ + + + + + + + + + + image/svg+xml + + + + + + + + Font + + diff --git a/examples/inline.svg b/examples/inline.svg new file mode 100644 index 000000000000..caa3eeb90551 --- /dev/null +++ b/examples/inline.svg @@ -0,0 +1,79 @@ + + + + + + + + + + image/svg+xml + + + + + + + + Inline + + diff --git a/examples/svg.css b/examples/svg.css new file mode 100644 index 000000000000..5157c7e2617a --- /dev/null +++ b/examples/svg.css @@ -0,0 +1,16 @@ +#map { + width: 400px; + height: 150px; +} +#font2 svg { + font-size: 2rem +} +#font2 circle { + fill: #65b0ff +} +#font4 svg { + font-size: 4rem +} +#font4 circle { + fill: #65b0ff +} diff --git a/examples/svg.html b/examples/svg.html new file mode 100644 index 000000000000..b289c5278a8e --- /dev/null +++ b/examples/svg.html @@ -0,0 +1,27 @@ + + + + SVG example + + + + + +

This example shows different ways to include an SVG in the application using Webpack.

+

In a map:

+
+

In the HTML:

+

Font (2rem): <%=require("./font.svg")%>.

+

Font (4rem): <%=require("./font.svg")%>.

+

Inline (?inline): <%=require("./inline.svg?inline")%>.

+

URL (?url): " />

+

Note for Inkscape, in the document property - Page:

+ +

Note: in font, and in inline mode, CSS rules can be used.

+ + diff --git a/examples/svg.js b/examples/svg.js new file mode 100644 index 000000000000..81c374bccc43 --- /dev/null +++ b/examples/svg.js @@ -0,0 +1,75 @@ +import angular from 'angular'; +import './svg.css'; +import EPSG21781 from '@geoblocks/proj/src/EPSG_21781.js'; + +import Map from 'ol/Map.js'; +import View from 'ol/View.js'; +import LayerVector from 'ol/layer/Vector.js'; +import SourceVector from 'ol/source/Vector.js'; +import Feature from 'ol/Feature.js'; +import Point from 'ol/geom/Point.js'; +import Style from 'ol/style/Style.js'; +import Icon from 'ol/style/Icon.js'; + +import MapModule from 'ngeo/map/module.js'; + + +/** @type {!angular.IModule} **/ +const appmodule = angular.module('app', [ + MapModule.name, +]); + + +/** + * @constructor + * @ngInject + * @hidden + */ +function MainController() { + const source = new SourceVector(); + const feature1 = new Feature({ + geometry: new Point([599000, 200000]) + }); + feature1.setStyle([new Style({ + image: new Icon({ + // @ts-ignore: For Webpack + src: 'data:image/svg+xml;base64,' + btoa(require('./inline.svg?inline')), + // For IE compatibility + imgSize: [65, 65] + }) + })]); + source.addFeature(feature1); + + const feature2 = new Feature({ + geometry: new Point([601000, 200000]) + }); + feature2.setStyle([new Style({ + image: new Icon({ + // @ts-ignore: For Webpack + src: require('./url.svg?url'), + // For IE compatibility + imgSize: [65, 65] + }) + })]); + source.addFeature(feature2); + + this.map = new Map({ + layers: [ + new LayerVector({ + source + }) + ], + view: new View({ + projection: EPSG21781, + resolutions: [200, 100, 50, 20, 10, 5, 2.5, 2, 1], + center: [600000, 200000], + zoom: 4 + }) + }); +} + + +appmodule.controller('MainController', MainController); + + +export default module; diff --git a/examples/url.svg b/examples/url.svg new file mode 100644 index 000000000000..12e4593221f6 --- /dev/null +++ b/examples/url.svg @@ -0,0 +1,80 @@ + + + + + + + + + + image/svg+xml + + + + + + + + URL + +