From 3f7f999db02e52e2bbd27ef4aab960517e1f56f2 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Sat, 21 Mar 2020 15:49:01 +0100 Subject: [PATCH] Avoid try/catch, DOM and workers --- src/ol/css.js | 79 +++++++++++++++++++------------- src/ol/dom.js | 8 ++-- src/ol/has.js | 20 ++------ src/ol/render/canvas.js | 72 +++++++++++------------------ src/ol/render/canvas/Executor.js | 4 +- tsconfig.json | 2 +- 6 files changed, 85 insertions(+), 100 deletions(-) diff --git a/src/ol/css.js b/src/ol/css.js index f4eb09eb59..88a6d5c9dc 100644 --- a/src/ol/css.js +++ b/src/ol/css.js @@ -4,10 +4,13 @@ /** * @typedef {Object} FontParameters - * @property {Array} families * @property {string} style + * @property {string} variant * @property {string} weight + * @property {string} size * @property {string} lineHeight + * @property {string} family + * @property {Array} families */ @@ -65,42 +68,52 @@ export const CLASS_CONTROL = 'ol-control'; */ export const CLASS_COLLAPSED = 'ol-collapsed'; +/** + * From http://stackoverflow.com/questions/10135697/regex-to-parse-any-css-font + * @type {RegExp} + */ +const fontRegEx = new RegExp([ + '^\\s*(?=(?:(?:[-a-z]+\\s*){0,2}(italic|oblique))?)', + '(?=(?:(?:[-a-z]+\\s*){0,2}(small-caps))?)', + '(?=(?:(?:[-a-z]+\\s*){0,2}(bold(?:er)?|lighter|[1-9]00 ))?)', + '(?:(?:normal|\\1|\\2|\\3)\\s*){0,3}((?:xx?-)?', + '(?:small|large)|medium|smaller|larger|[\\.\\d]+(?:\\%|in|[cem]m|ex|p[ctx]))', + '(?:\\s*\\/\\s*(normal|[\\.\\d]+(?:\\%|in|[cem]m|ex|p[ctx])?))', + '?\\s*([-,\\"\\\'\\sa-z]+?)\\s*$' +].join(''), 'i'); +const fontRegExMatchIndex = [ + 'style', + 'variant', + 'weight', + 'size', + 'lineHeight', + 'family' +]; /** * Get the list of font families from a font spec. Note that this doesn't work * for font families that have commas in them. * @param {string} fontSpec The CSS font property. - * @param {function(FontParameters):void} callback Called with the font families - * (or null if the input spec is invalid). + * @return {FontParameters} The font parameters (or null if the input spec is invalid). */ -export const getFontParameters = (function() { - /** - * @type {CSSStyleDeclaration} - */ - let style; - /** - * @type {Object} - */ - const cache = {}; - return function(fontSpec, callback) { - if (!style) { - style = document.createElement('div').style; +export const getFontParameters = function(fontSpec) { + const match = fontSpec.match(fontRegEx); + if (!match) { + return null; + } + const style = /** @type {FontParameters} */ ({ + lineHeight: 'normal', + size: '1.2em', + style: 'normal', + weight: 'normal', + variant: 'normal' + }); + for (let i = 0, ii = fontRegExMatchIndex.length; i < ii; ++i) { + const value = match[i + 1]; + if (value !== undefined) { + style[fontRegExMatchIndex[i]] = value; } - if (!(fontSpec in cache)) { - style.font = fontSpec; - const family = style.fontFamily; - if (!family) { - callback(null); - } - const families = family.split(/,\s?/); - cache[fontSpec] = { - families: families, - weight: style.fontWeight, - style: style.fontStyle, - lineHeight: style.lineHeight - }; - style.font = ''; - } - callback(cache[fontSpec]); - }; -})(); + } + style.families = style.family.split(/,\s?/); + return style; +}; diff --git a/src/ol/dom.js b/src/ol/dom.js index 37d188541e..e3c403cc99 100644 --- a/src/ol/dom.js +++ b/src/ol/dom.js @@ -1,3 +1,5 @@ +import {WORKER_OFFSCREEN_CANVAS} from './has.js'; + /** * @module ol/dom */ @@ -14,9 +16,9 @@ export function createCanvasContext2D(opt_width, opt_height, opt_canvasPool) { const canvas = opt_canvasPool && opt_canvasPool.length ? opt_canvasPool.shift() : - 'document' in self ? - document.createElement('canvas') : - new OffscreenCanvas(opt_width || 300, opt_height || 300); + WORKER_OFFSCREEN_CANVAS ? + new OffscreenCanvas(opt_width || 300, opt_height || 300) : + document.createElement('canvas'); if (opt_width) { canvas.width = opt_width; } diff --git a/src/ol/has.js b/src/ol/has.js index 24b053a2fe..3fdebf833c 100644 --- a/src/ol/has.js +++ b/src/ol/has.js @@ -37,27 +37,15 @@ export const MAC = ua.indexOf('macintosh') !== -1; * @type {number} * @api */ -export const DEVICE_PIXEL_RATIO = (function() { - try { - return self.devicePixelRatio; - } catch (e) { - return window.devicePixelRatio || 1; - } -})(); +export const DEVICE_PIXEL_RATIO = (typeof self !== 'undefined' ? self.devicePixelRatio : window.devicePixelRatio) || 1; /** - * The execution context is a window. + * The execution context is a worker with OffscreenCanvas available. * @const * @type {boolean} */ -export const WINDOW = (function() { - try { - return 'document' in self; - } catch (e) { - // ancient browsers don't have `self` - return true; - } -})(); +export const WORKER_OFFSCREEN_CANVAS = typeof WorkerGlobalScope !== 'undefined' && typeof OffscreenCanvas !== 'undefined' && + self instanceof WorkerGlobalScope; //eslint-disable-line /** * Image.prototype.decode() is supported. diff --git a/src/ol/render/canvas.js b/src/ol/render/canvas.js index 002ec4efd0..edd5737a07 100644 --- a/src/ol/render/canvas.js +++ b/src/ol/render/canvas.js @@ -6,7 +6,7 @@ import {createCanvasContext2D} from '../dom.js'; import {clear} from '../obj.js'; import BaseObject from '../Object.js'; import EventTarget from '../events/Target.js'; -import {WINDOW} from '../has.js'; +import {WORKER_OFFSCREEN_CANVAS} from '../has.js'; import {toString} from '../transform.js'; @@ -266,7 +266,8 @@ export const registerFont = (function() { } } - function fontCallback(font) { + return function(fontSpec) { + const font = getFontParameters(fontSpec); if (!font) { return; } @@ -284,31 +285,6 @@ export const registerFont = (function() { } } } - } - - return function(fontSpec) { - if (WINDOW) { - getFontParameters(fontSpec, fontCallback); - } else if (self.postMessage) { - /** @type {any} */ - const worker = self; - worker.postMessage({ - action: 'getFontParameters', - font: fontSpec - }); - worker.addEventListener('message', function handler(event) { - if (event.data.action === 'getFontParameters') { - worker.removeEventListener('message', handler); - const font = event.data.font; - fontCallback(font); - if (!textHeights[fontSpec]) { - const metrics = measureText(fontSpec, 'Žg'); - const lineHeight = isNaN(font.lineHeight) ? 1.2 : Number(font.lineHeight); - textHeights[fontSpec] = lineHeight * (metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent); - } - } - }); - } }; })(); @@ -323,22 +299,29 @@ export const measureTextHeight = (function() { */ let div; const heights = textHeights; - return function(font) { - let height = heights[font]; + return function(fontSpec) { + let height = heights[fontSpec]; if (height == undefined) { - if (!div) { - div = document.createElement('div'); - div.innerHTML = 'M'; - div.style.margin = '0 !important'; - div.style.padding = '0 !important'; - div.style.position = 'absolute !important'; - div.style.left = '-99999px !important'; + if (WORKER_OFFSCREEN_CANVAS) { + const font = getFontParameters(fontSpec); + const metrics = measureText(fontSpec, 'Žg'); + const lineHeight = isNaN(Number(font.lineHeight)) ? 1.2 : Number(font.lineHeight); + textHeights[fontSpec] = lineHeight * (metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent); + } else { + if (!div) { + div = document.createElement('div'); + div.innerHTML = 'M'; + div.style.margin = '0 !important'; + div.style.padding = '0 !important'; + div.style.position = 'absolute !important'; + div.style.left = '-99999px !important'; + } + div.style.font = fontSpec; + document.body.appendChild(div); + height = div.offsetHeight; + heights[fontSpec] = height; + document.body.removeChild(div); } - div.style.font = font; - document.body.appendChild(div); - height = div.offsetHeight; - heights[font] = height; - document.body.removeChild(div); } return height; }; @@ -369,7 +352,6 @@ export function measureTextWidth(font, text) { return measureText(font, text).width; } - /** * Measure text width using a cache. * @param {string} font The font. @@ -484,13 +466,13 @@ let createTransformStringCanvas = null; * @return {string} CSS transform. */ export function createTransformString(transform) { - if (WINDOW) { + if (WORKER_OFFSCREEN_CANVAS) { + return toString(transform); + } else { if (!createTransformStringCanvas) { createTransformStringCanvas = createCanvasContext2D(1, 1).canvas; } createTransformStringCanvas.style.transform = toString(transform); return createTransformStringCanvas.style.transform; - } else { - return toString(transform); } } diff --git a/src/ol/render/canvas/Executor.js b/src/ol/render/canvas/Executor.js index c9217a7ac4..5f88e0aa94 100644 --- a/src/ol/render/canvas/Executor.js +++ b/src/ol/render/canvas/Executor.js @@ -18,7 +18,7 @@ import { } from '../../transform.js'; import {defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js'; import RBush from 'rbush/rbush.js'; -import {WINDOW} from '../../has.js'; +import {WORKER_OFFSCREEN_CANVAS} from '../../has.js'; /** @@ -206,7 +206,7 @@ class Executor { contextInstructions.push('lineJoin', strokeState.lineJoin); contextInstructions.push('miterLimit', strokeState.miterLimit); // eslint-disable-next-line - const Context = WINDOW ? CanvasRenderingContext2D : OffscreenCanvasRenderingContext2D; + const Context = WORKER_OFFSCREEN_CANVAS ? OffscreenCanvasRenderingContext2D : CanvasRenderingContext2D; if (Context.prototype.setLineDash) { contextInstructions.push('setLineDash', [strokeState.lineDash]); contextInstructions.push('lineDashOffset', strokeState.lineDashOffset); diff --git a/tsconfig.json b/tsconfig.json index 438785ff84..3a208514a3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ /* Basic Options */ "target": "ES2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - "lib": ["es2017", "dom"], /* Specify library files to be included in the compilation. */ + "lib": ["es2017", "dom", "webworker"], /* Specify library files to be included in the compilation. */ "allowJs": true, /* Allow javascript files to be compiled. */ "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */