EVOLUTION-MANAGER
Edit File: wb.js
/* Licensed MIT https://github.com/kangax/fabric.js/blob/master/LICENSE */ /* build: `node build.js modules=ALL exclude=gestures,accessors requirejs minifier=uglifyjs` */ /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */ var fabric = fabric || { version: '2.7.0' }; if (typeof exports !== 'undefined') { exports.fabric = fabric; } /* _AMD_START_ */ else if (typeof define === 'function' && define.amd) { define([], function() { return fabric; }); } /* _AMD_END_ */ if (typeof document !== 'undefined' && typeof window !== 'undefined') { if (document instanceof HTMLDocument) fabric.document = document; else fabric.document = document.implementation.createHTMLDocument(""); fabric.window = window; } else { // assume we're running under node.js when document/window are not present fabric.document = require('jsdom') .jsdom( decodeURIComponent('%3C!DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3C%2Fhead%3E%3Cbody%3E%3C%2Fbody%3E%3C%2Fhtml%3E'), { features: { FetchExternalResources: ['img'] } }); fabric.jsdomImplForWrapper = require('jsdom/lib/jsdom/living/generated/utils').implForWrapper; fabric.nodeCanvas = require('jsdom/lib/jsdom/utils').Canvas; fabric.window = fabric.document.defaultView; DOMParser = require('xmldom').DOMParser; } /** * True when in environment that supports touch events * @type boolean */ fabric.isTouchSupported = 'ontouchstart' in fabric.window || 'ontouchstart' in fabric.document || (fabric.window && fabric.window.navigator && fabric.window.navigator.maxTouchPoints > 0); /** * True when in environment that's probably Node.js * @type boolean */ fabric.isLikelyNode = typeof Buffer !== 'undefined' && typeof window === 'undefined'; /* _FROM_SVG_START_ */ /** * Attributes parsed from all SVG elements * @type array */ fabric.SHARED_ATTRIBUTES = [ 'display', 'transform', 'fill', 'fill-opacity', 'fill-rule', 'opacity', 'stroke', 'stroke-dasharray', 'stroke-linecap', 'stroke-dashoffset', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'id', 'paint-order', 'vector-effect', 'instantiated_by_use', 'clip-path' ]; /* _FROM_SVG_END_ */ /** * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion. */ fabric.DPI = 96; fabric.reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)'; fabric.fontPaths = { }; fabric.iMatrix = [1, 0, 0, 1, 0, 0]; fabric.canvasModule = 'canvas'; /** * Pixel limit for cache canvases. 1Mpx , 4Mpx should be fine. * @since 1.7.14 * @type Number * @default */ fabric.perfLimitSizeTotal = 2097152; /** * Pixel limit for cache canvases width or height. IE fixes the maximum at 5000 * @since 1.7.14 * @type Number * @default */ fabric.maxCacheSideLimit = 4096; /** * Lowest pixel limit for cache canvases, set at 256PX * @since 1.7.14 * @type Number * @default */ fabric.minCacheSideLimit = 256; /** * Cache Object for widths of chars in text rendering. */ fabric.charWidthsCache = { }; /** * if webgl is enabled and available, textureSize will determine the size * of the canvas backend * @since 2.0.0 * @type Number * @default */ fabric.textureSize = 2048; /** * Enable webgl for filtering picture is available * A filtering backend will be initialized, this will both take memory and * time since a default 2048x2048 canvas will be created for the gl context * @since 2.0.0 * @type Boolean * @default */ fabric.enableGLFiltering = true; /** * Device Pixel Ratio * @see https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/SettingUptheCanvas/SettingUptheCanvas.html */ fabric.devicePixelRatio = fabric.window.devicePixelRatio || fabric.window.webkitDevicePixelRatio || fabric.window.mozDevicePixelRatio || 1; /** * Browser-specific constant to adjust CanvasRenderingContext2D.shadowBlur value, * which is unitless and not rendered equally across browsers. * * Values that work quite well (as of October 2017) are: * - Chrome: 1.5 * - Edge: 1.75 * - Firefox: 0.9 * - Safari: 0.95 * * @since 2.0.0 * @type Number * @default 1 */ fabric.browserShadowBlurConstant = 1; /** * This object contains the result of arc to beizer conversion for faster retrieving if the same arc needs to be converted again. * It was an internal variable, is accessible since version 2.3.4 */ fabric.arcToSegmentsCache = { }; /** * This object keeps the results of the boundsOfCurve calculation mapped by the joined arguments necessary to calculate it. * It does speed up calculation, if you parse and add always the same paths, but in case of heavy usage of freedrawing * you do not get any speed benefit and you get a big object in memory. * The object was a private variable before, while now is appended to the lib so that you have access to it and you * can eventually clear it. * It was an internal variable, is accessible since version 2.3.4 */ fabric.boundsOfCurveCache = { }; /** * If disabled boundsOfCurveCache is not used. For apps that make heavy usage of pencil drawing probably disabling it is better * @default true */ fabric.cachesBoundsOfCurve = true; fabric.initFilterBackend = function() { if (fabric.enableGLFiltering && fabric.isWebglSupported && fabric.isWebglSupported(fabric.textureSize)) { console.log('max texture size: ' + fabric.maxTextureSize); return (new fabric.WebglFilterBackend({ tileSize: fabric.textureSize })); } else if (fabric.Canvas2dFilterBackend) { return (new fabric.Canvas2dFilterBackend()); } }; if (typeof document !== 'undefined' && typeof window !== 'undefined') { // ensure globality even if entire library were function wrapped (as in Meteor.js packaging system) window.fabric = fabric; } (function() { /** * @private * @param {String} eventName * @param {Function} handler */ function _removeEventListener(eventName, handler) { if (!this.__eventListeners[eventName]) { return; } var eventListener = this.__eventListeners[eventName]; if (handler) { eventListener[eventListener.indexOf(handler)] = false; } else { fabric.util.array.fill(eventListener, false); } } /** * Observes specified event * @deprecated `observe` deprecated since 0.8.34 (use `on` instead) * @memberOf fabric.Observable * @alias on * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) * @param {Function} handler Function that receives a notification when an event of the specified type occurs * @return {Self} thisArg * @chainable */ function observe(eventName, handler) { if (!this.__eventListeners) { this.__eventListeners = { }; } // one object with key/value pairs was passed if (arguments.length === 1) { for (var prop in eventName) { this.on(prop, eventName[prop]); } } else { if (!this.__eventListeners[eventName]) { this.__eventListeners[eventName] = []; } this.__eventListeners[eventName].push(handler); } return this; } /** * Stops event observing for a particular event handler. Calling this method * without arguments removes all handlers for all events * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead) * @memberOf fabric.Observable * @alias off * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) * @param {Function} handler Function to be deleted from EventListeners * @return {Self} thisArg * @chainable */ function stopObserving(eventName, handler) { if (!this.__eventListeners) { return; } // remove all key/value pairs (event name -> event handler) if (arguments.length === 0) { for (eventName in this.__eventListeners) { _removeEventListener.call(this, eventName); } } // one object with key/value pairs was passed else if (arguments.length === 1 && typeof arguments[0] === 'object') { for (var prop in eventName) { _removeEventListener.call(this, prop, eventName[prop]); } } else { _removeEventListener.call(this, eventName, handler); } return this; } /** * Fires event with an optional options object * @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead) * @memberOf fabric.Observable * @alias trigger * @param {String} eventName Event name to fire * @param {Object} [options] Options object * @return {Self} thisArg * @chainable */ function fire(eventName, options) { if (!this.__eventListeners) { return; } var listenersForEvent = this.__eventListeners[eventName]; if (!listenersForEvent) { return; } for (var i = 0, len = listenersForEvent.length; i < len; i++) { listenersForEvent[i] && listenersForEvent[i].call(this, options || { }); } this.__eventListeners[eventName] = listenersForEvent.filter(function(value) { return value !== false; }); return this; } /** * @namespace fabric.Observable * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#events} * @see {@link http://fabricjs.com/events|Events demo} */ fabric.Observable = { observe: observe, stopObserving: stopObserving, fire: fire, on: observe, off: stopObserving, trigger: fire }; })(); /** * @namespace fabric.Collection */ fabric.Collection = { _objects: [], /** * Adds objects to collection, Canvas or Group, then renders canvas * (if `renderOnAddRemove` is not `false`). * in case of Group no changes to bounding box are made. * Objects should be instances of (or inherit from) fabric.Object * Use of this function is highly discouraged for groups. * you can add a bunch of objects with the add method but then you NEED * to run a addWithUpdate call for the Group class or position/bbox will be wrong. * @param {...fabric.Object} object Zero or more fabric instances * @return {Self} thisArg * @chainable */ add: function () { this._objects.push.apply(this._objects, arguments); if (this._onObjectAdded) { for (var i = 0, length = arguments.length; i < length; i++) { this._onObjectAdded(arguments[i]); } } this.renderOnAddRemove && this.requestRenderAll(); return this; }, /** * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`) * An object should be an instance of (or inherit from) fabric.Object * Use of this function is highly discouraged for groups. * you can add a bunch of objects with the insertAt method but then you NEED * to run a addWithUpdate call for the Group class or position/bbox will be wrong. * @param {Object} object Object to insert * @param {Number} index Index to insert object at * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs * @return {Self} thisArg * @chainable */ insertAt: function (object, index, nonSplicing) { var objects = this._objects; if (nonSplicing) { objects[index] = object; } else { objects.splice(index, 0, object); } this._onObjectAdded && this._onObjectAdded(object); this.renderOnAddRemove && this.requestRenderAll(); return this; }, /** * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) * @param {...fabric.Object} object Zero or more fabric instances * @return {Self} thisArg * @chainable */ remove: function() { var objects = this._objects, index, somethingRemoved = false; for (var i = 0, length = arguments.length; i < length; i++) { index = objects.indexOf(arguments[i]); // only call onObjectRemoved if an object was actually removed if (index !== -1) { somethingRemoved = true; objects.splice(index, 1); this._onObjectRemoved && this._onObjectRemoved(arguments[i]); } } this.renderOnAddRemove && somethingRemoved && this.requestRenderAll(); return this; }, /** * Executes given function for each object in this group * @param {Function} callback * Callback invoked with current object as first argument, * index - as second and an array of all objects - as third. * Callback is invoked in a context of Global Object (e.g. `window`) * when no `context` argument is given * * @param {Object} context Context (aka thisObject) * @return {Self} thisArg * @chainable */ forEachObject: function(callback, context) { var objects = this.getObjects(); for (var i = 0, len = objects.length; i < len; i++) { callback.call(context, objects[i], i, objects); } return this; }, /** * Returns an array of children objects of this instance * Type parameter introduced in 1.3.10 * since 2.3.5 this method return always a COPY of the array; * @param {String} [type] When specified, only objects of this type are returned * @return {Array} */ getObjects: function(type) { if (typeof type === 'undefined') { return this._objects.concat(); } return this._objects.filter(function(o) { return o.type === type; }); }, /** * Returns object at specified index * @param {Number} index * @return {Self} thisArg */ item: function (index) { return this._objects[index]; }, /** * Returns true if collection contains no objects * @return {Boolean} true if collection is empty */ isEmpty: function () { return this._objects.length === 0; }, /** * Returns a size of a collection (i.e: length of an array containing its objects) * @return {Number} Collection size */ size: function() { return this._objects.length; }, /** * Returns true if collection contains an object * @param {Object} object Object to check against * @return {Boolean} `true` if collection contains an object */ contains: function(object) { return this._objects.indexOf(object) > -1; }, /** * Returns number representation of a collection complexity * @return {Number} complexity */ complexity: function () { return this._objects.reduce(function (memo, current) { memo += current.complexity ? current.complexity() : 0; return memo; }, 0); } }; /** * @namespace fabric.CommonMethods */ fabric.CommonMethods = { /** * Sets object's properties from options * @param {Object} [options] Options object */ _setOptions: function(options) { for (var prop in options) { this.set(prop, options[prop]); } }, /** * @private * @param {Object} [filler] Options object * @param {String} [property] property to set the Gradient to */ _initGradient: function(filler, property) { if (filler && filler.colorStops && !(filler instanceof fabric.Gradient)) { this.set(property, new fabric.Gradient(filler)); } }, /** * @private * @param {Object} [filler] Options object * @param {String} [property] property to set the Pattern to * @param {Function} [callback] callback to invoke after pattern load */ _initPattern: function(filler, property, callback) { if (filler && filler.source && !(filler instanceof fabric.Pattern)) { this.set(property, new fabric.Pattern(filler, callback)); } else { callback && callback(); } }, /** * @private * @param {Object} [options] Options object */ _initClipping: function(options) { if (!options.clipTo || typeof options.clipTo !== 'string') { return; } var functionBody = fabric.util.getFunctionBody(options.clipTo); if (typeof functionBody !== 'undefined') { this.clipTo = new Function('ctx', functionBody); } }, /** * @private */ _setObject: function(obj) { for (var prop in obj) { this._set(prop, obj[prop]); } }, /** * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. * @param {String|Object} key Property name or object (if object, iterate over the object properties) * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one) * @return {fabric.Object} thisArg * @chainable */ set: function(key, value) { if (typeof key === 'object') { this._setObject(key); } else { if (typeof value === 'function' && key !== 'clipTo') { this._set(key, value(this.get(key))); } else { this._set(key, value); } } return this; }, _set: function(key, value) { this[key] = value; }, /** * Toggles specified property from `true` to `false` or from `false` to `true` * @param {String} property Property to toggle * @return {fabric.Object} thisArg * @chainable */ toggle: function(property) { var value = this.get(property); if (typeof value === 'boolean') { this.set(property, !value); } return this; }, /** * Basic getter * @param {String} property Property name * @return {*} value of a property */ get: function(property) { return this[property]; } }; (function(global) { var sqrt = Math.sqrt, atan2 = Math.atan2, pow = Math.pow, abs = Math.abs, PiBy180 = Math.PI / 180, PiBy2 = Math.PI / 2; /** * @namespace fabric.util */ fabric.util = { /** * Calculate the cos of an angle, avoiding returning floats for known results * @static * @memberOf fabric.util * @param {Number} angle the angle in radians or in degree * @return {Number} */ cos: function(angle) { if (angle === 0) { return 1; } if (angle < 0) { // cos(a) = cos(-a) angle = -angle; } var angleSlice = angle / PiBy2; switch (angleSlice) { case 1: case 3: return 0; case 2: return -1; } return Math.cos(angle); }, /** * Calculate the sin of an angle, avoiding returning floats for known results * @static * @memberOf fabric.util * @param {Number} angle the angle in radians or in degree * @return {Number} */ sin: function(angle) { if (angle === 0) { return 0; } var angleSlice = angle / PiBy2, sign = 1; if (angle < 0) { // sin(-a) = -sin(a) sign = -1; } switch (angleSlice) { case 1: return sign; case 2: return 0; case 3: return -sign; } return Math.sin(angle); }, /** * Removes value from an array. * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf` * @static * @memberOf fabric.util * @param {Array} array * @param {*} value * @return {Array} original array */ removeFromArray: function(array, value) { var idx = array.indexOf(value); if (idx !== -1) { array.splice(idx, 1); } return array; }, /** * Returns random number between 2 specified ones. * @static * @memberOf fabric.util * @param {Number} min lower limit * @param {Number} max upper limit * @return {Number} random value (between min and max) */ getRandomInt: function(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }, /** * Transforms degrees to radians. * @static * @memberOf fabric.util * @param {Number} degrees value in degrees * @return {Number} value in radians */ degreesToRadians: function(degrees) { return degrees * PiBy180; }, /** * Transforms radians to degrees. * @static * @memberOf fabric.util * @param {Number} radians value in radians * @return {Number} value in degrees */ radiansToDegrees: function(radians) { return radians / PiBy180; }, /** * Rotates `point` around `origin` with `radians` * @static * @memberOf fabric.util * @param {fabric.Point} point The point to rotate * @param {fabric.Point} origin The origin of the rotation * @param {Number} radians The radians of the angle for the rotation * @return {fabric.Point} The new rotated point */ rotatePoint: function(point, origin, radians) { point.subtractEquals(origin); var v = fabric.util.rotateVector(point, radians); return new fabric.Point(v.x, v.y).addEquals(origin); }, /** * Rotates `vector` with `radians` * @static * @memberOf fabric.util * @param {Object} vector The vector to rotate (x and y) * @param {Number} radians The radians of the angle for the rotation * @return {Object} The new rotated point */ rotateVector: function(vector, radians) { var sin = fabric.util.sin(radians), cos = fabric.util.cos(radians), rx = vector.x * cos - vector.y * sin, ry = vector.x * sin + vector.y * cos; return { x: rx, y: ry }; }, /** * Apply transform t to point p * @static * @memberOf fabric.util * @param {fabric.Point} p The point to transform * @param {Array} t The transform * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied * @return {fabric.Point} The transformed point */ transformPoint: function(p, t, ignoreOffset) { if (ignoreOffset) { return new fabric.Point( t[0] * p.x + t[2] * p.y, t[1] * p.x + t[3] * p.y ); } return new fabric.Point( t[0] * p.x + t[2] * p.y + t[4], t[1] * p.x + t[3] * p.y + t[5] ); }, /** * Returns coordinates of points's bounding rectangle (left, top, width, height) * @param {Array} points 4 points array * @return {Object} Object with left, top, width, height properties */ makeBoundingBoxFromPoints: function(points) { var xPoints = [points[0].x, points[1].x, points[2].x, points[3].x], minX = fabric.util.array.min(xPoints), maxX = fabric.util.array.max(xPoints), width = maxX - minX, yPoints = [points[0].y, points[1].y, points[2].y, points[3].y], minY = fabric.util.array.min(yPoints), maxY = fabric.util.array.max(yPoints), height = maxY - minY; return { left: minX, top: minY, width: width, height: height }; }, /** * Invert transformation t * @static * @memberOf fabric.util * @param {Array} t The transform * @return {Array} The inverted transform */ invertTransform: function(t) { var a = 1 / (t[0] * t[3] - t[1] * t[2]), r = [a * t[3], -a * t[1], -a * t[2], a * t[0]], o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r, true); r[4] = -o.x; r[5] = -o.y; return r; }, /** * A wrapper around Number#toFixed, which contrary to native method returns number, not string. * @static * @memberOf fabric.util * @param {Number|String} number number to operate on * @param {Number} fractionDigits number of fraction digits to "leave" * @return {Number} */ toFixed: function(number, fractionDigits) { return parseFloat(Number(number).toFixed(fractionDigits)); }, /** * Converts from attribute value to pixel value if applicable. * Returns converted pixels or original value not converted. * @param {Number|String} value number to operate on * @param {Number} fontSize * @return {Number|String} */ parseUnit: function(value, fontSize) { var unit = /\D{0,2}$/.exec(value), number = parseFloat(value); if (!fontSize) { fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; } switch (unit[0]) { case 'mm': return number * fabric.DPI / 25.4; case 'cm': return number * fabric.DPI / 2.54; case 'in': return number * fabric.DPI; case 'pt': return number * fabric.DPI / 72; // or * 4 / 3 case 'pc': return number * fabric.DPI / 72 * 12; // or * 16 case 'em': return number * fontSize; default: return number; } }, /** * Function which always returns `false`. * @static * @memberOf fabric.util * @return {Boolean} */ falseFunction: function() { return false; }, /** * Returns klass "Class" object of given namespace * @memberOf fabric.util * @param {String} type Type of object (eg. 'circle') * @param {String} namespace Namespace to get klass "Class" object from * @return {Object} klass "Class" */ getKlass: function(type, namespace) { // capitalize first letter only type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1)); return fabric.util.resolveNamespace(namespace)[type]; }, /** * Returns array of attributes for given svg that fabric parses * @memberOf fabric.util * @param {String} type Type of svg element (eg. 'circle') * @return {Array} string names of supported attributes */ getSvgAttributes: function(type) { var attributes = [ 'instantiated_by_use', 'style', 'id', 'class' ]; switch (type) { case 'linearGradient': attributes = attributes.concat(['x1', 'y1', 'x2', 'y2', 'gradientUnits', 'gradientTransform']); break; case 'radialGradient': attributes = attributes.concat(['gradientUnits', 'gradientTransform', 'cx', 'cy', 'r', 'fx', 'fy', 'fr']); break; case 'stop': attributes = attributes.concat(['offset', 'stop-color', 'stop-opacity']); break; } return attributes; }, /** * Returns object of given namespace * @memberOf fabric.util * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric' * @return {Object} Object for given namespace (default fabric) */ resolveNamespace: function(namespace) { if (!namespace) { return fabric; } var parts = namespace.split('.'), len = parts.length, i, obj = global || fabric.window; for (i = 0; i < len; ++i) { obj = obj[parts[i]]; } return obj; }, /** * Loads image element from given url and passes it to a callback * @memberOf fabric.util * @param {String} url URL representing an image * @param {Function} callback Callback; invoked with loaded image * @param {*} [context] Context to invoke callback in * @param {Object} [crossOrigin] crossOrigin value to set image element to */ loadImage: function(url, callback, context, crossOrigin) { if (!url) { callback && callback.call(context, url); return; } var img = fabric.util.createImage(); /** @ignore */ var onLoadCallback = function () { callback && callback.call(context, img); img = img.onload = img.onerror = null; }; img.onload = onLoadCallback; /** @ignore */ img.onerror = function() { fabric.log('Error loading ' + img.src); callback && callback.call(context, null, true); img = img.onload = img.onerror = null; }; // data-urls appear to be buggy with crossOrigin // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767 // see https://code.google.com/p/chromium/issues/detail?id=315152 // https://bugzilla.mozilla.org/show_bug.cgi?id=935069 if (url.indexOf('data') !== 0 && crossOrigin) { img.crossOrigin = crossOrigin; } // IE10 / IE11-Fix: SVG contents from data: URI // will only be available if the IMG is present // in the DOM (and visible) if (url.substring(0,14) === 'data:image/svg') { img.onload = null; fabric.util.loadImageInDom(img, onLoadCallback); } img.src = url; }, /** * Attaches SVG image with data: URL to the dom * @memberOf fabric.util * @param {Object} img Image object with data:image/svg src * @param {Function} callback Callback; invoked with loaded image * @return {Object} DOM element (div containing the SVG image) */ loadImageInDom: function(img, onLoadCallback) { var div = fabric.document.createElement('div'); div.style.width = div.style.height = '1px'; div.style.left = div.style.top = '-100%'; div.style.position = 'absolute'; div.appendChild(img); fabric.document.querySelector('body').appendChild(div); /** * Wrap in function to: * 1. Call existing callback * 2. Cleanup DOM */ img.onload = function () { onLoadCallback(); div.parentNode.removeChild(div); div = null; }; }, /** * Creates corresponding fabric instances from their object representations * @static * @memberOf fabric.util * @param {Array} objects Objects to enliven * @param {Function} callback Callback to invoke when all objects are created * @param {String} namespace Namespace to get klass "Class" object from * @param {Function} reviver Method for further parsing of object elements, * called after each fabric object created. */ enlivenObjects: function(objects, callback, namespace, reviver) { objects = objects || []; function onLoaded() { if (++numLoadedObjects === numTotalObjects) { callback && callback(enlivenedObjects); } } var enlivenedObjects = [], numLoadedObjects = 0, numTotalObjects = objects.length; if (!numTotalObjects) { callback && callback(enlivenedObjects); return; } objects.forEach(function (o, index) { // if sparse array if (!o || !o.type) { onLoaded(); return; } var klass = fabric.util.getKlass(o.type, namespace); klass.fromObject(o, function (obj, error) { error || (enlivenedObjects[index] = obj); reviver && reviver(o, obj, error); onLoaded(); }); }); }, /** * Create and wait for loading of patterns * @static * @memberOf fabric.util * @param {Array} patterns Objects to enliven * @param {Function} callback Callback to invoke when all objects are created * called after each fabric object created. */ enlivenPatterns: function(patterns, callback) { patterns = patterns || []; function onLoaded() { if (++numLoadedPatterns === numPatterns) { callback && callback(enlivenedPatterns); } } var enlivenedPatterns = [], numLoadedPatterns = 0, numPatterns = patterns.length; if (!numPatterns) { callback && callback(enlivenedPatterns); return; } patterns.forEach(function (p, index) { if (p && p.source) { new fabric.Pattern(p, function(pattern) { enlivenedPatterns[index] = pattern; onLoaded(); }); } else { enlivenedPatterns[index] = p; onLoaded(); } }); }, /** * Groups SVG elements (usually those retrieved from SVG document) * @static * @memberOf fabric.util * @param {Array} elements SVG elements to group * @param {Object} [options] Options object * @param {String} path Value to set sourcePath to * @return {fabric.Object|fabric.Group} */ groupSVGElements: function(elements, options, path) { var object; if (elements && elements.length === 1) { return elements[0]; } if (options) { if (options.width && options.height) { options.centerPoint = { x: options.width / 2, y: options.height / 2 }; } else { delete options.width; delete options.height; } } object = new fabric.Group(elements, options); if (typeof path !== 'undefined') { object.sourcePath = path; } return object; }, /** * Populates an object with properties of another object * @static * @memberOf fabric.util * @param {Object} source Source object * @param {Object} destination Destination object * @return {Array} properties Properties names to include */ populateWithProperties: function(source, destination, properties) { if (properties && Object.prototype.toString.call(properties) === '[object Array]') { for (var i = 0, len = properties.length; i < len; i++) { if (properties[i] in source) { destination[properties[i]] = source[properties[i]]; } } } }, /** * Draws a dashed line between two points * * This method is used to draw dashed line around selection area. * See <a href="http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas">dotted stroke in canvas</a> * * @param {CanvasRenderingContext2D} ctx context * @param {Number} x start x coordinate * @param {Number} y start y coordinate * @param {Number} x2 end x coordinate * @param {Number} y2 end y coordinate * @param {Array} da dash array pattern */ drawDashedLine: function(ctx, x, y, x2, y2, da) { var dx = x2 - x, dy = y2 - y, len = sqrt(dx * dx + dy * dy), rot = atan2(dy, dx), dc = da.length, di = 0, draw = true; ctx.save(); ctx.translate(x, y); ctx.moveTo(0, 0); ctx.rotate(rot); x = 0; while (len > x) { x += da[di++ % dc]; if (x > len) { x = len; } ctx[draw ? 'lineTo' : 'moveTo'](x, 0); draw = !draw; } ctx.restore(); }, /** * Creates canvas element * @static * @memberOf fabric.util * @return {CanvasElement} initialized canvas element */ createCanvasElement: function() { return fabric.document.createElement('canvas'); }, /** * Creates a canvas element that is a copy of another and is also painted * @param {CanvasElement} canvas to copy size and content of * @static * @memberOf fabric.util * @return {CanvasElement} initialized canvas element */ copyCanvasElement: function(canvas) { var newCanvas = fabric.util.createCanvasElement(); newCanvas.width = canvas.width; newCanvas.height = canvas.height; newCanvas.getContext('2d').drawImage(canvas, 0, 0); return newCanvas; }, /** * since 2.6.0 moved from canvas instance to utility. * @param {CanvasElement} canvasEl to copy size and content of * @param {String} format 'jpeg' or 'png', in some browsers 'webp' is ok too * @param {Number} quality <= 1 and > 0 * @static * @memberOf fabric.util * @return {String} data url */ toDataURL: function(canvasEl, format, quality) { return canvasEl.toDataURL('image/' + format, quality); }, /** * Creates image element (works on client and node) * @static * @memberOf fabric.util * @return {HTMLImageElement} HTML image element */ createImage: function() { return fabric.document.createElement('img'); }, /** * @static * @memberOf fabric.util * @deprecated since 2.0.0 * @param {fabric.Object} receiver Object implementing `clipTo` method * @param {CanvasRenderingContext2D} ctx Context to clip */ clipContext: function(receiver, ctx) { ctx.save(); ctx.beginPath(); receiver.clipTo(ctx); ctx.clip(); }, /** * Multiply matrix A by matrix B to nest transformations * @static * @memberOf fabric.util * @param {Array} a First transformMatrix * @param {Array} b Second transformMatrix * @param {Boolean} is2x2 flag to multiply matrices as 2x2 matrices * @return {Array} The product of the two transform matrices */ multiplyTransformMatrices: function(a, b, is2x2) { // Matrix multiply a * b return [ a[0] * b[0] + a[2] * b[1], a[1] * b[0] + a[3] * b[1], a[0] * b[2] + a[2] * b[3], a[1] * b[2] + a[3] * b[3], is2x2 ? 0 : a[0] * b[4] + a[2] * b[5] + a[4], is2x2 ? 0 : a[1] * b[4] + a[3] * b[5] + a[5] ]; }, /** * Decomposes standard 2x2 matrix into transform componentes * @static * @memberOf fabric.util * @param {Array} a transformMatrix * @return {Object} Components of transform */ qrDecompose: function(a) { var angle = atan2(a[1], a[0]), denom = pow(a[0], 2) + pow(a[1], 2), scaleX = sqrt(denom), scaleY = (a[0] * a[3] - a[2] * a [1]) / scaleX, skewX = atan2(a[0] * a[2] + a[1] * a [3], denom); return { angle: angle / PiBy180, scaleX: scaleX, scaleY: scaleY, skewX: skewX / PiBy180, skewY: 0, translateX: a[4], translateY: a[5] }; }, customTransformMatrix: function(scaleX, scaleY, skewX) { var skewMatrixX = [1, 0, abs(Math.tan(skewX * PiBy180)), 1], scaleMatrix = [abs(scaleX), 0, 0, abs(scaleY)]; return fabric.util.multiplyTransformMatrices(scaleMatrix, skewMatrixX, true); }, /** * reset an object transform state to neutral. Top and left are not accounted for * @static * @memberOf fabric.util * @param {fabric.Object} target object to transform */ resetObjectTransform: function (target) { target.scaleX = 1; target.scaleY = 1; target.skewX = 0; target.skewY = 0; target.flipX = false; target.flipY = false; target.rotate(0); }, /** * Extract Object transform values * @static * @memberOf fabric.util * @param {fabric.Object} target object to read from * @return {Object} Components of transform */ saveObjectTransform: function (target) { return { scaleX: target.scaleX, scaleY: target.scaleY, skewX: target.skewX, skewY: target.skewY, angle: target.angle, left: target.left, flipX: target.flipX, flipY: target.flipY, top: target.top }; }, /** * Returns string representation of function body * @param {Function} fn Function to get body of * @return {String} Function body */ getFunctionBody: function(fn) { return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1]; }, /** * Returns true if context has transparent pixel * at specified location (taking tolerance into account) * @param {CanvasRenderingContext2D} ctx context * @param {Number} x x coordinate * @param {Number} y y coordinate * @param {Number} tolerance Tolerance */ isTransparent: function(ctx, x, y, tolerance) { // If tolerance is > 0 adjust start coords to take into account. // If moves off Canvas fix to 0 if (tolerance > 0) { if (x > tolerance) { x -= tolerance; } else { x = 0; } if (y > tolerance) { y -= tolerance; } else { y = 0; } } var _isTransparent = true, i, temp, imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1), l = imageData.data.length; // Split image data - for tolerance > 1, pixelDataSize = 4; for (i = 3; i < l; i += 4) { temp = imageData.data[i]; _isTransparent = temp <= 0; if (_isTransparent === false) { break; // Stop if colour found } } imageData = null; return _isTransparent; }, /** * Parse preserveAspectRatio attribute from element * @param {string} attribute to be parsed * @return {Object} an object containing align and meetOrSlice attribute */ parsePreserveAspectRatioAttribute: function(attribute) { var meetOrSlice = 'meet', alignX = 'Mid', alignY = 'Mid', aspectRatioAttrs = attribute.split(' '), align; if (aspectRatioAttrs && aspectRatioAttrs.length) { meetOrSlice = aspectRatioAttrs.pop(); if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') { align = meetOrSlice; meetOrSlice = 'meet'; } else if (aspectRatioAttrs.length) { align = aspectRatioAttrs.pop(); } } //divide align in alignX and alignY alignX = align !== 'none' ? align.slice(1, 4) : 'none'; alignY = align !== 'none' ? align.slice(5, 8) : 'none'; return { meetOrSlice: meetOrSlice, alignX: alignX, alignY: alignY }; }, /** * Clear char widths cache for the given font family or all the cache if no * fontFamily is specified. * Use it if you know you are loading fonts in a lazy way and you are not waiting * for custom fonts to load properly when adding text objects to the canvas. * If a text object is added when its own font is not loaded yet, you will get wrong * measurement and so wrong bounding boxes. * After the font cache is cleared, either change the textObject text content or call * initDimensions() to trigger a recalculation * @memberOf fabric.util * @param {String} [fontFamily] font family to clear */ clearFabricFontCache: function(fontFamily) { fontFamily = (fontFamily || '').toLowerCase(); if (!fontFamily) { fabric.charWidthsCache = { }; } else if (fabric.charWidthsCache[fontFamily]) { delete fabric.charWidthsCache[fontFamily]; } }, /** * Given current aspect ratio, determines the max width and height that can * respect the total allowed area for the cache. * @memberOf fabric.util * @param {Number} ar aspect ratio * @param {Number} maximumArea Maximum area you want to achieve * @return {Object.x} Limited dimensions by X * @return {Object.y} Limited dimensions by Y */ limitDimsByArea: function(ar, maximumArea) { var roughWidth = Math.sqrt(maximumArea * ar), perfLimitSizeY = Math.floor(maximumArea / roughWidth); return { x: Math.floor(roughWidth), y: perfLimitSizeY }; }, capValue: function(min, value, max) { return Math.max(min, Math.min(value, max)); }, findScaleToFit: function(source, destination) { return Math.min(destination.width / source.width, destination.height / source.height); }, findScaleToCover: function(source, destination) { return Math.max(destination.width / source.width, destination.height / source.height); } }; })(typeof exports !== 'undefined' ? exports : this); (function() { var _join = Array.prototype.join; /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here * http://mozilla.org/MPL/2.0/ */ function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) { var argsString = _join.call(arguments); if (fabric.arcToSegmentsCache[argsString]) { return fabric.arcToSegmentsCache[argsString]; } var PI = Math.PI, th = rotateX * PI / 180, sinTh = fabric.util.sin(th), cosTh = fabric.util.cos(th), fromX = 0, fromY = 0; rx = Math.abs(rx); ry = Math.abs(ry); var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5, py = -cosTh * toY * 0.5 + sinTh * toX * 0.5, rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px, pl = rx2 * ry2 - rx2 * py2 - ry2 * px2, root = 0; if (pl < 0) { var s = Math.sqrt(1 - pl / (rx2 * ry2)); rx *= s; ry *= s; } else { root = (large === sweep ? -1.0 : 1.0) * Math.sqrt( pl / (rx2 * py2 + ry2 * px2)); } var cx = root * rx * py / ry, cy = -root * ry * px / rx, cx1 = cosTh * cx - sinTh * cy + toX * 0.5, cy1 = sinTh * cx + cosTh * cy + toY * 0.5, mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry), dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry); if (sweep === 0 && dtheta > 0) { dtheta -= 2 * PI; } else if (sweep === 1 && dtheta < 0) { dtheta += 2 * PI; } // Convert into cubic bezier segments <= 90deg var segments = Math.ceil(Math.abs(dtheta / PI * 2)), result = [], mDelta = dtheta / segments, mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2), th3 = mTheta + mDelta; for (var i = 0; i < segments; i++) { result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY); fromX = result[i][4]; fromY = result[i][5]; mTheta = th3; th3 += mDelta; } fabric.arcToSegmentsCache[argsString] = result; return result; } function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) { var costh2 = fabric.util.cos(th2), sinth2 = fabric.util.sin(th2), costh3 = fabric.util.cos(th3), sinth3 = fabric.util.sin(th3), toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1, toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1, cp1X = fromX + mT * ( -cosTh * rx * sinth2 - sinTh * ry * costh2), cp1Y = fromY + mT * ( -sinTh * rx * sinth2 + cosTh * ry * costh2), cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3), cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3); return [ cp1X, cp1Y, cp2X, cp2Y, toX, toY ]; } /* * Private */ function calcVectorAngle(ux, uy, vx, vy) { var ta = Math.atan2(uy, ux), tb = Math.atan2(vy, vx); if (tb >= ta) { return tb - ta; } else { return 2 * Math.PI - (ta - tb); } } /** * Draws arc * @param {CanvasRenderingContext2D} ctx * @param {Number} fx * @param {Number} fy * @param {Array} coords */ fabric.util.drawArc = function(ctx, fx, fy, coords) { var rx = coords[0], ry = coords[1], rot = coords[2], large = coords[3], sweep = coords[4], tx = coords[5], ty = coords[6], segs = [[], [], [], []], segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); for (var i = 0, len = segsNorm.length; i < len; i++) { segs[i][0] = segsNorm[i][0] + fx; segs[i][1] = segsNorm[i][1] + fy; segs[i][2] = segsNorm[i][2] + fx; segs[i][3] = segsNorm[i][3] + fy; segs[i][4] = segsNorm[i][4] + fx; segs[i][5] = segsNorm[i][5] + fy; ctx.bezierCurveTo.apply(ctx, segs[i]); } }; /** * Calculate bounding box of a elliptic-arc * @param {Number} fx start point of arc * @param {Number} fy * @param {Number} rx horizontal radius * @param {Number} ry vertical radius * @param {Number} rot angle of horizontal axe * @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points * @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction * @param {Number} tx end point of arc * @param {Number} ty */ fabric.util.getBoundsOfArc = function(fx, fy, rx, ry, rot, large, sweep, tx, ty) { var fromX = 0, fromY = 0, bound, bounds = [], segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); for (var i = 0, len = segs.length; i < len; i++) { bound = getBoundsOfCurve(fromX, fromY, segs[i][0], segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5]); bounds.push({ x: bound[0].x + fx, y: bound[0].y + fy }); bounds.push({ x: bound[1].x + fx, y: bound[1].y + fy }); fromX = segs[i][4]; fromY = segs[i][5]; } return bounds; }; /** * Calculate bounding box of a beziercurve * @param {Number} x0 starting point * @param {Number} y0 * @param {Number} x1 first control point * @param {Number} y1 * @param {Number} x2 secondo control point * @param {Number} y2 * @param {Number} x3 end of beizer * @param {Number} y3 */ // taken from http://jsbin.com/ivomiq/56/edit no credits available for that. function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) { var argsString; if (fabric.cachesBoundsOfCurve) { argsString = _join.call(arguments); if (fabric.boundsOfCurveCache[argsString]) { return fabric.boundsOfCurveCache[argsString]; } } var sqrt = Math.sqrt, min = Math.min, max = Math.max, abs = Math.abs, tvalues = [], bounds = [[], []], a, b, c, t, t1, t2, b2ac, sqrtb2ac; b = 6 * x0 - 12 * x1 + 6 * x2; a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; c = 3 * x1 - 3 * x0; for (var i = 0; i < 2; ++i) { if (i > 0) { b = 6 * y0 - 12 * y1 + 6 * y2; a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; c = 3 * y1 - 3 * y0; } if (abs(a) < 1e-12) { if (abs(b) < 1e-12) { continue; } t = -c / b; if (0 < t && t < 1) { tvalues.push(t); } continue; } b2ac = b * b - 4 * c * a; if (b2ac < 0) { continue; } sqrtb2ac = sqrt(b2ac); t1 = (-b + sqrtb2ac) / (2 * a); if (0 < t1 && t1 < 1) { tvalues.push(t1); } t2 = (-b - sqrtb2ac) / (2 * a); if (0 < t2 && t2 < 1) { tvalues.push(t2); } } var x, y, j = tvalues.length, jlen = j, mt; while (j--) { t = tvalues[j]; mt = 1 - t; x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3); bounds[0][j] = x; y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3); bounds[1][j] = y; } bounds[0][jlen] = x0; bounds[1][jlen] = y0; bounds[0][jlen + 1] = x3; bounds[1][jlen + 1] = y3; var result = [ { x: min.apply(null, bounds[0]), y: min.apply(null, bounds[1]) }, { x: max.apply(null, bounds[0]), y: max.apply(null, bounds[1]) } ]; if (fabric.cachesBoundsOfCurve) { fabric.boundsOfCurveCache[argsString] = result; } return result; } fabric.util.getBoundsOfCurve = getBoundsOfCurve; })(); (function() { var slice = Array.prototype.slice; /** * Invokes method on all items in a given array * @memberOf fabric.util.array * @param {Array} array Array to iterate over * @param {String} method Name of a method to invoke * @return {Array} */ function invoke(array, method) { var args = slice.call(arguments, 2), result = []; for (var i = 0, len = array.length; i < len; i++) { result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]); } return result; } /** * Finds maximum value in array (not necessarily "first" one) * @memberOf fabric.util.array * @param {Array} array Array to iterate over * @param {String} byProperty * @return {*} */ function max(array, byProperty) { return find(array, byProperty, function(value1, value2) { return value1 >= value2; }); } /** * Finds minimum value in array (not necessarily "first" one) * @memberOf fabric.util.array * @param {Array} array Array to iterate over * @param {String} byProperty * @return {*} */ function min(array, byProperty) { return find(array, byProperty, function(value1, value2) { return value1 < value2; }); } /** * @private */ function fill(array, value) { var k = array.length; while (k--) { array[k] = value; } return array; } /** * @private */ function find(array, byProperty, condition) { if (!array || array.length === 0) { return; } var i = array.length - 1, result = byProperty ? array[i][byProperty] : array[i]; if (byProperty) { while (i--) { if (condition(array[i][byProperty], result)) { result = array[i][byProperty]; } } } else { while (i--) { if (condition(array[i], result)) { result = array[i]; } } } return result; } /** * @namespace fabric.util.array */ fabric.util.array = { fill: fill, invoke: invoke, min: min, max: max }; })(); (function() { /** * Copies all enumerable properties of one js object to another * this does not and cannot compete with generic utils. * Does not clone or extend fabric.Object subclasses. * This is mostly for internal use and has extra handling for fabricJS objects * it skips the canvas property in deep cloning. * @memberOf fabric.util.object * @param {Object} destination Where to copy to * @param {Object} source Where to copy from * @return {Object} */ function extend(destination, source, deep) { // JScript DontEnum bug is not taken care of // the deep clone is for internal use, is not meant to avoid // javascript traps or cloning html element or self referenced objects. if (deep) { if (!fabric.isLikelyNode && source instanceof Element) { // avoid cloning deep images, canvases, destination = source; } else if (source instanceof Array) { destination = []; for (var i = 0, len = source.length; i < len; i++) { destination[i] = extend({ }, source[i], deep); } } else if (source && typeof source === 'object') { for (var property in source) { if (property === 'canvas') { destination[property] = extend({ }, source[property]); } else if (source.hasOwnProperty(property)) { destination[property] = extend({ }, source[property], deep); } } } else { // this sounds odd for an extend but is ok for recursive use destination = source; } } else { for (var property in source) { destination[property] = source[property]; } } return destination; } /** * Creates an empty object and copies all enumerable properties of another object to it * @memberOf fabric.util.object * TODO: this function return an empty object if you try to clone null * @param {Object} object Object to clone * @return {Object} */ function clone(object, deep) { return extend({ }, object, deep); } /** @namespace fabric.util.object */ fabric.util.object = { extend: extend, clone: clone }; fabric.util.object.extend(fabric.util, fabric.Observable); })(); (function() { /** * Camelizes a string * @memberOf fabric.util.string * @param {String} string String to camelize * @return {String} Camelized version of a string */ function camelize(string) { return string.replace(/-+(.)?/g, function(match, character) { return character ? character.toUpperCase() : ''; }); } /** * Capitalizes a string * @memberOf fabric.util.string * @param {String} string String to capitalize * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized * and other letters stay untouched, if false first letter is capitalized * and other letters are converted to lowercase. * @return {String} Capitalized version of a string */ function capitalize(string, firstLetterOnly) { return string.charAt(0).toUpperCase() + (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase()); } /** * Escapes XML in a string * @memberOf fabric.util.string * @param {String} string String to escape * @return {String} Escaped version of a string */ function escapeXml(string) { return string.replace(/&/g, '&') .replace(/"/g, '"') .replace(/'/g, ''') .replace(/</g, '<') .replace(/>/g, '>'); } /** * Divide a string in the user perceived single units * @memberOf fabric.util.string * @param {String} textstring String to escape * @return {Array} array containing the graphemes */ function graphemeSplit(textstring) { var i = 0, chr, graphemes = []; for (i = 0, chr; i < textstring.length; i++) { if ((chr = getWholeChar(textstring, i)) === false) { continue; } graphemes.push(chr); } return graphemes; } // taken from mdn in the charAt doc page. function getWholeChar(str, i) { var code = str.charCodeAt(i); if (isNaN(code)) { return ''; // Position not found } if (code < 0xD800 || code > 0xDFFF) { return str.charAt(i); } // High surrogate (could change last hex to 0xDB7F to treat high private // surrogates as single characters) if (0xD800 <= code && code <= 0xDBFF) { if (str.length <= (i + 1)) { throw 'High surrogate without following low surrogate'; } var next = str.charCodeAt(i + 1); if (0xDC00 > next || next > 0xDFFF) { throw 'High surrogate without following low surrogate'; } return str.charAt(i) + str.charAt(i + 1); } // Low surrogate (0xDC00 <= code && code <= 0xDFFF) if (i === 0) { throw 'Low surrogate without preceding high surrogate'; } var prev = str.charCodeAt(i - 1); // (could change last hex to 0xDB7F to treat high private // surrogates as single characters) if (0xD800 > prev || prev > 0xDBFF) { throw 'Low surrogate without preceding high surrogate'; } // We can pass over low surrogates now as the second component // in a pair which we have already processed return false; } /** * String utilities * @namespace fabric.util.string */ fabric.util.string = { camelize: camelize, capitalize: capitalize, escapeXml: escapeXml, graphemeSplit: graphemeSplit }; })(); (function() { var slice = Array.prototype.slice, emptyFunction = function() { }, IS_DONTENUM_BUGGY = (function() { for (var p in { toString: 1 }) { if (p === 'toString') { return false; } } return true; })(), /** @ignore */ addMethods = function(klass, source, parent) { for (var property in source) { if (property in klass.prototype && typeof klass.prototype[property] === 'function' && (source[property] + '').indexOf('callSuper') > -1) { klass.prototype[property] = (function(property) { return function() { var superclass = this.constructor.superclass; this.constructor.superclass = parent; var returnValue = source[property].apply(this, arguments); this.constructor.superclass = superclass; if (property !== 'initialize') { return returnValue; } }; })(property); } else { klass.prototype[property] = source[property]; } if (IS_DONTENUM_BUGGY) { if (source.toString !== Object.prototype.toString) { klass.prototype.toString = source.toString; } if (source.valueOf !== Object.prototype.valueOf) { klass.prototype.valueOf = source.valueOf; } } } }; function Subclass() { } function callSuper(methodName) { var parentMethod = null, _this = this; // climb prototype chain to find method not equal to callee's method while (_this.constructor.superclass) { var superClassMethod = _this.constructor.superclass.prototype[methodName]; if (_this[methodName] !== superClassMethod) { parentMethod = superClassMethod; break; } // eslint-disable-next-line _this = _this.constructor.superclass.prototype; } if (!parentMethod) { return console.log('tried to callSuper ' + methodName + ', method not found in prototype chain', this); } return (arguments.length > 1) ? parentMethod.apply(this, slice.call(arguments, 1)) : parentMethod.call(this); } /** * Helper for creation of "classes". * @memberOf fabric.util * @param {Function} [parent] optional "Class" to inherit from * @param {Object} [properties] Properties shared by all instances of this class * (be careful modifying objects defined here as this would affect all instances) */ function createClass() { var parent = null, properties = slice.call(arguments, 0); if (typeof properties[0] === 'function') { parent = properties.shift(); } function klass() { this.initialize.apply(this, arguments); } klass.superclass = parent; klass.subclasses = []; if (parent) { Subclass.prototype = parent.prototype; klass.prototype = new Subclass(); parent.subclasses.push(klass); } for (var i = 0, length = properties.length; i < length; i++) { addMethods(klass, properties[i], parent); } if (!klass.prototype.initialize) { klass.prototype.initialize = emptyFunction; } klass.prototype.constructor = klass; klass.prototype.callSuper = callSuper; return klass; } fabric.util.createClass = createClass; })(); (function () { var unknown = 'unknown'; /* EVENT HANDLING */ function areHostMethods(object) { var methodNames = Array.prototype.slice.call(arguments, 1), t, i, len = methodNames.length; for (i = 0; i < len; i++) { t = typeof object[methodNames[i]]; if (!(/^(?:function|object|unknown)$/).test(t)) { return false; } } return true; } /** @ignore */ var getElement, setElement, getUniqueId = (function () { var uid = 0; return function (element) { return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++); }; })(); (function () { var elements = { }; /** @ignore */ getElement = function (uid) { return elements[uid]; }; /** @ignore */ setElement = function (uid, element) { elements[uid] = element; }; })(); function createListener(uid, handler) { return { handler: handler, wrappedHandler: createWrappedHandler(uid, handler) }; } function createWrappedHandler(uid, handler) { return function (e) { handler.call(getElement(uid), e || fabric.window.event); }; } function createDispatcher(uid, eventName) { return function (e) { if (handlers[uid] && handlers[uid][eventName]) { var handlersForEvent = handlers[uid][eventName]; for (var i = 0, len = handlersForEvent.length; i < len; i++) { handlersForEvent[i].call(this, e || fabric.window.event); } } }; } var shouldUseAddListenerRemoveListener = ( areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') && areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')), shouldUseAttachEventDetachEvent = ( areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') && areHostMethods(fabric.window, 'attachEvent', 'detachEvent')), // IE branch listeners = { }, // DOM L0 branch handlers = { }, addListener, removeListener; if (shouldUseAddListenerRemoveListener) { /** @ignore */ addListener = function (element, eventName, handler, options) { // since ie10 or ie9 can use addEventListener but they do not support options, i need to check element && element.addEventListener(eventName, handler, shouldUseAttachEventDetachEvent ? false : options); }; /** @ignore */ removeListener = function (element, eventName, handler, options) { element && element.removeEventListener(eventName, handler, shouldUseAttachEventDetachEvent ? false : options); }; } else if (shouldUseAttachEventDetachEvent) { /** @ignore */ addListener = function (element, eventName, handler) { if (!element) { return; } var uid = getUniqueId(element); setElement(uid, element); if (!listeners[uid]) { listeners[uid] = { }; } if (!listeners[uid][eventName]) { listeners[uid][eventName] = []; } var listener = createListener(uid, handler); listeners[uid][eventName].push(listener); element.attachEvent('on' + eventName, listener.wrappedHandler); }; /** @ignore */ removeListener = function (element, eventName, handler) { if (!element) { return; } var uid = getUniqueId(element), listener; if (listeners[uid] && listeners[uid][eventName]) { for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) { listener = listeners[uid][eventName][i]; if (listener && listener.handler === handler) { element.detachEvent('on' + eventName, listener.wrappedHandler); listeners[uid][eventName][i] = null; } } } }; } else { /** @ignore */ addListener = function (element, eventName, handler) { if (!element) { return; } var uid = getUniqueId(element); if (!handlers[uid]) { handlers[uid] = { }; } if (!handlers[uid][eventName]) { handlers[uid][eventName] = []; var existingHandler = element['on' + eventName]; if (existingHandler) { handlers[uid][eventName].push(existingHandler); } element['on' + eventName] = createDispatcher(uid, eventName); } handlers[uid][eventName].push(handler); }; /** @ignore */ removeListener = function (element, eventName, handler) { if (!element) { return; } var uid = getUniqueId(element); if (handlers[uid] && handlers[uid][eventName]) { var handlersForEvent = handlers[uid][eventName]; for (var i = 0, len = handlersForEvent.length; i < len; i++) { if (handlersForEvent[i] === handler) { handlersForEvent.splice(i, 1); } } } }; } /** * Adds an event listener to an element * @function * @memberOf fabric.util * @param {HTMLElement} element * @param {String} eventName * @param {Function} handler */ fabric.util.addListener = addListener; /** * Removes an event listener from an element * @function * @memberOf fabric.util * @param {HTMLElement} element * @param {String} eventName * @param {Function} handler */ fabric.util.removeListener = removeListener; /** * Cross-browser wrapper for getting event's coordinates * @memberOf fabric.util * @param {Event} event Event object */ function getPointer(event) { event || (event = fabric.window.event); var element = event.target || (typeof event.srcElement !== unknown ? event.srcElement : null), scroll = fabric.util.getScrollLeftTop(element); return { x: pointerX(event) + scroll.left, y: pointerY(event) + scroll.top }; } var pointerX = function(event) { return event.clientX; }, pointerY = function(event) { return event.clientY; }; function _getPointer(event, pageProp, clientProp) { var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches'; var pointer, eventTouchProp = event[touchProp]; if (eventTouchProp && eventTouchProp[0]) { pointer = eventTouchProp[0][clientProp]; } if (typeof pointer === 'undefined') { pointer = event[clientProp]; } return pointer; } if (fabric.isTouchSupported) { pointerX = function(event) { return _getPointer(event, 'pageX', 'clientX'); }; pointerY = function(event) { return _getPointer(event, 'pageY', 'clientY'); }; } fabric.util.getPointer = getPointer; })(); (function () { /** * Cross-browser wrapper for setting element's style * @memberOf fabric.util * @param {HTMLElement} element * @param {Object} styles * @return {HTMLElement} Element that was passed as a first argument */ function setStyle(element, styles) { var elementStyle = element.style; if (!elementStyle) { return element; } if (typeof styles === 'string') { element.style.cssText += ';' + styles; return styles.indexOf('opacity') > -1 ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; } for (var property in styles) { if (property === 'opacity') { setOpacity(element, styles[property]); } else { var normalizedProperty = (property === 'float' || property === 'cssFloat') ? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat') : property; elementStyle[normalizedProperty] = styles[property]; } } return element; } var parseEl = fabric.document.createElement('div'), supportsOpacity = typeof parseEl.style.opacity === 'string', supportsFilters = typeof parseEl.style.filter === 'string', reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/, /** @ignore */ setOpacity = function (element) { return element; }; if (supportsOpacity) { /** @ignore */ setOpacity = function(element, value) { element.style.opacity = value; return element; }; } else if (supportsFilters) { /** @ignore */ setOpacity = function(element, value) { var es = element.style; if (element.currentStyle && !element.currentStyle.hasLayout) { es.zoom = 1; } if (reOpacity.test(es.filter)) { value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')'); es.filter = es.filter.replace(reOpacity, value); } else { es.filter += ' alpha(opacity=' + (value * 100) + ')'; } return element; }; } fabric.util.setStyle = setStyle; })(); (function() { var _slice = Array.prototype.slice; /** * Takes id and returns an element with that id (if one exists in a document) * @memberOf fabric.util * @param {String|HTMLElement} id * @return {HTMLElement|null} */ function getById(id) { return typeof id === 'string' ? fabric.document.getElementById(id) : id; } var sliceCanConvertNodelists, /** * Converts an array-like object (e.g. arguments or NodeList) to an array * @memberOf fabric.util * @param {Object} arrayLike * @return {Array} */ toArray = function(arrayLike) { return _slice.call(arrayLike, 0); }; try { sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array; } catch (err) { } if (!sliceCanConvertNodelists) { toArray = function(arrayLike) { var arr = new Array(arrayLike.length), i = arrayLike.length; while (i--) { arr[i] = arrayLike[i]; } return arr; }; } /** * Creates specified element with specified attributes * @memberOf fabric.util * @param {String} tagName Type of an element to create * @param {Object} [attributes] Attributes to set on an element * @return {HTMLElement} Newly created element */ function makeElement(tagName, attributes) { var el = fabric.document.createElement(tagName); for (var prop in attributes) { if (prop === 'class') { el.className = attributes[prop]; } else if (prop === 'for') { el.htmlFor = attributes[prop]; } else { el.setAttribute(prop, attributes[prop]); } } return el; } /** * Adds class to an element * @memberOf fabric.util * @param {HTMLElement} element Element to add class to * @param {String} className Class to add to an element */ function addClass(element, className) { if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) { element.className += (element.className ? ' ' : '') + className; } } /** * Wraps element with another element * @memberOf fabric.util * @param {HTMLElement} element Element to wrap * @param {HTMLElement|String} wrapper Element to wrap with * @param {Object} [attributes] Attributes to set on a wrapper * @return {HTMLElement} wrapper */ function wrapElement(element, wrapper, attributes) { if (typeof wrapper === 'string') { wrapper = makeElement(wrapper, attributes); } if (element.parentNode) { element.parentNode.replaceChild(wrapper, element); } wrapper.appendChild(element); return wrapper; } /** * Returns element scroll offsets * @memberOf fabric.util * @param {HTMLElement} element Element to operate on * @return {Object} Object with left/top values */ function getScrollLeftTop(element) { var left = 0, top = 0, docElement = fabric.document.documentElement, body = fabric.document.body || { scrollLeft: 0, scrollTop: 0 }; // While loop checks (and then sets element to) .parentNode OR .host // to account for ShadowDOM. We still want to traverse up out of ShadowDOM, // but the .parentNode of a root ShadowDOM node will always be null, instead // it should be accessed through .host. See http://stackoverflow.com/a/24765528/4383938 while (element && (element.parentNode || element.host)) { // Set element to element parent, or 'host' in case of ShadowDOM element = element.parentNode || element.host; if (element === fabric.document) { left = body.scrollLeft || docElement.scrollLeft || 0; top = body.scrollTop || docElement.scrollTop || 0; } else { left += element.scrollLeft || 0; top += element.scrollTop || 0; } if (element.nodeType === 1 && element.style.position === 'fixed') { break; } } return { left: left, top: top }; } /** * Returns offset for a given element * @function * @memberOf fabric.util * @param {HTMLElement} element Element to get offset for * @return {Object} Object with "left" and "top" properties */ function getElementOffset(element) { var docElem, doc = element && element.ownerDocument, box = { left: 0, top: 0 }, offset = { left: 0, top: 0 }, scrollLeftTop, offsetAttributes = { borderLeftWidth: 'left', borderTopWidth: 'top', paddingLeft: 'left', paddingTop: 'top' }; if (!doc) { return offset; } for (var attr in offsetAttributes) { offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0; } docElem = doc.documentElement; if ( typeof element.getBoundingClientRect !== 'undefined' ) { box = element.getBoundingClientRect(); } scrollLeftTop = getScrollLeftTop(element); return { left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left, top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top }; } /** * Returns style attribute value of a given element * @memberOf fabric.util * @param {HTMLElement} element Element to get style attribute for * @param {String} attr Style attribute to get for element * @return {String} Style attribute value of the given element. */ var getElementStyle; if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) { getElementStyle = function(element, attr) { var style = fabric.document.defaultView.getComputedStyle(element, null); return style ? style[attr] : undefined; }; } else { getElementStyle = function(element, attr) { var value = element.style[attr]; if (!value && element.currentStyle) { value = element.currentStyle[attr]; } return value; }; } (function () { var style = fabric.document.documentElement.style, selectProp = 'userSelect' in style ? 'userSelect' : 'MozUserSelect' in style ? 'MozUserSelect' : 'WebkitUserSelect' in style ? 'WebkitUserSelect' : 'KhtmlUserSelect' in style ? 'KhtmlUserSelect' : ''; /** * Makes element unselectable * @memberOf fabric.util * @param {HTMLElement} element Element to make unselectable * @return {HTMLElement} Element that was passed in */ function makeElementUnselectable(element) { if (typeof element.onselectstart !== 'undefined') { element.onselectstart = fabric.util.falseFunction; } if (selectProp) { element.style[selectProp] = 'none'; } else if (typeof element.unselectable === 'string') { element.unselectable = 'on'; } return element; } /** * Makes element selectable * @memberOf fabric.util * @param {HTMLElement} element Element to make selectable * @return {HTMLElement} Element that was passed in */ function makeElementSelectable(element) { if (typeof element.onselectstart !== 'undefined') { element.onselectstart = null; } if (selectProp) { element.style[selectProp] = ''; } else if (typeof element.unselectable === 'string') { element.unselectable = ''; } return element; } fabric.util.makeElementUnselectable = makeElementUnselectable; fabric.util.makeElementSelectable = makeElementSelectable; })(); (function() { /** * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading * @memberOf fabric.util * @param {String} url URL of a script to load * @param {Function} callback Callback to execute when script is finished loading */ function getScript(url, callback) { var headEl = fabric.document.getElementsByTagName('head')[0], scriptEl = fabric.document.createElement('script'), loading = true; /** @ignore */ scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) { if (loading) { if (typeof this.readyState === 'string' && this.readyState !== 'loaded' && this.readyState !== 'complete') { return; } loading = false; callback(e || fabric.window.event); scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null; } }; scriptEl.src = url; headEl.appendChild(scriptEl); // causes issue in Opera // headEl.removeChild(scriptEl); } fabric.util.getScript = getScript; })(); function getNodeCanvas(element) { var impl = fabric.jsdomImplForWrapper(element); return impl._canvas || impl._image; }; function cleanUpJsdomNode(element) { if (!fabric.isLikelyNode) { return; } var impl = fabric.jsdomImplForWrapper(element); if (impl) { impl._image = null; impl._canvas = null; // unsure if necessary impl._currentSrc = null; impl._attributes = null; impl._classList = null; } } fabric.util.getById = getById; fabric.util.toArray = toArray; fabric.util.makeElement = makeElement; fabric.util.addClass = addClass; fabric.util.wrapElement = wrapElement; fabric.util.getScrollLeftTop = getScrollLeftTop; fabric.util.getElementOffset = getElementOffset; fabric.util.getElementStyle = getElementStyle; fabric.util.getNodeCanvas = getNodeCanvas; fabric.util.cleanUpJsdomNode = cleanUpJsdomNode; })(); (function() { function addParamToUrl(url, param) { return url + (/\?/.test(url) ? '&' : '?') + param; } function emptyFn() { } /** * Cross-browser abstraction for sending XMLHttpRequest * @memberOf fabric.util * @param {String} url URL to send XMLHttpRequest to * @param {Object} [options] Options object * @param {String} [options.method="GET"] * @param {String} [options.parameters] parameters to append to url in GET or in body * @param {String} [options.body] body to send with POST or PUT request * @param {Function} options.onComplete Callback to invoke when request is completed * @return {XMLHttpRequest} request */ function request(url, options) { options || (options = { }); var method = options.method ? options.method.toUpperCase() : 'GET', onComplete = options.onComplete || function() { }, xhr = new fabric.window.XMLHttpRequest(), body = options.body || options.parameters; /** @ignore */ xhr.onreadystatechange = function() { if (xhr.readyState === 4) { onComplete(xhr); xhr.onreadystatechange = emptyFn; } }; if (method === 'GET') { body = null; if (typeof options.parameters === 'string') { url = addParamToUrl(url, options.parameters); } } xhr.open(method, url, true); if (method === 'POST' || method === 'PUT') { xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); } xhr.send(body); return xhr; } fabric.util.request = request; })(); /** * Wrapper around `console.log` (when available) * @param {*} [values] Values to log */ fabric.log = function() { }; /** * Wrapper around `console.warn` (when available) * @param {*} [values] Values to log as a warning */ fabric.warn = function() { }; /* eslint-disable */ if (typeof console !== 'undefined') { ['log', 'warn'].forEach(function(methodName) { if (typeof console[methodName] !== 'undefined' && typeof console[methodName].apply === 'function') { fabric[methodName] = function() { return console[methodName].apply(console, arguments); }; } }); } /* eslint-enable */ (function() { function noop() { return false; } /** * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. * @memberOf fabric.util * @param {Object} [options] Animation options * @param {Function} [options.onChange] Callback; invoked on every value change * @param {Function} [options.onComplete] Callback; invoked when value change is completed * @param {Number} [options.startValue=0] Starting value * @param {Number} [options.endValue=100] Ending value * @param {Number} [options.byValue=100] Value to modify the property by * @param {Function} [options.easing] Easing function * @param {Number} [options.duration=500] Duration of change (in ms) */ function animate(options) { requestAnimFrame(function(timestamp) { options || (options = { }); var start = timestamp || +new Date(), duration = options.duration || 500, finish = start + duration, time, onChange = options.onChange || noop, abort = options.abort || noop, onComplete = options.onComplete || noop, easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;}, startValue = 'startValue' in options ? options.startValue : 0, endValue = 'endValue' in options ? options.endValue : 100, byValue = options.byValue || endValue - startValue; options.onStart && options.onStart(); (function tick(ticktime) { if (abort()) { onComplete(endValue, 1, 1); return; } time = ticktime || +new Date(); var currentTime = time > finish ? duration : (time - start), timePerc = currentTime / duration, current = easing(currentTime, startValue, byValue, duration), valuePerc = Math.abs((current - startValue) / byValue); onChange(current, valuePerc, timePerc); if (time > finish) { options.onComplete && options.onComplete(); return; } requestAnimFrame(tick); })(start); }); } var _requestAnimFrame = fabric.window.requestAnimationFrame || fabric.window.webkitRequestAnimationFrame || fabric.window.mozRequestAnimationFrame || fabric.window.oRequestAnimationFrame || fabric.window.msRequestAnimationFrame || function(callback) { return fabric.window.setTimeout(callback, 1000 / 60); }; var _cancelAnimFrame = fabric.window.cancelAnimationFrame || fabric.window.clearTimeout; /** * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/ * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method * @memberOf fabric.util * @param {Function} callback Callback to invoke * @param {DOMElement} element optional Element to associate with animation */ function requestAnimFrame() { return _requestAnimFrame.apply(fabric.window, arguments); } function cancelAnimFrame() { return _cancelAnimFrame.apply(fabric.window, arguments); } fabric.util.animate = animate; fabric.util.requestAnimFrame = requestAnimFrame; fabric.util.cancelAnimFrame = cancelAnimFrame; })(); (function() { // Calculate an in-between color. Returns a "rgba()" string. // Credit: Edwin Martin <edwin@bitstorm.org> // http://www.bitstorm.org/jquery/color-animation/jquery.animate-colors.js function calculateColor(begin, end, pos) { var color = 'rgba(' + parseInt((begin[0] + pos * (end[0] - begin[0])), 10) + ',' + parseInt((begin[1] + pos * (end[1] - begin[1])), 10) + ',' + parseInt((begin[2] + pos * (end[2] - begin[2])), 10); color += ',' + (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1); color += ')'; return color; } /** * Changes the color from one to another within certain period of time, invoking callbacks as value is being changed. * @memberOf fabric.util * @param {String} fromColor The starting color in hex or rgb(a) format. * @param {String} toColor The starting color in hex or rgb(a) format. * @param {Number} [duration] Duration of change (in ms). * @param {Object} [options] Animation options * @param {Function} [options.onChange] Callback; invoked on every value change * @param {Function} [options.onComplete] Callback; invoked when value change is completed * @param {Function} [options.colorEasing] Easing function. Note that this function only take two arguments (currentTime, duration). Thus the regular animation easing functions cannot be used. */ function animateColor(fromColor, toColor, duration, options) { var startColor = new fabric.Color(fromColor).getSource(), endColor = new fabric.Color(toColor).getSource(); options = options || {}; fabric.util.animate(fabric.util.object.extend(options, { duration: duration || 500, startValue: startColor, endValue: endColor, byValue: endColor, easing: function (currentTime, startValue, byValue, duration) { var posValue = options.colorEasing ? options.colorEasing(currentTime, duration) : 1 - Math.cos(currentTime / duration * (Math.PI / 2)); return calculateColor(startValue, byValue, posValue); } })); } fabric.util.animateColor = animateColor; })(); (function() { function normalize(a, c, p, s) { if (a < Math.abs(c)) { a = c; s = p / 4; } else { //handle the 0/0 case: if (c === 0 && a === 0) { s = p / (2 * Math.PI) * Math.asin(1); } else { s = p / (2 * Math.PI) * Math.asin(c / a); } } return { a: a, c: c, p: p, s: s }; } function elastic(opts, t, d) { return opts.a * Math.pow(2, 10 * (t -= 1)) * Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p ); } /** * Cubic easing out * @memberOf fabric.util.ease */ function easeOutCubic(t, b, c, d) { return c * ((t = t / d - 1) * t * t + 1) + b; } /** * Cubic easing in and out * @memberOf fabric.util.ease */ function easeInOutCubic(t, b, c, d) { t /= d / 2; if (t < 1) { return c / 2 * t * t * t + b; } return c / 2 * ((t -= 2) * t * t + 2) + b; } /** * Quartic easing in * @memberOf fabric.util.ease */ function easeInQuart(t, b, c, d) { return c * (t /= d) * t * t * t + b; } /** * Quartic easing out * @memberOf fabric.util.ease */ function easeOutQuart(t, b, c, d) { return -c * ((t = t / d - 1) * t * t * t - 1) + b; } /** * Quartic easing in and out * @memberOf fabric.util.ease */ function easeInOutQuart(t, b, c, d) { t /= d / 2; if (t < 1) { return c / 2 * t * t * t * t + b; } return -c / 2 * ((t -= 2) * t * t * t - 2) + b; } /** * Quintic easing in * @memberOf fabric.util.ease */ function easeInQuint(t, b, c, d) { return c * (t /= d) * t * t * t * t + b; } /** * Quintic easing out * @memberOf fabric.util.ease */ function easeOutQuint(t, b, c, d) { return c * ((t = t / d - 1) * t * t * t * t + 1) + b; } /** * Quintic easing in and out * @memberOf fabric.util.ease */ function easeInOutQuint(t, b, c, d) { t /= d / 2; if (t < 1) { return c / 2 * t * t * t * t * t + b; } return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; } /** * Sinusoidal easing in * @memberOf fabric.util.ease */ function easeInSine(t, b, c, d) { return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; } /** * Sinusoidal easing out * @memberOf fabric.util.ease */ function easeOutSine(t, b, c, d) { return c * Math.sin(t / d * (Math.PI / 2)) + b; } /** * Sinusoidal easing in and out * @memberOf fabric.util.ease */ function easeInOutSine(t, b, c, d) { return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; } /** * Exponential easing in * @memberOf fabric.util.ease */ function easeInExpo(t, b, c, d) { return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; } /** * Exponential easing out * @memberOf fabric.util.ease */ function easeOutExpo(t, b, c, d) { return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; } /** * Exponential easing in and out * @memberOf fabric.util.ease */ function easeInOutExpo(t, b, c, d) { if (t === 0) { return b; } if (t === d) { return b + c; } t /= d / 2; if (t < 1) { return c / 2 * Math.pow(2, 10 * (t - 1)) + b; } return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; } /** * Circular easing in * @memberOf fabric.util.ease */ function easeInCirc(t, b, c, d) { return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; } /** * Circular easing out * @memberOf fabric.util.ease */ function easeOutCirc(t, b, c, d) { return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; } /** * Circular easing in and out * @memberOf fabric.util.ease */ function easeInOutCirc(t, b, c, d) { t /= d / 2; if (t < 1) { return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; } return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; } /** * Elastic easing in * @memberOf fabric.util.ease */ function easeInElastic(t, b, c, d) { var s = 1.70158, p = 0, a = c; if (t === 0) { return b; } t /= d; if (t === 1) { return b + c; } if (!p) { p = d * 0.3; } var opts = normalize(a, c, p, s); return -elastic(opts, t, d) + b; } /** * Elastic easing out * @memberOf fabric.util.ease */ function easeOutElastic(t, b, c, d) { var s = 1.70158, p = 0, a = c; if (t === 0) { return b; } t /= d; if (t === 1) { return b + c; } if (!p) { p = d * 0.3; } var opts = normalize(a, c, p, s); return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) + opts.c + b; } /** * Elastic easing in and out * @memberOf fabric.util.ease */ function easeInOutElastic(t, b, c, d) { var s = 1.70158, p = 0, a = c; if (t === 0) { return b; } t /= d / 2; if (t === 2) { return b + c; } if (!p) { p = d * (0.3 * 1.5); } var opts = normalize(a, c, p, s); if (t < 1) { return -0.5 * elastic(opts, t, d) + b; } return opts.a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) * 0.5 + opts.c + b; } /** * Backwards easing in * @memberOf fabric.util.ease */ function easeInBack(t, b, c, d, s) { if (s === undefined) { s = 1.70158; } return c * (t /= d) * t * ((s + 1) * t - s) + b; } /** * Backwards easing out * @memberOf fabric.util.ease */ function easeOutBack(t, b, c, d, s) { if (s === undefined) { s = 1.70158; } return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; } /** * Backwards easing in and out * @memberOf fabric.util.ease */ function easeInOutBack(t, b, c, d, s) { if (s === undefined) { s = 1.70158; } t /= d / 2; if (t < 1) { return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; } return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; } /** * Bouncing easing in * @memberOf fabric.util.ease */ function easeInBounce(t, b, c, d) { return c - easeOutBounce (d - t, 0, c, d) + b; } /** * Bouncing easing out * @memberOf fabric.util.ease */ function easeOutBounce(t, b, c, d) { if ((t /= d) < (1 / 2.75)) { return c * (7.5625 * t * t) + b; } else if (t < (2 / 2.75)) { return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; } else if (t < (2.5 / 2.75)) { return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; } else { return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; } } /** * Bouncing easing in and out * @memberOf fabric.util.ease */ function easeInOutBounce(t, b, c, d) { if (t < d / 2) { return easeInBounce (t * 2, 0, c, d) * 0.5 + b; } return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; } /** * Easing functions * See <a href="http://gizma.com/easing/">Easing Equations by Robert Penner</a> * @namespace fabric.util.ease */ fabric.util.ease = { /** * Quadratic easing in * @memberOf fabric.util.ease */ easeInQuad: function(t, b, c, d) { return c * (t /= d) * t + b; }, /** * Quadratic easing out * @memberOf fabric.util.ease */ easeOutQuad: function(t, b, c, d) { return -c * (t /= d) * (t - 2) + b; }, /** * Quadratic easing in and out * @memberOf fabric.util.ease */ easeInOutQuad: function(t, b, c, d) { t /= (d / 2); if (t < 1) { return c / 2 * t * t + b; } return -c / 2 * ((--t) * (t - 2) - 1) + b; }, /** * Cubic easing in * @memberOf fabric.util.ease */ easeInCubic: function(t, b, c, d) { return c * (t /= d) * t * t + b; }, easeOutCubic: easeOutCubic, easeInOutCubic: easeInOutCubic, easeInQuart: easeInQuart, easeOutQuart: easeOutQuart, easeInOutQuart: easeInOutQuart, easeInQuint: easeInQuint, easeOutQuint: easeOutQuint, easeInOutQuint: easeInOutQuint, easeInSine: easeInSine, easeOutSine: easeOutSine, easeInOutSine: easeInOutSine, easeInExpo: easeInExpo, easeOutExpo: easeOutExpo, easeInOutExpo: easeInOutExpo, easeInCirc: easeInCirc, easeOutCirc: easeOutCirc, easeInOutCirc: easeInOutCirc, easeInElastic: easeInElastic, easeOutElastic: easeOutElastic, easeInOutElastic: easeInOutElastic, easeInBack: easeInBack, easeOutBack: easeOutBack, easeInOutBack: easeInOutBack, easeInBounce: easeInBounce, easeOutBounce: easeOutBounce, easeInOutBounce: easeInOutBounce }; })(); (function(global) { 'use strict'; /** * @name fabric * @namespace */ var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, clone = fabric.util.object.clone, toFixed = fabric.util.toFixed, parseUnit = fabric.util.parseUnit, multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, svgValidTagNames = ['path', 'circle', 'polygon', 'polyline', 'ellipse', 'rect', 'line', 'image', 'text'], svgViewBoxElements = ['symbol', 'image', 'marker', 'pattern', 'view', 'svg'], svgInvalidAncestors = ['pattern', 'defs', 'symbol', 'metadata', 'clipPath', 'mask', 'desc'], svgValidParents = ['symbol', 'g', 'a', 'svg', 'clipPath', 'defs'], attributesMap = { cx: 'left', x: 'left', r: 'radius', cy: 'top', y: 'top', display: 'visible', visibility: 'visible', transform: 'transformMatrix', 'fill-opacity': 'fillOpacity', 'fill-rule': 'fillRule', 'font-family': 'fontFamily', 'font-size': 'fontSize', 'font-style': 'fontStyle', 'font-weight': 'fontWeight', 'letter-spacing': 'charSpacing', 'paint-order': 'paintFirst', 'stroke-dasharray': 'strokeDashArray', 'stroke-dashoffset': 'strokeDashOffset', 'stroke-linecap': 'strokeLineCap', 'stroke-linejoin': 'strokeLineJoin', 'stroke-miterlimit': 'strokeMiterLimit', 'stroke-opacity': 'strokeOpacity', 'stroke-width': 'strokeWidth', 'text-decoration': 'textDecoration', 'text-anchor': 'textAnchor', opacity: 'opacity', 'clip-path': 'clipPath', 'clip-rule': 'clipRule', 'vector-effect': 'strokeUniform' }, colorAttributes = { stroke: 'strokeOpacity', fill: 'fillOpacity' }; fabric.svgValidTagNamesRegEx = getSvgRegex(svgValidTagNames); fabric.svgViewBoxElementsRegEx = getSvgRegex(svgViewBoxElements); fabric.svgInvalidAncestorsRegEx = getSvgRegex(svgInvalidAncestors); fabric.svgValidParentsRegEx = getSvgRegex(svgValidParents); fabric.cssRules = { }; fabric.gradientDefs = { }; fabric.clipPaths = { }; function normalizeAttr(attr) { // transform attribute names if (attr in attributesMap) { return attributesMap[attr]; } return attr; } function normalizeValue(attr, value, parentAttributes, fontSize) { var isArray = Object.prototype.toString.call(value) === '[object Array]', parsed; if ((attr === 'fill' || attr === 'stroke') && value === 'none') { value = ''; } else if (attr === 'vector-effect') { value = value === 'non-scaling-stroke'; } else if (attr === 'strokeDashArray') { if (value === 'none') { value = null; } else { value = value.replace(/,/g, ' ').split(/\s+/).map(parseFloat); } } else if (attr === 'transformMatrix') { if (parentAttributes && parentAttributes.transformMatrix) { value = multiplyTransformMatrices( parentAttributes.transformMatrix, fabric.parseTransformAttribute(value)); } else { value = fabric.parseTransformAttribute(value); } } else if (attr === 'visible') { value = value !== 'none' && value !== 'hidden'; // display=none on parent element always takes precedence over child element if (parentAttributes && parentAttributes.visible === false) { value = false; } } else if (attr === 'opacity') { value = parseFloat(value); if (parentAttributes && typeof parentAttributes.opacity !== 'undefined') { value *= parentAttributes.opacity; } } else if (attr === 'textAnchor' /* text-anchor */) { value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center'; } else if (attr === 'charSpacing') { // parseUnit returns px and we convert it to em parsed = parseUnit(value, fontSize) / fontSize * 1000; } else if (attr === 'paintFirst') { var fillIndex = value.indexOf('fill'); var strokeIndex = value.indexOf('stroke'); var value = 'fill'; if (fillIndex > -1 && strokeIndex > -1 && strokeIndex < fillIndex) { value = 'stroke'; } else if (fillIndex === -1 && strokeIndex > -1) { value = 'stroke'; } } else if (attr === 'href' || attr === 'xlink:href') { return value; } else { parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize); } return (!isArray && isNaN(parsed) ? value : parsed); } /** * @private */ function getSvgRegex(arr) { return new RegExp('^(' + arr.join('|') + ')\\b', 'i'); } /** * @private * @param {Object} attributes Array of attributes to parse */ function _setStrokeFillOpacity(attributes) { for (var attr in colorAttributes) { if (typeof attributes[colorAttributes[attr]] === 'undefined' || attributes[attr] === '') { continue; } if (typeof attributes[attr] === 'undefined') { if (!fabric.Object.prototype[attr]) { continue; } attributes[attr] = fabric.Object.prototype[attr]; } if (attributes[attr].indexOf('url(') === 0) { continue; } var color = new fabric.Color(attributes[attr]); attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba(); } return attributes; } /** * @private */ function _getMultipleNodes(doc, nodeNames) { var nodeName, nodeArray = [], nodeList, i, len; for (i = 0, len = nodeNames.length; i < len; i++) { nodeName = nodeNames[i]; nodeList = doc.getElementsByTagName(nodeName); nodeArray = nodeArray.concat(Array.prototype.slice.call(nodeList)); } return nodeArray; } /** * Parses "transform" attribute, returning an array of values * @static * @function * @memberOf fabric * @param {String} attributeValue String containing attribute value * @return {Array} Array of 6 elements representing transformation matrix */ fabric.parseTransformAttribute = (function() { function rotateMatrix(matrix, args) { var cos = fabric.util.cos(args[0]), sin = fabric.util.sin(args[0]), x = 0, y = 0; if (args.length === 3) { x = args[1]; y = args[2]; } matrix[0] = cos; matrix[1] = sin; matrix[2] = -sin; matrix[3] = cos; matrix[4] = x - (cos * x - sin * y); matrix[5] = y - (sin * x + cos * y); } function scaleMatrix(matrix, args) { var multiplierX = args[0], multiplierY = (args.length === 2) ? args[1] : args[0]; matrix[0] = multiplierX; matrix[3] = multiplierY; } function skewMatrix(matrix, args, pos) { matrix[pos] = Math.tan(fabric.util.degreesToRadians(args[0])); } function translateMatrix(matrix, args) { matrix[4] = args[0]; if (args.length === 2) { matrix[5] = args[1]; } } // identity matrix var iMatrix = fabric.iMatrix, // == begin transform regexp number = fabric.reNum, commaWsp = '(?:\\s+,?\\s*|,\\s*)', skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))', skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))', rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' + commaWsp + '(' + number + ')' + commaWsp + '(' + number + '))?\\s*\\))', scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + commaWsp + '(' + number + '))?\\s*\\))', translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + commaWsp + '(' + number + '))?\\s*\\))', matrix = '(?:(matrix)\\s*\\(\\s*' + '(' + number + ')' + commaWsp + '(' + number + ')' + commaWsp + '(' + number + ')' + commaWsp + '(' + number + ')' + commaWsp + '(' + number + ')' + commaWsp + '(' + number + ')' + '\\s*\\))', transform = '(?:' + matrix + '|' + translate + '|' + scale + '|' + rotate + '|' + skewX + '|' + skewY + ')', transforms = '(?:' + transform + '(?:' + commaWsp + '*' + transform + ')*' + ')', transformList = '^\\s*(?:' + transforms + '?)\\s*$', // http://www.w3.org/TR/SVG/coords.html#TransformAttribute reTransformList = new RegExp(transformList), // == end transform regexp reTransform = new RegExp(transform, 'g'); return function(attributeValue) { // start with identity matrix var matrix = iMatrix.concat(), matrices = []; // return if no argument was given or // an argument does not match transform attribute regexp if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) { return matrix; } attributeValue.replace(reTransform, function(match) { var m = new RegExp(transform).exec(match).filter(function (match) { // match !== '' && match != null return (!!match); }), operation = m[1], args = m.slice(2).map(parseFloat); switch (operation) { case 'translate': translateMatrix(matrix, args); break; case 'rotate': args[0] = fabric.util.degreesToRadians(args[0]); rotateMatrix(matrix, args); break; case 'scale': scaleMatrix(matrix, args); break; case 'skewX': skewMatrix(matrix, args, 2); break; case 'skewY': skewMatrix(matrix, args, 1); break; case 'matrix': matrix = args; break; } // snapshot current matrix into matrices array matrices.push(matrix.concat()); // reset matrix = iMatrix.concat(); }); var combinedMatrix = matrices[0]; while (matrices.length > 1) { matrices.shift(); combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]); } return combinedMatrix; }; })(); /** * @private */ function parseStyleString(style, oStyle) { var attr, value; style.replace(/;\s*$/, '').split(';').forEach(function (chunk) { var pair = chunk.split(':'); attr = pair[0].trim().toLowerCase(); value = pair[1].trim(); oStyle[attr] = value; }); } /** * @private */ function parseStyleObject(style, oStyle) { var attr, value; for (var prop in style) { if (typeof style[prop] === 'undefined') { continue; } attr = prop.toLowerCase(); value = style[prop]; oStyle[attr] = value; } } /** * @private */ function getGlobalStylesForElement(element, svgUid) { var styles = { }; for (var rule in fabric.cssRules[svgUid]) { if (elementMatchesRule(element, rule.split(' '))) { for (var property in fabric.cssRules[svgUid][rule]) { styles[property] = fabric.cssRules[svgUid][rule][property]; } } } return styles; } /** * @private */ function elementMatchesRule(element, selectors) { var firstMatching, parentMatching = true; //start from rightmost selector. firstMatching = selectorMatches(element, selectors.pop()); if (firstMatching && selectors.length) { parentMatching = doesSomeParentMatch(element, selectors); } return firstMatching && parentMatching && (selectors.length === 0); } function doesSomeParentMatch(element, selectors) { var selector, parentMatching = true; while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) { if (parentMatching) { selector = selectors.pop(); } element = element.parentNode; parentMatching = selectorMatches(element, selector); } return selectors.length === 0; } /** * @private */ function selectorMatches(element, selector) { var nodeName = element.nodeName, classNames = element.getAttribute('class'), id = element.getAttribute('id'), matcher, i; // i check if a selector matches slicing away part from it. // if i get empty string i should match matcher = new RegExp('^' + nodeName, 'i'); selector = selector.replace(matcher, ''); if (id && selector.length) { matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i'); selector = selector.replace(matcher, ''); } if (classNames && selector.length) { classNames = classNames.split(' '); for (i = classNames.length; i--;) { matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i'); selector = selector.replace(matcher, ''); } } return selector.length === 0; } /** * @private * to support IE8 missing getElementById on SVGdocument and on node xmlDOM */ function elementById(doc, id) { var el; doc.getElementById && (el = doc.getElementById(id)); if (el) { return el; } var node, i, len, nodelist = doc.getElementsByTagName('*'); for (i = 0, len = nodelist.length; i < len; i++) { node = nodelist[i]; if (id === node.getAttribute('id')) { return node; } } } /** * @private */ function parseUseDirectives(doc) { var nodelist = _getMultipleNodes(doc, ['use', 'svg:use']), i = 0; while (nodelist.length && i < nodelist.length) { var el = nodelist[i], xlink = (el.getAttribute('xlink:href') || el.getAttribute('href')).substr(1), x = el.getAttribute('x') || 0, y = el.getAttribute('y') || 0, el2 = elementById(doc, xlink).cloneNode(true), currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')', parentNode, oldLength = nodelist.length, attr, j, attrs, len; applyViewboxTransform(el2); if (/^svg$/i.test(el2.nodeName)) { var el3 = el2.ownerDocument.createElement('g'); for (j = 0, attrs = el2.attributes, len = attrs.length; j < len; j++) { attr = attrs.item(j); el3.setAttribute(attr.nodeName, attr.nodeValue); } // el2.firstChild != null while (el2.firstChild) { el3.appendChild(el2.firstChild); } el2 = el3; } for (j = 0, attrs = el.attributes, len = attrs.length; j < len; j++) { attr = attrs.item(j); if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href' || attr.nodeName === 'href') { continue; } if (attr.nodeName === 'transform') { currentTrans = attr.nodeValue + ' ' + currentTrans; } else { el2.setAttribute(attr.nodeName, attr.nodeValue); } } el2.setAttribute('transform', currentTrans); el2.setAttribute('instantiated_by_use', '1'); el2.removeAttribute('id'); parentNode = el.parentNode; parentNode.replaceChild(el2, el); // some browsers do not shorten nodelist after replaceChild (IE8) if (nodelist.length === oldLength) { i++; } } } // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute // matches, e.g.: +14.56e-12, etc. var reViewBoxAttrValue = new RegExp( '^' + '\\s*(' + fabric.reNum + '+)\\s*,?' + '\\s*(' + fabric.reNum + '+)\\s*,?' + '\\s*(' + fabric.reNum + '+)\\s*,?' + '\\s*(' + fabric.reNum + '+)\\s*' + '$' ); /** * Add a <g> element that envelop all child elements and makes the viewbox transformMatrix descend on all elements */ function applyViewboxTransform(element) { var viewBoxAttr = element.getAttribute('viewBox'), scaleX = 1, scaleY = 1, minX = 0, minY = 0, viewBoxWidth, viewBoxHeight, matrix, el, widthAttr = element.getAttribute('width'), heightAttr = element.getAttribute('height'), x = element.getAttribute('x') || 0, y = element.getAttribute('y') || 0, preserveAspectRatio = element.getAttribute('preserveAspectRatio') || '', missingViewBox = (!viewBoxAttr || !fabric.svgViewBoxElementsRegEx.test(element.nodeName) || !(viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))), missingDimAttr = (!widthAttr || !heightAttr || widthAttr === '100%' || heightAttr === '100%'), toBeParsed = missingViewBox && missingDimAttr, parsedDim = { }, translateMatrix = '', widthDiff = 0, heightDiff = 0; parsedDim.width = 0; parsedDim.height = 0; parsedDim.toBeParsed = toBeParsed; if (toBeParsed) { return parsedDim; } if (missingViewBox) { parsedDim.width = parseUnit(widthAttr); parsedDim.height = parseUnit(heightAttr); return parsedDim; } minX = -parseFloat(viewBoxAttr[1]); minY = -parseFloat(viewBoxAttr[2]); viewBoxWidth = parseFloat(viewBoxAttr[3]); viewBoxHeight = parseFloat(viewBoxAttr[4]); if (!missingDimAttr) { parsedDim.width = parseUnit(widthAttr); parsedDim.height = parseUnit(heightAttr); scaleX = parsedDim.width / viewBoxWidth; scaleY = parsedDim.height / viewBoxHeight; } else { parsedDim.width = viewBoxWidth; parsedDim.height = viewBoxHeight; } // default is to preserve aspect ratio preserveAspectRatio = fabric.util.parsePreserveAspectRatioAttribute(preserveAspectRatio); if (preserveAspectRatio.alignX !== 'none') { //translate all container for the effect of Mid, Min, Max if (preserveAspectRatio.meetOrSlice === 'meet') { scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX); // calculate additional translation to move the viewbox } if (preserveAspectRatio.meetOrSlice === 'slice') { scaleY = scaleX = (scaleX > scaleY ? scaleX : scaleY); // calculate additional translation to move the viewbox } widthDiff = parsedDim.width - viewBoxWidth * scaleX; heightDiff = parsedDim.height - viewBoxHeight * scaleX; if (preserveAspectRatio.alignX === 'Mid') { widthDiff /= 2; } if (preserveAspectRatio.alignY === 'Mid') { heightDiff /= 2; } if (preserveAspectRatio.alignX === 'Min') { widthDiff = 0; } if (preserveAspectRatio.alignY === 'Min') { heightDiff = 0; } } if (scaleX === 1 && scaleY === 1 && minX === 0 && minY === 0 && x === 0 && y === 0) { return parsedDim; } if (x || y) { translateMatrix = ' translate(' + parseUnit(x) + ' ' + parseUnit(y) + ') '; } matrix = translateMatrix + ' matrix(' + scaleX + ' 0' + ' 0 ' + scaleY + ' ' + (minX * scaleX + widthDiff) + ' ' + (minY * scaleY + heightDiff) + ') '; parsedDim.viewboxTransform = fabric.parseTransformAttribute(matrix); if (element.nodeName === 'svg') { el = element.ownerDocument.createElement('g'); // element.firstChild != null while (element.firstChild) { el.appendChild(element.firstChild); } element.appendChild(el); } else { el = element; matrix = el.getAttribute('transform') + matrix; } el.setAttribute('transform', matrix); return parsedDim; } function hasAncestorWithNodeName(element, nodeName) { while (element && (element = element.parentNode)) { if (element.nodeName && nodeName.test(element.nodeName.replace('svg:', '')) && !element.getAttribute('instantiated_by_use')) { return true; } } return false; } /** * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback * @static * @function * @memberOf fabric * @param {SVGDocument} doc SVG document to parse * @param {Function} callback Callback to call when parsing is finished; * It's being passed an array of elements (parsed from a document). * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. * @param {Object} [parsingOptions] options for parsing document * @param {String} [parsingOptions.crossOrigin] crossOrigin settings */ fabric.parseSVGDocument = function(doc, callback, reviver, parsingOptions) { if (!doc) { return; } parseUseDirectives(doc); var svgUid = fabric.Object.__uid++, i, len, options = applyViewboxTransform(doc), descendants = fabric.util.toArray(doc.getElementsByTagName('*')); options.crossOrigin = parsingOptions && parsingOptions.crossOrigin; options.svgUid = svgUid; if (descendants.length === 0 && fabric.isLikelyNode) { // we're likely in node, where "o3-xml" library fails to gEBTN("*") // https://github.com/ajaxorg/node-o3-xml/issues/21 descendants = doc.selectNodes('//*[name(.)!="svg"]'); var arr = []; for (i = 0, len = descendants.length; i < len; i++) { arr[i] = descendants[i]; } descendants = arr; } var elements = descendants.filter(function(el) { applyViewboxTransform(el); return fabric.svgValidTagNamesRegEx.test(el.nodeName.replace('svg:', '')) && !hasAncestorWithNodeName(el, fabric.svgInvalidAncestorsRegEx); // http://www.w3.org/TR/SVG/struct.html#DefsElement }); if (!elements || (elements && !elements.length)) { callback && callback([], {}); return; } var clipPaths = { }; descendants.filter(function(el) { return el.nodeName.replace('svg:', '') === 'clipPath'; }).forEach(function(el) { var id = el.getAttribute('id'); clipPaths[id] = fabric.util.toArray(el.getElementsByTagName('*')).filter(function(el) { return fabric.svgValidTagNamesRegEx.test(el.nodeName.replace('svg:', '')); }); }); fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc); fabric.cssRules[svgUid] = fabric.getCSSRules(doc); fabric.clipPaths[svgUid] = clipPaths; // Precedence of rules: style > class > attribute fabric.parseElements(elements, function(instances, elements) { if (callback) { callback(instances, options, elements, descendants); delete fabric.gradientDefs[svgUid]; delete fabric.cssRules[svgUid]; delete fabric.clipPaths[svgUid]; } }, clone(options), reviver, parsingOptions); }; function recursivelyParseGradientsXlink(doc, gradient) { var gradientsAttrs = ['gradientTransform', 'x1', 'x2', 'y1', 'y2', 'gradientUnits', 'cx', 'cy', 'r', 'fx', 'fy'], xlinkAttr = 'xlink:href', xLink = gradient.getAttribute(xlinkAttr).substr(1), referencedGradient = elementById(doc, xLink); if (referencedGradient && referencedGradient.getAttribute(xlinkAttr)) { recursivelyParseGradientsXlink(doc, referencedGradient); } gradientsAttrs.forEach(function(attr) { if (!gradient.hasAttribute(attr)) { gradient.setAttribute(attr, referencedGradient.getAttribute(attr)); } }); if (!gradient.children.length) { var referenceClone = referencedGradient.cloneNode(true); while (referenceClone.firstChild) { gradient.appendChild(referenceClone.firstChild); } } gradient.removeAttribute(xlinkAttr); } var reFontDeclaration = new RegExp( '(normal|italic)?\\s*(normal|small-caps)?\\s*' + '(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(' + fabric.reNum + '(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|' + fabric.reNum + '))?\\s+(.*)'); extend(fabric, { /** * Parses a short font declaration, building adding its properties to a style object * @static * @function * @memberOf fabric * @param {String} value font declaration * @param {Object} oStyle definition */ parseFontDeclaration: function(value, oStyle) { var match = value.match(reFontDeclaration); if (!match) { return; } var fontStyle = match[1], // font variant is not used // fontVariant = match[2], fontWeight = match[3], fontSize = match[4], lineHeight = match[5], fontFamily = match[6]; if (fontStyle) { oStyle.fontStyle = fontStyle; } if (fontWeight) { oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); } if (fontSize) { oStyle.fontSize = parseUnit(fontSize); } if (fontFamily) { oStyle.fontFamily = fontFamily; } if (lineHeight) { oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight; } }, /** * Parses an SVG document, returning all of the gradient declarations found in it * @static * @function * @memberOf fabric * @param {SVGDocument} doc SVG document to parse * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element */ getGradientDefs: function(doc) { var tagArray = [ 'linearGradient', 'radialGradient', 'svg:linearGradient', 'svg:radialGradient'], elList = _getMultipleNodes(doc, tagArray), el, j = 0, gradientDefs = { }; j = elList.length; while (j--) { el = elList[j]; if (el.getAttribute('xlink:href')) { recursivelyParseGradientsXlink(doc, el); } gradientDefs[el.getAttribute('id')] = el; } return gradientDefs; }, /** * Returns an object of attributes' name/value, given element and an array of attribute names; * Parses parent "g" nodes recursively upwards. * @static * @memberOf fabric * @param {DOMElement} element Element to parse * @param {Array} attributes Array of attributes to parse * @return {Object} object containing parsed attributes' names/values */ parseAttributes: function(element, attributes, svgUid) { if (!element) { return; } var value, parentAttributes = { }, fontSize, parentFontSize; if (typeof svgUid === 'undefined') { svgUid = element.getAttribute('svgUid'); } // if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards if (element.parentNode && fabric.svgValidParentsRegEx.test(element.parentNode.nodeName)) { parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid); } var ownAttributes = attributes.reduce(function(memo, attr) { value = element.getAttribute(attr); if (value) { // eslint-disable-line memo[attr] = value; } return memo; }, { }); // add values parsed from style, which take precedence over attributes // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes) ownAttributes = extend(ownAttributes, extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element))); fontSize = parentFontSize = parentAttributes.fontSize || fabric.Text.DEFAULT_SVG_FONT_SIZE; if (ownAttributes['font-size']) { // looks like the minimum should be 9px when dealing with ems. this is what looks like in browsers. ownAttributes['font-size'] = fontSize = parseUnit(ownAttributes['font-size'], parentFontSize); } var normalizedAttr, normalizedValue, normalizedStyle = {}; for (var attr in ownAttributes) { normalizedAttr = normalizeAttr(attr); normalizedValue = normalizeValue(normalizedAttr, ownAttributes[attr], parentAttributes, fontSize); normalizedStyle[normalizedAttr] = normalizedValue; } if (normalizedStyle && normalizedStyle.font) { fabric.parseFontDeclaration(normalizedStyle.font, normalizedStyle); } var mergedAttrs = extend(parentAttributes, normalizedStyle); return fabric.svgValidParentsRegEx.test(element.nodeName) ? mergedAttrs : _setStrokeFillOpacity(mergedAttrs); }, /** * Transforms an array of svg elements to corresponding fabric.* instances * @static * @memberOf fabric * @param {Array} elements Array of elements to parse * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements) * @param {Object} [options] Options object * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. */ parseElements: function(elements, callback, options, reviver, parsingOptions) { new fabric.ElementsParser(elements, callback, options, reviver, parsingOptions).parse(); }, /** * Parses "style" attribute, retuning an object with values * @static * @memberOf fabric * @param {SVGElement} element Element to parse * @return {Object} Objects with values parsed from style attribute of an element */ parseStyleAttribute: function(element) { var oStyle = { }, style = element.getAttribute('style'); if (!style) { return oStyle; } if (typeof style === 'string') { parseStyleString(style, oStyle); } else { parseStyleObject(style, oStyle); } return oStyle; }, /** * Parses "points" attribute, returning an array of values * @static * @memberOf fabric * @param {String} points points attribute string * @return {Array} array of points */ parsePointsAttribute: function(points) { // points attribute is required and must not be empty if (!points) { return null; } // replace commas with whitespace and remove bookending whitespace points = points.replace(/,/g, ' ').trim(); points = points.split(/\s+/); var parsedPoints = [], i, len; for (i = 0, len = points.length; i < len; i += 2) { parsedPoints.push({ x: parseFloat(points[i]), y: parseFloat(points[i + 1]) }); } // odd number of points is an error // if (parsedPoints.length % 2 !== 0) { // return null; // } return parsedPoints; }, /** * Returns CSS rules for a given SVG document * @static * @function * @memberOf fabric * @param {SVGDocument} doc SVG document to parse * @return {Object} CSS rules of this document */ getCSSRules: function(doc) { var styles = doc.getElementsByTagName('style'), i, len, allRules = { }, rules; // very crude parsing of style contents for (i = 0, len = styles.length; i < len; i++) { // IE9 doesn't support textContent, but provides text instead. var styleContents = styles[i].textContent || styles[i].text; // remove comments styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, ''); if (styleContents.trim() === '') { continue; } rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g); rules = rules.map(function(rule) { return rule.trim(); }); // eslint-disable-next-line no-loop-func rules.forEach(function(rule) { var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/), ruleObj = { }, declaration = match[2].trim(), propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/); for (i = 0, len = propertyValuePairs.length; i < len; i++) { var pair = propertyValuePairs[i].split(/\s*:\s*/), property = pair[0], value = pair[1]; ruleObj[property] = value; } rule = match[1]; rule.split(',').forEach(function(_rule) { _rule = _rule.replace(/^svg/i, '').trim(); if (_rule === '') { return; } if (allRules[_rule]) { fabric.util.object.extend(allRules[_rule], ruleObj); } else { allRules[_rule] = fabric.util.object.clone(ruleObj); } }); }); } return allRules; }, /** * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. * Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy) * @memberOf fabric * @param {String} url * @param {Function} callback * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. * @param {Object} [options] Object containing options for parsing * @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources */ loadSVGFromURL: function(url, callback, reviver, options) { url = url.replace(/^\n\s*/, '').trim(); new fabric.util.request(url, { method: 'get', onComplete: onComplete }); function onComplete(r) { var xml = r.responseXML; if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) { xml = new ActiveXObject('Microsoft.XMLDOM'); xml.async = 'false'; //IE chokes on DOCTYPE xml.loadXML(r.responseText.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, '')); } if (!xml || !xml.documentElement) { callback && callback(null); return false; } fabric.parseSVGDocument(xml.documentElement, function (results, _options, elements, allElements) { callback && callback(results, _options, elements, allElements); }, reviver, options); } }, /** * Takes string corresponding to an SVG document, and parses it into a set of fabric objects * @memberOf fabric * @param {String} string * @param {Function} callback * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. * @param {Object} [options] Object containing options for parsing * @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources */ loadSVGFromString: function(string, callback, reviver, options) { string = string.trim(); var doc; if (typeof DOMParser !== 'undefined') { var parser = new DOMParser(); if (parser && parser.parseFromString) { doc = parser.parseFromString(string, 'text/xml'); } } else if (fabric.window.ActiveXObject) { doc = new ActiveXObject('Microsoft.XMLDOM'); doc.async = 'false'; // IE chokes on DOCTYPE doc.loadXML(string.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, '')); } fabric.parseSVGDocument(doc.documentElement, function (results, _options, elements, allElements) { callback(results, _options, elements, allElements); }, reviver, options); } }); })(typeof exports !== 'undefined' ? exports : this); fabric.ElementsParser = function(elements, callback, options, reviver, parsingOptions) { this.elements = elements; this.callback = callback; this.options = options; this.reviver = reviver; this.svgUid = (options && options.svgUid) || 0; this.parsingOptions = parsingOptions; this.regexUrl = /^url\(['"]?#([^'"]+)['"]?\)/g; }; (function(proto) { proto.parse = function() { this.instances = new Array(this.elements.length); this.numElements = this.elements.length; this.createObjects(); }; proto.createObjects = function() { var _this = this; this.elements.forEach(function(element, i) { element.setAttribute('svgUid', _this.svgUid); _this.createObject(element, i); }); }; proto.findTag = function(el) { return fabric[fabric.util.string.capitalize(el.tagName.replace('svg:', ''))]; }; proto.createObject = function(el, index) { var klass = this.findTag(el); if (klass && klass.fromElement) { try { klass.fromElement(el, this.createCallback(index, el), this.options); } catch (err) { fabric.log(err); } } else { this.checkIfDone(); } }; proto.createCallback = function(index, el) { var _this = this; return function(obj) { var _options; _this.resolveGradient(obj, 'fill'); _this.resolveGradient(obj, 'stroke'); if (obj instanceof fabric.Image && obj._originalElement) { _options = obj.parsePreserveAspectRatioAttribute(el); } obj._removeTransformMatrix(_options); _this.resolveClipPath(obj); _this.reviver && _this.reviver(el, obj); _this.instances[index] = obj; _this.checkIfDone(); }; }; proto.extractPropertyDefinition = function(obj, property, storage) { var value = obj[property]; if (!(/^url\(/).test(value)) { return; } var id = this.regexUrl.exec(value)[1]; this.regexUrl.lastIndex = 0; return fabric[storage][this.svgUid][id]; }; proto.resolveGradient = function(obj, property) { var gradientDef = this.extractPropertyDefinition(obj, property, 'gradientDefs'); if (gradientDef) { obj.set(property, fabric.Gradient.fromElement(gradientDef, obj)); } }; proto.createClipPathCallback = function(obj, container) { return function(_newObj) { _newObj._removeTransformMatrix(); _newObj.fillRule = _newObj.clipRule; container.push(_newObj); }; }; proto.resolveClipPath = function(obj) { var clipPath = this.extractPropertyDefinition(obj, 'clipPath', 'clipPaths'), element, klass, objTransformInv, container, gTransform, options; if (clipPath) { container = []; objTransformInv = fabric.util.invertTransform(obj.calcTransformMatrix()); for (var i = 0; i < clipPath.length; i++) { element = clipPath[i]; klass = this.findTag(element); klass.fromElement( element, this.createClipPathCallback(obj, container), this.options ); } if (container.length === 1) { clipPath = container[0]; } else { clipPath = new fabric.Group(container); } gTransform = fabric.util.multiplyTransformMatrices( objTransformInv, clipPath.calcTransformMatrix() ); var options = fabric.util.qrDecompose(gTransform); clipPath.flipX = false; clipPath.flipY = false; clipPath.set('scaleX', options.scaleX); clipPath.set('scaleY', options.scaleY); clipPath.angle = options.angle; clipPath.skewX = options.skewX; clipPath.skewY = 0; clipPath.setPositionByOrigin({ x: options.translateX, y: options.translateY }, 'center', 'center'); obj.clipPath = clipPath; } }; proto.checkIfDone = function() { if (--this.numElements === 0) { this.instances = this.instances.filter(function(el) { // eslint-disable-next-line no-eq-null, eqeqeq return el != null; }); this.callback(this.instances, this.elements); } }; })(fabric.ElementsParser.prototype); (function(global) { 'use strict'; /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ var fabric = global.fabric || (global.fabric = { }); if (fabric.Point) { fabric.warn('fabric.Point is already defined'); return; } fabric.Point = Point; /** * Point class * @class fabric.Point * @memberOf fabric * @constructor * @param {Number} x * @param {Number} y * @return {fabric.Point} thisArg */ function Point(x, y) { this.x = x; this.y = y; } Point.prototype = /** @lends fabric.Point.prototype */ { type: 'point', constructor: Point, /** * Adds another point to this one and returns another one * @param {fabric.Point} that * @return {fabric.Point} new Point instance with added values */ add: function (that) { return new Point(this.x + that.x, this.y + that.y); }, /** * Adds another point to this one * @param {fabric.Point} that * @return {fabric.Point} thisArg * @chainable */ addEquals: function (that) { this.x += that.x; this.y += that.y; return this; }, /** * Adds value to this point and returns a new one * @param {Number} scalar * @return {fabric.Point} new Point with added value */ scalarAdd: function (scalar) { return new Point(this.x + scalar, this.y + scalar); }, /** * Adds value to this point * @param {Number} scalar * @return {fabric.Point} thisArg * @chainable */ scalarAddEquals: function (scalar) { this.x += scalar; this.y += scalar; return this; }, /** * Subtracts another point from this point and returns a new one * @param {fabric.Point} that * @return {fabric.Point} new Point object with subtracted values */ subtract: function (that) { return new Point(this.x - that.x, this.y - that.y); }, /** * Subtracts another point from this point * @param {fabric.Point} that * @return {fabric.Point} thisArg * @chainable */ subtractEquals: function (that) { this.x -= that.x; this.y -= that.y; return this; }, /** * Subtracts value from this point and returns a new one * @param {Number} scalar * @return {fabric.Point} */ scalarSubtract: function (scalar) { return new Point(this.x - scalar, this.y - scalar); }, /** * Subtracts value from this point * @param {Number} scalar * @return {fabric.Point} thisArg * @chainable */ scalarSubtractEquals: function (scalar) { this.x -= scalar; this.y -= scalar; return this; }, /** * Multiplies this point by a value and returns a new one * TODO: rename in scalarMultiply in 2.0 * @param {Number} scalar * @return {fabric.Point} */ multiply: function (scalar) { return new Point(this.x * scalar, this.y * scalar); }, /** * Multiplies this point by a value * TODO: rename in scalarMultiplyEquals in 2.0 * @param {Number} scalar * @return {fabric.Point} thisArg * @chainable */ multiplyEquals: function (scalar) { this.x *= scalar; this.y *= scalar; return this; }, /** * Divides this point by a value and returns a new one * TODO: rename in scalarDivide in 2.0 * @param {Number} scalar * @return {fabric.Point} */ divide: function (scalar) { return new Point(this.x / scalar, this.y / scalar); }, /** * Divides this point by a value * TODO: rename in scalarDivideEquals in 2.0 * @param {Number} scalar * @return {fabric.Point} thisArg * @chainable */ divideEquals: function (scalar) { this.x /= scalar; this.y /= scalar; return this; }, /** * Returns true if this point is equal to another one * @param {fabric.Point} that * @return {Boolean} */ eq: function (that) { return (this.x === that.x && this.y === that.y); }, /** * Returns true if this point is less than another one * @param {fabric.Point} that * @return {Boolean} */ lt: function (that) { return (this.x < that.x && this.y < that.y); }, /** * Returns true if this point is less than or equal to another one * @param {fabric.Point} that * @return {Boolean} */ lte: function (that) { return (this.x <= that.x && this.y <= that.y); }, /** * Returns true if this point is greater another one * @param {fabric.Point} that * @return {Boolean} */ gt: function (that) { return (this.x > that.x && this.y > that.y); }, /** * Returns true if this point is greater than or equal to another one * @param {fabric.Point} that * @return {Boolean} */ gte: function (that) { return (this.x >= that.x && this.y >= that.y); }, /** * Returns new point which is the result of linear interpolation with this one and another one * @param {fabric.Point} that * @param {Number} t , position of interpolation, between 0 and 1 default 0.5 * @return {fabric.Point} */ lerp: function (that, t) { if (typeof t === 'undefined') { t = 0.5; } t = Math.max(Math.min(1, t), 0); return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t); }, /** * Returns distance from this point and another one * @param {fabric.Point} that * @return {Number} */ distanceFrom: function (that) { var dx = this.x - that.x, dy = this.y - that.y; return Math.sqrt(dx * dx + dy * dy); }, /** * Returns the point between this point and another one * @param {fabric.Point} that * @return {fabric.Point} */ midPointFrom: function (that) { return this.lerp(that); }, /** * Returns a new point which is the min of this and another one * @param {fabric.Point} that * @return {fabric.Point} */ min: function (that) { return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y)); }, /** * Returns a new point which is the max of this and another one * @param {fabric.Point} that * @return {fabric.Point} */ max: function (that) { return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y)); }, /** * Returns string representation of this point * @return {String} */ toString: function () { return this.x + ',' + this.y; }, /** * Sets x/y of this point * @param {Number} x * @param {Number} y * @chainable */ setXY: function (x, y) { this.x = x; this.y = y; return this; }, /** * Sets x of this point * @param {Number} x * @chainable */ setX: function (x) { this.x = x; return this; }, /** * Sets y of this point * @param {Number} y * @chainable */ setY: function (y) { this.y = y; return this; }, /** * Sets x/y of this point from another point * @param {fabric.Point} that * @chainable */ setFromPoint: function (that) { this.x = that.x; this.y = that.y; return this; }, /** * Swaps x/y of this point and another point * @param {fabric.Point} that */ swap: function (that) { var x = this.x, y = this.y; this.x = that.x; this.y = that.y; that.x = x; that.y = y; }, /** * return a cloned instance of the point * @return {fabric.Point} */ clone: function () { return new Point(this.x, this.y); } }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ var fabric = global.fabric || (global.fabric = { }); if (fabric.Intersection) { fabric.warn('fabric.Intersection is already defined'); return; } /** * Intersection class * @class fabric.Intersection * @memberOf fabric * @constructor */ function Intersection(status) { this.status = status; this.points = []; } fabric.Intersection = Intersection; fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ { constructor: Intersection, /** * Appends a point to intersection * @param {fabric.Point} point * @return {fabric.Intersection} thisArg * @chainable */ appendPoint: function (point) { this.points.push(point); return this; }, /** * Appends points to intersection * @param {Array} points * @return {fabric.Intersection} thisArg * @chainable */ appendPoints: function (points) { this.points = this.points.concat(points); return this; } }; /** * Checks if one line intersects another * TODO: rename in intersectSegmentSegment * @static * @param {fabric.Point} a1 * @param {fabric.Point} a2 * @param {fabric.Point} b1 * @param {fabric.Point} b2 * @return {fabric.Intersection} */ fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { var result, uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); if (uB !== 0) { var ua = uaT / uB, ub = ubT / uB; if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { result = new Intersection('Intersection'); result.appendPoint(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); } else { result = new Intersection(); } } else { if (uaT === 0 || ubT === 0) { result = new Intersection('Coincident'); } else { result = new Intersection('Parallel'); } } return result; }; /** * Checks if line intersects polygon * TODO: rename in intersectSegmentPolygon * fix detection of coincident * @static * @param {fabric.Point} a1 * @param {fabric.Point} a2 * @param {Array} points * @return {fabric.Intersection} */ fabric.Intersection.intersectLinePolygon = function(a1, a2, points) { var result = new Intersection(), length = points.length, b1, b2, inter, i; for (i = 0; i < length; i++) { b1 = points[i]; b2 = points[(i + 1) % length]; inter = Intersection.intersectLineLine(a1, a2, b1, b2); result.appendPoints(inter.points); } if (result.points.length > 0) { result.status = 'Intersection'; } return result; }; /** * Checks if polygon intersects another polygon * @static * @param {Array} points1 * @param {Array} points2 * @return {fabric.Intersection} */ fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { var result = new Intersection(), length = points1.length, i; for (i = 0; i < length; i++) { var a1 = points1[i], a2 = points1[(i + 1) % length], inter = Intersection.intersectLinePolygon(a1, a2, points2); result.appendPoints(inter.points); } if (result.points.length > 0) { result.status = 'Intersection'; } return result; }; /** * Checks if polygon intersects rectangle * @static * @param {Array} points * @param {fabric.Point} r1 * @param {fabric.Point} r2 * @return {fabric.Intersection} */ fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { var min = r1.min(r2), max = r1.max(r2), topRight = new fabric.Point(max.x, min.y), bottomLeft = new fabric.Point(min.x, max.y), inter1 = Intersection.intersectLinePolygon(min, topRight, points), inter2 = Intersection.intersectLinePolygon(topRight, max, points), inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), result = new Intersection(); result.appendPoints(inter1.points); result.appendPoints(inter2.points); result.appendPoints(inter3.points); result.appendPoints(inter4.points); if (result.points.length > 0) { result.status = 'Intersection'; } return result; }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }); if (fabric.Color) { fabric.warn('fabric.Color is already defined.'); return; } /** * Color class * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations; * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects. * * @class fabric.Color * @param {String} color optional in hex or rgb(a) or hsl format or from known color list * @return {fabric.Color} thisArg * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors} */ function Color(color) { if (!color) { this.setSource([0, 0, 0, 1]); } else { this._tryParsingColor(color); } } fabric.Color = Color; fabric.Color.prototype = /** @lends fabric.Color.prototype */ { /** * @private * @param {String|Array} color Color value to parse */ _tryParsingColor: function(color) { var source; if (color in Color.colorNameMap) { color = Color.colorNameMap[color]; } if (color === 'transparent') { source = [255, 255, 255, 0]; } if (!source) { source = Color.sourceFromHex(color); } if (!source) { source = Color.sourceFromRgb(color); } if (!source) { source = Color.sourceFromHsl(color); } if (!source) { //if color is not recognize let's make black as canvas does source = [0, 0, 0, 1]; } if (source) { this.setSource(source); } }, /** * Adapted from <a href="https://rawgithub.com/mjijackson/mjijackson.github.com/master/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript.html">https://github.com/mjijackson</a> * @private * @param {Number} r Red color value * @param {Number} g Green color value * @param {Number} b Blue color value * @return {Array} Hsl color */ _rgbToHsl: function(r, g, b) { r /= 255; g /= 255; b /= 255; var h, s, l, max = fabric.util.array.max([r, g, b]), min = fabric.util.array.min([r, g, b]); l = (max + min) / 2; if (max === min) { h = s = 0; // achromatic } else { var d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return [ Math.round(h * 360), Math.round(s * 100), Math.round(l * 100) ]; }, /** * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) * @return {Array} */ getSource: function() { return this._source; }, /** * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) * @param {Array} source */ setSource: function(source) { this._source = source; }, /** * Returns color representation in RGB format * @return {String} ex: rgb(0-255,0-255,0-255) */ toRgb: function() { var source = this.getSource(); return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')'; }, /** * Returns color representation in RGBA format * @return {String} ex: rgba(0-255,0-255,0-255,0-1) */ toRgba: function() { var source = this.getSource(); return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')'; }, /** * Returns color representation in HSL format * @return {String} ex: hsl(0-360,0%-100%,0%-100%) */ toHsl: function() { var source = this.getSource(), hsl = this._rgbToHsl(source[0], source[1], source[2]); return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)'; }, /** * Returns color representation in HSLA format * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) */ toHsla: function() { var source = this.getSource(), hsl = this._rgbToHsl(source[0], source[1], source[2]); return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')'; }, /** * Returns color representation in HEX format * @return {String} ex: FF5555 */ toHex: function() { var source = this.getSource(), r, g, b; r = source[0].toString(16); r = (r.length === 1) ? ('0' + r) : r; g = source[1].toString(16); g = (g.length === 1) ? ('0' + g) : g; b = source[2].toString(16); b = (b.length === 1) ? ('0' + b) : b; return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); }, /** * Returns color representation in HEXA format * @return {String} ex: FF5555CC */ toHexa: function() { var source = this.getSource(), a; a = Math.round(source[3] * 255); a = a.toString(16); a = (a.length === 1) ? ('0' + a) : a; return this.toHex() + a.toUpperCase(); }, /** * Gets value of alpha channel for this color * @return {Number} 0-1 */ getAlpha: function() { return this.getSource()[3]; }, /** * Sets value of alpha channel for this color * @param {Number} alpha Alpha value 0-1 * @return {fabric.Color} thisArg */ setAlpha: function(alpha) { var source = this.getSource(); source[3] = alpha; this.setSource(source); return this; }, /** * Transforms color to its grayscale representation * @return {fabric.Color} thisArg */ toGrayscale: function() { var source = this.getSource(), average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10), currentAlpha = source[3]; this.setSource([average, average, average, currentAlpha]); return this; }, /** * Transforms color to its black and white representation * @param {Number} threshold * @return {fabric.Color} thisArg */ toBlackWhite: function(threshold) { var source = this.getSource(), average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), currentAlpha = source[3]; threshold = threshold || 127; average = (Number(average) < Number(threshold)) ? 0 : 255; this.setSource([average, average, average, currentAlpha]); return this; }, /** * Overlays color with another color * @param {String|fabric.Color} otherColor * @return {fabric.Color} thisArg */ overlayWith: function(otherColor) { if (!(otherColor instanceof Color)) { otherColor = new Color(otherColor); } var result = [], alpha = this.getAlpha(), otherAlpha = 0.5, source = this.getSource(), otherSource = otherColor.getSource(), i; for (i = 0; i < 3; i++) { result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha))); } result[3] = alpha; this.setSource(result); return this; } }; /** * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5)) * @static * @field * @memberOf fabric.Color */ // eslint-disable-next-line max-len fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*((?:\d*\.?\d+)?)\s*)?\)$/i; /** * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 )) * @static * @field * @memberOf fabric.Color */ fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/i; /** * Regex matching color in HEX format (ex: #FF5544CC, #FF5555, 010155, aff) * @static * @field * @memberOf fabric.Color */ fabric.Color.reHex = /^#?([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})$/i; /** * Map of the 148 color names with HEX code * @static * @field * @memberOf fabric.Color * @see: https://www.w3.org/TR/css3-color/#svg-color */ fabric.Color.colorNameMap = { aliceblue: '#F0F8FF', antiquewhite: '#FAEBD7', aqua: '#00FFFF', aquamarine: '#7FFFD4', azure: '#F0FFFF', beige: '#F5F5DC', bisque: '#FFE4C4', black: '#000000', blanchedalmond: '#FFEBCD', blue: '#0000FF', blueviolet: '#8A2BE2', brown: '#A52A2A', burlywood: '#DEB887', cadetblue: '#5F9EA0', chartreuse: '#7FFF00', chocolate: '#D2691E', coral: '#FF7F50', cornflowerblue: '#6495ED', cornsilk: '#FFF8DC', crimson: '#DC143C', cyan: '#00FFFF', darkblue: '#00008B', darkcyan: '#008B8B', darkgoldenrod: '#B8860B', darkgray: '#A9A9A9', darkgrey: '#A9A9A9', darkgreen: '#006400', darkkhaki: '#BDB76B', darkmagenta: '#8B008B', darkolivegreen: '#556B2F', darkorange: '#FF8C00', darkorchid: '#9932CC', darkred: '#8B0000', darksalmon: '#E9967A', darkseagreen: '#8FBC8F', darkslateblue: '#483D8B', darkslategray: '#2F4F4F', darkslategrey: '#2F4F4F', darkturquoise: '#00CED1', darkviolet: '#9400D3', deeppink: '#FF1493', deepskyblue: '#00BFFF', dimgray: '#696969', dimgrey: '#696969', dodgerblue: '#1E90FF', firebrick: '#B22222', floralwhite: '#FFFAF0', forestgreen: '#228B22', fuchsia: '#FF00FF', gainsboro: '#DCDCDC', ghostwhite: '#F8F8FF', gold: '#FFD700', goldenrod: '#DAA520', gray: '#808080', grey: '#808080', green: '#008000', greenyellow: '#ADFF2F', honeydew: '#F0FFF0', hotpink: '#FF69B4', indianred: '#CD5C5C', indigo: '#4B0082', ivory: '#FFFFF0', khaki: '#F0E68C', lavender: '#E6E6FA', lavenderblush: '#FFF0F5', lawngreen: '#7CFC00', lemonchiffon: '#FFFACD', lightblue: '#ADD8E6', lightcoral: '#F08080', lightcyan: '#E0FFFF', lightgoldenrodyellow: '#FAFAD2', lightgray: '#D3D3D3', lightgrey: '#D3D3D3', lightgreen: '#90EE90', lightpink: '#FFB6C1', lightsalmon: '#FFA07A', lightseagreen: '#20B2AA', lightskyblue: '#87CEFA', lightslategray: '#778899', lightslategrey: '#778899', lightsteelblue: '#B0C4DE', lightyellow: '#FFFFE0', lime: '#00FF00', limegreen: '#32CD32', linen: '#FAF0E6', magenta: '#FF00FF', maroon: '#800000', mediumaquamarine: '#66CDAA', mediumblue: '#0000CD', mediumorchid: '#BA55D3', mediumpurple: '#9370DB', mediumseagreen: '#3CB371', mediumslateblue: '#7B68EE', mediumspringgreen: '#00FA9A', mediumturquoise: '#48D1CC', mediumvioletred: '#C71585', midnightblue: '#191970', mintcream: '#F5FFFA', mistyrose: '#FFE4E1', moccasin: '#FFE4B5', navajowhite: '#FFDEAD', navy: '#000080', oldlace: '#FDF5E6', olive: '#808000', olivedrab: '#6B8E23', orange: '#FFA500', orangered: '#FF4500', orchid: '#DA70D6', palegoldenrod: '#EEE8AA', palegreen: '#98FB98', paleturquoise: '#AFEEEE', palevioletred: '#DB7093', papayawhip: '#FFEFD5', peachpuff: '#FFDAB9', peru: '#CD853F', pink: '#FFC0CB', plum: '#DDA0DD', powderblue: '#B0E0E6', purple: '#800080', rebeccapurple: '#663399', red: '#FF0000', rosybrown: '#BC8F8F', royalblue: '#4169E1', saddlebrown: '#8B4513', salmon: '#FA8072', sandybrown: '#F4A460', seagreen: '#2E8B57', seashell: '#FFF5EE', sienna: '#A0522D', silver: '#C0C0C0', skyblue: '#87CEEB', slateblue: '#6A5ACD', slategray: '#708090', slategrey: '#708090', snow: '#FFFAFA', springgreen: '#00FF7F', steelblue: '#4682B4', tan: '#D2B48C', teal: '#008080', thistle: '#D8BFD8', tomato: '#FF6347', turquoise: '#40E0D0', violet: '#EE82EE', wheat: '#F5DEB3', white: '#FFFFFF', whitesmoke: '#F5F5F5', yellow: '#FFFF00', yellowgreen: '#9ACD32' }; /** * @private * @param {Number} p * @param {Number} q * @param {Number} t * @return {Number} */ function hue2rgb(p, q, t) { if (t < 0) { t += 1; } if (t > 1) { t -= 1; } if (t < 1 / 6) { return p + (q - p) * 6 * t; } if (t < 1 / 2) { return q; } if (t < 2 / 3) { return p + (q - p) * (2 / 3 - t) * 6; } return p; } /** * Returns new color object, when given a color in RGB format * @memberOf fabric.Color * @param {String} color Color value ex: rgb(0-255,0-255,0-255) * @return {fabric.Color} */ fabric.Color.fromRgb = function(color) { return Color.fromSource(Color.sourceFromRgb(color)); }; /** * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format * @memberOf fabric.Color * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%) * @return {Array} source */ fabric.Color.sourceFromRgb = function(color) { var match = color.match(Color.reRGBa); if (match) { var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1), b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1); return [ parseInt(r, 10), parseInt(g, 10), parseInt(b, 10), match[4] ? parseFloat(match[4]) : 1 ]; } }; /** * Returns new color object, when given a color in RGBA format * @static * @function * @memberOf fabric.Color * @param {String} color * @return {fabric.Color} */ fabric.Color.fromRgba = Color.fromRgb; /** * Returns new color object, when given a color in HSL format * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%) * @memberOf fabric.Color * @return {fabric.Color} */ fabric.Color.fromHsl = function(color) { return Color.fromSource(Color.sourceFromHsl(color)); }; /** * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format. * Adapted from <a href="https://rawgithub.com/mjijackson/mjijackson.github.com/master/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript.html">https://github.com/mjijackson</a> * @memberOf fabric.Color * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1) * @return {Array} source * @see http://http://www.w3.org/TR/css3-color/#hsl-color */ fabric.Color.sourceFromHsl = function(color) { var match = color.match(Color.reHSLa); if (!match) { return; } var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), r, g, b; if (s === 0) { r = g = b = l; } else { var q = l <= 0.5 ? l * (s + 1) : l + s - l * s, p = l * 2 - q; r = hue2rgb(p, q, h + 1 / 3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1 / 3); } return [ Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), match[4] ? parseFloat(match[4]) : 1 ]; }; /** * Returns new color object, when given a color in HSLA format * @static * @function * @memberOf fabric.Color * @param {String} color * @return {fabric.Color} */ fabric.Color.fromHsla = Color.fromHsl; /** * Returns new color object, when given a color in HEX format * @static * @memberOf fabric.Color * @param {String} color Color value ex: FF5555 * @return {fabric.Color} */ fabric.Color.fromHex = function(color) { return Color.fromSource(Color.sourceFromHex(color)); }; /** * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HEX format * @static * @memberOf fabric.Color * @param {String} color ex: FF5555 or FF5544CC (RGBa) * @return {Array} source */ fabric.Color.sourceFromHex = function(color) { if (color.match(Color.reHex)) { var value = color.slice(color.indexOf('#') + 1), isShortNotation = (value.length === 3 || value.length === 4), isRGBa = (value.length === 8 || value.length === 4), r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2), g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4), b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6), a = isRGBa ? (isShortNotation ? (value.charAt(3) + value.charAt(3)) : value.substring(6, 8)) : 'FF'; return [ parseInt(r, 16), parseInt(g, 16), parseInt(b, 16), parseFloat((parseInt(a, 16) / 255).toFixed(2)) ]; } }; /** * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) * @static * @memberOf fabric.Color * @param {Array} source * @return {fabric.Color} */ fabric.Color.fromSource = function(source) { var oColor = new Color(); oColor.setSource(source); return oColor; }; })(typeof exports !== 'undefined' ? exports : this); (function() { /* _FROM_SVG_START_ */ function getColorStop(el) { var style = el.getAttribute('style'), offset = el.getAttribute('offset') || 0, color, colorAlpha, opacity, i; // convert percents to absolute values offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1); offset = offset < 0 ? 0 : offset > 1 ? 1 : offset; if (style) { var keyValuePairs = style.split(/\s*;\s*/); if (keyValuePairs[keyValuePairs.length - 1] === '') { keyValuePairs.pop(); } for (i = keyValuePairs.length; i--; ) { var split = keyValuePairs[i].split(/\s*:\s*/), key = split[0].trim(), value = split[1].trim(); if (key === 'stop-color') { color = value; } else if (key === 'stop-opacity') { opacity = value; } } } if (!color) { color = el.getAttribute('stop-color') || 'rgb(0,0,0)'; } if (!opacity) { opacity = el.getAttribute('stop-opacity'); } color = new fabric.Color(color); colorAlpha = color.getAlpha(); opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity); opacity *= colorAlpha; return { offset: offset, color: color.toRgb(), opacity: opacity }; } function getLinearCoords(el) { return { x1: el.getAttribute('x1') || 0, y1: el.getAttribute('y1') || 0, x2: el.getAttribute('x2') || '100%', y2: el.getAttribute('y2') || 0 }; } function getRadialCoords(el) { return { x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%', y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%', r1: 0, x2: el.getAttribute('cx') || '50%', y2: el.getAttribute('cy') || '50%', r2: el.getAttribute('r') || '50%' }; } /* _FROM_SVG_END_ */ var clone = fabric.util.object.clone; /** * Gradient class * @class fabric.Gradient * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#gradients} * @see {@link fabric.Gradient#initialize} for constructor definition */ fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ { /** * Horizontal offset for aligning gradients coming from SVG when outside pathgroups * @type Number * @default 0 */ offsetX: 0, /** * Vertical offset for aligning gradients coming from SVG when outside pathgroups * @type Number * @default 0 */ offsetY: 0, /** * Constructor * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops * @return {fabric.Gradient} thisArg */ initialize: function(options) { options || (options = { }); var coords = { }; this.id = fabric.Object.__uid++; this.type = options.type || 'linear'; coords = { x1: options.coords.x1 || 0, y1: options.coords.y1 || 0, x2: options.coords.x2 || 0, y2: options.coords.y2 || 0 }; if (this.type === 'radial') { coords.r1 = options.coords.r1 || 0; coords.r2 = options.coords.r2 || 0; } this.coords = coords; this.colorStops = options.colorStops.slice(); if (options.gradientTransform) { this.gradientTransform = options.gradientTransform; } this.offsetX = options.offsetX || this.offsetX; this.offsetY = options.offsetY || this.offsetY; }, /** * Adds another colorStop * @param {Object} colorStop Object with offset and color * @return {fabric.Gradient} thisArg */ addColorStop: function(colorStops) { for (var position in colorStops) { var color = new fabric.Color(colorStops[position]); this.colorStops.push({ offset: parseFloat(position), color: color.toRgb(), opacity: color.getAlpha() }); } return this; }, /** * Returns object representation of a gradient * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} */ toObject: function(propertiesToInclude) { var object = { type: this.type, coords: this.coords, colorStops: this.colorStops, offsetX: this.offsetX, offsetY: this.offsetY, gradientTransform: this.gradientTransform ? this.gradientTransform.concat() : this.gradientTransform }; fabric.util.populateWithProperties(this, object, propertiesToInclude); return object; }, /* _TO_SVG_START_ */ /** * Returns SVG representation of an gradient * @param {Object} object Object to create a gradient for * @return {String} SVG representation of an gradient (linear/radial) */ toSVG: function(object) { var coords = clone(this.coords, true), i, len, markup, commonAttributes, colorStops = clone(this.colorStops, true), needsSwap = coords.r1 > coords.r2, transform = this.gradientTransform ? this.gradientTransform.concat() : fabric.iMatrix.concat(), offsetX = object.width / 2 - this.offsetX, offsetY = object.height / 2 - this.offsetY; // colorStops must be sorted ascending colorStops.sort(function(a, b) { return a.offset - b.offset; }); if (object.type === 'path') { offsetX -= object.pathOffset.x; offsetY -= object.pathOffset.y; } transform[4] -= offsetX; transform[5] -= offsetY; commonAttributes = 'id="SVGID_' + this.id + '" gradientUnits="userSpaceOnUse"'; commonAttributes += ' gradientTransform="matrix(' + transform.join(' ') + ')" '; if (this.type === 'linear') { markup = [ '<linearGradient ', commonAttributes, ' x1="', coords.x1, '" y1="', coords.y1, '" x2="', coords.x2, '" y2="', coords.y2, '">\n' ]; } else if (this.type === 'radial') { // svg radial gradient has just 1 radius. the biggest. markup = [ '<radialGradient ', commonAttributes, ' cx="', needsSwap ? coords.x1 : coords.x2, '" cy="', needsSwap ? coords.y1 : coords.y2, '" r="', needsSwap ? coords.r1 : coords.r2, '" fx="', needsSwap ? coords.x2 : coords.x1, '" fy="', needsSwap ? coords.y2 : coords.y1, '">\n' ]; } if (this.type === 'radial') { if (needsSwap) { // svg goes from internal to external radius. if radius are inverted, swap color stops. colorStops = colorStops.concat(); colorStops.reverse(); for (i = 0, len = colorStops.length; i < len; i++) { colorStops[i].offset = 1 - colorStops[i].offset; } } var minRadius = Math.min(coords.r1, coords.r2); if (minRadius > 0) { // i have to shift all colorStops and add new one in 0. var maxRadius = Math.max(coords.r1, coords.r2), percentageShift = minRadius / maxRadius; for (i = 0, len = colorStops.length; i < len; i++) { colorStops[i].offset += percentageShift * (1 - colorStops[i].offset); } } } for (i = 0, len = colorStops.length; i < len; i++) { var colorStop = colorStops[i]; markup.push( '<stop ', 'offset="', (colorStop.offset * 100) + '%', '" style="stop-color:', colorStop.color, (typeof colorStop.opacity !== 'undefined' ? ';stop-opacity: ' + colorStop.opacity : ';'), '"/>\n' ); } markup.push((this.type === 'linear' ? '</linearGradient>\n' : '</radialGradient>\n')); return markup.join(''); }, /* _TO_SVG_END_ */ /** * Returns an instance of CanvasGradient * @param {CanvasRenderingContext2D} ctx Context to render on * @return {CanvasGradient} */ toLive: function(ctx) { var gradient, coords = fabric.util.object.clone(this.coords), i, len; if (!this.type) { return; } if (this.type === 'linear') { gradient = ctx.createLinearGradient( coords.x1, coords.y1, coords.x2, coords.y2); } else if (this.type === 'radial') { gradient = ctx.createRadialGradient( coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, coords.r2); } for (i = 0, len = this.colorStops.length; i < len; i++) { var color = this.colorStops[i].color, opacity = this.colorStops[i].opacity, offset = this.colorStops[i].offset; if (typeof opacity !== 'undefined') { color = new fabric.Color(color).setAlpha(opacity).toRgba(); } gradient.addColorStop(offset, color); } return gradient; } }); fabric.util.object.extend(fabric.Gradient, { /* _FROM_SVG_START_ */ /** * Returns {@link fabric.Gradient} instance from an SVG element * @static * @memberOf fabric.Gradient * @param {SVGGradientElement} el SVG gradient element * @param {fabric.Object} instance * @return {fabric.Gradient} Gradient instance * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement */ fromElement: function(el, instance) { /** * @example: * * <linearGradient id="linearGrad1"> * <stop offset="0%" stop-color="white"/> * <stop offset="100%" stop-color="black"/> * </linearGradient> * * OR * * <linearGradient id="linearGrad2"> * <stop offset="0" style="stop-color:rgb(255,255,255)"/> * <stop offset="1" style="stop-color:rgb(0,0,0)"/> * </linearGradient> * * OR * * <radialGradient id="radialGrad1"> * <stop offset="0%" stop-color="white" stop-opacity="1" /> * <stop offset="50%" stop-color="black" stop-opacity="0.5" /> * <stop offset="100%" stop-color="white" stop-opacity="1" /> * </radialGradient> * * OR * * <radialGradient id="radialGrad2"> * <stop offset="0" stop-color="rgb(255,255,255)" /> * <stop offset="0.5" stop-color="rgb(0,0,0)" /> * <stop offset="1" stop-color="rgb(255,255,255)" /> * </radialGradient> * */ var colorStopEls = el.getElementsByTagName('stop'), type, gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox', gradientTransform = el.getAttribute('gradientTransform'), colorStops = [], coords, ellipseMatrix, i; if (el.nodeName === 'linearGradient' || el.nodeName === 'LINEARGRADIENT') { type = 'linear'; } else { type = 'radial'; } if (type === 'linear') { coords = getLinearCoords(el); } else if (type === 'radial') { coords = getRadialCoords(el); } for (i = colorStopEls.length; i--; ) { colorStops.push(getColorStop(colorStopEls[i])); } ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits); var gradient = new fabric.Gradient({ type: type, coords: coords, colorStops: colorStops, offsetX: -instance.left, offsetY: -instance.top }); if (gradientTransform || ellipseMatrix !== '') { gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix); } return gradient; }, /* _FROM_SVG_END_ */ /** * Returns {@link fabric.Gradient} instance from its object representation * @static * @memberOf fabric.Gradient * @param {Object} obj * @param {Object} [options] Options object */ forObject: function(obj, options) { options || (options = { }); _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse'); return new fabric.Gradient(options); } }); /** * @private */ function _convertPercentUnitsToValues(object, options, gradientUnits) { var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = ''; for (var prop in options) { if (options[prop] === 'Infinity') { options[prop] = 1; } else if (options[prop] === '-Infinity') { options[prop] = 0; } propValue = parseFloat(options[prop], 10); if (typeof options[prop] === 'string' && /^(\d+\.\d+)%|(\d+)%$/.test(options[prop])) { multFactor = 0.01; } else { multFactor = 1; } if (prop === 'x1' || prop === 'x2' || prop === 'r2') { multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1; addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0; } else if (prop === 'y1' || prop === 'y2') { multFactor *= gradientUnits === 'objectBoundingBox' ? object.height : 1; addFactor = gradientUnits === 'objectBoundingBox' ? object.top || 0 : 0; } options[prop] = propValue * multFactor + addFactor; } if (object.type === 'ellipse' && options.r2 !== null && gradientUnits === 'objectBoundingBox' && object.rx !== object.ry) { var scaleFactor = object.ry / object.rx; ellipseMatrix = ' scale(1, ' + scaleFactor + ')'; if (options.y1) { options.y1 /= scaleFactor; } if (options.y2) { options.y2 /= scaleFactor; } } return ellipseMatrix; } })(); (function() { 'use strict'; var toFixed = fabric.util.toFixed; /** * Pattern class * @class fabric.Pattern * @see {@link http://fabricjs.com/patterns|Pattern demo} * @see {@link http://fabricjs.com/dynamic-patterns|DynamicPattern demo} * @see {@link fabric.Pattern#initialize} for constructor definition */ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ { /** * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) * @type String * @default */ repeat: 'repeat', /** * Pattern horizontal offset from object's left/top corner * @type Number * @default */ offsetX: 0, /** * Pattern vertical offset from object's left/top corner * @type Number * @default */ offsetY: 0, /** * crossOrigin value (one of "", "anonymous", "use-credentials") * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes * @type String * @default */ crossOrigin: '', /** * transform matrix to change the pattern, imported from svgs. * @type Array * @default */ patternTransform: null, /** * Constructor * @param {Object} [options] Options object * @param {Function} [callback] function to invoke after callback init. * @return {fabric.Pattern} thisArg */ initialize: function(options, callback) { options || (options = { }); this.id = fabric.Object.__uid++; this.setOptions(options); if (!options.source || (options.source && typeof options.source !== 'string')) { callback && callback(this); return; } // function string if (typeof fabric.util.getFunctionBody(options.source) !== 'undefined') { this.source = new Function(fabric.util.getFunctionBody(options.source)); callback && callback(this); } else { // img src string var _this = this; this.source = fabric.util.createImage(); fabric.util.loadImage(options.source, function(img) { _this.source = img; callback && callback(_this); }, null, this.crossOrigin); } }, /** * Returns object representation of a pattern * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} Object representation of a pattern instance */ toObject: function(propertiesToInclude) { var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, source, object; // callback if (typeof this.source === 'function') { source = String(this.source); } // <img> element else if (typeof this.source.src === 'string') { source = this.source.src; } // <canvas> element else if (typeof this.source === 'object' && this.source.toDataURL) { source = this.source.toDataURL(); } object = { type: 'pattern', source: source, repeat: this.repeat, crossOrigin: this.crossOrigin, offsetX: toFixed(this.offsetX, NUM_FRACTION_DIGITS), offsetY: toFixed(this.offsetY, NUM_FRACTION_DIGITS), patternTransform: this.patternTransform ? this.patternTransform.concat() : null }; fabric.util.populateWithProperties(this, object, propertiesToInclude); return object; }, /* _TO_SVG_START_ */ /** * Returns SVG representation of a pattern * @param {fabric.Object} object * @return {String} SVG representation of a pattern */ toSVG: function(object) { var patternSource = typeof this.source === 'function' ? this.source() : this.source, patternWidth = patternSource.width / object.width, patternHeight = patternSource.height / object.height, patternOffsetX = this.offsetX / object.width, patternOffsetY = this.offsetY / object.height, patternImgSrc = ''; if (this.repeat === 'repeat-x' || this.repeat === 'no-repeat') { patternHeight = 1; if (patternOffsetY) { patternHeight += Math.abs(patternOffsetY); } } if (this.repeat === 'repeat-y' || this.repeat === 'no-repeat') { patternWidth = 1; if (patternOffsetX) { patternWidth += Math.abs(patternOffsetX); } } if (patternSource.src) { patternImgSrc = patternSource.src; } else if (patternSource.toDataURL) { patternImgSrc = patternSource.toDataURL(); } return '<pattern id="SVGID_' + this.id + '" x="' + patternOffsetX + '" y="' + patternOffsetY + '" width="' + patternWidth + '" height="' + patternHeight + '">\n' + '<image x="0" y="0"' + ' width="' + patternSource.width + '" height="' + patternSource.height + '" xlink:href="' + patternImgSrc + '"></image>\n' + '</pattern>\n'; }, /* _TO_SVG_END_ */ setOptions: function(options) { for (var prop in options) { this[prop] = options[prop]; } }, /** * Returns an instance of CanvasPattern * @param {CanvasRenderingContext2D} ctx Context to create pattern * @return {CanvasPattern} */ toLive: function(ctx) { var source = typeof this.source === 'function' ? this.source() : this.source; // if the image failed to load, return, and allow rest to continue loading if (!source) { return ''; } // if an image if (typeof source.src !== 'undefined') { if (!source.complete) { return ''; } if (source.naturalWidth === 0 || source.naturalHeight === 0) { return ''; } } return ctx.createPattern(source, this.repeat); } }); })(); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), toFixed = fabric.util.toFixed; if (fabric.Shadow) { fabric.warn('fabric.Shadow is already defined.'); return; } /** * Shadow class * @class fabric.Shadow * @see {@link http://fabricjs.com/shadows|Shadow demo} * @see {@link fabric.Shadow#initialize} for constructor definition */ fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ { /** * Shadow color * @type String * @default */ color: 'rgb(0,0,0)', /** * Shadow blur * @type Number */ blur: 0, /** * Shadow horizontal offset * @type Number * @default */ offsetX: 0, /** * Shadow vertical offset * @type Number * @default */ offsetY: 0, /** * Whether the shadow should affect stroke operations * @type Boolean * @default */ affectStroke: false, /** * Indicates whether toObject should include default values * @type Boolean * @default */ includeDefaultValues: true, /** * Constructor * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetY properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px") * @return {fabric.Shadow} thisArg */ initialize: function(options) { if (typeof options === 'string') { options = this._parseShadow(options); } for (var prop in options) { this[prop] = options[prop]; } this.id = fabric.Object.__uid++; }, /** * @private * @param {String} shadow Shadow value to parse * @return {Object} Shadow object with color, offsetX, offsetY and blur */ _parseShadow: function(shadow) { var shadowStr = shadow.trim(), offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [], color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)'; return { color: color.trim(), offsetX: parseInt(offsetsAndBlur[1], 10) || 0, offsetY: parseInt(offsetsAndBlur[2], 10) || 0, blur: parseInt(offsetsAndBlur[3], 10) || 0 }; }, /** * Returns a string representation of an instance * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow * @return {String} Returns CSS3 text-shadow declaration */ toString: function() { return [this.offsetX, this.offsetY, this.blur, this.color].join('px '); }, /* _TO_SVG_START_ */ /** * Returns SVG representation of a shadow * @param {fabric.Object} object * @return {String} SVG representation of a shadow */ toSVG: function(object) { var fBoxX = 40, fBoxY = 40, NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, offset = fabric.util.rotateVector( { x: this.offsetX, y: this.offsetY }, fabric.util.degreesToRadians(-object.angle)), BLUR_BOX = 20, color = new fabric.Color(this.color); if (object.width && object.height) { //http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion // we add some extra space to filter box to contain the blur ( 20 ) fBoxX = toFixed((Math.abs(offset.x) + this.blur) / object.width, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX; fBoxY = toFixed((Math.abs(offset.y) + this.blur) / object.height, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX; } if (object.flipX) { offset.x *= -1; } if (object.flipY) { offset.y *= -1; } return ( '<filter id="SVGID_' + this.id + '" y="-' + fBoxY + '%" height="' + (100 + 2 * fBoxY) + '%" ' + 'x="-' + fBoxX + '%" width="' + (100 + 2 * fBoxX) + '%" ' + '>\n' + '\t<feGaussianBlur in="SourceAlpha" stdDeviation="' + toFixed(this.blur ? this.blur / 2 : 0, NUM_FRACTION_DIGITS) + '"></feGaussianBlur>\n' + '\t<feOffset dx="' + toFixed(offset.x, NUM_FRACTION_DIGITS) + '" dy="' + toFixed(offset.y, NUM_FRACTION_DIGITS) + '" result="oBlur" ></feOffset>\n' + '\t<feFlood flood-color="' + color.toRgb() + '" flood-opacity="' + color.getAlpha() + '"/>\n' + '\t<feComposite in2="oBlur" operator="in" />\n' + '\t<feMerge>\n' + '\t\t<feMergeNode></feMergeNode>\n' + '\t\t<feMergeNode in="SourceGraphic"></feMergeNode>\n' + '\t</feMerge>\n' + '</filter>\n'); }, /* _TO_SVG_END_ */ /** * Returns object representation of a shadow * @return {Object} Object representation of a shadow instance */ toObject: function() { if (this.includeDefaultValues) { return { color: this.color, blur: this.blur, offsetX: this.offsetX, offsetY: this.offsetY, affectStroke: this.affectStroke }; } var obj = { }, proto = fabric.Shadow.prototype; ['color', 'blur', 'offsetX', 'offsetY', 'affectStroke'].forEach(function(prop) { if (this[prop] !== proto[prop]) { obj[prop] = this[prop]; } }, this); return obj; } }); /** * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px") * @static * @field * @memberOf fabric.Shadow */ // eslint-disable-next-line max-len fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/; })(typeof exports !== 'undefined' ? exports : this); (function () { 'use strict'; if (fabric.StaticCanvas) { fabric.warn('fabric.StaticCanvas is already defined.'); return; } // aliases for faster resolution var extend = fabric.util.object.extend, getElementOffset = fabric.util.getElementOffset, removeFromArray = fabric.util.removeFromArray, toFixed = fabric.util.toFixed, transformPoint = fabric.util.transformPoint, invertTransform = fabric.util.invertTransform, getNodeCanvas = fabric.util.getNodeCanvas, createCanvasElement = fabric.util.createCanvasElement, CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element'); /** * Static canvas class * @class fabric.StaticCanvas * @mixes fabric.Collection * @mixes fabric.Observable * @see {@link http://fabricjs.com/static_canvas|StaticCanvas demo} * @see {@link fabric.StaticCanvas#initialize} for constructor definition * @fires before:render * @fires after:render * @fires canvas:cleared * @fires object:added * @fires object:removed */ fabric.StaticCanvas = fabric.util.createClass(fabric.CommonMethods, /** @lends fabric.StaticCanvas.prototype */ { /** * Constructor * @param {HTMLElement | String} el <canvas> element to initialize instance on * @param {Object} [options] Options object * @return {Object} thisArg */ initialize: function(el, options) { options || (options = { }); this.renderAndResetBound = this.renderAndReset.bind(this); this.requestRenderAllBound = this.requestRenderAll.bind(this); this._initStatic(el, options); }, /** * Background color of canvas instance. * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}. * @type {(String|fabric.Pattern)} * @default */ backgroundColor: '', /** * Background image of canvas instance. * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}. * <b>Backwards incompatibility note:</b> The "backgroundImageOpacity" * and "backgroundImageStretch" properties are deprecated since 1.3.9. * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}. * since 2.4.0 image caching is active, please when putting an image as background, add to the * canvas property a reference to the canvas it is on. Otherwise the image cannot detect the zoom * vale. As an alternative you can disable image objectCaching * @type fabric.Image * @default */ backgroundImage: null, /** * Overlay color of canvas instance. * Should be set via {@link fabric.StaticCanvas#setOverlayColor} * @since 1.3.9 * @type {(String|fabric.Pattern)} * @default */ overlayColor: '', /** * Overlay image of canvas instance. * Should be set via {@link fabric.StaticCanvas#setOverlayImage}. * <b>Backwards incompatibility note:</b> The "overlayImageLeft" * and "overlayImageTop" properties are deprecated since 1.3.9. * Use {@link fabric.Image#left} and {@link fabric.Image#top}. * since 2.4.0 image caching is active, please when putting an image as overlay, add to the * canvas property a reference to the canvas it is on. Otherwise the image cannot detect the zoom * vale. As an alternative you can disable image objectCaching * @type fabric.Image * @default */ overlayImage: null, /** * Indicates whether toObject/toDatalessObject should include default values * if set to false, takes precedence over the object value. * @type Boolean * @default */ includeDefaultValues: true, /** * Indicates whether objects' state should be saved * @type Boolean * @default */ stateful: false, /** * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove}, * {@link fabric.StaticCanvas.moveTo}, {@link fabric.StaticCanvas.clear} and many more, should also re-render canvas. * Disabling this option will not give a performance boost when adding/removing a lot of objects to/from canvas at once * since the renders are quequed and executed one per frame. * Disabling is suggested anyway and managing the renders of the app manually is not a big effort ( canvas.requestRenderAll() ) * Left default to true to do not break documentation and old app, fiddles. * @type Boolean * @default */ renderOnAddRemove: true, /** * Function that determines clipping of entire canvas area * Being passed context as first argument. * If you are using code minification, ctx argument can be minified/manglied you should use * as a workaround `var ctx = arguments[0];` in the function; * See clipping canvas area in {@link https://github.com/kangax/fabric.js/wiki/FAQ} * @deprecated since 2.0.0 * @type Function * @default */ clipTo: null, /** * Indicates whether object controls (borders/controls) are rendered above overlay image * @type Boolean * @default */ controlsAboveOverlay: false, /** * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas * @type Boolean * @default */ allowTouchScrolling: false, /** * Indicates whether this canvas will use image smoothing, this is on by default in browsers * @type Boolean * @default */ imageSmoothingEnabled: true, /** * The transformation (in the format of Canvas transform) which focuses the viewport * @type Array * @default */ viewportTransform: fabric.iMatrix.concat(), /** * if set to false background image is not affected by viewport transform * @since 1.6.3 * @type Boolean * @default */ backgroundVpt: true, /** * if set to false overlya image is not affected by viewport transform * @since 1.6.3 * @type Boolean * @default */ overlayVpt: true, /** * Callback; invoked right before object is about to be scaled/rotated * @deprecated since 2.3.0 * Use before:transform event */ onBeforeScaleRotate: function () { /* NOOP */ }, /** * When true, canvas is scaled by devicePixelRatio for better rendering on retina screens * @type Boolean * @default */ enableRetinaScaling: true, /** * Describe canvas element extension over design * properties are tl,tr,bl,br. * if canvas is not zoomed/panned those points are the four corner of canvas * if canvas is viewportTransformed you those points indicate the extension * of canvas element in plain untrasformed coordinates * The coordinates get updated with @method calcViewportBoundaries. * @memberOf fabric.StaticCanvas.prototype */ vptCoords: { }, /** * Based on vptCoords and object.aCoords, skip rendering of objects that * are not included in current viewport. * May greatly help in applications with crowded canvas and use of zoom/pan * If One of the corner of the bounding box of the object is on the canvas * the objects get rendered. * @memberOf fabric.StaticCanvas.prototype * @type Boolean * @default */ skipOffscreen: true, /** * a fabricObject that, without stroke define a clipping area with their shape. filled in black * the clipPath object gets used when the canvas has rendered, and the context is placed in the * top left corner of the canvas. * clipPath will clip away controls, if you do not want this to happen use controlsAboveOverlay = true * @type fabric.Object */ clipPath: undefined, /** * @private * @param {HTMLElement | String} el <canvas> element to initialize instance on * @param {Object} [options] Options object */ _initStatic: function(el, options) { var cb = this.requestRenderAllBound; this._objects = []; this._createLowerCanvas(el); this._initOptions(options); this._setImageSmoothing(); // only initialize retina scaling once if (!this.interactive) { this._initRetinaScaling(); } if (options.overlayImage) { this.setOverlayImage(options.overlayImage, cb); } if (options.backgroundImage) { this.setBackgroundImage(options.backgroundImage, cb); } if (options.backgroundColor) { this.setBackgroundColor(options.backgroundColor, cb); } if (options.overlayColor) { this.setOverlayColor(options.overlayColor, cb); } this.calcOffset(); }, /** * @private */ _isRetinaScaling: function() { return (fabric.devicePixelRatio !== 1 && this.enableRetinaScaling); }, /** * @private * @return {Number} retinaScaling if applied, otherwise 1; */ getRetinaScaling: function() { return this._isRetinaScaling() ? fabric.devicePixelRatio : 1; }, /** * @private */ _initRetinaScaling: function() { if (!this._isRetinaScaling()) { return; } this.lowerCanvasEl.setAttribute('width', this.width * fabric.devicePixelRatio); this.lowerCanvasEl.setAttribute('height', this.height * fabric.devicePixelRatio); this.contextContainer.scale(fabric.devicePixelRatio, fabric.devicePixelRatio); }, /** * Calculates canvas element offset relative to the document * This method is also attached as "resize" event handler of window * @return {fabric.Canvas} instance * @chainable */ calcOffset: function () { this._offset = getElementOffset(this.lowerCanvasEl); return this; }, /** * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to * @param {Function} callback callback to invoke when image is loaded and set as an overlay * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}. * @return {fabric.Canvas} thisArg * @chainable * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo} * @example <caption>Normal overlayImage with left/top = 0</caption> * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { * // Needed to position overlayImage at 0/0 * originX: 'left', * originY: 'top' * }); * @example <caption>overlayImage with different properties</caption> * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { * opacity: 0.5, * angle: 45, * left: 400, * top: 400, * originX: 'left', * originY: 'top' * }); * @example <caption>Stretched overlayImage #1 - width/height correspond to canvas width/height</caption> * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) { * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas)); * }); * @example <caption>Stretched overlayImage #2 - width/height correspond to canvas width/height</caption> * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { * width: canvas.width, * height: canvas.height, * // Needed to position overlayImage at 0/0 * originX: 'left', * originY: 'top' * }); * @example <caption>overlayImage loaded from cross-origin</caption> * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { * opacity: 0.5, * angle: 45, * left: 400, * top: 400, * originX: 'left', * originY: 'top', * crossOrigin: 'anonymous' * }); */ setOverlayImage: function (image, callback, options) { return this.__setBgOverlayImage('overlayImage', image, callback, options); }, /** * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to * @param {Function} callback Callback to invoke when image is loaded and set as background * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}. * @return {fabric.Canvas} thisArg * @chainable * @see {@link http://jsfiddle.net/djnr8o7a/28/|jsFiddle demo} * @example <caption>Normal backgroundImage with left/top = 0</caption> * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { * // Needed to position backgroundImage at 0/0 * originX: 'left', * originY: 'top' * }); * @example <caption>backgroundImage with different properties</caption> * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { * opacity: 0.5, * angle: 45, * left: 400, * top: 400, * originX: 'left', * originY: 'top' * }); * @example <caption>Stretched backgroundImage #1 - width/height correspond to canvas width/height</caption> * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) { * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); * }); * @example <caption>Stretched backgroundImage #2 - width/height correspond to canvas width/height</caption> * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { * width: canvas.width, * height: canvas.height, * // Needed to position backgroundImage at 0/0 * originX: 'left', * originY: 'top' * }); * @example <caption>backgroundImage loaded from cross-origin</caption> * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { * opacity: 0.5, * angle: 45, * left: 400, * top: 400, * originX: 'left', * originY: 'top', * crossOrigin: 'anonymous' * }); */ // TODO: fix stretched examples setBackgroundImage: function (image, callback, options) { return this.__setBgOverlayImage('backgroundImage', image, callback, options); }, /** * Sets {@link fabric.StaticCanvas#overlayColor|foreground color} for this canvas * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set foreground color to * @param {Function} callback Callback to invoke when foreground color is set * @return {fabric.Canvas} thisArg * @chainable * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo} * @example <caption>Normal overlayColor - color value</caption> * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); * @example <caption>fabric.Pattern used as overlayColor</caption> * canvas.setOverlayColor({ * source: 'http://fabricjs.com/assets/escheresque_ste.png' * }, canvas.renderAll.bind(canvas)); * @example <caption>fabric.Pattern used as overlayColor with repeat and offset</caption> * canvas.setOverlayColor({ * source: 'http://fabricjs.com/assets/escheresque_ste.png', * repeat: 'repeat', * offsetX: 200, * offsetY: 100 * }, canvas.renderAll.bind(canvas)); */ setOverlayColor: function(overlayColor, callback) { return this.__setBgOverlayColor('overlayColor', overlayColor, callback); }, /** * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to * @param {Function} callback Callback to invoke when background color is set * @return {fabric.Canvas} thisArg * @chainable * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo} * @example <caption>Normal backgroundColor - color value</caption> * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); * @example <caption>fabric.Pattern used as backgroundColor</caption> * canvas.setBackgroundColor({ * source: 'http://fabricjs.com/assets/escheresque_ste.png' * }, canvas.renderAll.bind(canvas)); * @example <caption>fabric.Pattern used as backgroundColor with repeat and offset</caption> * canvas.setBackgroundColor({ * source: 'http://fabricjs.com/assets/escheresque_ste.png', * repeat: 'repeat', * offsetX: 200, * offsetY: 100 * }, canvas.renderAll.bind(canvas)); */ setBackgroundColor: function(backgroundColor, callback) { return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback); }, /** * @private * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-imagesmoothingenabled|WhatWG Canvas Standard} */ _setImageSmoothing: function() { var ctx = this.getContext(); ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled || ctx.webkitImageSmoothingEnabled || ctx.mozImageSmoothingEnabled || ctx.msImageSmoothingEnabled || ctx.oImageSmoothingEnabled; ctx.imageSmoothingEnabled = this.imageSmoothingEnabled; }, /** * @private * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage} * or {@link fabric.StaticCanvas#overlayImage|overlayImage}) * @param {(fabric.Image|String|null)} image fabric.Image instance, URL of an image or null to set background or overlay to * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}. */ __setBgOverlayImage: function(property, image, callback, options) { if (typeof image === 'string') { fabric.util.loadImage(image, function(img) { if (img) { var instance = new fabric.Image(img, options); this[property] = instance; instance.canvas = this; } callback && callback(img); }, this, options && options.crossOrigin); } else { options && image.setOptions(options); this[property] = image; image && (image.canvas = this); callback && callback(image); } return this; }, /** * @private * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor} * or {@link fabric.StaticCanvas#overlayColor|overlayColor}) * @param {(Object|String|null)} color Object with pattern information, color value or null * @param {Function} [callback] Callback is invoked when color is set */ __setBgOverlayColor: function(property, color, callback) { this[property] = color; this._initGradient(color, property); this._initPattern(color, property, callback); return this; }, /** * @private */ _createCanvasElement: function() { var element = createCanvasElement(); if (!element) { throw CANVAS_INIT_ERROR; } if (!element.style) { element.style = { }; } if (typeof element.getContext === 'undefined') { throw CANVAS_INIT_ERROR; } return element; }, /** * @private * @param {Object} [options] Options object */ _initOptions: function (options) { var lowerCanvasEl = this.lowerCanvasEl; this._setOptions(options); this.width = this.width || parseInt(lowerCanvasEl.width, 10) || 0; this.height = this.height || parseInt(lowerCanvasEl.height, 10) || 0; if (!this.lowerCanvasEl.style) { return; } lowerCanvasEl.width = this.width; lowerCanvasEl.height = this.height; lowerCanvasEl.style.width = this.width + 'px'; lowerCanvasEl.style.height = this.height + 'px'; this.viewportTransform = this.viewportTransform.slice(); }, /** * Creates a bottom canvas * @private * @param {HTMLElement} [canvasEl] */ _createLowerCanvas: function (canvasEl) { // canvasEl === 'HTMLCanvasElement' does not work on jsdom/node if (canvasEl && canvasEl.getContext) { this.lowerCanvasEl = canvasEl; } else { this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement(); } fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas'); if (this.interactive) { this._applyCanvasStyle(this.lowerCanvasEl); } this.contextContainer = this.lowerCanvasEl.getContext('2d'); }, /** * Returns canvas width (in px) * @return {Number} */ getWidth: function () { return this.width; }, /** * Returns canvas height (in px) * @return {Number} */ getHeight: function () { return this.height; }, /** * Sets width of this canvas instance * @param {Number|String} value Value to set width to * @param {Object} [options] Options object * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions * @return {fabric.Canvas} instance * @chainable true */ setWidth: function (value, options) { return this.setDimensions({ width: value }, options); }, /** * Sets height of this canvas instance * @param {Number|String} value Value to set height to * @param {Object} [options] Options object * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions * @return {fabric.Canvas} instance * @chainable true */ setHeight: function (value, options) { return this.setDimensions({ height: value }, options); }, /** * Sets dimensions (width, height) of this canvas instance. when options.cssOnly flag active you should also supply the unit of measure (px/%/em) * @param {Object} dimensions Object with width/height properties * @param {Number|String} [dimensions.width] Width of canvas element * @param {Number|String} [dimensions.height] Height of canvas element * @param {Object} [options] Options object * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions * @return {fabric.Canvas} thisArg * @chainable */ setDimensions: function (dimensions, options) { var cssValue; options = options || {}; for (var prop in dimensions) { cssValue = dimensions[prop]; if (!options.cssOnly) { this._setBackstoreDimension(prop, dimensions[prop]); cssValue += 'px'; this.hasLostContext = true; } if (!options.backstoreOnly) { this._setCssDimension(prop, cssValue); } } if (this._isCurrentlyDrawing) { this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles(); } this._initRetinaScaling(); this._setImageSmoothing(); this.calcOffset(); if (!options.cssOnly) { this.requestRenderAll(); } return this; }, /** * Helper for setting width/height * @private * @param {String} prop property (width|height) * @param {Number} value value to set property to * @return {fabric.Canvas} instance * @chainable true */ _setBackstoreDimension: function (prop, value) { this.lowerCanvasEl[prop] = value; if (this.upperCanvasEl) { this.upperCanvasEl[prop] = value; } if (this.cacheCanvasEl) { this.cacheCanvasEl[prop] = value; } this[prop] = value; return this; }, /** * Helper for setting css width/height * @private * @param {String} prop property (width|height) * @param {String} value value to set property to * @return {fabric.Canvas} instance * @chainable true */ _setCssDimension: function (prop, value) { this.lowerCanvasEl.style[prop] = value; if (this.upperCanvasEl) { this.upperCanvasEl.style[prop] = value; } if (this.wrapperEl) { this.wrapperEl.style[prop] = value; } return this; }, /** * Returns canvas zoom level * @return {Number} */ getZoom: function () { return this.viewportTransform[0]; }, /** * Sets viewport transform of this canvas instance * @param {Array} vpt the transform in the form of context.transform * @return {fabric.Canvas} instance * @chainable true */ setViewportTransform: function (vpt) { var activeObject = this._activeObject, object, ignoreVpt = false, skipAbsolute = true, i, len; this.viewportTransform = vpt; for (i = 0, len = this._objects.length; i < len; i++) { object = this._objects[i]; object.group || object.setCoords(ignoreVpt, skipAbsolute); } if (activeObject && activeObject.type === 'activeSelection') { activeObject.setCoords(ignoreVpt, skipAbsolute); } this.calcViewportBoundaries(); this.renderOnAddRemove && this.requestRenderAll(); return this; }, /** * Sets zoom level of this canvas instance, zoom centered around point * @param {fabric.Point} point to zoom with respect to * @param {Number} value to set zoom to, less than 1 zooms out * @return {fabric.Canvas} instance * @chainable true */ zoomToPoint: function (point, value) { // TODO: just change the scale, preserve other transformations var before = point, vpt = this.viewportTransform.slice(0); point = transformPoint(point, invertTransform(this.viewportTransform)); vpt[0] = value; vpt[3] = value; var after = transformPoint(point, vpt); vpt[4] += before.x - after.x; vpt[5] += before.y - after.y; return this.setViewportTransform(vpt); }, /** * Sets zoom level of this canvas instance * @param {Number} value to set zoom to, less than 1 zooms out * @return {fabric.Canvas} instance * @chainable true */ setZoom: function (value) { this.zoomToPoint(new fabric.Point(0, 0), value); return this; }, /** * Pan viewport so as to place point at top left corner of canvas * @param {fabric.Point} point to move to * @return {fabric.Canvas} instance * @chainable true */ absolutePan: function (point) { var vpt = this.viewportTransform.slice(0); vpt[4] = -point.x; vpt[5] = -point.y; return this.setViewportTransform(vpt); }, /** * Pans viewpoint relatively * @param {fabric.Point} point (position vector) to move by * @return {fabric.Canvas} instance * @chainable true */ relativePan: function (point) { return this.absolutePan(new fabric.Point( -point.x - this.viewportTransform[4], -point.y - this.viewportTransform[5] )); }, /** * Returns <canvas> element corresponding to this instance * @return {HTMLCanvasElement} */ getElement: function () { return this.lowerCanvasEl; }, /** * @private * @param {fabric.Object} obj Object that was added */ _onObjectAdded: function(obj) { this.stateful && obj.setupState(); obj._set('canvas', this); obj.setCoords(); this.fire('object:added', { target: obj }); obj.fire('added'); }, /** * @private * @param {fabric.Object} obj Object that was removed */ _onObjectRemoved: function(obj) { this.fire('object:removed', { target: obj }); obj.fire('removed'); delete obj.canvas; }, /** * Clears specified context of canvas element * @param {CanvasRenderingContext2D} ctx Context to clear * @return {fabric.Canvas} thisArg * @chainable */ clearContext: function(ctx) { ctx.clearRect(0, 0, this.width, this.height); return this; }, /** * Returns context of canvas where objects are drawn * @return {CanvasRenderingContext2D} */ getContext: function () { return this.contextContainer; }, /** * Clears all contexts (background, main, top) of an instance * @return {fabric.Canvas} thisArg * @chainable */ clear: function () { this._objects.length = 0; this.backgroundImage = null; this.overlayImage = null; this.backgroundColor = ''; this.overlayColor = ''; if (this._hasITextHandlers) { this.off('mouse:up', this._mouseUpITextHandler); this._iTextInstances = null; this._hasITextHandlers = false; } this.clearContext(this.contextContainer); this.fire('canvas:cleared'); this.renderOnAddRemove && this.requestRenderAll(); return this; }, /** * Renders the canvas * @return {fabric.Canvas} instance * @chainable */ renderAll: function () { var canvasToDrawOn = this.contextContainer; this.renderCanvas(canvasToDrawOn, this._objects); return this; }, /** * Function created to be instance bound at initialization * used in requestAnimationFrame rendering * Let the fabricJS call it. If you call it manually you could have more * animationFrame stacking on to of each other * for an imperative rendering, use canvas.renderAll * @private * @return {fabric.Canvas} instance * @chainable */ renderAndReset: function() { this.isRendering = 0; this.renderAll(); }, /** * Append a renderAll request to next animation frame. * unless one is already in progress, in that case nothing is done * a boolean flag will avoid appending more. * @return {fabric.Canvas} instance * @chainable */ requestRenderAll: function () { if (!this.isRendering) { this.isRendering = fabric.util.requestAnimFrame(this.renderAndResetBound); } return this; }, /** * Calculate the position of the 4 corner of canvas with current viewportTransform. * helps to determinate when an object is in the current rendering viewport using * object absolute coordinates ( aCoords ) * @return {Object} points.tl * @chainable */ calcViewportBoundaries: function() { var points = { }, width = this.width, height = this.height, iVpt = invertTransform(this.viewportTransform); points.tl = transformPoint({ x: 0, y: 0 }, iVpt); points.br = transformPoint({ x: width, y: height }, iVpt); points.tr = new fabric.Point(points.br.x, points.tl.y); points.bl = new fabric.Point(points.tl.x, points.br.y); this.vptCoords = points; return points; }, cancelRequestedRender: function() { if (this.isRendering) { fabric.util.cancelAnimFrame(this.isRendering); this.isRendering = 0; } }, /** * Renders background, objects, overlay and controls. * @param {CanvasRenderingContext2D} ctx * @param {Array} objects to render * @return {fabric.Canvas} instance * @chainable */ renderCanvas: function(ctx, objects) { var v = this.viewportTransform, path = this.clipPath; this.cancelRequestedRender(); this.calcViewportBoundaries(); this.clearContext(ctx); this.fire('before:render', { ctx: ctx, }); if (this.clipTo) { fabric.util.clipContext(this, ctx); } this._renderBackground(ctx); ctx.save(); //apply viewport transform once for all rendering process ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); this._renderObjects(ctx, objects); ctx.restore(); if (!this.controlsAboveOverlay && this.interactive) { this.drawControls(ctx); } if (this.clipTo) { ctx.restore(); } if (path) { path.canvas = this; // needed to setup a couple of variables path.shouldCache(); path._transformDone = true; path.renderCache({ forClipping: true }); this.drawClipPathOnCanvas(ctx); } this._renderOverlay(ctx); if (this.controlsAboveOverlay && this.interactive) { this.drawControls(ctx); } this.fire('after:render', { ctx: ctx, }); }, /** * Paint the cached clipPath on the lowerCanvasEl * @param {CanvasRenderingContext2D} ctx Context to render on */ drawClipPathOnCanvas: function(ctx) { var v = this.viewportTransform, path = this.clipPath; ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); // DEBUG: uncomment this line, comment the following // ctx.globalAlpha = 0.4; ctx.globalCompositeOperation = 'destination-in'; path.transform(ctx); ctx.scale(1 / path.zoomX, 1 / path.zoomY); ctx.drawImage(path._cacheCanvas, -path.cacheTranslationX, -path.cacheTranslationY); ctx.restore(); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Array} objects to render */ _renderObjects: function(ctx, objects) { var i, len; for (i = 0, len = objects.length; i < len; ++i) { objects[i] && objects[i].render(ctx); } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {string} property 'background' or 'overlay' */ _renderBackgroundOrOverlay: function(ctx, property) { var object = this[property + 'Color'], v; if (object) { ctx.fillStyle = object.toLive ? object.toLive(ctx, this) : object; ctx.fillRect( object.offsetX || 0, object.offsetY || 0, this.width, this.height); } object = this[property + 'Image']; if (object) { if (this[property + 'Vpt']) { v = this.viewportTransform; ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); } object.render(ctx); this[property + 'Vpt'] && ctx.restore(); } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderBackground: function(ctx) { this._renderBackgroundOrOverlay(ctx, 'background'); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderOverlay: function(ctx) { this._renderBackgroundOrOverlay(ctx, 'overlay'); }, /** * Returns coordinates of a center of canvas. * Returned value is an object with top and left properties * @return {Object} object with "top" and "left" number values */ getCenter: function () { return { top: this.height / 2, left: this.width / 2 }; }, /** * Centers object horizontally in the canvas * @param {fabric.Object} object Object to center horizontally * @return {fabric.Canvas} thisArg */ centerObjectH: function (object) { return this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y)); }, /** * Centers object vertically in the canvas * @param {fabric.Object} object Object to center vertically * @return {fabric.Canvas} thisArg * @chainable */ centerObjectV: function (object) { return this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top)); }, /** * Centers object vertically and horizontally in the canvas * @param {fabric.Object} object Object to center vertically and horizontally * @return {fabric.Canvas} thisArg * @chainable */ centerObject: function(object) { var center = this.getCenter(); return this._centerObject(object, new fabric.Point(center.left, center.top)); }, /** * Centers object vertically and horizontally in the viewport * @param {fabric.Object} object Object to center vertically and horizontally * @return {fabric.Canvas} thisArg * @chainable */ viewportCenterObject: function(object) { var vpCenter = this.getVpCenter(); return this._centerObject(object, vpCenter); }, /** * Centers object horizontally in the viewport, object.top is unchanged * @param {fabric.Object} object Object to center vertically and horizontally * @return {fabric.Canvas} thisArg * @chainable */ viewportCenterObjectH: function(object) { var vpCenter = this.getVpCenter(); this._centerObject(object, new fabric.Point(vpCenter.x, object.getCenterPoint().y)); return this; }, /** * Centers object Vertically in the viewport, object.top is unchanged * @param {fabric.Object} object Object to center vertically and horizontally * @return {fabric.Canvas} thisArg * @chainable */ viewportCenterObjectV: function(object) { var vpCenter = this.getVpCenter(); return this._centerObject(object, new fabric.Point(object.getCenterPoint().x, vpCenter.y)); }, /** * Calculate the point in canvas that correspond to the center of actual viewport. * @return {fabric.Point} vpCenter, viewport center * @chainable */ getVpCenter: function() { var center = this.getCenter(), iVpt = invertTransform(this.viewportTransform); return transformPoint({ x: center.left, y: center.top }, iVpt); }, /** * @private * @param {fabric.Object} object Object to center * @param {fabric.Point} center Center point * @return {fabric.Canvas} thisArg * @chainable */ _centerObject: function(object, center) { object.setPositionByOrigin(center, 'center', 'center'); object.setCoords(); this.renderOnAddRemove && this.requestRenderAll(); return this; }, /** * Returs dataless JSON representation of canvas * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {String} json string */ toDatalessJSON: function (propertiesToInclude) { return this.toDatalessObject(propertiesToInclude); }, /** * Returns object representation of canvas * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function (propertiesToInclude) { return this._toObjectMethod('toObject', propertiesToInclude); }, /** * Returns dataless object representation of canvas * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toDatalessObject: function (propertiesToInclude) { return this._toObjectMethod('toDatalessObject', propertiesToInclude); }, /** * @private */ _toObjectMethod: function (methodName, propertiesToInclude) { var clipPath = this.clipPath, data = { version: fabric.version, objects: this._toObjects(methodName, propertiesToInclude), }; if (clipPath) { data.clipPath = this._toObjectMethod(clipPath, methodName, propertiesToInclude); } extend(data, this.__serializeBgOverlay(methodName, propertiesToInclude)); fabric.util.populateWithProperties(this, data, propertiesToInclude); return data; }, /** * @private */ _toObjects: function(methodName, propertiesToInclude) { return this._objects.filter(function(object) { return !object.excludeFromExport; }).map(function(instance) { return this._toObject(instance, methodName, propertiesToInclude); }, this); }, /** * @private */ _toObject: function(instance, methodName, propertiesToInclude) { var originalValue; if (!this.includeDefaultValues) { originalValue = instance.includeDefaultValues; instance.includeDefaultValues = false; } var object = instance[methodName](propertiesToInclude); if (!this.includeDefaultValues) { instance.includeDefaultValues = originalValue; } return object; }, /** * @private */ __serializeBgOverlay: function(methodName, propertiesToInclude) { var data = { }, bgImage = this.backgroundImage, overlay = this.overlayImage; if (this.backgroundColor) { data.background = this.backgroundColor.toObject ? this.backgroundColor.toObject(propertiesToInclude) : this.backgroundColor; } if (this.overlayColor) { data.overlay = this.overlayColor.toObject ? this.overlayColor.toObject(propertiesToInclude) : this.overlayColor; } if (bgImage && !bgImage.excludeFromExport) { data.backgroundImage = this._toObject(bgImage, methodName, propertiesToInclude); } if (overlay && !overlay.excludeFromExport) { data.overlayImage = this._toObject(overlay, methodName, propertiesToInclude); } return data; }, /* _TO_SVG_START_ */ /** * When true, getSvgTransform() will apply the StaticCanvas.viewportTransform to the SVG transformation. When true, * a zoomed canvas will then produce zoomed SVG output. * @type Boolean * @default */ svgViewportTransformation: true, /** * Returns SVG representation of canvas * @function * @param {Object} [options] Options object for SVG output * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included * @param {Object} [options.viewBox] SVG viewbox object * @param {Number} [options.viewBox.x] x-cooridnate of viewbox * @param {Number} [options.viewBox.y] y-coordinate of viewbox * @param {Number} [options.viewBox.width] Width of viewbox * @param {Number} [options.viewBox.height] Height of viewbox * @param {String} [options.encoding=UTF-8] Encoding of SVG output * @param {String} [options.width] desired width of svg with or without units * @param {String} [options.height] desired height of svg with or without units * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation. * @return {String} SVG string * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#serialization} * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo} * @example <caption>Normal SVG output</caption> * var svg = canvas.toSVG(); * @example <caption>SVG output without preamble (without <?xml ../>)</caption> * var svg = canvas.toSVG({suppressPreamble: true}); * @example <caption>SVG output with viewBox attribute</caption> * var svg = canvas.toSVG({ * viewBox: { * x: 100, * y: 100, * width: 200, * height: 300 * } * }); * @example <caption>SVG output with different encoding (default: UTF-8)</caption> * var svg = canvas.toSVG({encoding: 'ISO-8859-1'}); * @example <caption>Modify SVG output with reviver function</caption> * var svg = canvas.toSVG(null, function(svg) { * return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', ''); * }); */ toSVG: function(options, reviver) { options || (options = { }); options.reviver = reviver; var markup = []; this._setSVGPreamble(markup, options); this._setSVGHeader(markup, options); if (this.clipPath) { markup.push('<g clip-path="url(#' + this.clipPath.clipPathId + ')" >\n'); } this._setSVGBgOverlayColor(markup, 'backgroundColor'); this._setSVGBgOverlayImage(markup, 'backgroundImage', reviver); this._setSVGObjects(markup, reviver); if (this.clipPath) { markup.push('</g>\n'); } this._setSVGBgOverlayColor(markup, 'overlayColor'); this._setSVGBgOverlayImage(markup, 'overlayImage', reviver); markup.push('</svg>'); return markup.join(''); }, /** * @private */ _setSVGPreamble: function(markup, options) { if (options.suppressPreamble) { return; } markup.push( '<?xml version="1.0" encoding="', (options.encoding || 'UTF-8'), '" standalone="no" ?>\n', '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ', '"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' ); }, /** * @private */ _setSVGHeader: function(markup, options) { var width = options.width || this.width, height = options.height || this.height, vpt, viewBox = 'viewBox="0 0 ' + this.width + ' ' + this.height + '" ', NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; if (options.viewBox) { viewBox = 'viewBox="' + options.viewBox.x + ' ' + options.viewBox.y + ' ' + options.viewBox.width + ' ' + options.viewBox.height + '" '; } else { if (this.svgViewportTransformation) { vpt = this.viewportTransform; viewBox = 'viewBox="' + toFixed(-vpt[4] / vpt[0], NUM_FRACTION_DIGITS) + ' ' + toFixed(-vpt[5] / vpt[3], NUM_FRACTION_DIGITS) + ' ' + toFixed(this.width / vpt[0], NUM_FRACTION_DIGITS) + ' ' + toFixed(this.height / vpt[3], NUM_FRACTION_DIGITS) + '" '; } } markup.push( '<svg ', 'xmlns="http://www.w3.org/2000/svg" ', 'xmlns:xlink="http://www.w3.org/1999/xlink" ', 'version="1.1" ', 'width="', width, '" ', 'height="', height, '" ', viewBox, 'xml:space="preserve">\n', '<desc>Created with Fabric.js ', fabric.version, '</desc>\n', '<defs>\n', this.createSVGFontFacesMarkup(), this.createSVGRefElementsMarkup(), this.createSVGClipPathMarkup(options), '</defs>\n' ); }, createSVGClipPathMarkup: function(options) { var clipPath = this.clipPath; if (clipPath) { clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++; return '<clipPath id="' + clipPath.clipPathId + '" >\n' + this.clipPath.toClipPathSVG(options.reviver) + '</clipPath>\n'; } return ''; }, /** * Creates markup containing SVG referenced elements like patterns, gradients etc. * @return {String} */ createSVGRefElementsMarkup: function() { var _this = this, markup = ['backgroundColor', 'overlayColor'].map(function(prop) { var fill = _this[prop]; if (fill && fill.toLive) { return fill.toSVG(_this, false); } }); return markup.join(''); }, /** * Creates markup containing SVG font faces, * font URLs for font faces must be collected by developers * and are not extracted from the DOM by fabricjs * @param {Array} objects Array of fabric objects * @return {String} */ createSVGFontFacesMarkup: function() { var markup = '', fontList = { }, obj, fontFamily, style, row, rowIndex, _char, charIndex, i, len, fontPaths = fabric.fontPaths, objects = this._objects; for (i = 0, len = objects.length; i < len; i++) { obj = objects[i]; fontFamily = obj.fontFamily; if (obj.type.indexOf('text') === -1 || fontList[fontFamily] || !fontPaths[fontFamily]) { continue; } fontList[fontFamily] = true; if (!obj.styles) { continue; } style = obj.styles; for (rowIndex in style) { row = style[rowIndex]; for (charIndex in row) { _char = row[charIndex]; fontFamily = _char.fontFamily; if (!fontList[fontFamily] && fontPaths[fontFamily]) { fontList[fontFamily] = true; } } } } for (var j in fontList) { markup += [ '\t\t@font-face {\n', '\t\t\tfont-family: \'', j, '\';\n', '\t\t\tsrc: url(\'', fontPaths[j], '\');\n', '\t\t}\n' ].join(''); } if (markup) { markup = [ '\t<style type="text/css">', '<![CDATA[\n', markup, ']]>', '</style>\n' ].join(''); } return markup; }, /** * @private */ _setSVGObjects: function(markup, reviver) { var instance, i, len, objects = this._objects; for (i = 0, len = objects.length; i < len; i++) { instance = objects[i]; if (instance.excludeFromExport) { continue; } this._setSVGObject(markup, instance, reviver); } }, /** * @private */ _setSVGObject: function(markup, instance, reviver) { markup.push(instance.toSVG(reviver)); }, /** * @private */ _setSVGBgOverlayImage: function(markup, property, reviver) { if (this[property] && !this[property].excludeFromExport && this[property].toSVG) { markup.push(this[property].toSVG(reviver)); } }, /** * @private */ _setSVGBgOverlayColor: function(markup, property) { var filler = this[property], vpt = this.viewportTransform, finalWidth = this.width / vpt[0], finalHeight = this.height / vpt[3]; if (!filler) { return; } if (filler.toLive) { var repeat = filler.repeat; markup.push( '<rect transform="translate(', finalWidth / 2, ',', finalHeight / 2, ')"', ' x="', filler.offsetX - finalWidth / 2, '" y="', filler.offsetY - finalHeight / 2, '" ', 'width="', (repeat === 'repeat-y' || repeat === 'no-repeat' ? filler.source.width : finalWidth ), '" height="', (repeat === 'repeat-x' || repeat === 'no-repeat' ? filler.source.height : finalHeight), '" fill="url(#SVGID_' + filler.id + ')"', '></rect>\n' ); } else { markup.push( '<rect x="0" y="0" width="100%" height="100%" ', 'fill="', this[property], '"', '></rect>\n' ); } }, /* _TO_SVG_END_ */ /** * Moves an object or the objects of a multiple selection * to the bottom of the stack of drawn objects * @param {fabric.Object} object Object to send to back * @return {fabric.Canvas} thisArg * @chainable */ sendToBack: function (object) { if (!object) { return this; } var activeSelection = this._activeObject, i, obj, objs; if (object === activeSelection && object.type === 'activeSelection') { objs = activeSelection._objects; for (i = objs.length; i--;) { obj = objs[i]; removeFromArray(this._objects, obj); this._objects.unshift(obj); } } else { removeFromArray(this._objects, object); this._objects.unshift(object); } this.renderOnAddRemove && this.requestRenderAll(); return this; }, /** * Moves an object or the objects of a multiple selection * to the top of the stack of drawn objects * @param {fabric.Object} object Object to send * @return {fabric.Canvas} thisArg * @chainable */ bringToFront: function (object) { if (!object) { return this; } var activeSelection = this._activeObject, i, obj, objs; if (object === activeSelection && object.type === 'activeSelection') { objs = activeSelection._objects; for (i = 0; i < objs.length; i++) { obj = objs[i]; removeFromArray(this._objects, obj); this._objects.push(obj); } } else { removeFromArray(this._objects, object); this._objects.push(object); } this.renderOnAddRemove && this.requestRenderAll(); return this; }, /** * Moves an object or a selection down in stack of drawn objects * An optional paramter, intersecting allowes to move the object in behind * the first intersecting object. Where intersection is calculated with * bounding box. If no intersection is found, there will not be change in the * stack. * @param {fabric.Object} object Object to send * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object * @return {fabric.Canvas} thisArg * @chainable */ sendBackwards: function (object, intersecting) { if (!object) { return this; } var activeSelection = this._activeObject, i, obj, idx, newIdx, objs, objsMoved = 0; if (object === activeSelection && object.type === 'activeSelection') { objs = activeSelection._objects; for (i = 0; i < objs.length; i++) { obj = objs[i]; idx = this._objects.indexOf(obj); if (idx > 0 + objsMoved) { newIdx = idx - 1; removeFromArray(this._objects, obj); this._objects.splice(newIdx, 0, obj); } objsMoved++; } } else { idx = this._objects.indexOf(object); if (idx !== 0) { // if object is not on the bottom of stack newIdx = this._findNewLowerIndex(object, idx, intersecting); removeFromArray(this._objects, object); this._objects.splice(newIdx, 0, object); } } this.renderOnAddRemove && this.requestRenderAll(); return this; }, /** * @private */ _findNewLowerIndex: function(object, idx, intersecting) { var newIdx, i; if (intersecting) { newIdx = idx; // traverse down the stack looking for the nearest intersecting object for (i = idx - 1; i >= 0; --i) { var isIntersecting = object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i]) || this._objects[i].isContainedWithinObject(object); if (isIntersecting) { newIdx = i; break; } } } else { newIdx = idx - 1; } return newIdx; }, /** * Moves an object or a selection up in stack of drawn objects * An optional paramter, intersecting allowes to move the object in front * of the first intersecting object. Where intersection is calculated with * bounding box. If no intersection is found, there will not be change in the * stack. * @param {fabric.Object} object Object to send * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object * @return {fabric.Canvas} thisArg * @chainable */ bringForward: function (object, intersecting) { if (!object) { return this; } var activeSelection = this._activeObject, i, obj, idx, newIdx, objs, objsMoved = 0; if (object === activeSelection && object.type === 'activeSelection') { objs = activeSelection._objects; for (i = objs.length; i--;) { obj = objs[i]; idx = this._objects.indexOf(obj); if (idx < this._objects.length - 1 - objsMoved) { newIdx = idx + 1; removeFromArray(this._objects, obj); this._objects.splice(newIdx, 0, obj); } objsMoved++; } } else { idx = this._objects.indexOf(object); if (idx !== this._objects.length - 1) { // if object is not on top of stack (last item in an array) newIdx = this._findNewUpperIndex(object, idx, intersecting); removeFromArray(this._objects, object); this._objects.splice(newIdx, 0, object); } } this.renderOnAddRemove && this.requestRenderAll(); return this; }, /** * @private */ _findNewUpperIndex: function(object, idx, intersecting) { var newIdx, i, len; if (intersecting) { newIdx = idx; // traverse up the stack looking for the nearest intersecting object for (i = idx + 1, len = this._objects.length; i < len; ++i) { var isIntersecting = object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i]) || this._objects[i].isContainedWithinObject(object); if (isIntersecting) { newIdx = i; break; } } } else { newIdx = idx + 1; } return newIdx; }, /** * Moves an object to specified level in stack of drawn objects * @param {fabric.Object} object Object to send * @param {Number} index Position to move to * @return {fabric.Canvas} thisArg * @chainable */ moveTo: function (object, index) { removeFromArray(this._objects, object); this._objects.splice(index, 0, object); return this.renderOnAddRemove && this.requestRenderAll(); }, /** * Clears a canvas element and dispose objects * @return {fabric.Canvas} thisArg * @chainable */ dispose: function () { // cancel eventually ongoing renders if (this.isRendering) { fabric.util.cancelAnimFrame(this.isRendering); this.isRendering = 0; } this.forEachObject(function(object) { object.dispose && object.dispose(); }); this._objects = []; if (this.backgroundImage && this.backgroundImage.dispose) { this.backgroundImage.dispose(); } this.backgroundImage = null; if (this.overlayImage && this.overlayImage.dispose) { this.overlayImage.dispose(); } this.overlayImage = null; this._iTextInstances = null; this.contextContainer = null; fabric.util.cleanUpJsdomNode(this.lowerCanvasEl); this.lowerCanvasEl = undefined; return this; }, /** * Returns a string representation of an instance * @return {String} string representation of an instance */ toString: function () { return '#<fabric.Canvas (' + this.complexity() + '): ' + '{ objects: ' + this._objects.length + ' }>'; } }); extend(fabric.StaticCanvas.prototype, fabric.Observable); extend(fabric.StaticCanvas.prototype, fabric.Collection); extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter); extend(fabric.StaticCanvas, /** @lends fabric.StaticCanvas */ { /** * @static * @type String * @default */ EMPTY_JSON: '{"objects": [], "background": "white"}', /** * Provides a way to check support of some of the canvas methods * (either those of HTMLCanvasElement itself, or rendering context) * * @param {String} methodName Method to check support for; * Could be one of "setLineDash" * @return {Boolean | null} `true` if method is supported (or at least exists), * `null` if canvas element or context can not be initialized */ supports: function (methodName) { var el = createCanvasElement(); if (!el || !el.getContext) { return null; } var ctx = el.getContext('2d'); if (!ctx) { return null; } switch (methodName) { case 'setLineDash': return typeof ctx.setLineDash !== 'undefined'; default: return null; } } }); /** * Returns JSON representation of canvas * @function * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {String} JSON string * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#serialization} * @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo} * @example <caption>JSON without additional properties</caption> * var json = canvas.toJSON(); * @example <caption>JSON with additional properties included</caption> * var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY', 'lockUniScaling']); * @example <caption>JSON without default values</caption> * canvas.includeDefaultValues = false; * var json = canvas.toJSON(); */ fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject; if (fabric.isLikelyNode) { fabric.StaticCanvas.prototype.createPNGStream = function() { var impl = getNodeCanvas(this.lowerCanvasEl); return impl && impl.createPNGStream(); }; fabric.StaticCanvas.prototype.createJPEGStream = function(opts) { var impl = getNodeCanvas(this.lowerCanvasEl); return impl && impl.createJPEGStream(opts); }; } })(); /** * BaseBrush class * @class fabric.BaseBrush * @see {@link http://fabricjs.com/freedrawing|Freedrawing demo} */ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ { /** * Color of a brush * @type String * @default */ color: 'rgb(0, 0, 0)', /** * Width of a brush, has to be a Number, no string literals * @type Number * @default */ width: 1, /** * Shadow object representing shadow of this shape. * <b>Backwards incompatibility note:</b> This property replaces "shadowColor" (String), "shadowOffsetX" (Number), * "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12 * @type fabric.Shadow * @default */ shadow: null, /** * Line endings style of a brush (one of "butt", "round", "square") * @type String * @default */ strokeLineCap: 'round', /** * Corner style of a brush (one of "bevel", "round", "miter") * @type String * @default */ strokeLineJoin: 'round', /** * Maximum miter length (used for strokeLineJoin = "miter") of a brush's * @type Number * @default */ strokeMiterLimit: 10, /** * Stroke Dash Array. * @type Array * @default */ strokeDashArray: null, /** * Sets shadow of an object * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") * @return {fabric.Object} thisArg * @chainable */ setShadow: function(options) { this.shadow = new fabric.Shadow(options); return this; }, /** * Sets brush styles * @private */ _setBrushStyles: function() { var ctx = this.canvas.contextTop; ctx.strokeStyle = this.color; ctx.lineWidth = this.width; ctx.lineCap = this.strokeLineCap; ctx.miterLimit = this.strokeMiterLimit; ctx.lineJoin = this.strokeLineJoin; if (fabric.StaticCanvas.supports('setLineDash')) { ctx.setLineDash(this.strokeDashArray || []); } }, /** * Sets the transformation on given context * @param {RenderingContext2d} ctx context to render on * @private */ _saveAndTransform: function(ctx) { var v = this.canvas.viewportTransform; ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); }, /** * Sets brush shadow styles * @private */ _setShadow: function() { if (!this.shadow) { return; } var ctx = this.canvas.contextTop, zoom = this.canvas.getZoom(); ctx.shadowColor = this.shadow.color; ctx.shadowBlur = this.shadow.blur * zoom; ctx.shadowOffsetX = this.shadow.offsetX * zoom; ctx.shadowOffsetY = this.shadow.offsetY * zoom; }, /** * Removes brush shadow styles * @private */ _resetShadow: function() { var ctx = this.canvas.contextTop; ctx.shadowColor = ''; ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; } }); (function() { /** * PencilBrush class * @class fabric.PencilBrush * @extends fabric.BaseBrush */ fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.PencilBrush.prototype */ { /** * Constructor * @param {fabric.Canvas} canvas * @return {fabric.PencilBrush} Instance of a pencil brush */ initialize: function(canvas) { this.canvas = canvas; this._points = []; }, /** * Invoked inside on mouse down and mouse move * @param {Object} pointer */ _drawSegment: function (ctx, p1, p2) { var midPoint = p1.midPointFrom(p2); ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); return midPoint; }, /** * Inovoked on mouse down * @param {Object} pointer */ onMouseDown: function(pointer) { this._prepareForDrawing(pointer); // capture coordinates immediately // this allows to draw dots (when movement never occurs) this._captureDrawingPath(pointer); this._render(); }, /** * Inovoked on mouse move * @param {Object} pointer */ onMouseMove: function(pointer) { if (this._captureDrawingPath(pointer) && this._points.length > 1) { if (this.needsFullRender) { // redraw curve // clear top canvas this.canvas.clearContext(this.canvas.contextTop); this._render(); } else { var points = this._points, length = points.length, ctx = this.canvas.contextTop; // draw the curve update this._saveAndTransform(ctx); if (this.oldEnd) { ctx.beginPath(); ctx.moveTo(this.oldEnd.x, this.oldEnd.y); } this.oldEnd = this._drawSegment(ctx, points[length - 2], points[length - 1], true); ctx.stroke(); ctx.restore(); } } }, /** * Invoked on mouse up */ onMouseUp: function() { this.oldEnd = undefined; this._finalizeAndAddPath(); }, /** * @private * @param {Object} pointer Actual mouse position related to the canvas. */ _prepareForDrawing: function(pointer) { var p = new fabric.Point(pointer.x, pointer.y); this._reset(); this._addPoint(p); this.canvas.contextTop.moveTo(p.x, p.y); }, /** * @private * @param {fabric.Point} point Point to be added to points array */ _addPoint: function(point) { if (this._points.length > 1 && point.eq(this._points[this._points.length - 1])) { return false; } this._points.push(point); return true; }, /** * Clear points array and set contextTop canvas style. * @private */ _reset: function() { this._points.length = 0; this._setBrushStyles(); var color = new fabric.Color(this.color); this.needsFullRender = (color.getAlpha() < 1); this._setShadow(); }, /** * @private * @param {Object} pointer Actual mouse position related to the canvas. */ _captureDrawingPath: function(pointer) { var pointerPoint = new fabric.Point(pointer.x, pointer.y); return this._addPoint(pointerPoint); }, /** * Draw a smooth path on the topCanvas using quadraticCurveTo * @private */ _render: function() { var ctx = this.canvas.contextTop, i, len, p1 = this._points[0], p2 = this._points[1]; this._saveAndTransform(ctx); ctx.beginPath(); //if we only have 2 points in the path and they are the same //it means that the user only clicked the canvas without moving the mouse //then we should be drawing a dot. A path isn't drawn between two identical dots //that's why we set them apart a bit if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { var width = this.width / 1000; p1 = new fabric.Point(p1.x, p1.y); p2 = new fabric.Point(p2.x, p2.y); p1.x -= width; p2.x += width; } ctx.moveTo(p1.x, p1.y); for (i = 1, len = this._points.length; i < len; i++) { // we pick the point between pi + 1 & pi + 2 as the // end point and p1 as our control point. this._drawSegment(ctx, p1, p2); p1 = this._points[i]; p2 = this._points[i + 1]; } // Draw last line as a straight line while // we wait for the next point to be able to calculate // the bezier control point ctx.lineTo(p1.x, p1.y); ctx.stroke(); ctx.restore(); }, /** * Converts points to SVG path * @param {Array} points Array of points * @return {String} SVG path */ convertPointsToSVGPath: function(points) { var path = [], i, width = this.width / 1000, p1 = new fabric.Point(points[0].x, points[0].y), p2 = new fabric.Point(points[1].x, points[1].y), len = points.length, multSignX = 1, multSignY = 1, manyPoints = len > 2; if (manyPoints) { multSignX = points[2].x < p2.x ? -1 : points[2].x === p2.x ? 0 : 1; multSignY = points[2].y < p2.y ? -1 : points[2].y === p2.y ? 0 : 1; } path.push('M ', p1.x - multSignX * width, ' ', p1.y - multSignY * width, ' '); for (i = 1; i < len; i++) { if (!p1.eq(p2)) { var midPoint = p1.midPointFrom(p2); // p1 is our bezier control point // midpoint is our endpoint // start point is p(i-1) value. path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' '); } p1 = points[i]; if ((i + 1) < points.length) { p2 = points[i + 1]; } } if (manyPoints) { multSignX = p1.x > points[i - 2].x ? 1 : p1.x === points[i - 2].x ? 0 : -1; multSignY = p1.y > points[i - 2].y ? 1 : p1.y === points[i - 2].y ? 0 : -1; } path.push('L ', p1.x + multSignX * width, ' ', p1.y + multSignY * width); return path; }, /** * Creates fabric.Path object to add on canvas * @param {String} pathData Path data * @return {fabric.Path} Path to add on canvas */ createPath: function(pathData) { var path = new fabric.Path(pathData, { fill: null, stroke: this.color, strokeWidth: this.width, strokeLineCap: this.strokeLineCap, strokeMiterLimit: this.strokeMiterLimit, strokeLineJoin: this.strokeLineJoin, strokeDashArray: this.strokeDashArray, }); var position = new fabric.Point(path.left + path.width / 2, path.top + path.height / 2); position = path.translateToGivenOrigin(position, 'center', 'center', path.originX, path.originY); path.top = position.y; path.left = position.x; if (this.shadow) { this.shadow.affectStroke = true; path.setShadow(this.shadow); } return path; }, /** * On mouseup after drawing the path on contextTop canvas * we use the points captured to create an new fabric path object * and add it to the fabric canvas. */ _finalizeAndAddPath: function() { var ctx = this.canvas.contextTop; ctx.closePath(); var pathData = this.convertPointsToSVGPath(this._points).join(''); if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') { // do not create 0 width/height paths, as they are // rendered inconsistently across browsers // Firefox 4, for example, renders a dot, // whereas Chrome 10 renders nothing this.canvas.requestRenderAll(); return; } var path = this.createPath(pathData); this.canvas.clearContext(this.canvas.contextTop); this.canvas.add(path); this.canvas.renderAll(); path.setCoords(); this._resetShadow(); // fire event 'path' created this.canvas.fire('path:created', { path: path }); } }); })(); /** * CircleBrush class * @class fabric.CircleBrush */ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.CircleBrush.prototype */ { /** * Width of a brush * @type Number * @default */ width: 10, /** * Constructor * @param {fabric.Canvas} canvas * @return {fabric.CircleBrush} Instance of a circle brush */ initialize: function(canvas) { this.canvas = canvas; this.points = []; }, /** * Invoked inside on mouse down and mouse move * @param {Object} pointer */ drawDot: function(pointer) { var point = this.addPoint(pointer), ctx = this.canvas.contextTop; this._saveAndTransform(ctx); ctx.fillStyle = point.fill; ctx.beginPath(); ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); ctx.closePath(); ctx.fill(); ctx.restore(); }, /** * Invoked on mouse down */ onMouseDown: function(pointer) { this.points.length = 0; this.canvas.clearContext(this.canvas.contextTop); this._setShadow(); this.drawDot(pointer); }, /** * Render the full state of the brush * @private */ _render: function() { var ctx = this.canvas.contextTop, i, len, points = this.points, point; this._saveAndTransform(ctx); for (i = 0, len = points.length; i < len; i++) { point = points[i]; ctx.fillStyle = point.fill; ctx.beginPath(); ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); ctx.closePath(); ctx.fill(); } ctx.restore(); }, /** * Invoked on mouse move * @param {Object} pointer */ onMouseMove: function(pointer) { this.drawDot(pointer); }, /** * Invoked on mouse up */ onMouseUp: function() { var originalRenderOnAddRemove = this.canvas.renderOnAddRemove, i, len; this.canvas.renderOnAddRemove = false; var circles = []; for (i = 0, len = this.points.length; i < len; i++) { var point = this.points[i], circle = new fabric.Circle({ radius: point.radius, left: point.x, top: point.y, originX: 'center', originY: 'center', fill: point.fill }); this.shadow && circle.setShadow(this.shadow); circles.push(circle); } var group = new fabric.Group(circles); group.canvas = this.canvas; this.canvas.add(group); this.canvas.fire('path:created', { path: group }); this.canvas.clearContext(this.canvas.contextTop); this._resetShadow(); this.canvas.renderOnAddRemove = originalRenderOnAddRemove; this.canvas.requestRenderAll(); }, /** * @param {Object} pointer * @return {fabric.Point} Just added pointer point */ addPoint: function(pointer) { var pointerPoint = new fabric.Point(pointer.x, pointer.y), circleRadius = fabric.util.getRandomInt( Math.max(0, this.width - 20), this.width + 20) / 2, circleColor = new fabric.Color(this.color) .setAlpha(fabric.util.getRandomInt(0, 100) / 100) .toRgba(); pointerPoint.radius = circleRadius; pointerPoint.fill = circleColor; this.points.push(pointerPoint); return pointerPoint; } }); /** * SprayBrush class * @class fabric.SprayBrush */ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric.SprayBrush.prototype */ { /** * Width of a spray * @type Number * @default */ width: 10, /** * Density of a spray (number of dots per chunk) * @type Number * @default */ density: 20, /** * Width of spray dots * @type Number * @default */ dotWidth: 1, /** * Width variance of spray dots * @type Number * @default */ dotWidthVariance: 1, /** * Whether opacity of a dot should be random * @type Boolean * @default */ randomOpacity: false, /** * Whether overlapping dots (rectangles) should be removed (for performance reasons) * @type Boolean * @default */ optimizeOverlapping: true, /** * Constructor * @param {fabric.Canvas} canvas * @return {fabric.SprayBrush} Instance of a spray brush */ initialize: function(canvas) { this.canvas = canvas; this.sprayChunks = []; }, /** * Invoked on mouse down * @param {Object} pointer */ onMouseDown: function(pointer) { this.sprayChunks.length = 0; this.canvas.clearContext(this.canvas.contextTop); this._setShadow(); this.addSprayChunk(pointer); this.render(this.sprayChunkPoints); }, /** * Invoked on mouse move * @param {Object} pointer */ onMouseMove: function(pointer) { this.addSprayChunk(pointer); this.render(this.sprayChunkPoints); }, /** * Invoked on mouse up */ onMouseUp: function() { var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; this.canvas.renderOnAddRemove = false; var rects = []; for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { var sprayChunk = this.sprayChunks[i]; for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) { var rect = new fabric.Rect({ width: sprayChunk[j].width, height: sprayChunk[j].width, left: sprayChunk[j].x + 1, top: sprayChunk[j].y + 1, originX: 'center', originY: 'center', fill: this.color }); rects.push(rect); } } if (this.optimizeOverlapping) { rects = this._getOptimizedRects(rects); } var group = new fabric.Group(rects); this.shadow && group.setShadow(this.shadow); this.canvas.add(group); this.canvas.fire('path:created', { path: group }); this.canvas.clearContext(this.canvas.contextTop); this._resetShadow(); this.canvas.renderOnAddRemove = originalRenderOnAddRemove; this.canvas.requestRenderAll(); }, /** * @private * @param {Array} rects */ _getOptimizedRects: function(rects) { // avoid creating duplicate rects at the same coordinates var uniqueRects = { }, key, i, len; for (i = 0, len = rects.length; i < len; i++) { key = rects[i].left + '' + rects[i].top; if (!uniqueRects[key]) { uniqueRects[key] = rects[i]; } } var uniqueRectsArray = []; for (key in uniqueRects) { uniqueRectsArray.push(uniqueRects[key]); } return uniqueRectsArray; }, /** * Render new chunk of spray brush */ render: function(sprayChunk) { var ctx = this.canvas.contextTop, i, len; ctx.fillStyle = this.color; this._saveAndTransform(ctx); for (i = 0, len = sprayChunk.length; i < len; i++) { var point = sprayChunk[i]; if (typeof point.opacity !== 'undefined') { ctx.globalAlpha = point.opacity; } ctx.fillRect(point.x, point.y, point.width, point.width); } ctx.restore(); }, /** * Render all spray chunks */ _render: function() { var ctx = this.canvas.contextTop, i, ilen; ctx.fillStyle = this.color; this._saveAndTransform(ctx); for (i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { this.render(this.sprayChunks[i]); } ctx.restore(); }, /** * @param {Object} pointer */ addSprayChunk: function(pointer) { this.sprayChunkPoints = []; var x, y, width, radius = this.width / 2, i; for (i = 0; i < this.density; i++) { x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius); y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius); if (this.dotWidthVariance) { width = fabric.util.getRandomInt( // bottom clamp width to 1 Math.max(1, this.dotWidth - this.dotWidthVariance), this.dotWidth + this.dotWidthVariance); } else { width = this.dotWidth; } var point = new fabric.Point(x, y); point.width = width; if (this.randomOpacity) { point.opacity = fabric.util.getRandomInt(0, 100) / 100; } this.sprayChunkPoints.push(point); } this.sprayChunks.push(this.sprayChunkPoints); } }); /** * PatternBrush class * @class fabric.PatternBrush * @extends fabric.BaseBrush */ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fabric.PatternBrush.prototype */ { getPatternSrc: function() { var dotWidth = 20, dotDistance = 5, patternCanvas = fabric.util.createCanvasElement(), patternCtx = patternCanvas.getContext('2d'); patternCanvas.width = patternCanvas.height = dotWidth + dotDistance; patternCtx.fillStyle = this.color; patternCtx.beginPath(); patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false); patternCtx.closePath(); patternCtx.fill(); return patternCanvas; }, getPatternSrcFunction: function() { return String(this.getPatternSrc).replace('this.color', '"' + this.color + '"'); }, /** * Creates "pattern" instance property */ getPattern: function() { return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat'); }, /** * Sets brush styles */ _setBrushStyles: function() { this.callSuper('_setBrushStyles'); this.canvas.contextTop.strokeStyle = this.getPattern(); }, /** * Creates path */ createPath: function(pathData) { var path = this.callSuper('createPath', pathData), topLeft = path._getLeftTopCoords().scalarAdd(path.strokeWidth / 2); path.stroke = new fabric.Pattern({ source: this.source || this.getPatternSrcFunction(), offsetX: -topLeft.x, offsetY: -topLeft.y }); return path; } }); (function() { var getPointer = fabric.util.getPointer, degreesToRadians = fabric.util.degreesToRadians, radiansToDegrees = fabric.util.radiansToDegrees, atan2 = Math.atan2, abs = Math.abs, supportLineDash = fabric.StaticCanvas.supports('setLineDash'), STROKE_OFFSET = 0.5; /** * Canvas class * @class fabric.Canvas * @extends fabric.StaticCanvas * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#canvas} * @see {@link fabric.Canvas#initialize} for constructor definition * * @fires object:modified * @fires object:rotated * @fires object:scaled * @fires object:moved * @fires object:skewed * @fires object:rotating * @fires object:scaling * @fires object:moving * @fires object:skewing * @fires object:selected this event is deprecated. use selection:created * * @fires before:transform * @fires before:selection:cleared * @fires selection:cleared * @fires selection:updated * @fires selection:created * * @fires path:created * @fires mouse:down * @fires mouse:move * @fires mouse:up * @fires mouse:down:before * @fires mouse:move:before * @fires mouse:up:before * @fires mouse:over * @fires mouse:out * @fires mouse:dblclick * * @fires dragover * @fires dragenter * @fires dragleave * @fires drop * */ fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ { /** * Constructor * @param {HTMLElement | String} el <canvas> element to initialize instance on * @param {Object} [options] Options object * @return {Object} thisArg */ initialize: function(el, options) { options || (options = { }); this.renderAndResetBound = this.renderAndReset.bind(this); this.requestRenderAllBound = this.requestRenderAll.bind(this); this._initStatic(el, options); this._initInteractive(); this._createCacheCanvas(); }, /** * When true, objects can be transformed by one side (unproportionally) * @type Boolean * @default */ uniScaleTransform: false, /** * Indicates which key enable unproportional scaling * values: 'altKey', 'shiftKey', 'ctrlKey'. * If `null` or 'none' or any other string that is not a modifier key * feature is disabled feature disabled. * @since 1.6.2 * @type String * @default */ uniScaleKey: 'shiftKey', /** * When true, objects use center point as the origin of scale transformation. * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean). * @since 1.3.4 * @type Boolean * @default */ centeredScaling: false, /** * When true, objects use center point as the origin of rotate transformation. * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean). * @since 1.3.4 * @type Boolean * @default */ centeredRotation: false, /** * Indicates which key enable centered Transform * values: 'altKey', 'shiftKey', 'ctrlKey'. * If `null` or 'none' or any other string that is not a modifier key * feature is disabled feature disabled. * @since 1.6.2 * @type String * @default */ centeredKey: 'altKey', /** * Indicates which key enable alternate action on corner * values: 'altKey', 'shiftKey', 'ctrlKey'. * If `null` or 'none' or any other string that is not a modifier key * feature is disabled feature disabled. * @since 1.6.2 * @type String * @default */ altActionKey: 'shiftKey', /** * Indicates that canvas is interactive. This property should not be changed. * @type Boolean * @default */ interactive: true, /** * Indicates whether group selection should be enabled * @type Boolean * @default */ selection: true, /** * Indicates which key or keys enable multiple click selection * Pass value as a string or array of strings * values: 'altKey', 'shiftKey', 'ctrlKey'. * If `null` or empty or containing any other string that is not a modifier key * feature is disabled. * @since 1.6.2 * @type String|Array * @default */ selectionKey: 'shiftKey', /** * Indicates which key enable alternative selection * in case of target overlapping with active object * values: 'altKey', 'shiftKey', 'ctrlKey'. * For a series of reason that come from the general expectations on how * things should work, this feature works only for preserveObjectStacking true. * If `null` or 'none' or any other string that is not a modifier key * feature is disabled. * @since 1.6.5 * @type null|String * @default */ altSelectionKey: null, /** * Color of selection * @type String * @default */ selectionColor: 'rgba(100, 100, 255, 0.3)', // blue /** * Default dash array pattern * If not empty the selection border is dashed * @type Array */ selectionDashArray: [], /** * Color of the border of selection (usually slightly darker than color of selection itself) * @type String * @default */ selectionBorderColor: 'rgba(255, 255, 255, 0.3)', /** * Width of a line used in object/group selection * @type Number * @default */ selectionLineWidth: 1, /** * Select only shapes that are fully contained in the dragged selection rectangle. * @type Boolean * @default */ selectionFullyContained: false, /** * Default cursor value used when hovering over an object on canvas * @type String * @default */ hoverCursor: 'move', /** * Default cursor value used when moving an object on canvas * @type String * @default */ moveCursor: 'move', /** * Default cursor value used for the entire canvas * @type String * @default */ defaultCursor: 'default', /** * Cursor value used during free drawing * @type String * @default */ freeDrawingCursor: 'crosshair', /** * Cursor value used for rotation point * @type String * @default */ rotationCursor: 'crosshair', /** * Cursor value used for disabled elements ( corners with disabled action ) * @type String * @since 2.0.0 * @default */ notAllowedCursor: 'not-allowed', /** * Default element class that's given to wrapper (div) element of canvas * @type String * @default */ containerClass: 'canvas-container', /** * When true, object detection happens on per-pixel basis rather than on per-bounding-box * @type Boolean * @default */ perPixelTargetFind: false, /** * Number of pixels around target pixel to tolerate (consider active) during object detection * @type Number * @default */ targetFindTolerance: 0, /** * When true, target detection is skipped when hovering over canvas. This can be used to improve performance. * @type Boolean * @default */ skipTargetFind: false, /** * When true, mouse events on canvas (mousedown/mousemove/mouseup) result in free drawing. * After mousedown, mousemove creates a shape, * and then mouseup finalizes it and adds an instance of `fabric.Path` onto canvas. * @tutorial {@link http://fabricjs.com/fabric-intro-part-4#free_drawing} * @type Boolean * @default */ isDrawingMode: false, /** * Indicates whether objects should remain in current stack position when selected. * When false objects are brought to top and rendered as part of the selection group * @type Boolean * @default */ preserveObjectStacking: false, /** * Indicates the angle that an object will lock to while rotating. * @type Number * @since 1.6.7 * @default */ snapAngle: 0, /** * Indicates the distance from the snapAngle the rotation will lock to the snapAngle. * When `null`, the snapThreshold will default to the snapAngle. * @type null|Number * @since 1.6.7 * @default */ snapThreshold: null, /** * Indicates if the right click on canvas can output the context menu or not * @type Boolean * @since 1.6.5 * @default */ stopContextMenu: false, /** * Indicates if the canvas can fire right click events * @type Boolean * @since 1.6.5 * @default */ fireRightClick: false, /** * Indicates if the canvas can fire middle click events * @type Boolean * @since 1.7.8 * @default */ fireMiddleClick: false, /** * @private */ _initInteractive: function() { this._currentTransform = null; this._groupSelector = null; this._initWrapperElement(); this._createUpperCanvas(); this._initEventListeners(); this._initRetinaScaling(); this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this); this.calcOffset(); }, /** * Divides objects in two groups, one to render immediately * and one to render as activeGroup. * @return {Array} objects to render immediately and pushes the other in the activeGroup. */ _chooseObjectsToRender: function() { var activeObjects = this.getActiveObjects(), object, objsToRender, activeGroupObjects; if (activeObjects.length > 0 && !this.preserveObjectStacking) { objsToRender = []; activeGroupObjects = []; for (var i = 0, length = this._objects.length; i < length; i++) { object = this._objects[i]; if (activeObjects.indexOf(object) === -1 ) { objsToRender.push(object); } else { activeGroupObjects.push(object); } } if (activeObjects.length > 1) { this._activeObject._objects = activeGroupObjects; } objsToRender.push.apply(objsToRender, activeGroupObjects); } else { objsToRender = this._objects; } return objsToRender; }, /** * Renders both the top canvas and the secondary container canvas. * @return {fabric.Canvas} instance * @chainable */ renderAll: function () { if (this.contextTopDirty && !this._groupSelector && !this.isDrawingMode) { this.clearContext(this.contextTop); this.contextTopDirty = false; } if (this.hasLostContext) { this.renderTopLayer(this.contextTop); } var canvasToDrawOn = this.contextContainer; this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender()); return this; }, renderTopLayer: function(ctx) { ctx.save(); if (this.isDrawingMode && this._isCurrentlyDrawing) { this.freeDrawingBrush && this.freeDrawingBrush._render(); this.contextTopDirty = true; } // we render the top context - last object if (this.selection && this._groupSelector) { this._drawSelection(ctx); this.contextTopDirty = true; } ctx.restore(); }, /** * Method to render only the top canvas. * Also used to render the group selection box. * @return {fabric.Canvas} thisArg * @chainable */ renderTop: function () { var ctx = this.contextTop; this.clearContext(ctx); this.renderTopLayer(ctx); this.fire('after:render'); return this; }, /** * Resets the current transform to its original values and chooses the type of resizing based on the event * @private */ _resetCurrentTransform: function() { var t = this._currentTransform; t.target.set({ scaleX: t.original.scaleX, scaleY: t.original.scaleY, skewX: t.original.skewX, skewY: t.original.skewY, left: t.original.left, top: t.original.top }); if (this._shouldCenterTransform(t.target)) { if (t.originX !== 'center') { if (t.originX === 'right') { t.mouseXSign = -1; } else { t.mouseXSign = 1; } } if (t.originY !== 'center') { if (t.originY === 'bottom') { t.mouseYSign = -1; } else { t.mouseYSign = 1; } } t.originX = 'center'; t.originY = 'center'; } else { t.originX = t.original.originX; t.originY = t.original.originY; } }, /** * Checks if point is contained within an area of given object * @param {Event} e Event object * @param {fabric.Object} target Object to test against * @param {Object} [point] x,y object of point coordinates we want to check. * @return {Boolean} true if point is contained within an area of given object */ containsPoint: function (e, target, point) { var ignoreZoom = true, pointer = point || this.getPointer(e, ignoreZoom), xy; if (target.group && target.group === this._activeObject && target.group.type === 'activeSelection') { xy = this._normalizePointer(target.group, pointer); } else { xy = { x: pointer.x, y: pointer.y }; } // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html return (target.containsPoint(xy) || target._findTargetCorner(pointer)); }, /** * @private */ _normalizePointer: function (object, pointer) { var m = object.calcTransformMatrix(), invertedM = fabric.util.invertTransform(m), vptPointer = this.restorePointerVpt(pointer); return fabric.util.transformPoint(vptPointer, invertedM); }, /** * Returns true if object is transparent at a certain location * @param {fabric.Object} target Object to check * @param {Number} x Left coordinate * @param {Number} y Top coordinate * @return {Boolean} */ isTargetTransparent: function (target, x, y) { // in case the target is the activeObject, we cannot execute this optimization // because we need to draw controls too. if (target.shouldCache() && target._cacheCanvas && target !== this._activeObject) { var normalizedPointer = this._normalizePointer(target, {x: x, y: y}), targetRelativeX = Math.max(target.cacheTranslationX + (normalizedPointer.x * target.zoomX), 0), targetRelativeY = Math.max(target.cacheTranslationY + (normalizedPointer.y * target.zoomY), 0); var isTransparent = fabric.util.isTransparent( target._cacheContext, Math.round(targetRelativeX), Math.round(targetRelativeY), this.targetFindTolerance); return isTransparent; } var ctx = this.contextCache, originalColor = target.selectionBackgroundColor, v = this.viewportTransform; target.selectionBackgroundColor = ''; this.clearContext(ctx); ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); target.render(ctx); ctx.restore(); target === this._activeObject && target._renderControls(ctx, { hasBorders: false, transparentCorners: false }, { hasBorders: false, }); target.selectionBackgroundColor = originalColor; var isTransparent = fabric.util.isTransparent( ctx, x, y, this.targetFindTolerance); return isTransparent; }, /** * takes an event and determins if selection key has been pressed * @private * @param {Event} e Event object */ _isSelectionKeyPressed: function(e) { var selectionKeyPressed = false; if (Object.prototype.toString.call(this.selectionKey) === '[object Array]') { selectionKeyPressed = !!this.selectionKey.find(function(key) { return e[key] === true; }); } else { selectionKeyPressed = e[this.selectionKey]; } return selectionKeyPressed; }, /** * @private * @param {Event} e Event object * @param {fabric.Object} target */ _shouldClearSelection: function (e, target) { var activeObjects = this.getActiveObjects(), activeObject = this._activeObject; return ( !target || (target && activeObject && activeObjects.length > 1 && activeObjects.indexOf(target) === -1 && activeObject !== target && !this._isSelectionKeyPressed(e)) || (target && !target.evented) || (target && !target.selectable && activeObject && activeObject !== target) ); }, /** * centeredScaling from object can't override centeredScaling from canvas. * this should be fixed, since object setting should take precedence over canvas. * @private * @param {fabric.Object} target */ _shouldCenterTransform: function (target) { if (!target) { return; } var t = this._currentTransform, centerTransform; if (t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY') { centerTransform = this.centeredScaling || target.centeredScaling; } else if (t.action === 'rotate') { centerTransform = this.centeredRotation || target.centeredRotation; } return centerTransform ? !t.altKey : t.altKey; }, /** * @private */ _getOriginFromCorner: function(target, corner) { var origin = { x: target.originX, y: target.originY }; if (corner === 'ml' || corner === 'tl' || corner === 'bl') { origin.x = 'right'; } else if (corner === 'mr' || corner === 'tr' || corner === 'br') { origin.x = 'left'; } if (corner === 'tl' || corner === 'mt' || corner === 'tr') { origin.y = 'bottom'; } else if (corner === 'bl' || corner === 'mb' || corner === 'br') { origin.y = 'top'; } return origin; }, /** * @private * @param {Boolean} alreadySelected true if target is already selected * @param {String} corner a string representing the corner ml, mr, tl ... * @param {Event} e Event object * @param {fabric.Object} [target] inserted back to help overriding. Unused */ _getActionFromCorner: function(alreadySelected, corner, e /* target */) { if (!corner || !alreadySelected) { return 'drag'; } switch (corner) { case 'mtr': return 'rotate'; case 'ml': case 'mr': return e[this.altActionKey] ? 'skewY' : 'scaleX'; case 'mt': case 'mb': return e[this.altActionKey] ? 'skewX' : 'scaleY'; default: return 'scale'; } }, /** * @private * @param {Event} e Event object * @param {fabric.Object} target */ _setupCurrentTransform: function (e, target, alreadySelected) { if (!target) { return; } var pointer = this.getPointer(e), corner = target._findTargetCorner(this.getPointer(e, true)), action = this._getActionFromCorner(alreadySelected, corner, e, target), origin = this._getOriginFromCorner(target, corner); this._currentTransform = { target: target, action: action, corner: corner, scaleX: target.scaleX, scaleY: target.scaleY, skewX: target.skewX, skewY: target.skewY, // used by transation offsetX: pointer.x - target.left, offsetY: pointer.y - target.top, originX: origin.x, originY: origin.y, ex: pointer.x, ey: pointer.y, lastX: pointer.x, lastY: pointer.y, // unsure they are usefull anymore. // left: target.left, // top: target.top, theta: degreesToRadians(target.angle), // end of unsure width: target.width * target.scaleX, mouseXSign: 1, mouseYSign: 1, shiftKey: e.shiftKey, altKey: e[this.centeredKey], original: fabric.util.saveObjectTransform(target), }; this._currentTransform.original.originX = origin.x; this._currentTransform.original.originY = origin.y; this._resetCurrentTransform(); this._beforeTransform(e); }, /** * Translates object by "setting" its left/top * @private * @param {Number} x pointer's x coordinate * @param {Number} y pointer's y coordinate * @return {Boolean} true if the translation occurred */ _translateObject: function (x, y) { var transform = this._currentTransform, target = transform.target, newLeft = x - transform.offsetX, newTop = y - transform.offsetY, moveX = !target.get('lockMovementX') && target.left !== newLeft, moveY = !target.get('lockMovementY') && target.top !== newTop; moveX && target.set('left', newLeft); moveY && target.set('top', newTop); return moveX || moveY; }, /** * Check if we are increasing a positive skew or lower it, * checking mouse direction and pressed corner. * @private */ _changeSkewTransformOrigin: function(mouseMove, t, by) { var property = 'originX', origins = { 0: 'center' }, skew = t.target.skewX, originA = 'left', originB = 'right', corner = t.corner === 'mt' || t.corner === 'ml' ? 1 : -1, flipSign = 1; mouseMove = mouseMove > 0 ? 1 : -1; if (by === 'y') { skew = t.target.skewY; originA = 'top'; originB = 'bottom'; property = 'originY'; } origins[-1] = originA; origins[1] = originB; t.target.flipX && (flipSign *= -1); t.target.flipY && (flipSign *= -1); if (skew === 0) { t.skewSign = -corner * mouseMove * flipSign; t[property] = origins[-mouseMove]; } else { skew = skew > 0 ? 1 : -1; t.skewSign = skew; t[property] = origins[skew * corner * flipSign]; } }, /** * Skew object by mouse events * @private * @param {Number} x pointer's x coordinate * @param {Number} y pointer's y coordinate * @param {String} by Either 'x' or 'y' * @return {Boolean} true if the skewing occurred */ _skewObject: function (x, y, by) { var t = this._currentTransform, target = t.target, skewed = false, lockSkewingX = target.get('lockSkewingX'), lockSkewingY = target.get('lockSkewingY'); if ((lockSkewingX && by === 'x') || (lockSkewingY && by === 'y')) { return false; } // Get the constraint point var center = target.getCenterPoint(), actualMouseByCenter = target.toLocalPoint(new fabric.Point(x, y), 'center', 'center')[by], lastMouseByCenter = target.toLocalPoint(new fabric.Point(t.lastX, t.lastY), 'center', 'center')[by], actualMouseByOrigin, constraintPosition, dim = target._getTransformedDimensions(); this._changeSkewTransformOrigin(actualMouseByCenter - lastMouseByCenter, t, by); actualMouseByOrigin = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY)[by]; constraintPosition = target.translateToOriginPoint(center, t.originX, t.originY); // Actually skew the object skewed = this._setObjectSkew(actualMouseByOrigin, t, by, dim); t.lastX = x; t.lastY = y; // Make sure the constraints apply target.setPositionByOrigin(constraintPosition, t.originX, t.originY); return skewed; }, /** * Set object skew * @private * @return {Boolean} true if the skewing occurred */ _setObjectSkew: function(localMouse, transform, by, _dim) { var target = transform.target, newValue, skewed = false, skewSign = transform.skewSign, newDim, dimNoSkew, otherBy, _otherBy, _by, newDimMouse, skewX, skewY; if (by === 'x') { otherBy = 'y'; _otherBy = 'Y'; _by = 'X'; skewX = 0; skewY = target.skewY; } else { otherBy = 'x'; _otherBy = 'X'; _by = 'Y'; skewX = target.skewX; skewY = 0; } dimNoSkew = target._getTransformedDimensions(skewX, skewY); newDimMouse = 2 * Math.abs(localMouse) - dimNoSkew[by]; if (newDimMouse <= 2) { newValue = 0; } else { newValue = skewSign * Math.atan((newDimMouse / target['scale' + _by]) / (dimNoSkew[otherBy] / target['scale' + _otherBy])); newValue = fabric.util.radiansToDegrees(newValue); } skewed = target['skew' + _by] !== newValue; target.set('skew' + _by, newValue); if (target['skew' + _otherBy] !== 0) { newDim = target._getTransformedDimensions(); newValue = (_dim[otherBy] / newDim[otherBy]) * target['scale' + _otherBy]; target.set('scale' + _otherBy, newValue); } return skewed; }, /** * Scales object by invoking its scaleX/scaleY methods * @private * @param {Number} x pointer's x coordinate * @param {Number} y pointer's y coordinate * @param {String} by Either 'x' or 'y' - specifies dimension constraint by which to scale an object. * When not provided, an object is scaled by both dimensions equally * @return {Boolean} true if the scaling occurred */ _scaleObject: function (x, y, by) { var t = this._currentTransform, target = t.target, lockScalingX = target.lockScalingX, lockScalingY = target.lockScalingY, lockScalingFlip = target.lockScalingFlip; if (lockScalingX && lockScalingY) { return false; } // Get the constraint point var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY), localMouse = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY), dim = target._getTransformedDimensions(), scaled = false; this._setLocalMouse(localMouse, t); // Actually scale the object scaled = this._setObjectScale(localMouse, t, lockScalingX, lockScalingY, by, lockScalingFlip, dim); // Make sure the constraints apply target.setPositionByOrigin(constraintPosition, t.originX, t.originY); return scaled; }, /** * @private * @return {Boolean} true if the scaling occurred */ _setObjectScale: function(localMouse, transform, lockScalingX, lockScalingY, by, lockScalingFlip, _dim) { var target = transform.target, forbidScalingX = false, forbidScalingY = false, scaled = false, scaleX = localMouse.x * target.scaleX / _dim.x, scaleY = localMouse.y * target.scaleY / _dim.y, changeX = target.scaleX !== scaleX, changeY = target.scaleY !== scaleY; if (lockScalingFlip && scaleX <= 0 && scaleX < target.scaleX) { forbidScalingX = true; localMouse.x = 0; } if (lockScalingFlip && scaleY <= 0 && scaleY < target.scaleY) { forbidScalingY = true; localMouse.y = 0; } if (by === 'equally' && !lockScalingX && !lockScalingY) { scaled = this._scaleObjectEqually(localMouse, target, transform, _dim); } else if (!by) { forbidScalingX || lockScalingX || (target.set('scaleX', scaleX) && (scaled = scaled || changeX)); forbidScalingY || lockScalingY || (target.set('scaleY', scaleY) && (scaled = scaled || changeY)); } else if (by === 'x' && !target.get('lockUniScaling')) { forbidScalingX || lockScalingX || (target.set('scaleX', scaleX) && (scaled = changeX)); } else if (by === 'y' && !target.get('lockUniScaling')) { forbidScalingY || lockScalingY || (target.set('scaleY', scaleY) && (scaled = changeY)); } transform.newScaleX = scaleX; transform.newScaleY = scaleY; forbidScalingX || forbidScalingY || this._flipObject(transform, by); return scaled; }, /** * @private * @return {Boolean} true if the scaling occurred */ _scaleObjectEqually: function(localMouse, target, transform, _dim) { var dist = localMouse.y + localMouse.x, lastDist = _dim.y * transform.original.scaleY / target.scaleY + _dim.x * transform.original.scaleX / target.scaleX, scaled, signX = localMouse.x < 0 ? -1 : 1, signY = localMouse.y < 0 ? -1 : 1, newScaleX, newScaleY; // We use transform.scaleX/Y instead of target.scaleX/Y // because the object may have a min scale and we'll loose the proportions newScaleX = signX * Math.abs(transform.original.scaleX * dist / lastDist); newScaleY = signY * Math.abs(transform.original.scaleY * dist / lastDist); scaled = newScaleX !== target.scaleX || newScaleY !== target.scaleY; target.set('scaleX', newScaleX); target.set('scaleY', newScaleY); return scaled; }, /** * @private */ _flipObject: function(transform, by) { if (transform.newScaleX < 0 && by !== 'y') { if (transform.originX === 'left') { transform.originX = 'right'; } else if (transform.originX === 'right') { transform.originX = 'left'; } } if (transform.newScaleY < 0 && by !== 'x') { if (transform.originY === 'top') { transform.originY = 'bottom'; } else if (transform.originY === 'bottom') { transform.originY = 'top'; } } }, /** * @private */ _setLocalMouse: function(localMouse, t) { var target = t.target, zoom = this.getZoom(), padding = target.padding / zoom; if (t.originX === 'right') { localMouse.x *= -1; } else if (t.originX === 'center') { localMouse.x *= t.mouseXSign * 2; if (localMouse.x < 0) { t.mouseXSign = -t.mouseXSign; } } if (t.originY === 'bottom') { localMouse.y *= -1; } else if (t.originY === 'center') { localMouse.y *= t.mouseYSign * 2; if (localMouse.y < 0) { t.mouseYSign = -t.mouseYSign; } } // adjust the mouse coordinates when dealing with padding if (abs(localMouse.x) > padding) { if (localMouse.x < 0) { localMouse.x += padding; } else { localMouse.x -= padding; } } else { // mouse is within the padding, set to 0 localMouse.x = 0; } if (abs(localMouse.y) > padding) { if (localMouse.y < 0) { localMouse.y += padding; } else { localMouse.y -= padding; } } else { localMouse.y = 0; } }, /** * Rotates object by invoking its rotate method * @private * @param {Number} x pointer's x coordinate * @param {Number} y pointer's y coordinate * @return {Boolean} true if the rotation occurred */ _rotateObject: function (x, y) { var t = this._currentTransform, target = t.target, constraintPosition, constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY); if (target.lockRotation) { return false; } var lastAngle = atan2(t.ey - constraintPosition.y, t.ex - constraintPosition.x), curAngle = atan2(y - constraintPosition.y, x - constraintPosition.x), angle = radiansToDegrees(curAngle - lastAngle + t.theta), hasRotated = true; if (target.snapAngle > 0) { var snapAngle = target.snapAngle, snapThreshold = target.snapThreshold || snapAngle, rightAngleLocked = Math.ceil(angle / snapAngle) * snapAngle, leftAngleLocked = Math.floor(angle / snapAngle) * snapAngle; if (Math.abs(angle - leftAngleLocked) < snapThreshold) { angle = leftAngleLocked; } else if (Math.abs(angle - rightAngleLocked) < snapThreshold) { angle = rightAngleLocked; } } // normalize angle to positive value if (angle < 0) { angle = 360 + angle; } angle %= 360; if (target.angle === angle) { hasRotated = false; } else { // rotation only happen here target.angle = angle; // Make sure the constraints apply target.setPositionByOrigin(constraintPosition, t.originX, t.originY); } return hasRotated; }, /** * Set the cursor type of the canvas element * @param {String} value Cursor type of the canvas element. * @see http://www.w3.org/TR/css3-ui/#cursor */ setCursor: function (value) { this.upperCanvasEl.style.cursor = value; }, /** * @private * @param {CanvasRenderingContext2D} ctx to draw the selection on */ _drawSelection: function (ctx) { var groupSelector = this._groupSelector, left = groupSelector.left, top = groupSelector.top, aleft = abs(left), atop = abs(top); if (this.selectionColor) { ctx.fillStyle = this.selectionColor; ctx.fillRect( groupSelector.ex - ((left > 0) ? 0 : -left), groupSelector.ey - ((top > 0) ? 0 : -top), aleft, atop ); } if (!this.selectionLineWidth || !this.selectionBorderColor) { return; } ctx.lineWidth = this.selectionLineWidth; ctx.strokeStyle = this.selectionBorderColor; // selection border if (this.selectionDashArray.length > 1 && !supportLineDash) { var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft), py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop); ctx.beginPath(); fabric.util.drawDashedLine(ctx, px, py, px + aleft, py, this.selectionDashArray); fabric.util.drawDashedLine(ctx, px, py + atop - 1, px + aleft, py + atop - 1, this.selectionDashArray); fabric.util.drawDashedLine(ctx, px, py, px, py + atop, this.selectionDashArray); fabric.util.drawDashedLine(ctx, px + aleft - 1, py, px + aleft - 1, py + atop, this.selectionDashArray); ctx.closePath(); ctx.stroke(); } else { fabric.Object.prototype._setLineDash.call(this, ctx, this.selectionDashArray); ctx.strokeRect( groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft), groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop), aleft, atop ); } }, /** * Method that determines what object we are clicking on * the skipGroup parameter is for internal use, is needed for shift+click action * 11/09/2018 TODO: would be cool if findTarget could discern between being a full target * or the outside part of the corner. * @param {Event} e mouse event * @param {Boolean} skipGroup when true, activeGroup is skipped and only objects are traversed through * @return {fabric.Object} the target found */ findTarget: function (e, skipGroup) { if (this.skipTargetFind) { return; } var ignoreZoom = true, pointer = this.getPointer(e, ignoreZoom), activeObject = this._activeObject, aObjects = this.getActiveObjects(), activeTarget, activeTargetSubs; // first check current group (if one exists) // active group does not check sub targets like normal groups. // if active group just exits. this.targets = []; if (aObjects.length > 1 && !skipGroup && activeObject === this._searchPossibleTargets([activeObject], pointer)) { return activeObject; } // if we hit the corner of an activeObject, let's return that. if (aObjects.length === 1 && activeObject._findTargetCorner(pointer)) { return activeObject; } if (aObjects.length === 1 && activeObject === this._searchPossibleTargets([activeObject], pointer)) { if (!this.preserveObjectStacking) { return activeObject; } else { activeTarget = activeObject; activeTargetSubs = this.targets; this.targets = []; } } var target = this._searchPossibleTargets(this._objects, pointer); if (e[this.altSelectionKey] && target && activeTarget && target !== activeTarget) { target = activeTarget; this.targets = activeTargetSubs; } return target; }, /** * Checks point is inside the object. * @param {Object} [pointer] x,y object of point coordinates we want to check. * @param {fabric.Object} obj Object to test against * @param {Object} [globalPointer] x,y object of point coordinates relative to canvas used to search per pixel target. * @return {Boolean} true if point is contained within an area of given object * @private */ _checkTarget: function(pointer, obj, globalPointer) { if (obj && obj.visible && obj.evented && this.containsPoint(null, obj, pointer)){ if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) { var isTransparent = this.isTargetTransparent(obj, globalPointer.x, globalPointer.y); if (!isTransparent) { return true; } } else { return true; } } }, /** * Function used to search inside objects an object that contains pointer in bounding box or that contains pointerOnCanvas when painted * @param {Array} [objects] objects array to look into * @param {Object} [pointer] x,y object of point coordinates we want to check. * @return {fabric.Object} object that contains pointer * @private */ _searchPossibleTargets: function(objects, pointer) { // Cache all targets where their bounding box contains point. var target, i = objects.length, subTarget; // Do not check for currently grouped objects, since we check the parent group itself. // until we call this function specifically to search inside the activeGroup while (i--) { var objToCheck = objects[i]; var pointerToUse = objToCheck.group && objToCheck.group.type !== 'activeSelection' ? this._normalizePointer(objToCheck.group, pointer) : pointer; if (this._checkTarget(pointerToUse, objToCheck, pointer)) { target = objects[i]; if (target.subTargetCheck && target instanceof fabric.Group) { subTarget = this._searchPossibleTargets(target._objects, pointer); subTarget && this.targets.push(subTarget); } break; } } return target; }, /** * Returns pointer coordinates without the effect of the viewport * @param {Object} pointer with "x" and "y" number values * @return {Object} object with "x" and "y" number values */ restorePointerVpt: function(pointer) { return fabric.util.transformPoint( pointer, fabric.util.invertTransform(this.viewportTransform) ); }, /** * Returns pointer coordinates relative to canvas. * Can return coordinates with or without viewportTransform. * ignoreZoom false gives back coordinates that represent * the point clicked on canvas element. * ignoreZoom true gives back coordinates after being processed * by the viewportTransform ( sort of coordinates of what is displayed * on the canvas where you are clicking. * ignoreZoom true = HTMLElement coordinates relative to top,left * ignoreZoom false, default = fabric space coordinates, the same used for shape position * To interact with your shapes top and left you want to use ignoreZoom true * most of the time, while ignoreZoom false will give you coordinates * compatible with the object.oCoords system. * of the time. * @param {Event} e * @param {Boolean} ignoreZoom * @return {Object} object with "x" and "y" number values */ getPointer: function (e, ignoreZoom) { // return cached values if we are in the event processing chain if (this._absolutePointer && !ignoreZoom) { return this._absolutePointer; } if (this._pointer && ignoreZoom) { return this._pointer; } var pointer = getPointer(e), upperCanvasEl = this.upperCanvasEl, bounds = upperCanvasEl.getBoundingClientRect(), boundsWidth = bounds.width || 0, boundsHeight = bounds.height || 0, cssScale; if (!boundsWidth || !boundsHeight ) { if ('top' in bounds && 'bottom' in bounds) { boundsHeight = Math.abs( bounds.top - bounds.bottom ); } if ('right' in bounds && 'left' in bounds) { boundsWidth = Math.abs( bounds.right - bounds.left ); } } this.calcOffset(); pointer.x = pointer.x - this._offset.left; pointer.y = pointer.y - this._offset.top; if (!ignoreZoom) { pointer = this.restorePointerVpt(pointer); } if (boundsWidth === 0 || boundsHeight === 0) { // If bounds are not available (i.e. not visible), do not apply scale. cssScale = { width: 1, height: 1 }; } else { cssScale = { width: upperCanvasEl.width / boundsWidth, height: upperCanvasEl.height / boundsHeight }; } return { x: pointer.x * cssScale.width, y: pointer.y * cssScale.height }; }, /** * @private * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized */ _createUpperCanvas: function () { var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, ''); // there is no need to create a new upperCanvas element if we have already one. if (this.upperCanvasEl) { this.upperCanvasEl.className = ''; } else { this.upperCanvasEl = this._createCanvasElement(); } fabric.util.addClass(this.upperCanvasEl, 'upper-canvas ' + lowerCanvasClass); this.wrapperEl.appendChild(this.upperCanvasEl); this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl); this._applyCanvasStyle(this.upperCanvasEl); this.contextTop = this.upperCanvasEl.getContext('2d'); }, /** * @private */ _createCacheCanvas: function () { this.cacheCanvasEl = this._createCanvasElement(); this.cacheCanvasEl.setAttribute('width', this.width); this.cacheCanvasEl.setAttribute('height', this.height); this.contextCache = this.cacheCanvasEl.getContext('2d'); }, /** * @private */ _initWrapperElement: function () { this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', { 'class': this.containerClass }); fabric.util.setStyle(this.wrapperEl, { width: this.width + 'px', height: this.height + 'px', position: 'relative' }); fabric.util.makeElementUnselectable(this.wrapperEl); }, /** * @private * @param {HTMLElement} element canvas element to apply styles on */ _applyCanvasStyle: function (element) { var width = this.width || element.width, height = this.height || element.height; fabric.util.setStyle(element, { position: 'absolute', width: width + 'px', height: height + 'px', left: 0, top: 0, 'touch-action': this.allowTouchScrolling ? 'manipulation' : 'none' }); element.width = width; element.height = height; fabric.util.makeElementUnselectable(element); }, /** * Copy the entire inline style from one element (fromEl) to another (toEl) * @private * @param {Element} fromEl Element style is copied from * @param {Element} toEl Element copied style is applied to */ _copyCanvasStyle: function (fromEl, toEl) { toEl.style.cssText = fromEl.style.cssText; }, /** * Returns context of canvas where object selection is drawn * @return {CanvasRenderingContext2D} */ getSelectionContext: function() { return this.contextTop; }, /** * Returns <canvas> element on which object selection is drawn * @return {HTMLCanvasElement} */ getSelectionElement: function () { return this.upperCanvasEl; }, /** * Returns currently active object * @return {fabric.Object} active object */ getActiveObject: function () { return this._activeObject; }, /** * Returns an array with the current selected objects * @return {fabric.Object} active object */ getActiveObjects: function () { var active = this._activeObject; if (active) { if (active.type === 'activeSelection' && active._objects) { return active._objects.slice(0); } else { return [active]; } } return []; }, /** * @private * @param {fabric.Object} obj Object that was removed */ _onObjectRemoved: function(obj) { // removing active object should fire "selection:cleared" events if (obj === this._activeObject) { this.fire('before:selection:cleared', { target: obj }); this._discardActiveObject(); this.fire('selection:cleared', { target: obj }); obj.fire('deselected'); } if (this._hoveredTarget === obj) { this._hoveredTarget = null; } this.callSuper('_onObjectRemoved', obj); }, /** * @private * Compares the old activeObject with the current one and fires correct events * @param {fabric.Object} obj old activeObject */ _fireSelectionEvents: function(oldObjects, e) { var somethingChanged = false, objects = this.getActiveObjects(), added = [], removed = [], opt = { e: e }; oldObjects.forEach(function(oldObject) { if (objects.indexOf(oldObject) === -1) { somethingChanged = true; oldObject.fire('deselected', opt); removed.push(oldObject); } }); objects.forEach(function(object) { if (oldObjects.indexOf(object) === -1) { somethingChanged = true; object.fire('selected', opt); added.push(object); } }); if (oldObjects.length > 0 && objects.length > 0) { opt.selected = added; opt.deselected = removed; // added for backward compatibility opt.updated = added[0] || removed[0]; opt.target = this._activeObject; somethingChanged && this.fire('selection:updated', opt); } else if (objects.length > 0) { // deprecated event if (objects.length === 1) { opt.target = added[0]; this.fire('object:selected', opt); } opt.selected = added; // added for backward compatibility opt.target = this._activeObject; this.fire('selection:created', opt); } else if (oldObjects.length > 0) { opt.deselected = removed; this.fire('selection:cleared', opt); } }, /** * Sets given object as the only active object on canvas * @param {fabric.Object} object Object to set as an active one * @param {Event} [e] Event (passed along when firing "object:selected") * @return {fabric.Canvas} thisArg * @chainable */ setActiveObject: function (object, e) { var currentActives = this.getActiveObjects(); this._setActiveObject(object, e); this._fireSelectionEvents(currentActives, e); return this; }, /** * @private * @param {Object} object to set as active * @param {Event} [e] Event (passed along when firing "object:selected") * @return {Boolean} true if the selection happened */ _setActiveObject: function(object, e) { if (this._activeObject === object) { return false; } if (!this._discardActiveObject(e, object)) { return false; } if (object.onSelect({ e: e })) { return false; } this._activeObject = object; return true; }, /** * @private */ _discardActiveObject: function(e, object) { var obj = this._activeObject; if (obj) { // onDeselect return TRUE to cancel selection; if (obj.onDeselect({ e: e, object: object })) { return false; } this._activeObject = null; } return true; }, /** * Discards currently active object and fire events. If the function is called by fabric * as a consequence of a mouse event, the event is passed as a parameter and * sent to the fire function for the custom events. When used as a method the * e param does not have any application. * @param {event} e * @return {fabric.Canvas} thisArg * @chainable */ discardActiveObject: function (e) { var currentActives = this.getActiveObjects(); if (currentActives.length) { this.fire('before:selection:cleared', { target: currentActives[0], e: e }); } this._discardActiveObject(e); this._fireSelectionEvents(currentActives, e); return this; }, /** * Clears a canvas element and removes all event listeners * @return {fabric.Canvas} thisArg * @chainable */ dispose: function () { var wrapper = this.wrapperEl; this.removeListeners(); wrapper.removeChild(this.upperCanvasEl); wrapper.removeChild(this.lowerCanvasEl); this.contextCache = null; this.contextTop = null; ['upperCanvasEl', 'cacheCanvasEl'].forEach((function(element) { fabric.util.cleanUpJsdomNode(this[element]); this[element] = undefined; }).bind(this)); if (wrapper.parentNode) { wrapper.parentNode.replaceChild(this.lowerCanvasEl, this.wrapperEl); } delete this.wrapperEl; fabric.StaticCanvas.prototype.dispose.call(this); return this; }, /** * Clears all contexts (background, main, top) of an instance * @return {fabric.Canvas} thisArg * @chainable */ clear: function () { // this.discardActiveGroup(); this.discardActiveObject(); this.clearContext(this.contextTop); return this.callSuper('clear'); }, /** * Draws objects' controls (borders/controls) * @param {CanvasRenderingContext2D} ctx Context to render controls on */ drawControls: function(ctx) { var activeObject = this._activeObject; if (activeObject) { activeObject._renderControls(ctx); } }, /** * @private */ _toObject: function(instance, methodName, propertiesToInclude) { //If the object is part of the current selection group, it should //be transformed appropriately //i.e. it should be serialised as it would appear if the selection group //were to be destroyed. var originalProperties = this._realizeGroupTransformOnObject(instance), object = this.callSuper('_toObject', instance, methodName, propertiesToInclude); //Undo the damage we did by changing all of its properties this._unwindGroupTransformOnObject(instance, originalProperties); return object; }, /** * Realises an object's group transformation on it * @private * @param {fabric.Object} [instance] the object to transform (gets mutated) * @returns the original values of instance which were changed */ _realizeGroupTransformOnObject: function(instance) { if (instance.group && instance.group.type === 'activeSelection' && this._activeObject === instance.group) { var layoutProps = ['angle', 'flipX', 'flipY', 'left', 'scaleX', 'scaleY', 'skewX', 'skewY', 'top']; //Copy all the positionally relevant properties across now var originalValues = {}; layoutProps.forEach(function(prop) { originalValues[prop] = instance[prop]; }); this._activeObject.realizeTransform(instance); return originalValues; } else { return null; } }, /** * Restores the changed properties of instance * @private * @param {fabric.Object} [instance] the object to un-transform (gets mutated) * @param {Object} [originalValues] the original values of instance, as returned by _realizeGroupTransformOnObject */ _unwindGroupTransformOnObject: function(instance, originalValues) { if (originalValues) { instance.set(originalValues); } }, /** * @private */ _setSVGObject: function(markup, instance, reviver) { //If the object is in a selection group, simulate what would happen to that //object when the group is deselected var originalProperties = this._realizeGroupTransformOnObject(instance); this.callSuper('_setSVGObject', markup, instance, reviver); this._unwindGroupTransformOnObject(instance, originalProperties); }, setViewportTransform: function (vpt) { if (this.renderOnAddRemove && this._activeObject && this._activeObject.isEditing) { this._activeObject.clearContextTop(); } fabric.StaticCanvas.prototype.setViewportTransform.call(this, vpt); } }); // copying static properties manually to work around Opera's bug, // where "prototype" property is enumerable and overrides existing prototype for (var prop in fabric.StaticCanvas) { if (prop !== 'prototype') { fabric.Canvas[prop] = fabric.StaticCanvas[prop]; } } if (fabric.isTouchSupported) { /** @ignore */ fabric.Canvas.prototype._setCursorFromEvent = function() { }; } })(); (function() { var cursorOffset = { mt: 0, // n tr: 1, // ne mr: 2, // e br: 3, // se mb: 4, // s bl: 5, // sw ml: 6, // w tl: 7 // nw }, addListener = fabric.util.addListener, removeListener = fabric.util.removeListener, RIGHT_CLICK = 3, MIDDLE_CLICK = 2, LEFT_CLICK = 1, addEventOptions = { passive: false }; function checkClick(e, value) { return 'which' in e ? e.which === value : e.button === value - 1; } fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { /** * Map of cursor style values for each of the object controls * @private */ cursorMap: [ 'n-resize', 'ne-resize', 'e-resize', 'se-resize', 's-resize', 'sw-resize', 'w-resize', 'nw-resize' ], /** * Adds mouse listeners to canvas * @private */ _initEventListeners: function () { // in case we initialized the class twice. This should not happen normally // but in some kind of applications where the canvas element may be changed // this is a workaround to having double listeners. this.removeListeners(); this._bindEvents(); this.addOrRemove(addListener, 'add'); }, addOrRemove: function(functor, eventjsFunctor) { functor(fabric.window, 'resize', this._onResize); functor(this.upperCanvasEl, 'mousedown', this._onMouseDown); functor(this.upperCanvasEl, 'mousemove', this._onMouseMove, addEventOptions); functor(this.upperCanvasEl, 'mouseout', this._onMouseOut); functor(this.upperCanvasEl, 'mouseenter', this._onMouseEnter); functor(this.upperCanvasEl, 'wheel', this._onMouseWheel); functor(this.upperCanvasEl, 'contextmenu', this._onContextMenu); functor(this.upperCanvasEl, 'dblclick', this._onDoubleClick); functor(this.upperCanvasEl, 'touchstart', this._onMouseDown, addEventOptions); functor(this.upperCanvasEl, 'touchmove', this._onMouseMove, addEventOptions); functor(this.upperCanvasEl, 'dragover', this._onDragOver); functor(this.upperCanvasEl, 'dragenter', this._onDragEnter); functor(this.upperCanvasEl, 'dragleave', this._onDragLeave); functor(this.upperCanvasEl, 'drop', this._onDrop); if (typeof eventjs !== 'undefined' && eventjsFunctor in eventjs) { eventjs[eventjsFunctor](this.upperCanvasEl, 'gesture', this._onGesture); eventjs[eventjsFunctor](this.upperCanvasEl, 'drag', this._onDrag); eventjs[eventjsFunctor](this.upperCanvasEl, 'orientation', this._onOrientationChange); eventjs[eventjsFunctor](this.upperCanvasEl, 'shake', this._onShake); eventjs[eventjsFunctor](this.upperCanvasEl, 'longpress', this._onLongPress); } }, /** * Removes all event listeners */ removeListeners: function() { this.addOrRemove(removeListener, 'remove'); // if you dispose on a mouseDown, before mouse up, you need to clean document to... removeListener(fabric.document, 'mouseup', this._onMouseUp); removeListener(fabric.document, 'touchend', this._onMouseUp, addEventOptions); removeListener(fabric.document, 'mousemove', this._onMouseMove, addEventOptions); removeListener(fabric.document, 'touchmove', this._onMouseMove, addEventOptions); }, /** * @private */ _bindEvents: function() { if (this.eventsBound) { // for any reason we pass here twice we do not want to bind events twice. return; } this._onMouseDown = this._onMouseDown.bind(this); this._onMouseMove = this._onMouseMove.bind(this); this._onMouseUp = this._onMouseUp.bind(this); this._onResize = this._onResize.bind(this); this._onGesture = this._onGesture.bind(this); this._onDrag = this._onDrag.bind(this); this._onShake = this._onShake.bind(this); this._onLongPress = this._onLongPress.bind(this); this._onOrientationChange = this._onOrientationChange.bind(this); this._onMouseWheel = this._onMouseWheel.bind(this); this._onMouseOut = this._onMouseOut.bind(this); this._onMouseEnter = this._onMouseEnter.bind(this); this._onContextMenu = this._onContextMenu.bind(this); this._onDoubleClick = this._onDoubleClick.bind(this); this._onDragOver = this._onDragOver.bind(this); this._onDragEnter = this._simpleEventHandler.bind(this, 'dragenter'); this._onDragLeave = this._simpleEventHandler.bind(this, 'dragleave'); this._onDrop = this._simpleEventHandler.bind(this, 'drop'); this.eventsBound = true; }, /** * @private * @param {Event} [e] Event object fired on Event.js gesture * @param {Event} [self] Inner Event object */ _onGesture: function(e, self) { this.__onTransformGesture && this.__onTransformGesture(e, self); }, /** * @private * @param {Event} [e] Event object fired on Event.js drag * @param {Event} [self] Inner Event object */ _onDrag: function(e, self) { this.__onDrag && this.__onDrag(e, self); }, /** * @private * @param {Event} [e] Event object fired on wheel event */ _onMouseWheel: function(e) { this.__onMouseWheel(e); }, /** * @private * @param {Event} e Event object fired on mousedown */ _onMouseOut: function(e) { var target = this._hoveredTarget; this.fire('mouse:out', { target: target, e: e }); this._hoveredTarget = null; target && target.fire('mouseout', { e: e }); if (this._iTextInstances) { this._iTextInstances.forEach(function(obj) { if (obj.isEditing) { obj.hiddenTextarea.focus(); } }); } }, /** * @private * @param {Event} e Event object fired on mouseenter */ _onMouseEnter: function(e) { if (!this.findTarget(e)) { this.fire('mouse:over', { target: null, e: e }); this._hoveredTarget = null; } }, /** * @private * @param {Event} [e] Event object fired on Event.js orientation change * @param {Event} [self] Inner Event object */ _onOrientationChange: function(e, self) { this.__onOrientationChange && this.__onOrientationChange(e, self); }, /** * @private * @param {Event} [e] Event object fired on Event.js shake * @param {Event} [self] Inner Event object */ _onShake: function(e, self) { this.__onShake && this.__onShake(e, self); }, /** * @private * @param {Event} [e] Event object fired on Event.js shake * @param {Event} [self] Inner Event object */ _onLongPress: function(e, self) { this.__onLongPress && this.__onLongPress(e, self); }, /** * prevent default to allow drop event to be fired * @private * @param {Event} [e] Event object fired on Event.js shake */ _onDragOver: function(e) { e.preventDefault(); var target = this._simpleEventHandler('dragover', e); this._fireEnterLeaveEvents(target, e); }, /** * @private * @param {Event} e Event object fired on mousedown */ _onContextMenu: function (e) { if (this.stopContextMenu) { e.stopPropagation(); e.preventDefault(); } return false; }, /** * @private * @param {Event} e Event object fired on mousedown */ _onDoubleClick: function (e) { this._cacheTransformEventData(e); this._handleEvent(e, 'dblclick'); this._resetTransformEventData(e); }, /** * @private * @param {Event} e Event object fired on mousedown */ _onMouseDown: function (e) { this.__onMouseDown(e); this._resetTransformEventData(); addListener(fabric.document, 'touchend', this._onMouseUp, addEventOptions); addListener(fabric.document, 'touchmove', this._onMouseMove, addEventOptions); removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove, addEventOptions); removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove, addEventOptions); if (e.type === 'touchstart') { // Unbind mousedown to prevent double triggers from touch devices removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); } else { addListener(fabric.document, 'mouseup', this._onMouseUp); addListener(fabric.document, 'mousemove', this._onMouseMove, addEventOptions); } }, /** * @private * @param {Event} e Event object fired on mouseup */ _onMouseUp: function (e) { this.__onMouseUp(e); this._resetTransformEventData(); removeListener(fabric.document, 'mouseup', this._onMouseUp); removeListener(fabric.document, 'touchend', this._onMouseUp, addEventOptions); removeListener(fabric.document, 'mousemove', this._onMouseMove, addEventOptions); removeListener(fabric.document, 'touchmove', this._onMouseMove, addEventOptions); addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove, addEventOptions); addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove, addEventOptions); if (e.type === 'touchend') { // Wait 400ms before rebinding mousedown to prevent double triggers // from touch devices var _this = this; setTimeout(function() { addListener(_this.upperCanvasEl, 'mousedown', _this._onMouseDown); }, 400); } }, /** * @private * @param {Event} e Event object fired on mousemove */ _onMouseMove: function (e) { !this.allowTouchScrolling && e.preventDefault && e.preventDefault(); this.__onMouseMove(e); }, /** * @private */ _onResize: function () { this.calcOffset(); }, /** * Decides whether the canvas should be redrawn in mouseup and mousedown events. * @private * @param {Object} target */ _shouldRender: function(target) { var activeObject = this._activeObject; if ( !!activeObject !== !!target || (activeObject && target && (activeObject !== target)) ) { // this covers: switch of target, from target to no target, selection of target // multiSelection with key and mouse return true; } else if (activeObject && activeObject.isEditing) { // if we mouse up/down over a editing textbox a cursor change, // there is no need to re render return false; } return false; }, /** * Method that defines the actions when mouse is released on canvas. * The method resets the currentTransform parameters, store the image corner * position in the image object and render the canvas on top. * @private * @param {Event} e Event object fired on mouseup */ __onMouseUp: function (e) { var target, transform = this._currentTransform, groupSelector = this._groupSelector, shouldRender = false, isClick = (!groupSelector || (groupSelector.left === 0 && groupSelector.top === 0)); this._cacheTransformEventData(e); target = this._target; this._handleEvent(e, 'up:before'); // if right/middle click just fire events and return // target undefined will make the _handleEvent search the target if (checkClick(e, RIGHT_CLICK)) { if (this.fireRightClick) { this._handleEvent(e, 'up', RIGHT_CLICK, isClick); } return; } if (checkClick(e, MIDDLE_CLICK)) { if (this.fireMiddleClick) { this._handleEvent(e, 'up', MIDDLE_CLICK, isClick); } this._resetTransformEventData(); return; } if (this.isDrawingMode && this._isCurrentlyDrawing) { this._onMouseUpInDrawingMode(e); return; } if (transform) { this._finalizeCurrentTransform(e); shouldRender = transform.actionPerformed; } if (!isClick) { this._maybeGroupObjects(e); shouldRender || (shouldRender = this._shouldRender(target)); } if (target) { target.isMoving = false; } this._setCursorFromEvent(e, target); this._handleEvent(e, 'up', LEFT_CLICK, isClick); this._groupSelector = null; this._currentTransform = null; // reset the target information about which corner is selected target && (target.__corner = 0); if (shouldRender) { this.requestRenderAll(); } else if (!isClick) { this.renderTop(); } }, /** * @private * Handle event firing for target and subtargets * @param {Event} e event from mouse * @param {String} eventType event to fire (up, down or move) * @return {Fabric.Object} target return the the target found, for internal reasons. */ _simpleEventHandler: function(eventType, e) { var target = this.findTarget(e), targets = this.targets, options = { e: e, target: target, subTargets: targets, }; this.fire(eventType, options); target && target.fire(eventType, options); if (!targets) { return target; } for (var i = 0; i < targets.length; i++) { targets[i].fire(eventType, options); } return target; }, /** * @private * Handle event firing for target and subtargets * @param {Event} e event from mouse * @param {String} eventType event to fire (up, down or move) * @param {fabric.Object} targetObj receiving event * @param {Number} [button] button used in the event 1 = left, 2 = middle, 3 = right * @param {Boolean} isClick for left button only, indicates that the mouse up happened without move. */ _handleEvent: function(e, eventType, button, isClick) { var target = this._target, targets = this.targets || [], options = { e: e, target: target, subTargets: targets, button: button || LEFT_CLICK, isClick: isClick || false, pointer: this._pointer, absolutePointer: this._absolutePointer, transform: this._currentTransform }; this.fire('mouse:' + eventType, options); target && target.fire('mouse' + eventType, options); for (var i = 0; i < targets.length; i++) { targets[i].fire('mouse' + eventType, options); } }, /** * @private * @param {Event} e send the mouse event that generate the finalize down, so it can be used in the event */ _finalizeCurrentTransform: function(e) { var transform = this._currentTransform, target = transform.target, eventName, options = { e: e, target: target, transform: transform, }; if (target._scaling) { target._scaling = false; } target.setCoords(); if (transform.actionPerformed || (this.stateful && target.hasStateChanged())) { if (transform.actionPerformed) { eventName = this._addEventOptions(options, transform); this._fire(eventName, options); } this._fire('modified', options); } }, /** * Mutate option object in order to add by property and give back the event name. * @private * @param {Object} options to mutate * @param {Object} transform to inspect action from */ _addEventOptions: function(options, transform) { // we can probably add more details at low cost // scale change, rotation changes, translation changes var eventName, by; switch (transform.action) { case 'scaleX': eventName = 'scaled'; by = 'x'; break; case 'scaleY': eventName = 'scaled'; by = 'y'; break; case 'skewX': eventName = 'skewed'; by = 'x'; break; case 'skewY': eventName = 'skewed'; by = 'y'; break; case 'scale': eventName = 'scaled'; by = 'equally'; break; case 'rotate': eventName = 'rotated'; break; case 'drag': eventName = 'moved'; break; } options.by = by; return eventName; }, /** * @private * @param {Event} e Event object fired on mousedown */ _onMouseDownInDrawingMode: function(e) { this._isCurrentlyDrawing = true; if (this.getActiveObject()) { this.discardActiveObject(e).requestRenderAll(); } if (this.clipTo) { fabric.util.clipContext(this, this.contextTop); } var pointer = this.getPointer(e); this.freeDrawingBrush.onMouseDown(pointer); this._handleEvent(e, 'down'); }, /** * @private * @param {Event} e Event object fired on mousemove */ _onMouseMoveInDrawingMode: function(e) { if (this._isCurrentlyDrawing) { var pointer = this.getPointer(e); this.freeDrawingBrush.onMouseMove(pointer); } this.setCursor(this.freeDrawingCursor); this._handleEvent(e, 'move'); }, /** * @private * @param {Event} e Event object fired on mouseup */ _onMouseUpInDrawingMode: function(e) { this._isCurrentlyDrawing = false; if (this.clipTo) { this.contextTop.restore(); } this.freeDrawingBrush.onMouseUp(); this._handleEvent(e, 'up'); }, /** * Method that defines the actions when mouse is clicked on canvas. * The method inits the currentTransform parameters and renders all the * canvas so the current image can be placed on the top canvas and the rest * in on the container one. * @private * @param {Event} e Event object fired on mousedown */ __onMouseDown: function (e) { this._cacheTransformEventData(e); this._handleEvent(e, 'down:before'); var target = this._target; // if right click just fire events if (checkClick(e, RIGHT_CLICK)) { if (this.fireRightClick) { this._handleEvent(e, 'down', RIGHT_CLICK); } return; } if (checkClick(e, MIDDLE_CLICK)) { if (this.fireMiddleClick) { this._handleEvent(e, 'down', MIDDLE_CLICK); } return; } if (this.isDrawingMode) { this._onMouseDownInDrawingMode(e); return; } // ignore if some object is being transformed at this moment if (this._currentTransform) { return; } var pointer = this._pointer; // save pointer for check in __onMouseUp event this._previousPointer = pointer; var shouldRender = this._shouldRender(target), shouldGroup = this._shouldGroup(e, target); if (this._shouldClearSelection(e, target)) { this.discardActiveObject(e); } else if (shouldGroup) { this._handleGrouping(e, target); target = this._activeObject; } if (this.selection && (!target || (!target.selectable && !target.isEditing && target !== this._activeObject))) { this._groupSelector = { ex: pointer.x, ey: pointer.y, top: 0, left: 0 }; } if (target) { var alreadySelected = target === this._activeObject; if (target.selectable) { this.setActiveObject(target, e); } if (target === this._activeObject && (target.__corner || !shouldGroup)) { this._setupCurrentTransform(e, target, alreadySelected); } } this._handleEvent(e, 'down'); // we must renderAll so that we update the visuals (shouldRender || shouldGroup) && this.requestRenderAll(); }, /** * reset cache form common information needed during event processing * @private */ _resetTransformEventData: function() { this._target = null; this._pointer = null; this._absolutePointer = null; }, /** * Cache common information needed during event processing * @private * @param {Event} e Event object fired on event */ _cacheTransformEventData: function(e) { // reset in order to avoid stale caching this._resetTransformEventData(); this._pointer = this.getPointer(e, true); this._absolutePointer = this.restorePointerVpt(this._pointer); this._target = this._currentTransform ? this._currentTransform.target : this.findTarget(e) || null; }, /** * @private */ _beforeTransform: function(e) { var t = this._currentTransform; this.stateful && t.target.saveState(); this.fire('before:transform', { e: e, transform: t, }); // determine if it's a drag or rotate case if (t.corner) { this.onBeforeScaleRotate(t.target); } }, /** * Method that defines the actions when mouse is hovering the canvas. * The currentTransform parameter will definde whether the user is rotating/scaling/translating * an image or neither of them (only hovering). A group selection is also possible and would cancel * all any other type of action. * In case of an image transformation only the top canvas will be rendered. * @private * @param {Event} e Event object fired on mousemove */ __onMouseMove: function (e) { this._handleEvent(e, 'move:before'); this._cacheTransformEventData(e); var target, pointer; if (this.isDrawingMode) { this._onMouseMoveInDrawingMode(e); return; } if (typeof e.touches !== 'undefined' && e.touches.length > 1) { return; } var groupSelector = this._groupSelector; // We initially clicked in an empty area, so we draw a box for multiple selection if (groupSelector) { pointer = this._pointer; groupSelector.left = pointer.x - groupSelector.ex; groupSelector.top = pointer.y - groupSelector.ey; this.renderTop(); } else if (!this._currentTransform) { target = this.findTarget(e) || null; this._setCursorFromEvent(e, target); this._fireOverOutEvents(target, e); } else { this._transformObject(e); } this._handleEvent(e, 'move'); this._resetTransformEventData(); }, /** * Manage the mouseout, mouseover events for the fabric object on the canvas * @param {Fabric.Object} target the target where the target from the mousemove event * @param {Event} e Event object fired on mousemove * @private */ _fireOverOutEvents: function(target, e) { this.fireSynteticInOutEvents(target, e, { targetName: '_hoveredTarget', canvasEvtOut: 'mouse:out', evtOut: 'mouseout', canvasEvtIn: 'mouse:over', evtIn: 'mouseover', }); }, /** * Manage the dragEnter, dragLeave events for the fabric objects on the canvas * @param {Fabric.Object} target the target where the target from the onDrag event * @param {Event} e Event object fired on ondrag * @private */ _fireEnterLeaveEvents: function(target, e) { this.fireSynteticInOutEvents(target, e, { targetName: '_draggedoverTarget', evtOut: 'dragleave', evtIn: 'dragenter', }); }, /** * Manage the syntetic in/out events for the fabric objects on the canvas * @param {Fabric.Object} target the target where the target from the supported events * @param {Event} e Event object fired * @param {Object} config configuration for the function to work * @param {String} config.targetName property on the canvas where the old target is stored * @param {String} [config.canvasEvtOut] name of the event to fire at canvas level for out * @param {String} config.evtOut name of the event to fire for out * @param {String} [config.canvasEvtIn] name of the event to fire at canvas level for in * @param {String} config.evtIn name of the event to fire for in * @private */ fireSynteticInOutEvents: function(target, e, config) { var inOpt, outOpt, oldTarget = this[config.targetName], outFires, inFires, targetChanged = oldTarget !== target, canvasEvtIn = config.canvasEvtIn, canvasEvtOut = config.canvasEvtOut; if (targetChanged) { inOpt = { e: e, target: target, previousTarget: oldTarget }; outOpt = { e: e, target: oldTarget, nextTarget: target }; this[config.targetName] = target; } inFires = target && targetChanged; outFires = oldTarget && targetChanged; if (outFires) { canvasEvtOut && this.fire(canvasEvtOut, outOpt); oldTarget.fire(config.evtOut, outOpt); } if (inFires) { canvasEvtIn && this.fire(canvasEvtIn, inOpt); target.fire(config.evtIn, inOpt); } }, /** * Method that defines actions when an Event Mouse Wheel * @param {Event} e Event object fired on mouseup */ __onMouseWheel: function(e) { this._cacheTransformEventData(e); this._handleEvent(e, 'wheel'); this._resetTransformEventData(); }, /** * @private * @param {Event} e Event fired on mousemove */ _transformObject: function(e) { var pointer = this.getPointer(e), transform = this._currentTransform; transform.reset = false; transform.target.isMoving = true; transform.shiftKey = e.shiftKey; transform.altKey = e[this.centeredKey]; this._beforeScaleTransform(e, transform); this._performTransformAction(e, transform, pointer); transform.actionPerformed && this.requestRenderAll(); }, /** * @private */ _performTransformAction: function(e, transform, pointer) { var x = pointer.x, y = pointer.y, action = transform.action, actionPerformed = false, options = { target: transform.target, e: e, transform: transform, pointer: pointer }; if (action === 'rotate') { (actionPerformed = this._rotateObject(x, y)) && this._fire('rotating', options); } else if (action === 'scale') { (actionPerformed = this._onScale(e, transform, x, y)) && this._fire('scaling', options); } else if (action === 'scaleX') { (actionPerformed = this._scaleObject(x, y, 'x')) && this._fire('scaling', options); } else if (action === 'scaleY') { (actionPerformed = this._scaleObject(x, y, 'y')) && this._fire('scaling', options); } else if (action === 'skewX') { (actionPerformed = this._skewObject(x, y, 'x')) && this._fire('skewing', options); } else if (action === 'skewY') { (actionPerformed = this._skewObject(x, y, 'y')) && this._fire('skewing', options); } else { actionPerformed = this._translateObject(x, y); if (actionPerformed) { this._fire('moving', options); this.setCursor(options.target.moveCursor || this.moveCursor); } } transform.actionPerformed = transform.actionPerformed || actionPerformed; }, /** * @private */ _fire: function(eventName, options) { this.fire('object:' + eventName, options); options.target.fire(eventName, options); }, /** * @private */ _beforeScaleTransform: function(e, transform) { if (transform.action === 'scale' || transform.action === 'scaleX' || transform.action === 'scaleY') { var centerTransform = this._shouldCenterTransform(transform.target); // Switch from a normal resize to center-based if ((centerTransform && (transform.originX !== 'center' || transform.originY !== 'center')) || // Switch from center-based resize to normal one (!centerTransform && transform.originX === 'center' && transform.originY === 'center') ) { this._resetCurrentTransform(); transform.reset = true; } } }, /** * @private * @param {Event} e Event object * @param {Object} transform current tranform * @param {Number} x mouse position x from origin * @param {Number} y mouse poistion y from origin * @return {Boolean} true if the scaling occurred */ _onScale: function(e, transform, x, y) { if (this._isUniscalePossible(e, transform.target)) { transform.currentAction = 'scale'; return this._scaleObject(x, y); } else { // Switch from a normal resize to proportional if (!transform.reset && transform.currentAction === 'scale') { this._resetCurrentTransform(); } transform.currentAction = 'scaleEqually'; return this._scaleObject(x, y, 'equally'); } }, /** * @private * @param {Event} e Event object * @param {fabric.Object} target current target * @return {Boolean} true if unproportional scaling is possible */ _isUniscalePossible: function(e, target) { return (e[this.uniScaleKey] || this.uniScaleTransform) && !target.get('lockUniScaling'); }, /** * Sets the cursor depending on where the canvas is being hovered. * Note: very buggy in Opera * @param {Event} e Event object * @param {Object} target Object that the mouse is hovering, if so. */ _setCursorFromEvent: function (e, target) { if (!target) { this.setCursor(this.defaultCursor); return false; } var hoverCursor = target.hoverCursor || this.hoverCursor, activeSelection = this._activeObject && this._activeObject.type === 'activeSelection' ? this._activeObject : null, // only show proper corner when group selection is not active corner = (!activeSelection || !activeSelection.contains(target)) && target._findTargetCorner(this.getPointer(e, true)); if (!corner) { this.setCursor(hoverCursor); } else { this.setCursor(this.getCornerCursor(corner, target, e)); } }, /** * @private */ getCornerCursor: function(corner, target, e) { if (this.actionIsDisabled(corner, target, e)) { return this.notAllowedCursor; } else if (corner in cursorOffset) { return this._getRotatedCornerCursor(corner, target, e); } else if (corner === 'mtr' && target.hasRotatingPoint) { return this.rotationCursor; } else { return this.defaultCursor; } }, actionIsDisabled: function(corner, target, e) { if (corner === 'mt' || corner === 'mb') { return e[this.altActionKey] ? target.lockSkewingX : target.lockScalingY; } else if (corner === 'ml' || corner === 'mr') { return e[this.altActionKey] ? target.lockSkewingY : target.lockScalingX; } else if (corner === 'mtr') { return target.lockRotation; } else { return this._isUniscalePossible(e, target) ? target.lockScalingX && target.lockScalingY : target.lockScalingX || target.lockScalingY; } }, /** * @private */ _getRotatedCornerCursor: function(corner, target, e) { var n = Math.round((target.angle % 360) / 45); if (n < 0) { n += 8; // full circle ahead } n += cursorOffset[corner]; if (e[this.altActionKey] && cursorOffset[corner] % 2 === 0) { //if we are holding shift and we are on a mx corner... n += 2; } // normalize n to be from 0 to 7 n %= 8; return this.cursorMap[n]; } }); })(); (function() { var min = Math.min, max = Math.max; fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { /** * @private * @param {Event} e Event object * @param {fabric.Object} target * @return {Boolean} */ _shouldGroup: function(e, target) { var activeObject = this._activeObject; return activeObject && this._isSelectionKeyPressed(e) && target && target.selectable && this.selection && (activeObject !== target || activeObject.type === 'activeSelection') && !target.onSelect({ e: e }); }, /** * @private * @param {Event} e Event object * @param {fabric.Object} target */ _handleGrouping: function (e, target) { var activeObject = this._activeObject; // avoid multi select when shift click on a corner if (activeObject.__corner) { return; } if (target === activeObject) { // if it's a group, find target again, using activeGroup objects target = this.findTarget(e, true); // if even object is not found or we are on activeObjectCorner, bail out if (!target || !target.selectable) { return; } } if (activeObject && activeObject.type === 'activeSelection') { this._updateActiveSelection(target, e); } else { this._createActiveSelection(target, e); } }, /** * @private */ _updateActiveSelection: function(target, e) { var activeSelection = this._activeObject, currentActiveObjects = activeSelection._objects.slice(0); if (activeSelection.contains(target)) { activeSelection.removeWithUpdate(target); this._hoveredTarget = target; if (activeSelection.size() === 1) { // activate last remaining object this._setActiveObject(activeSelection.item(0), e); } } else { activeSelection.addWithUpdate(target); this._hoveredTarget = activeSelection; } this._fireSelectionEvents(currentActiveObjects, e); }, /** * @private */ _createActiveSelection: function(target, e) { var currentActives = this.getActiveObjects(), group = this._createGroup(target); this._hoveredTarget = group; this._setActiveObject(group, e); this._fireSelectionEvents(currentActives, e); }, /** * @private * @param {Object} target */ _createGroup: function(target) { var objects = this._objects, isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target), groupObjects = isActiveLower ? [this._activeObject, target] : [target, this._activeObject]; this._activeObject.isEditing && this._activeObject.exitEditing(); return new fabric.ActiveSelection(groupObjects, { canvas: this }); }, /** * @private * @param {Event} e mouse event */ _groupSelectedObjects: function (e) { var group = this._collectObjects(e), aGroup; // do not create group for 1 element only if (group.length === 1) { this.setActiveObject(group[0], e); } else if (group.length > 1) { aGroup = new fabric.ActiveSelection(group.reverse(), { canvas: this }); this.setActiveObject(aGroup, e); } }, /** * @private */ _collectObjects: function(e) { var group = [], currentObject, x1 = this._groupSelector.ex, y1 = this._groupSelector.ey, x2 = x1 + this._groupSelector.left, y2 = y1 + this._groupSelector.top, selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)), selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)), allowIntersect = !this.selectionFullyContained, isClick = x1 === x2 && y1 === y2; // we iterate reverse order to collect top first in case of click. for (var i = this._objects.length; i--; ) { currentObject = this._objects[i]; if (!currentObject || !currentObject.selectable || !currentObject.visible || currentObject.onSelect({ e: e })) { continue; } if ((allowIntersect && currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2)) || currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) || (allowIntersect && currentObject.containsPoint(selectionX1Y1)) || (allowIntersect && currentObject.containsPoint(selectionX2Y2)) ) { group.push(currentObject); // only add one object if it's a click if (isClick) { break; } } } return group; }, /** * @private */ _maybeGroupObjects: function(e) { if (this.selection && this._groupSelector) { this._groupSelectedObjects(e); } this.setCursor(this.defaultCursor); // clear selection and current transformation this._groupSelector = null; } }); })(); (function () { fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { /** * Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately * @param {Object} [options] Options object * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. * @param {Number} [options.multiplier=1] Multiplier to scale by, to have consistent * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 2.0.0 * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format * @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo} * @example <caption>Generate jpeg dataURL with lower quality</caption> * var dataURL = canvas.toDataURL({ * format: 'jpeg', * quality: 0.8 * }); * @example <caption>Generate cropped png dataURL (clipping of canvas)</caption> * var dataURL = canvas.toDataURL({ * format: 'png', * left: 100, * top: 100, * width: 200, * height: 200 * }); * @example <caption>Generate double scaled png dataURL</caption> * var dataURL = canvas.toDataURL({ * format: 'png', * multiplier: 2 * }); */ toDataURL: function (options) { options || (options = { }); var format = options.format || 'png', quality = options.quality || 1, multiplier = (options.multiplier || 1) * (options.enableRetinaScaling ? this.getRetinaScaling() : 1), canvasEl = this.toCanvasElement(multiplier, options); return fabric.util.toDataURL(canvasEl, format, quality); }, /** * Create a new HTMLCanvas element painted with the current canvas content. * No need to resize the actual one or repaint it. * Will transfer object ownership to a new canvas, paint it, and set everything back. * This is an intermediary step used to get to a dataUrl but also it is usefull to * create quick image copies of a canvas without passing for the dataUrl string * @param {Number} [multiplier] a zoom factor. * @param {Object} [cropping] Cropping informations * @param {Number} [cropping.left] Cropping left offset. * @param {Number} [cropping.top] Cropping top offset. * @param {Number} [cropping.width] Cropping width. * @param {Number} [cropping.height] Cropping height. */ toCanvasElement: function(multiplier, cropping) { multiplier = multiplier || 1; cropping = cropping || { }; var scaledWidth = (cropping.width || this.width) * multiplier, scaledHeight = (cropping.height || this.height) * multiplier, zoom = this.getZoom(), originalWidth = this.width, originalHeight = this.height, newZoom = zoom * multiplier, vp = this.viewportTransform, translateX = (vp[4] - (cropping.left || 0)) * multiplier, translateY = (vp[5] - (cropping.top || 0)) * multiplier, originalInteractive = this.interactive, originalContext = this.contextContainer, newVp = [newZoom, 0, 0, newZoom, translateX, translateY], canvasEl = fabric.util.createCanvasElement(); canvasEl.width = scaledWidth; canvasEl.height = scaledHeight; this.interactive = false; this.viewportTransform = newVp; this.width = scaledWidth; this.height = scaledHeight; this.calcViewportBoundaries(); this.contextContainer = canvasEl.getContext('2d'); // will be renderAllExport(); this.renderAll(); this.viewportTransform = vp; this.width = originalWidth; this.height = originalHeight; this.calcViewportBoundaries(); this.contextContainer = originalContext; this.interactive = originalInteractive; return canvasEl; }, }); })(); fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { /** * Populates canvas with data from the specified dataless JSON. * JSON format must conform to the one of {@link fabric.Canvas#toDatalessJSON} * @deprecated since 1.2.2 * @param {String|Object} json JSON string or object * @param {Function} callback Callback, invoked when json is parsed * and corresponding objects (e.g: {@link fabric.Image}) * are initialized * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. * @return {fabric.Canvas} instance * @chainable * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#deserialization} */ loadFromDatalessJSON: function (json, callback, reviver) { return this.loadFromJSON(json, callback, reviver); }, /** * Populates canvas with data from the specified JSON. * JSON format must conform to the one of {@link fabric.Canvas#toJSON} * @param {String|Object} json JSON string or object * @param {Function} callback Callback, invoked when json is parsed * and corresponding objects (e.g: {@link fabric.Image}) * are initialized * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. * @return {fabric.Canvas} instance * @chainable * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#deserialization} * @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo} * @example <caption>loadFromJSON</caption> * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas)); * @example <caption>loadFromJSON with reviver</caption> * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) { * // `o` = json object * // `object` = fabric.Object instance * // ... do some stuff ... * }); */ loadFromJSON: function (json, callback, reviver) { if (!json) { return; } // serialize if it wasn't already var serialized = (typeof json === 'string') ? JSON.parse(json) : fabric.util.object.clone(json); var _this = this, renderOnAddRemove = this.renderOnAddRemove; this.renderOnAddRemove = false; this._enlivenObjects(serialized.objects, function (enlivenedObjects) { _this.clear(); _this._setBgOverlay(serialized, function () { enlivenedObjects.forEach(function(obj, index) { // we splice the array just in case some custom classes restored from JSON // will add more object to canvas at canvas init. _this.insertAt(obj, index); }); _this.renderOnAddRemove = renderOnAddRemove; // remove parts i cannot set as options delete serialized.objects; delete serialized.backgroundImage; delete serialized.overlayImage; delete serialized.background; delete serialized.overlay; // this._initOptions does too many things to just // call it. Normally loading an Object from JSON // create the Object instance. Here the Canvas is // already an instance and we are just loading things over it _this._setOptions(serialized); _this.renderAll(); callback && callback(); }); }, reviver); return this; }, /** * @private * @param {Object} serialized Object with background and overlay information * @param {Function} callback Invoked after all background and overlay images/patterns loaded */ _setBgOverlay: function(serialized, callback) { var loaded = { backgroundColor: false, overlayColor: false, backgroundImage: false, overlayImage: false }; if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) { callback && callback(); return; } var cbIfLoaded = function () { if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) { callback && callback(); } }; this.__setBgOverlay('backgroundImage', serialized.backgroundImage, loaded, cbIfLoaded); this.__setBgOverlay('overlayImage', serialized.overlayImage, loaded, cbIfLoaded); this.__setBgOverlay('backgroundColor', serialized.background, loaded, cbIfLoaded); this.__setBgOverlay('overlayColor', serialized.overlay, loaded, cbIfLoaded); }, /** * @private * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor) * @param {(Object|String)} value Value to set * @param {Object} loaded Set loaded property to true if property is set * @param {Object} callback Callback function to invoke after property is set */ __setBgOverlay: function(property, value, loaded, callback) { var _this = this; if (!value) { loaded[property] = true; callback && callback(); return; } if (property === 'backgroundImage' || property === 'overlayImage') { fabric.util.enlivenObjects([value], function(enlivedObject){ _this[property] = enlivedObject[0]; loaded[property] = true; callback && callback(); }); } else { this['set' + fabric.util.string.capitalize(property, true)](value, function() { loaded[property] = true; callback && callback(); }); } }, /** * @private * @param {Array} objects * @param {Function} callback * @param {Function} [reviver] */ _enlivenObjects: function (objects, callback, reviver) { if (!objects || objects.length === 0) { callback && callback([]); return; } fabric.util.enlivenObjects(objects, function(enlivenedObjects) { callback && callback(enlivenedObjects); }, null, reviver); }, /** * @private * @param {String} format * @param {Function} callback */ _toDataURL: function (format, callback) { this.clone(function (clone) { callback(clone.toDataURL(format)); }); }, /** * @private * @param {String} format * @param {Number} multiplier * @param {Function} callback */ _toDataURLWithMultiplier: function (format, multiplier, callback) { this.clone(function (clone) { callback(clone.toDataURLWithMultiplier(format, multiplier)); }); }, /** * Clones canvas instance * @param {Object} [callback] Receives cloned instance as a first argument * @param {Array} [properties] Array of properties to include in the cloned canvas and children */ clone: function (callback, properties) { var data = JSON.stringify(this.toJSON(properties)); this.cloneWithoutData(function(clone) { clone.loadFromJSON(data, function() { callback && callback(clone); }); }); }, /** * Clones canvas instance without cloning existing data. * This essentially copies canvas dimensions, clipping properties, etc. * but leaves data empty (so that you can populate it with your own) * @param {Object} [callback] Receives cloned instance as a first argument */ cloneWithoutData: function(callback) { var el = fabric.util.createCanvasElement(); el.width = this.width; el.height = this.height; var clone = new fabric.Canvas(el); clone.clipTo = this.clipTo; if (this.backgroundImage) { clone.setBackgroundImage(this.backgroundImage.src, function() { clone.renderAll(); callback && callback(clone); }); clone.backgroundImageOpacity = this.backgroundImageOpacity; clone.backgroundImageStretch = this.backgroundImageStretch; } else { callback && callback(clone); } } }); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, clone = fabric.util.object.clone, toFixed = fabric.util.toFixed, capitalize = fabric.util.string.capitalize, degreesToRadians = fabric.util.degreesToRadians, supportsLineDash = fabric.StaticCanvas.supports('setLineDash'), objectCaching = !fabric.isLikelyNode, ALIASING_LIMIT = 2; if (fabric.Object) { return; } /** * Root object class from which all 2d shape classes inherit from * @class fabric.Object * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#objects} * @see {@link fabric.Object#initialize} for constructor definition * * @fires added * @fires removed * * @fires selected * @fires deselected * @fires modified * @fires modified * @fires moved * @fires scaled * @fires rotated * @fires skewed * * @fires rotating * @fires scaling * @fires moving * @fires skewing * * @fires mousedown * @fires mouseup * @fires mouseover * @fires mouseout * @fires mousewheel * @fires mousedblclick * * @fires dragover * @fires dragenter * @fires dragleave * @fires drop */ fabric.Object = fabric.util.createClass(fabric.CommonMethods, /** @lends fabric.Object.prototype */ { /** * Type of an object (rect, circle, path, etc.). * Note that this property is meant to be read-only and not meant to be modified. * If you modify, certain parts of Fabric (such as JSON loading) won't work correctly. * @type String * @default */ type: 'object', /** * Horizontal origin of transformation of an object (one of "left", "right", "center") * See http://jsfiddle.net/1ow02gea/244/ on how originX/originY affect objects in groups * @type String * @default */ originX: 'left', /** * Vertical origin of transformation of an object (one of "top", "bottom", "center") * See http://jsfiddle.net/1ow02gea/244/ on how originX/originY affect objects in groups * @type String * @default */ originY: 'top', /** * Top position of an object. Note that by default it's relative to object top. You can change this by setting originY={top/center/bottom} * @type Number * @default */ top: 0, /** * Left position of an object. Note that by default it's relative to object left. You can change this by setting originX={left/center/right} * @type Number * @default */ left: 0, /** * Object width * @type Number * @default */ width: 0, /** * Object height * @type Number * @default */ height: 0, /** * Object scale factor (horizontal) * @type Number * @default */ scaleX: 1, /** * Object scale factor (vertical) * @type Number * @default */ scaleY: 1, /** * When true, an object is rendered as flipped horizontally * @type Boolean * @default */ flipX: false, /** * When true, an object is rendered as flipped vertically * @type Boolean * @default */ flipY: false, /** * Opacity of an object * @type Number * @default */ opacity: 1, /** * Angle of rotation of an object (in degrees) * @type Number * @default */ angle: 0, /** * Angle of skew on x axes of an object (in degrees) * @type Number * @default */ skewX: 0, /** * Angle of skew on y axes of an object (in degrees) * @type Number * @default */ skewY: 0, /** * Size of object's controlling corners (in pixels) * @type Number * @default */ cornerSize: 13, /** * When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill) * @type Boolean * @default */ transparentCorners: true, /** * Default cursor value used when hovering over this object on canvas * @type String * @default */ hoverCursor: null, /** * Default cursor value used when moving this object on canvas * @type String * @default */ moveCursor: null, /** * Padding between object and its controlling borders (in pixels) * @type Number * @default */ padding: 0, /** * Color of controlling borders of an object (when it's active) * @type String * @default */ borderColor: 'rgba(102,153,255,0.75)', /** * Array specifying dash pattern of an object's borders (hasBorder must be true) * @since 1.6.2 * @type Array */ borderDashArray: null, /** * Color of controlling corners of an object (when it's active) * @type String * @default */ cornerColor: 'rgba(102,153,255,0.5)', /** * Color of controlling corners of an object (when it's active and transparentCorners false) * @since 1.6.2 * @type String * @default */ cornerStrokeColor: null, /** * Specify style of control, 'rect' or 'circle' * @since 1.6.2 * @type String */ cornerStyle: 'rect', /** * Array specifying dash pattern of an object's control (hasBorder must be true) * @since 1.6.2 * @type Array */ cornerDashArray: null, /** * When true, this object will use center point as the origin of transformation * when being scaled via the controls. * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean). * @since 1.3.4 * @type Boolean * @default */ centeredScaling: false, /** * When true, this object will use center point as the origin of transformation * when being rotated via the controls. * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean). * @since 1.3.4 * @type Boolean * @default */ centeredRotation: true, /** * Color of object's fill * takes css colors https://www.w3.org/TR/css-color-3/ * @type String * @default */ fill: 'rgb(0,0,0)', /** * Fill rule used to fill an object * accepted values are nonzero, evenodd * <b>Backwards incompatibility note:</b> This property was used for setting globalCompositeOperation until v1.4.12 (use `fabric.Object#globalCompositeOperation` instead) * @type String * @default */ fillRule: 'nonzero', /** * Composite rule used for canvas globalCompositeOperation * @type String * @default */ globalCompositeOperation: 'source-over', /** * Background color of an object. * takes css colors https://www.w3.org/TR/css-color-3/ * @type String * @default */ backgroundColor: '', /** * Selection Background color of an object. colored layer behind the object when it is active. * does not mix good with globalCompositeOperation methods. * @type String * @default */ selectionBackgroundColor: '', /** * When defined, an object is rendered via stroke and this property specifies its color * takes css colors https://www.w3.org/TR/css-color-3/ * @type String * @default */ stroke: null, /** * Width of a stroke used to render this object * @type Number * @default */ strokeWidth: 1, /** * Array specifying dash pattern of an object's stroke (stroke must be defined) * @type Array */ strokeDashArray: null, /** * Line offset of an object's stroke * @type Number * @default */ strokeDashOffset: 0, /** * Line endings style of an object's stroke (one of "butt", "round", "square") * @type String * @default */ strokeLineCap: 'butt', /** * Corner style of an object's stroke (one of "bevil", "round", "miter") * @type String * @default */ strokeLineJoin: 'miter', /** * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke * @type Number * @default */ strokeMiterLimit: 4, /** * Shadow object representing shadow of this shape * @type fabric.Shadow * @default */ shadow: null, /** * Opacity of object's controlling borders when object is active and moving * @type Number * @default */ borderOpacityWhenMoving: 0.4, /** * Scale factor of object's controlling borders * @type Number * @default */ borderScaleFactor: 1, /** * Transform matrix (similar to SVG's transform matrix) * @type Array */ transformMatrix: null, /** * Minimum allowed scale value of an object * @type Number * @default */ minScaleLimit: 0, /** * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection). * But events still fire on it. * @type Boolean * @default */ selectable: true, /** * When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4 * @type Boolean * @default */ evented: true, /** * When set to `false`, an object is not rendered on canvas * @type Boolean * @default */ visible: true, /** * When set to `false`, object's controls are not displayed and can not be used to manipulate object * @type Boolean * @default */ hasControls: true, /** * When set to `false`, object's controlling borders are not rendered * @type Boolean * @default */ hasBorders: true, /** * When set to `false`, object's controlling rotating point will not be visible or selectable * @type Boolean * @default */ hasRotatingPoint: true, /** * Offset for object's controlling rotating point (when enabled via `hasRotatingPoint`) * @type Number * @default */ rotatingPointOffset: 40, /** * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box * @type Boolean * @default */ perPixelTargetFind: false, /** * When `false`, default object's values are not included in its serialization * @type Boolean * @default */ includeDefaultValues: true, /** * Function that determines clipping of an object (context is passed as a first argument). * If you are using code minification, ctx argument can be minified/manglied you should use * as a workaround `var ctx = arguments[0];` in the function; * Note that context origin is at the object's center point (not left/top corner) * @deprecated since 2.0.0 * @type Function */ clipTo: null, /** * When `true`, object horizontal movement is locked * @type Boolean * @default */ lockMovementX: false, /** * When `true`, object vertical movement is locked * @type Boolean * @default */ lockMovementY: false, /** * When `true`, object rotation is locked * @type Boolean * @default */ lockRotation: false, /** * When `true`, object horizontal scaling is locked * @type Boolean * @default */ lockScalingX: false, /** * When `true`, object vertical scaling is locked * @type Boolean * @default */ lockScalingY: false, /** * When `true`, object non-uniform scaling is locked * @type Boolean * @default */ lockUniScaling: false, /** * When `true`, object horizontal skewing is locked * @type Boolean * @default */ lockSkewingX: false, /** * When `true`, object vertical skewing is locked * @type Boolean * @default */ lockSkewingY: false, /** * When `true`, object cannot be flipped by scaling into negative values * @type Boolean * @default */ lockScalingFlip: false, /** * When `true`, object is not exported in OBJECT/JSON * since 1.6.3 * @type Boolean * @default */ excludeFromExport: false, /** * When `true`, object is cached on an additional canvas. * default to true * since 1.7.0 * @type Boolean * @default true */ objectCaching: objectCaching, /** * When `true`, object properties are checked for cache invalidation. In some particular * situation you may want this to be disabled ( spray brush, very big, groups) * or if your application does not allow you to modify properties for groups child you want * to disable it for groups. * default to false * since 1.7.0 * @type Boolean * @default false */ statefullCache: false, /** * When `true`, cache does not get updated during scaling. The picture will get blocky if scaled * too much and will be redrawn with correct details at the end of scaling. * this setting is performance and application dependant. * default to true * since 1.7.0 * @type Boolean * @default true */ noScaleCache: true, /** * When `false`, the stoke width will scale with the object. * When `true`, the stroke will always match the exact pixel size entered for stroke width. * default to false * @since 2.6.0 * @type Boolean * @default false * @type Boolean * @default false */ strokeUniform: false, /** * When set to `true`, object's cache will be rerendered next render call. * since 1.7.0 * @type Boolean * @default true */ dirty: true, /** * keeps the value of the last hovered corner during mouse move. * 0 is no corner, or 'mt', 'ml', 'mtr' etc.. * It should be private, but there is no harm in using it as * a read-only property. * @type number|string|any * @default 0 */ __corner: 0, /** * Determines if the fill or the stroke is drawn first (one of "fill" or "stroke") * @type String * @default */ paintFirst: 'fill', /** * List of properties to consider when checking if state * of an object is changed (fabric.Object#hasStateChanged) * as well as for history (undo/redo) purposes * @type Array */ stateProperties: ( 'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' + 'stroke strokeWidth strokeDashArray strokeLineCap strokeDashOffset strokeLineJoin strokeMiterLimit ' + 'angle opacity fill globalCompositeOperation shadow clipTo visible backgroundColor ' + 'skewX skewY fillRule paintFirst clipPath strokeUniform' ).split(' '), /** * List of properties to consider when checking if cache needs refresh * Those properties are checked by statefullCache ON ( or lazy mode if we want ) or from single * calls to Object.set(key, value). If the key is in this list, the object is marked as dirty * and refreshed at the next render * @type Array */ cacheProperties: ( 'fill stroke strokeWidth strokeDashArray width height paintFirst strokeUniform' + ' strokeLineCap strokeDashOffset strokeLineJoin strokeMiterLimit backgroundColor clipPath' ).split(' '), /** * a fabricObject that, without stroke define a clipping area with their shape. filled in black * the clipPath object gets used when the object has rendered, and the context is placed in the center * of the object cacheCanvas. * If you want 0,0 of a clipPath to align with an object center, use clipPath.originX/Y to 'center' * @type fabric.Object */ clipPath: undefined, /** * Meaningful ONLY when the object is used as clipPath. * if true, the clipPath will make the object clip to the outside of the clipPath * since 2.4.0 * @type boolean * @default false */ inverted: false, /** * Meaningful ONLY when the object is used as clipPath. * if true, the clipPath will have its top and left relative to canvas, and will * not be influenced by the object transform. This will make the clipPath relative * to the canvas, but clipping just a particular object. * WARNING this is beta, this feature may change or be renamed. * since 2.4.0 * @type boolean * @default false */ absolutePositioned: false, /** * Constructor * @param {Object} [options] Options object */ initialize: function(options) { if (options) { this.setOptions(options); } }, /** * Create a the canvas used to keep the cached copy of the object * @private */ _createCacheCanvas: function() { this._cacheProperties = {}; this._cacheCanvas = fabric.util.createCanvasElement(); this._cacheContext = this._cacheCanvas.getContext('2d'); this._updateCacheCanvas(); // if canvas gets created, is empty, so dirty. this.dirty = true; }, /** * Limit the cache dimensions so that X * Y do not cross fabric.perfLimitSizeTotal * and each side do not cross fabric.cacheSideLimit * those numbers are configurable so that you can get as much detail as you want * making bargain with performances. * @param {Object} dims * @param {Object} dims.width width of canvas * @param {Object} dims.height height of canvas * @param {Object} dims.zoomX zoomX zoom value to unscale the canvas before drawing cache * @param {Object} dims.zoomY zoomY zoom value to unscale the canvas before drawing cache * @return {Object}.width width of canvas * @return {Object}.height height of canvas * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache */ _limitCacheSize: function(dims) { var perfLimitSizeTotal = fabric.perfLimitSizeTotal, width = dims.width, height = dims.height, max = fabric.maxCacheSideLimit, min = fabric.minCacheSideLimit; if (width <= max && height <= max && width * height <= perfLimitSizeTotal) { if (width < min) { dims.width = min; } if (height < min) { dims.height = min; } return dims; } var ar = width / height, limitedDims = fabric.util.limitDimsByArea(ar, perfLimitSizeTotal), capValue = fabric.util.capValue, x = capValue(min, limitedDims.x, max), y = capValue(min, limitedDims.y, max); if (width > x) { dims.zoomX /= width / x; dims.width = x; dims.capped = true; } if (height > y) { dims.zoomY /= height / y; dims.height = y; dims.capped = true; } return dims; }, /** * Return the dimension and the zoom level needed to create a cache canvas * big enough to host the object to be cached. * @private * @return {Object}.x width of object to be cached * @return {Object}.y height of object to be cached * @return {Object}.width width of canvas * @return {Object}.height height of canvas * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache */ _getCacheCanvasDimensions: function() { var objectScale = this.getTotalObjectScaling(), dim = this._getNonTransformedDimensions(), zoomX = objectScale.scaleX, zoomY = objectScale.scaleY, width = dim.x * zoomX, height = dim.y * zoomY; return { // for sure this ALIASING_LIMIT is slightly crating problem // in situation in wich the cache canvas gets an upper limit width: width + ALIASING_LIMIT, height: height + ALIASING_LIMIT, zoomX: zoomX, zoomY: zoomY, x: dim.x, y: dim.y }; }, /** * Update width and height of the canvas for cache * returns true or false if canvas needed resize. * @private * @return {Boolean} true if the canvas has been resized */ _updateCacheCanvas: function() { var targetCanvas = this.canvas; if (this.noScaleCache && targetCanvas && targetCanvas._currentTransform) { var target = targetCanvas._currentTransform.target, action = targetCanvas._currentTransform.action; if (this === target && action.slice && action.slice(0, 5) === 'scale') { return false; } } var canvas = this._cacheCanvas, dims = this._limitCacheSize(this._getCacheCanvasDimensions()), minCacheSize = fabric.minCacheSideLimit, width = dims.width, height = dims.height, drawingWidth, drawingHeight, zoomX = dims.zoomX, zoomY = dims.zoomY, dimensionsChanged = width !== this.cacheWidth || height !== this.cacheHeight, zoomChanged = this.zoomX !== zoomX || this.zoomY !== zoomY, shouldRedraw = dimensionsChanged || zoomChanged, additionalWidth = 0, additionalHeight = 0, shouldResizeCanvas = false; if (dimensionsChanged) { var canvasWidth = this._cacheCanvas.width, canvasHeight = this._cacheCanvas.height, sizeGrowing = width > canvasWidth || height > canvasHeight, sizeShrinking = (width < canvasWidth * 0.9 || height < canvasHeight * 0.9) && canvasWidth > minCacheSize && canvasHeight > minCacheSize; shouldResizeCanvas = sizeGrowing || sizeShrinking; if (sizeGrowing && !dims.capped && (width > minCacheSize || height > minCacheSize)) { additionalWidth = width * 0.1; additionalHeight = height * 0.1; } } if (shouldRedraw) { if (shouldResizeCanvas) { canvas.width = Math.ceil(width + additionalWidth); canvas.height = Math.ceil(height + additionalHeight); } else { this._cacheContext.setTransform(1, 0, 0, 1, 0, 0); this._cacheContext.clearRect(0, 0, canvas.width, canvas.height); } drawingWidth = dims.x * zoomX / 2; drawingHeight = dims.y * zoomY / 2; this.cacheTranslationX = Math.round(canvas.width / 2 - drawingWidth) + drawingWidth; this.cacheTranslationY = Math.round(canvas.height / 2 - drawingHeight) + drawingHeight; this.cacheWidth = width; this.cacheHeight = height; this._cacheContext.translate(this.cacheTranslationX, this.cacheTranslationY); this._cacheContext.scale(zoomX, zoomY); this.zoomX = zoomX; this.zoomY = zoomY; return true; } return false; }, /** * Sets object's properties from options * @param {Object} [options] Options object */ setOptions: function(options) { this._setOptions(options); this._initGradient(options.fill, 'fill'); this._initGradient(options.stroke, 'stroke'); this._initClipping(options); this._initPattern(options.fill, 'fill'); this._initPattern(options.stroke, 'stroke'); }, /** * Transforms context when rendering an object * @param {CanvasRenderingContext2D} ctx Context */ transform: function(ctx) { var m; if (this.group && !this.group._transformDone) { m = this.calcTransformMatrix(); } else { m = this.calcOwnMatrix(); } ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); }, /** * Returns an object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} Object representation of an instance */ toObject: function(propertiesToInclude) { var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, object = { type: this.type, version: fabric.version, originX: this.originX, originY: this.originY, left: toFixed(this.left, NUM_FRACTION_DIGITS), top: toFixed(this.top, NUM_FRACTION_DIGITS), width: toFixed(this.width, NUM_FRACTION_DIGITS), height: toFixed(this.height, NUM_FRACTION_DIGITS), fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), strokeDashArray: this.strokeDashArray ? this.strokeDashArray.concat() : this.strokeDashArray, strokeLineCap: this.strokeLineCap, strokeDashOffset: this.strokeDashOffset, strokeLineJoin: this.strokeLineJoin, strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), angle: toFixed(this.angle, NUM_FRACTION_DIGITS), flipX: this.flipX, flipY: this.flipY, opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, visible: this.visible, clipTo: this.clipTo && String(this.clipTo), backgroundColor: this.backgroundColor, fillRule: this.fillRule, paintFirst: this.paintFirst, globalCompositeOperation: this.globalCompositeOperation, transformMatrix: this.transformMatrix ? this.transformMatrix.concat() : null, skewX: toFixed(this.skewX, NUM_FRACTION_DIGITS), skewY: toFixed(this.skewY, NUM_FRACTION_DIGITS), }; if (this.clipPath) { object.clipPath = this.clipPath.toObject(propertiesToInclude); object.clipPath.inverted = this.clipPath.inverted; object.clipPath.absolutePositioned = this.clipPath.absolutePositioned; } fabric.util.populateWithProperties(this, object, propertiesToInclude); if (!this.includeDefaultValues) { object = this._removeDefaultValues(object); } return object; }, /** * Returns (dataless) object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} Object representation of an instance */ toDatalessObject: function(propertiesToInclude) { // will be overwritten by subclasses return this.toObject(propertiesToInclude); }, /** * @private * @param {Object} object */ _removeDefaultValues: function(object) { var prototype = fabric.util.getKlass(object.type).prototype, stateProperties = prototype.stateProperties; stateProperties.forEach(function(prop) { if (object[prop] === prototype[prop]) { delete object[prop]; } var isArray = Object.prototype.toString.call(object[prop]) === '[object Array]' && Object.prototype.toString.call(prototype[prop]) === '[object Array]'; // basically a check for [] === [] if (isArray && object[prop].length === 0 && prototype[prop].length === 0) { delete object[prop]; } }); return object; }, /** * Returns a string representation of an instance * @return {String} */ toString: function() { return '#<fabric.' + capitalize(this.type) + '>'; }, /** * Return the object scale factor counting also the group scaling * @return {Object} object with scaleX and scaleY properties */ getObjectScaling: function() { var scaleX = this.scaleX, scaleY = this.scaleY; if (this.group) { var scaling = this.group.getObjectScaling(); scaleX *= scaling.scaleX; scaleY *= scaling.scaleY; } return { scaleX: scaleX, scaleY: scaleY }; }, /** * Return the object scale factor counting also the group scaling, zoom and retina * @return {Object} object with scaleX and scaleY properties */ getTotalObjectScaling: function() { var scale = this.getObjectScaling(), scaleX = scale.scaleX, scaleY = scale.scaleY; if (this.canvas) { var zoom = this.canvas.getZoom(); var retina = this.canvas.getRetinaScaling(); scaleX *= zoom * retina; scaleY *= zoom * retina; } return { scaleX: scaleX, scaleY: scaleY }; }, /** * Return the object opacity counting also the group property * @return {Number} */ getObjectOpacity: function() { var opacity = this.opacity; if (this.group) { opacity *= this.group.getObjectOpacity(); } return opacity; }, /** * @private * @param {String} key * @param {*} value * @return {fabric.Object} thisArg */ _set: function(key, value) { var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY'), isChanged = this[key] !== value, groupNeedsUpdate = false; if (shouldConstrainValue) { value = this._constrainScale(value); } if (key === 'scaleX' && value < 0) { this.flipX = !this.flipX; value *= -1; } else if (key === 'scaleY' && value < 0) { this.flipY = !this.flipY; value *= -1; } else if (key === 'shadow' && value && !(value instanceof fabric.Shadow)) { value = new fabric.Shadow(value); } else if (key === 'dirty' && this.group) { this.group.set('dirty', value); } this[key] = value; if (isChanged) { groupNeedsUpdate = this.group && this.group.isOnACache(); if (this.cacheProperties.indexOf(key) > -1) { this.dirty = true; groupNeedsUpdate && this.group.set('dirty', true); } else if (groupNeedsUpdate && this.stateProperties.indexOf(key) > -1) { this.group.set('dirty', true); } } return this; }, /** * This callback function is called by the parent group of an object every * time a non-delegated property changes on the group. It is passed the key * and value as parameters. Not adding in this function's signature to avoid * Travis build error about unused variables. */ setOnGroup: function() { // implemented by sub-classes, as needed. }, /** * Retrieves viewportTransform from Object's canvas if possible * @method getViewportTransform * @memberOf fabric.Object.prototype * @return {Array} */ getViewportTransform: function() { if (this.canvas && this.canvas.viewportTransform) { return this.canvas.viewportTransform; } return fabric.iMatrix.concat(); }, /* * @private * return if the object would be visible in rendering * @memberOf fabric.Object.prototype * @return {Boolean} */ isNotVisible: function() { return this.opacity === 0 || (this.width === 0 && this.height === 0 && this.strokeWidth === 0) || !this.visible; }, /** * Renders an object on a specified context * @param {CanvasRenderingContext2D} ctx Context to render on */ render: function(ctx) { // do not render if width/height are zeros or object is not visible if (this.isNotVisible()) { return; } if (this.canvas && this.canvas.skipOffscreen && !this.group && !this.isOnScreen()) { return; } ctx.save(); this._setupCompositeOperation(ctx); this.drawSelectionBackground(ctx); this.transform(ctx); this._setOpacity(ctx); this._setShadow(ctx, this); if (this.transformMatrix) { ctx.transform.apply(ctx, this.transformMatrix); } this.clipTo && fabric.util.clipContext(this, ctx); if (this.shouldCache()) { this.renderCache(); this.drawCacheOnCanvas(ctx); } else { this._removeCacheCanvas(); this.dirty = false; this.drawObject(ctx); if (this.objectCaching && this.statefullCache) { this.saveState({ propertySet: 'cacheProperties' }); } } this.clipTo && ctx.restore(); ctx.restore(); }, renderCache: function(options) { options = options || {}; if (!this._cacheCanvas) { this._createCacheCanvas(); } if (this.isCacheDirty()) { this.statefullCache && this.saveState({ propertySet: 'cacheProperties' }); this.drawObject(this._cacheContext, options.forClipping); this.dirty = false; } }, /** * Remove cacheCanvas and its dimensions from the objects */ _removeCacheCanvas: function() { this._cacheCanvas = null; this.cacheWidth = 0; this.cacheHeight = 0; }, /** * When set to `true`, force the object to have its own cache, even if it is inside a group * it may be needed when your object behave in a particular way on the cache and always needs * its own isolated canvas to render correctly. * Created to be overridden * since 1.7.12 * @returns false */ needsItsOwnCache: function() { if (this.paintFirst === 'stroke' && typeof this.shadow === 'object') { return true; } if (this.clipPath) { return true; } return false; }, /** * Decide if the object should cache or not. Create its own cache level * objectCaching is a global flag, wins over everything * needsItsOwnCache should be used when the object drawing method requires * a cache step. None of the fabric classes requires it. * Generally you do not cache objects in groups because the group outside is cached. * @return {Boolean} */ shouldCache: function() { this.ownCaching = this.objectCaching && (!this.group || this.needsItsOwnCache() || !this.group.isOnACache()); return this.ownCaching; }, /** * Check if this object or a child object will cast a shadow * used by Group.shouldCache to know if child has a shadow recursively * @return {Boolean} */ willDrawShadow: function() { return !!this.shadow && (this.shadow.offsetX !== 0 || this.shadow.offsetY !== 0); }, /** * Execute the drawing operation for an object clipPath * @param {CanvasRenderingContext2D} ctx Context to render on */ drawClipPathOnCache: function(ctx) { var path = this.clipPath; ctx.save(); // DEBUG: uncomment this line, comment the following // ctx.globalAlpha = 0.4 if (path.inverted) { ctx.globalCompositeOperation = 'destination-out'; } else { ctx.globalCompositeOperation = 'destination-in'; } //ctx.scale(1 / 2, 1 / 2); if (path.absolutePositioned) { var m = fabric.util.invertTransform(this.calcTransformMatrix()); ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } path.transform(ctx); ctx.scale(1 / path.zoomX, 1 / path.zoomY); ctx.drawImage(path._cacheCanvas, -path.cacheTranslationX, -path.cacheTranslationY); ctx.restore(); }, /** * Execute the drawing operation for an object on a specified context * @param {CanvasRenderingContext2D} ctx Context to render on */ drawObject: function(ctx, forClipping) { var originalFill = this.fill, originalStroke = this.stroke; if (forClipping) { this.fill = 'black'; this.stroke = ''; this._setClippingProperties(ctx); } else { this._renderBackground(ctx); this._setStrokeStyles(ctx, this); this._setFillStyles(ctx, this); } this._render(ctx); this._drawClipPath(ctx); this.fill = originalFill; this.stroke = originalStroke; }, _drawClipPath: function(ctx) { var path = this.clipPath; if (!path) { return; } // needed to setup a couple of variables // path canvas gets overridden with this one. // TODO find a better solution? path.canvas = this.canvas; path.shouldCache(); path._transformDone = true; path.renderCache({ forClipping: true }); this.drawClipPathOnCache(ctx); }, /** * Paint the cached copy of the object on the target context. * @param {CanvasRenderingContext2D} ctx Context to render on */ drawCacheOnCanvas: function(ctx) { ctx.scale(1 / this.zoomX, 1 / this.zoomY); ctx.drawImage(this._cacheCanvas, -this.cacheTranslationX, -this.cacheTranslationY); }, /** * Check if cache is dirty * @param {Boolean} skipCanvas skip canvas checks because this object is painted * on parent canvas. */ isCacheDirty: function(skipCanvas) { if (this.isNotVisible()) { return false; } if (this._cacheCanvas && !skipCanvas && this._updateCacheCanvas()) { // in this case the context is already cleared. return true; } else { if (this.dirty || (this.clipPath && this.clipPath.absolutePositioned) || (this.statefullCache && this.hasStateChanged('cacheProperties')) ) { if (this._cacheCanvas && !skipCanvas) { var width = this.cacheWidth / this.zoomX; var height = this.cacheHeight / this.zoomY; this._cacheContext.clearRect(-width / 2, -height / 2, width, height); } return true; } } return false; }, /** * Draws a background for the object big as its untransformed dimensions * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderBackground: function(ctx) { if (!this.backgroundColor) { return; } var dim = this._getNonTransformedDimensions(); ctx.fillStyle = this.backgroundColor; ctx.fillRect( -dim.x / 2, -dim.y / 2, dim.x, dim.y ); // if there is background color no other shadows // should be casted this._removeShadow(ctx); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _setOpacity: function(ctx) { if (this.group && !this.group._transformDone) { ctx.globalAlpha = this.getObjectOpacity(); } else { ctx.globalAlpha *= this.opacity; } }, _setStrokeStyles: function(ctx, decl) { if (decl.stroke) { ctx.lineWidth = decl.strokeWidth; ctx.lineCap = decl.strokeLineCap; ctx.lineDashOffset = decl.strokeDashOffset; ctx.lineJoin = decl.strokeLineJoin; ctx.miterLimit = decl.strokeMiterLimit; ctx.strokeStyle = decl.stroke.toLive ? decl.stroke.toLive(ctx, this) : decl.stroke; } }, _setFillStyles: function(ctx, decl) { if (decl.fill) { ctx.fillStyle = decl.fill.toLive ? decl.fill.toLive(ctx, this) : decl.fill; } }, _setClippingProperties: function(ctx) { ctx.globalAlpha = 1; ctx.strokeStyle = 'transparent'; ctx.fillStyle = '#000000'; }, /** * @private * Sets line dash * @param {CanvasRenderingContext2D} ctx Context to set the dash line on * @param {Array} dashArray array representing dashes * @param {Function} alternative function to call if browser does not support lineDash */ _setLineDash: function(ctx, dashArray, alternative) { if (!dashArray) { return; } // Spec requires the concatenation of two copies the dash list when the number of elements is odd if (1 & dashArray.length) { dashArray.push.apply(dashArray, dashArray); } if (supportsLineDash) { ctx.setLineDash(dashArray); } else { alternative && alternative(ctx); } if (this.strokeUniform) { ctx.setLineDash(ctx.getLineDash().map(function(value) { return value * ctx.lineWidth; })); } }, /** * Renders controls and borders for the object * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Object} [styleOverride] properties to override the object style */ _renderControls: function(ctx, styleOverride) { var vpt = this.getViewportTransform(), matrix = this.calcTransformMatrix(), options, drawBorders, drawControls; styleOverride = styleOverride || { }; drawBorders = typeof styleOverride.hasBorders !== 'undefined' ? styleOverride.hasBorders : this.hasBorders; drawControls = typeof styleOverride.hasControls !== 'undefined' ? styleOverride.hasControls : this.hasControls; matrix = fabric.util.multiplyTransformMatrices(vpt, matrix); options = fabric.util.qrDecompose(matrix); ctx.save(); ctx.translate(options.translateX, options.translateY); ctx.lineWidth = 1 * this.borderScaleFactor; if (!this.group) { ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; } if (styleOverride.forActiveSelection) { ctx.rotate(degreesToRadians(options.angle)); drawBorders && this.drawBordersInGroup(ctx, options, styleOverride); } else { ctx.rotate(degreesToRadians(this.angle)); drawBorders && this.drawBorders(ctx, styleOverride); } drawControls && this.drawControls(ctx, styleOverride); ctx.restore(); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _setShadow: function(ctx) { if (!this.shadow) { return; } var shadow = this.shadow, canvas = this.canvas, multX = (canvas && canvas.viewportTransform[0]) || 1, multY = (canvas && canvas.viewportTransform[3]) || 1, scaling = this.getObjectScaling(); if (canvas && canvas._isRetinaScaling()) { multX *= fabric.devicePixelRatio; multY *= fabric.devicePixelRatio; } ctx.shadowColor = shadow.color; ctx.shadowBlur = shadow.blur * fabric.browserShadowBlurConstant * (multX + multY) * (scaling.scaleX + scaling.scaleY) / 4; ctx.shadowOffsetX = shadow.offsetX * multX * scaling.scaleX; ctx.shadowOffsetY = shadow.offsetY * multY * scaling.scaleY; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _removeShadow: function(ctx) { if (!this.shadow) { return; } ctx.shadowColor = ''; ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Object} filler fabric.Pattern or fabric.Gradient * @return {Object} offset.offsetX offset for text rendering * @return {Object} offset.offsetY offset for text rendering */ _applyPatternGradientTransform: function(ctx, filler) { if (!filler || !filler.toLive) { return { offsetX: 0, offsetY: 0 }; } var t = filler.gradientTransform || filler.patternTransform; var offsetX = -this.width / 2 + filler.offsetX || 0, offsetY = -this.height / 2 + filler.offsetY || 0; ctx.translate(offsetX, offsetY); if (t) { ctx.transform(t[0], t[1], t[2], t[3], t[4], t[5]); } return { offsetX: offsetX, offsetY: offsetY }; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderPaintInOrder: function(ctx) { if (this.paintFirst === 'stroke') { this._renderStroke(ctx); this._renderFill(ctx); } else { this._renderFill(ctx); this._renderStroke(ctx); } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderFill: function(ctx) { if (!this.fill) { return; } ctx.save(); this._applyPatternGradientTransform(ctx, this.fill); if (this.fillRule === 'evenodd') { ctx.fill('evenodd'); } else { ctx.fill(); } ctx.restore(); }, _renderStroke: function(ctx) { if (!this.stroke || this.strokeWidth === 0) { return; } if (this.shadow && !this.shadow.affectStroke) { this._removeShadow(ctx); } ctx.save(); if (this.strokeUniform) { ctx.scale(1 / this.scaleX, 1 / this.scaleY); } this._setLineDash(ctx, this.strokeDashArray, this._renderDashedStroke); this._applyPatternGradientTransform(ctx, this.stroke); ctx.stroke(); ctx.restore(); }, /** * This function is an helper for svg import. it returns the center of the object in the svg * untransformed coordinates * @private * @return {Object} center point from element coordinates */ _findCenterFromElement: function() { return { x: this.left + this.width / 2, y: this.top + this.height / 2 }; }, /** * This function is an helper for svg import. it decompose the transformMatrix * and assign properties to object. * untransformed coordinates * @private * @chainable */ _assignTransformMatrixProps: function() { if (this.transformMatrix) { var options = fabric.util.qrDecompose(this.transformMatrix); this.flipX = false; this.flipY = false; this.set('scaleX', options.scaleX); this.set('scaleY', options.scaleY); this.angle = options.angle; this.skewX = options.skewX; this.skewY = 0; } }, /** * This function is an helper for svg import. it removes the transform matrix * and set to object properties that fabricjs can handle * @private * @param {Object} preserveAspectRatioOptions * @return {thisArg} */ _removeTransformMatrix: function(preserveAspectRatioOptions) { var center = this._findCenterFromElement(); if (this.transformMatrix) { this._assignTransformMatrixProps(); center = fabric.util.transformPoint(center, this.transformMatrix); } this.transformMatrix = null; if (preserveAspectRatioOptions) { this.scaleX *= preserveAspectRatioOptions.scaleX; this.scaleY *= preserveAspectRatioOptions.scaleY; this.cropX = preserveAspectRatioOptions.cropX; this.cropY = preserveAspectRatioOptions.cropY; center.x += preserveAspectRatioOptions.offsetLeft; center.y += preserveAspectRatioOptions.offsetTop; this.width = preserveAspectRatioOptions.width; this.height = preserveAspectRatioOptions.height; } this.setPositionByOrigin(center, 'center', 'center'); }, /** * Clones an instance, using a callback method will work for every object. * @param {Function} callback Callback is invoked with a clone as a first argument * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output */ clone: function(callback, propertiesToInclude) { var objectForm = this.toObject(propertiesToInclude); if (this.constructor.fromObject) { this.constructor.fromObject(objectForm, callback); } else { fabric.Object._fromObject('Object', objectForm, callback); } }, /** * Creates an instance of fabric.Image out of an object * could make use of both toDataUrl or toCanvasElement. * @param {Function} callback callback, invoked with an instance as a first argument * @param {Object} [options] for clone as image, passed to toDataURL * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. * @param {Number} [options.multiplier=1] Multiplier to scale by * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 1.6.4 * @param {Boolean} [options.withoutTransform] Remove current object transform ( no scale , no angle, no flip, no skew ). Introduced in 2.3.4 * @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2 * @return {fabric.Object} thisArg */ cloneAsImage: function(callback, options) { var canvasEl = this.toCanvasElement(options); if (callback) { callback(new fabric.Image(canvasEl)); } return this; }, /** * Converts an object into a HTMLCanvas element * @param {Object} options Options object * @param {Number} [options.multiplier=1] Multiplier to scale by * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 1.6.4 * @param {Boolean} [options.withoutTransform] Remove current object transform ( no scale , no angle, no flip, no skew ). Introduced in 2.3.4 * @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2 * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format */ toCanvasElement: function(options) { options || (options = { }); var utils = fabric.util, origParams = utils.saveObjectTransform(this), originalShadow = this.shadow, abs = Math.abs, multiplier = (options.multiplier || 1) * (options.enableRetinaScaling ? fabric.devicePixelRatio : 1); if (options.withoutTransform) { utils.resetObjectTransform(this); } if (options.withoutShadow) { this.shadow = null; } var el = fabric.util.createCanvasElement(), // skip canvas zoom and calculate with setCoords now. boundingRect = this.getBoundingRect(true, true), shadow = this.shadow, scaling, shadowOffset = { x: 0, y: 0 }, shadowBlur; if (shadow) { shadowBlur = shadow.blur; scaling = this.getObjectScaling(); shadowOffset.x = 2 * Math.round((abs(shadow.offsetX) + shadowBlur) * abs(scaling.scaleX)); shadowOffset.y = 2 * Math.round((abs(shadow.offsetY) + shadowBlur) * abs(scaling.scaleY)); } el.width = boundingRect.width + shadowOffset.x; el.height = boundingRect.height + shadowOffset.y; el.width += el.width % 2 ? 2 - el.width % 2 : 0; el.height += el.height % 2 ? 2 - el.height % 2 : 0; var canvas = new fabric.StaticCanvas(el, { enableRetinaScaling: false, renderOnAddRemove: false, skipOffscreen: false, }); if (options.format === 'jpeg') { canvas.backgroundColor = '#fff'; } this.setPositionByOrigin(new fabric.Point(canvas.width / 2, canvas.height / 2), 'center', 'center'); var originalCanvas = this.canvas; canvas.add(this); var canvasEl = canvas.toCanvasElement(multiplier || 1, options); this.shadow = originalShadow; this.canvas = originalCanvas; this.set(origParams).setCoords(); // canvas.dispose will call image.dispose that will nullify the elements // since this canvas is a simple element for the process, we remove references // to objects in this way in order to avoid object trashing. canvas._objects = []; canvas.dispose(); canvas = null; return canvasEl; }, /** * Converts an object into a data-url-like string * @param {Object} options Options object * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. * @param {Number} [options.multiplier=1] Multiplier to scale by * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 1.6.4 * @param {Boolean} [options.withoutTransform] Remove current object transform ( no scale , no angle, no flip, no skew ). Introduced in 2.3.4 * @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2 * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format */ toDataURL: function(options) { options || (options = { }); return fabric.util.toDataURL(this.toCanvasElement(options), options.format || 'png', options.quality || 1); }, /** * Returns true if specified type is identical to the type of an instance * @param {String} type Type to check against * @return {Boolean} */ isType: function(type) { return this.type === type; }, /** * Returns complexity of an instance * @return {Number} complexity of this instance (is 1 unless subclassed) */ complexity: function() { return 1; }, /** * Returns a JSON representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} JSON */ toJSON: function(propertiesToInclude) { // delegate, not alias return this.toObject(propertiesToInclude); }, /** * Sets gradient (fill or stroke) of an object * <b>Backwards incompatibility note:</b> This method was named "setGradientFill" until v1.1.0 * @param {String} property Property name 'stroke' or 'fill' * @param {Object} [options] Options object * @param {String} [options.type] Type of gradient 'radial' or 'linear' * @param {Number} [options.x1=0] x-coordinate of start point * @param {Number} [options.y1=0] y-coordinate of start point * @param {Number} [options.x2=0] x-coordinate of end point * @param {Number} [options.y2=0] y-coordinate of end point * @param {Number} [options.r1=0] Radius of start point (only for radial gradients) * @param {Number} [options.r2=0] Radius of end point (only for radial gradients) * @param {Object} [options.colorStops] Color stops object eg. {0: 'ff0000', 1: '000000'} * @param {Object} [options.gradientTransform] transformMatrix for gradient * @return {fabric.Object} thisArg * @chainable * @see {@link http://jsfiddle.net/fabricjs/58y8b/|jsFiddle demo} * @example <caption>Set linear gradient</caption> * object.setGradient('fill', { * type: 'linear', * x1: -object.width / 2, * y1: 0, * x2: object.width / 2, * y2: 0, * colorStops: { * 0: 'red', * 0.5: '#005555', * 1: 'rgba(0,0,255,0.5)' * } * }); * canvas.renderAll(); * @example <caption>Set radial gradient</caption> * object.setGradient('fill', { * type: 'radial', * x1: 0, * y1: 0, * x2: 0, * y2: 0, * r1: object.width / 2, * r2: 10, * colorStops: { * 0: 'red', * 0.5: '#005555', * 1: 'rgba(0,0,255,0.5)' * } * }); * canvas.renderAll(); */ setGradient: function(property, options) { options || (options = { }); var gradient = { colorStops: [] }; gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear'); gradient.coords = { x1: options.x1, y1: options.y1, x2: options.x2, y2: options.y2 }; if (options.r1 || options.r2) { gradient.coords.r1 = options.r1; gradient.coords.r2 = options.r2; } gradient.gradientTransform = options.gradientTransform; fabric.Gradient.prototype.addColorStop.call(gradient, options.colorStops); return this.set(property, fabric.Gradient.forObject(this, gradient)); }, /** * Sets pattern fill of an object * @param {Object} options Options object * @param {(String|HTMLImageElement)} options.source Pattern source * @param {String} [options.repeat=repeat] Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) * @param {Number} [options.offsetX=0] Pattern horizontal offset from object's left/top corner * @param {Number} [options.offsetY=0] Pattern vertical offset from object's left/top corner * @param {Function} [callback] Callback to invoke when image set as a pattern * @return {fabric.Object} thisArg * @chainable * @see {@link http://jsfiddle.net/fabricjs/QT3pa/|jsFiddle demo} * @example <caption>Set pattern</caption> * object.setPatternFill({ * source: 'http://fabricjs.com/assets/escheresque_ste.png', * repeat: 'repeat' * },canvas.renderAll.bind(canvas)); */ setPatternFill: function(options, callback) { return this.set('fill', new fabric.Pattern(options, callback)); }, /** * Sets {@link fabric.Object#shadow|shadow} of an object * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") * @param {String} [options.color=rgb(0,0,0)] Shadow color * @param {Number} [options.blur=0] Shadow blur * @param {Number} [options.offsetX=0] Shadow horizontal offset * @param {Number} [options.offsetY=0] Shadow vertical offset * @return {fabric.Object} thisArg * @chainable * @see {@link http://jsfiddle.net/fabricjs/7gvJG/|jsFiddle demo} * @example <caption>Set shadow with string notation</caption> * object.setShadow('2px 2px 10px rgba(0,0,0,0.2)'); * canvas.renderAll(); * @example <caption>Set shadow with object notation</caption> * object.setShadow({ * color: 'red', * blur: 10, * offsetX: 20, * offsetY: 20 * }); * canvas.renderAll(); */ setShadow: function(options) { return this.set('shadow', options ? new fabric.Shadow(options) : null); }, /** * Sets "color" of an instance (alias of `set('fill', …)`) * @param {String} color Color value * @return {fabric.Object} thisArg * @chainable */ setColor: function(color) { this.set('fill', color); return this; }, /** * Sets "angle" of an instance with centered rotation * @param {Number} angle Angle value (in degrees) * @return {fabric.Object} thisArg * @chainable */ rotate: function(angle) { var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation; if (shouldCenterOrigin) { this._setOriginToCenter(); } this.set('angle', angle); if (shouldCenterOrigin) { this._resetOrigin(); } return this; }, /** * Centers object horizontally on canvas to which it was added last. * You might need to call `setCoords` on an object after centering, to update controls area. * @return {fabric.Object} thisArg * @chainable */ centerH: function () { this.canvas && this.canvas.centerObjectH(this); return this; }, /** * Centers object horizontally on current viewport of canvas to which it was added last. * You might need to call `setCoords` on an object after centering, to update controls area. * @return {fabric.Object} thisArg * @chainable */ viewportCenterH: function () { this.canvas && this.canvas.viewportCenterObjectH(this); return this; }, /** * Centers object vertically on canvas to which it was added last. * You might need to call `setCoords` on an object after centering, to update controls area. * @return {fabric.Object} thisArg * @chainable */ centerV: function () { this.canvas && this.canvas.centerObjectV(this); return this; }, /** * Centers object vertically on current viewport of canvas to which it was added last. * You might need to call `setCoords` on an object after centering, to update controls area. * @return {fabric.Object} thisArg * @chainable */ viewportCenterV: function () { this.canvas && this.canvas.viewportCenterObjectV(this); return this; }, /** * Centers object vertically and horizontally on canvas to which is was added last * You might need to call `setCoords` on an object after centering, to update controls area. * @return {fabric.Object} thisArg * @chainable */ center: function () { this.canvas && this.canvas.centerObject(this); return this; }, /** * Centers object on current viewport of canvas to which it was added last. * You might need to call `setCoords` on an object after centering, to update controls area. * @return {fabric.Object} thisArg * @chainable */ viewportCenter: function () { this.canvas && this.canvas.viewportCenterObject(this); return this; }, /** * Returns coordinates of a pointer relative to an object * @param {Event} e Event to operate upon * @param {Object} [pointer] Pointer to operate upon (instead of event) * @return {Object} Coordinates of a pointer (x, y) */ getLocalPointer: function(e, pointer) { pointer = pointer || this.canvas.getPointer(e); var pClicked = new fabric.Point(pointer.x, pointer.y), objectLeftTop = this._getLeftTopCoords(); if (this.angle) { pClicked = fabric.util.rotatePoint( pClicked, objectLeftTop, degreesToRadians(-this.angle)); } return { x: pClicked.x - objectLeftTop.x, y: pClicked.y - objectLeftTop.y }; }, /** * Sets canvas globalCompositeOperation for specific object * custom composition operation for the particular object can be specified using globalCompositeOperation property * @param {CanvasRenderingContext2D} ctx Rendering canvas context */ _setupCompositeOperation: function (ctx) { if (this.globalCompositeOperation) { ctx.globalCompositeOperation = this.globalCompositeOperation; } } }); fabric.util.createAccessors && fabric.util.createAccessors(fabric.Object); extend(fabric.Object.prototype, fabric.Observable); /** * Defines the number of fraction digits to use when serializing object values. * You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc. * @static * @memberOf fabric.Object * @constant * @type Number */ fabric.Object.NUM_FRACTION_DIGITS = 2; fabric.Object._fromObject = function(className, object, callback, extraParam) { var klass = fabric[className]; object = clone(object, true); fabric.util.enlivenPatterns([object.fill, object.stroke], function(patterns) { if (typeof patterns[0] !== 'undefined') { object.fill = patterns[0]; } if (typeof patterns[1] !== 'undefined') { object.stroke = patterns[1]; } fabric.util.enlivenObjects([object.clipPath], function(enlivedProps) { object.clipPath = enlivedProps[0]; var instance = extraParam ? new klass(object[extraParam], object) : new klass(object); callback && callback(instance); }); }); }; /** * Unique id used internally when creating SVG elements * @static * @memberOf fabric.Object * @type Number */ fabric.Object.__uid = 0; })(typeof exports !== 'undefined' ? exports : this); (function() { var degreesToRadians = fabric.util.degreesToRadians, originXOffset = { left: -0.5, center: 0, right: 0.5 }, originYOffset = { top: -0.5, center: 0, bottom: 0.5 }; fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { /** * Translates the coordinates from a set of origin to another (based on the object's dimensions) * @param {fabric.Point} point The point which corresponds to the originX and originY params * @param {String} fromOriginX Horizontal origin: 'left', 'center' or 'right' * @param {String} fromOriginY Vertical origin: 'top', 'center' or 'bottom' * @param {String} toOriginX Horizontal origin: 'left', 'center' or 'right' * @param {String} toOriginY Vertical origin: 'top', 'center' or 'bottom' * @return {fabric.Point} */ translateToGivenOrigin: function(point, fromOriginX, fromOriginY, toOriginX, toOriginY) { var x = point.x, y = point.y, offsetX, offsetY, dim; if (typeof fromOriginX === 'string') { fromOriginX = originXOffset[fromOriginX]; } else { fromOriginX -= 0.5; } if (typeof toOriginX === 'string') { toOriginX = originXOffset[toOriginX]; } else { toOriginX -= 0.5; } offsetX = toOriginX - fromOriginX; if (typeof fromOriginY === 'string') { fromOriginY = originYOffset[fromOriginY]; } else { fromOriginY -= 0.5; } if (typeof toOriginY === 'string') { toOriginY = originYOffset[toOriginY]; } else { toOriginY -= 0.5; } offsetY = toOriginY - fromOriginY; if (offsetX || offsetY) { dim = this._getTransformedDimensions(); x = point.x + offsetX * dim.x; y = point.y + offsetY * dim.y; } return new fabric.Point(x, y); }, /** * Translates the coordinates from origin to center coordinates (based on the object's dimensions) * @param {fabric.Point} point The point which corresponds to the originX and originY params * @param {String} originX Horizontal origin: 'left', 'center' or 'right' * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' * @return {fabric.Point} */ translateToCenterPoint: function(point, originX, originY) { var p = this.translateToGivenOrigin(point, originX, originY, 'center', 'center'); if (this.angle) { return fabric.util.rotatePoint(p, point, degreesToRadians(this.angle)); } return p; }, /** * Translates the coordinates from center to origin coordinates (based on the object's dimensions) * @param {fabric.Point} center The point which corresponds to center of the object * @param {String} originX Horizontal origin: 'left', 'center' or 'right' * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' * @return {fabric.Point} */ translateToOriginPoint: function(center, originX, originY) { var p = this.translateToGivenOrigin(center, 'center', 'center', originX, originY); if (this.angle) { return fabric.util.rotatePoint(p, center, degreesToRadians(this.angle)); } return p; }, /** * Returns the real center coordinates of the object * @return {fabric.Point} */ getCenterPoint: function() { var leftTop = new fabric.Point(this.left, this.top); return this.translateToCenterPoint(leftTop, this.originX, this.originY); }, /** * Returns the coordinates of the object based on center coordinates * @param {fabric.Point} point The point which corresponds to the originX and originY params * @return {fabric.Point} */ // getOriginPoint: function(center) { // return this.translateToOriginPoint(center, this.originX, this.originY); // }, /** * Returns the coordinates of the object as if it has a different origin * @param {String} originX Horizontal origin: 'left', 'center' or 'right' * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' * @return {fabric.Point} */ getPointByOrigin: function(originX, originY) { var center = this.getCenterPoint(); return this.translateToOriginPoint(center, originX, originY); }, /** * Returns the point in local coordinates * @param {fabric.Point} point The point relative to the global coordinate system * @param {String} originX Horizontal origin: 'left', 'center' or 'right' * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' * @return {fabric.Point} */ toLocalPoint: function(point, originX, originY) { var center = this.getCenterPoint(), p, p2; if (typeof originX !== 'undefined' && typeof originY !== 'undefined' ) { p = this.translateToGivenOrigin(center, 'center', 'center', originX, originY); } else { p = new fabric.Point(this.left, this.top); } p2 = new fabric.Point(point.x, point.y); if (this.angle) { p2 = fabric.util.rotatePoint(p2, center, -degreesToRadians(this.angle)); } return p2.subtractEquals(p); }, /** * Returns the point in global coordinates * @param {fabric.Point} The point relative to the local coordinate system * @return {fabric.Point} */ // toGlobalPoint: function(point) { // return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top)); // }, /** * Sets the position of the object taking into consideration the object's origin * @param {fabric.Point} pos The new position of the object * @param {String} originX Horizontal origin: 'left', 'center' or 'right' * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' * @return {void} */ setPositionByOrigin: function(pos, originX, originY) { var center = this.translateToCenterPoint(pos, originX, originY), position = this.translateToOriginPoint(center, this.originX, this.originY); this.set('left', position.x); this.set('top', position.y); }, /** * @param {String} to One of 'left', 'center', 'right' */ adjustPosition: function(to) { var angle = degreesToRadians(this.angle), hypotFull = this.getScaledWidth(), xFull = fabric.util.cos(angle) * hypotFull, yFull = fabric.util.sin(angle) * hypotFull, offsetFrom, offsetTo; //TODO: this function does not consider mixed situation like top, center. if (typeof this.originX === 'string') { offsetFrom = originXOffset[this.originX]; } else { offsetFrom = this.originX - 0.5; } if (typeof to === 'string') { offsetTo = originXOffset[to]; } else { offsetTo = to - 0.5; } this.left += xFull * (offsetTo - offsetFrom); this.top += yFull * (offsetTo - offsetFrom); this.setCoords(); this.originX = to; }, /** * Sets the origin/position of the object to it's center point * @private * @return {void} */ _setOriginToCenter: function() { this._originalOriginX = this.originX; this._originalOriginY = this.originY; var center = this.getCenterPoint(); this.originX = 'center'; this.originY = 'center'; this.left = center.x; this.top = center.y; }, /** * Resets the origin/position of the object to it's original origin * @private * @return {void} */ _resetOrigin: function() { var originPoint = this.translateToOriginPoint( this.getCenterPoint(), this._originalOriginX, this._originalOriginY); this.originX = this._originalOriginX; this.originY = this._originalOriginY; this.left = originPoint.x; this.top = originPoint.y; this._originalOriginX = null; this._originalOriginY = null; }, /** * @private */ _getLeftTopCoords: function() { return this.translateToOriginPoint(this.getCenterPoint(), 'left', 'top'); }, }); })(); (function() { function getCoords(coords) { return [ new fabric.Point(coords.tl.x, coords.tl.y), new fabric.Point(coords.tr.x, coords.tr.y), new fabric.Point(coords.br.x, coords.br.y), new fabric.Point(coords.bl.x, coords.bl.y) ]; } var degreesToRadians = fabric.util.degreesToRadians, multiplyMatrices = fabric.util.multiplyTransformMatrices, transformPoint = fabric.util.transformPoint; fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { /** * Describe object's corner position in canvas element coordinates. * properties are tl,mt,tr,ml,mr,bl,mb,br,mtr for the main controls. * each property is an object with x, y and corner. * The `corner` property contains in a similar manner the 4 points of the * interactive area of the corner. * The coordinates depends from this properties: width, height, scaleX, scaleY * skewX, skewY, angle, strokeWidth, viewportTransform, top, left, padding. * The coordinates get updated with @method setCoords. * You can calculate them without updating with @method calcCoords; * @memberOf fabric.Object.prototype */ oCoords: null, /** * Describe object's corner position in canvas object absolute coordinates * properties are tl,tr,bl,br and describe the four main corner. * each property is an object with x, y, instance of Fabric.Point. * The coordinates depends from this properties: width, height, scaleX, scaleY * skewX, skewY, angle, strokeWidth, top, left. * Those coordinates are usefull to understand where an object is. They get updated * with oCoords but they do not need to be updated when zoom or panning change. * The coordinates get updated with @method setCoords. * You can calculate them without updating with @method calcCoords(true); * @memberOf fabric.Object.prototype */ aCoords: null, /** * storage for object transform matrix */ ownMatrixCache: null, /** * storage for object full transform matrix */ matrixCache: null, /** * return correct set of coordinates for intersection */ getCoords: function(absolute, calculate) { if (!this.oCoords) { this.setCoords(); } var coords = absolute ? this.aCoords : this.oCoords; return getCoords(calculate ? this.calcCoords(absolute) : coords); }, /** * Checks if object intersects with an area formed by 2 points * @param {Object} pointTL top-left point of area * @param {Object} pointBR bottom-right point of area * @param {Boolean} [absolute] use coordinates without viewportTransform * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords * @return {Boolean} true if object intersects with an area formed by 2 points */ intersectsWithRect: function(pointTL, pointBR, absolute, calculate) { var coords = this.getCoords(absolute, calculate), intersection = fabric.Intersection.intersectPolygonRectangle( coords, pointTL, pointBR ); return intersection.status === 'Intersection'; }, /** * Checks if object intersects with another object * @param {Object} other Object to test * @param {Boolean} [absolute] use coordinates without viewportTransform * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords * @return {Boolean} true if object intersects with another object */ intersectsWithObject: function(other, absolute, calculate) { var intersection = fabric.Intersection.intersectPolygonPolygon( this.getCoords(absolute, calculate), other.getCoords(absolute, calculate) ); return intersection.status === 'Intersection' || other.isContainedWithinObject(this, absolute, calculate) || this.isContainedWithinObject(other, absolute, calculate); }, /** * Checks if object is fully contained within area of another object * @param {Object} other Object to test * @param {Boolean} [absolute] use coordinates without viewportTransform * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords * @return {Boolean} true if object is fully contained within area of another object */ isContainedWithinObject: function(other, absolute, calculate) { var points = this.getCoords(absolute, calculate), i = 0, lines = other._getImageLines( calculate ? other.calcCoords(absolute) : absolute ? other.aCoords : other.oCoords ); for (; i < 4; i++) { if (!other.containsPoint(points[i], lines)) { return false; } } return true; }, /** * Checks if object is fully contained within area formed by 2 points * @param {Object} pointTL top-left point of area * @param {Object} pointBR bottom-right point of area * @param {Boolean} [absolute] use coordinates without viewportTransform * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords * @return {Boolean} true if object is fully contained within area formed by 2 points */ isContainedWithinRect: function(pointTL, pointBR, absolute, calculate) { var boundingRect = this.getBoundingRect(absolute, calculate); return ( boundingRect.left >= pointTL.x && boundingRect.left + boundingRect.width <= pointBR.x && boundingRect.top >= pointTL.y && boundingRect.top + boundingRect.height <= pointBR.y ); }, /** * Checks if point is inside the object * @param {fabric.Point} point Point to check against * @param {Object} [lines] object returned from @method _getImageLines * @param {Boolean} [absolute] use coordinates without viewportTransform * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords * @return {Boolean} true if point is inside the object */ containsPoint: function(point, lines, absolute, calculate) { var lines = lines || this._getImageLines( calculate ? this.calcCoords(absolute) : absolute ? this.aCoords : this.oCoords ), xPoints = this._findCrossPoints(point, lines); // if xPoints is odd then point is inside the object return (xPoints !== 0 && xPoints % 2 === 1); }, /** * Checks if object is contained within the canvas with current viewportTransform * the check is done stopping at first point that appears on screen * @param {Boolean} [calculate] use coordinates of current position instead of .aCoords * @return {Boolean} true if object is fully or partially contained within canvas */ isOnScreen: function(calculate) { if (!this.canvas) { return false; } var pointTL = this.canvas.vptCoords.tl, pointBR = this.canvas.vptCoords.br; var points = this.getCoords(true, calculate), point; for (var i = 0; i < 4; i++) { point = points[i]; if (point.x <= pointBR.x && point.x >= pointTL.x && point.y <= pointBR.y && point.y >= pointTL.y) { return true; } } // no points on screen, check intersection with absolute coordinates if (this.intersectsWithRect(pointTL, pointBR, true, calculate)) { return true; } return this._containsCenterOfCanvas(pointTL, pointBR, calculate); }, /** * Checks if the object contains the midpoint between canvas extremities * Does not make sense outside the context of isOnScreen and isPartiallyOnScreen * @private * @param {Fabric.Point} pointTL Top Left point * @param {Fabric.Point} pointBR Top Right point * @param {Boolean} calculate use coordinates of current position instead of .oCoords * @return {Boolean} true if the objects containe the point */ _containsCenterOfCanvas: function(pointTL, pointBR, calculate) { // worst case scenario the object is so big that contains the screen var centerPoint = { x: (pointTL.x + pointBR.x) / 2, y: (pointTL.y + pointBR.y) / 2 }; if (this.containsPoint(centerPoint, null, true, calculate)) { return true; } return false; }, /** * Checks if object is partially contained within the canvas with current viewportTransform * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords * @return {Boolean} true if object is partially contained within canvas */ isPartiallyOnScreen: function(calculate) { if (!this.canvas) { return false; } var pointTL = this.canvas.vptCoords.tl, pointBR = this.canvas.vptCoords.br; if (this.intersectsWithRect(pointTL, pointBR, true, calculate)) { return true; } return this._containsCenterOfCanvas(pointTL, pointBR, calculate); }, /** * Method that returns an object with the object edges in it, given the coordinates of the corners * @private * @param {Object} oCoords Coordinates of the object corners */ _getImageLines: function(oCoords) { return { topline: { o: oCoords.tl, d: oCoords.tr }, rightline: { o: oCoords.tr, d: oCoords.br }, bottomline: { o: oCoords.br, d: oCoords.bl }, leftline: { o: oCoords.bl, d: oCoords.tl } }; }, /** * Helper method to determine how many cross points are between the 4 object edges * and the horizontal line determined by a point on canvas * @private * @param {fabric.Point} point Point to check * @param {Object} lines Coordinates of the object being evaluated */ // remove yi, not used but left code here just in case. _findCrossPoints: function(point, lines) { var b1, b2, a1, a2, xi, // yi, xcount = 0, iLine; for (var lineKey in lines) { iLine = lines[lineKey]; // optimisation 1: line below point. no cross if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) { continue; } // optimisation 2: line above point. no cross if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) { continue; } // optimisation 3: vertical line case if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) { xi = iLine.o.x; // yi = point.y; } // calculate the intersection point else { b1 = 0; b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); a1 = point.y - b1 * point.x; a2 = iLine.o.y - b2 * iLine.o.x; xi = -(a1 - a2) / (b1 - b2); // yi = a1 + b1 * xi; } // dont count xi < point.x cases if (xi >= point.x) { xcount += 1; } // optimisation 4: specific for square images if (xcount === 2) { break; } } return xcount; }, /** * Returns coordinates of object's bounding rectangle (left, top, width, height) * the box is intented as aligned to axis of canvas. * @param {Boolean} [absolute] use coordinates without viewportTransform * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords / .aCoords * @return {Object} Object with left, top, width, height properties */ getBoundingRect: function(absolute, calculate) { var coords = this.getCoords(absolute, calculate); return fabric.util.makeBoundingBoxFromPoints(coords); }, /** * Returns width of an object bounding box counting transformations * before 2.0 it was named getWidth(); * @return {Number} width value */ getScaledWidth: function() { return this._getTransformedDimensions().x; }, /** * Returns height of an object bounding box counting transformations * before 2.0 it was named getHeight(); * @return {Number} height value */ getScaledHeight: function() { return this._getTransformedDimensions().y; }, /** * Makes sure the scale is valid and modifies it if necessary * @private * @param {Number} value * @return {Number} */ _constrainScale: function(value) { if (Math.abs(value) < this.minScaleLimit) { if (value < 0) { return -this.minScaleLimit; } else { return this.minScaleLimit; } } else if (value === 0) { return 0.0001; } return value; }, /** * Scales an object (equally by x and y) * @param {Number} value Scale factor * @return {fabric.Object} thisArg * @chainable */ scale: function(value) { this._set('scaleX', value); this._set('scaleY', value); return this.setCoords(); }, /** * Scales an object to a given width, with respect to bounding box (scaling by x/y equally) * @param {Number} value New width value * @param {Boolean} absolute ignore viewport * @return {fabric.Object} thisArg * @chainable */ scaleToWidth: function(value, absolute) { // adjust to bounding rect factor so that rotated shapes would fit as well var boundingRectFactor = this.getBoundingRect(absolute).width / this.getScaledWidth(); return this.scale(value / this.width / boundingRectFactor); }, /** * Scales an object to a given height, with respect to bounding box (scaling by x/y equally) * @param {Number} value New height value * @param {Boolean} absolute ignore viewport * @return {fabric.Object} thisArg * @chainable */ scaleToHeight: function(value, absolute) { // adjust to bounding rect factor so that rotated shapes would fit as well var boundingRectFactor = this.getBoundingRect(absolute).height / this.getScaledHeight(); return this.scale(value / this.height / boundingRectFactor); }, /** * Calculate and returns the .coords of an object. * @return {Object} Object with tl, tr, br, bl .... * @chainable */ calcCoords: function(absolute) { var rotateMatrix = this._calcRotateMatrix(), translateMatrix = this._calcTranslateMatrix(), startMatrix = multiplyMatrices(translateMatrix, rotateMatrix), vpt = this.getViewportTransform(), finalMatrix = absolute ? startMatrix : multiplyMatrices(vpt, startMatrix), dim = this._getTransformedDimensions(), w = dim.x / 2, h = dim.y / 2, tl = transformPoint({ x: -w, y: -h }, finalMatrix), tr = transformPoint({ x: w, y: -h }, finalMatrix), bl = transformPoint({ x: -w, y: h }, finalMatrix), br = transformPoint({ x: w, y: h }, finalMatrix); if (!absolute) { var padding = this.padding, angle = degreesToRadians(this.angle), cos = fabric.util.cos(angle), sin = fabric.util.sin(angle), cosP = cos * padding, sinP = sin * padding, cosPSinP = cosP + sinP, cosPMinusSinP = cosP - sinP; if (padding) { tl.x -= cosPMinusSinP; tl.y -= cosPSinP; tr.x += cosPSinP; tr.y -= cosPMinusSinP; bl.x -= cosPSinP; bl.y += cosPMinusSinP; br.x += cosPMinusSinP; br.y += cosPSinP; } var ml = new fabric.Point((tl.x + bl.x) / 2, (tl.y + bl.y) / 2), mt = new fabric.Point((tr.x + tl.x) / 2, (tr.y + tl.y) / 2), mr = new fabric.Point((br.x + tr.x) / 2, (br.y + tr.y) / 2), mb = new fabric.Point((br.x + bl.x) / 2, (br.y + bl.y) / 2), mtr = new fabric.Point(mt.x + sin * this.rotatingPointOffset, mt.y - cos * this.rotatingPointOffset); } // if (!absolute) { // var canvas = this.canvas; // setTimeout(function() { // canvas.contextTop.clearRect(0, 0, 700, 700); // canvas.contextTop.fillStyle = 'green'; // canvas.contextTop.fillRect(mb.x, mb.y, 3, 3); // canvas.contextTop.fillRect(bl.x, bl.y, 3, 3); // canvas.contextTop.fillRect(br.x, br.y, 3, 3); // canvas.contextTop.fillRect(tl.x, tl.y, 3, 3); // canvas.contextTop.fillRect(tr.x, tr.y, 3, 3); // canvas.contextTop.fillRect(ml.x, ml.y, 3, 3); // canvas.contextTop.fillRect(mr.x, mr.y, 3, 3); // canvas.contextTop.fillRect(mt.x, mt.y, 3, 3); // canvas.contextTop.fillRect(mtr.x, mtr.y, 3, 3); // }, 50); // } var coords = { // corners tl: tl, tr: tr, br: br, bl: bl, }; if (!absolute) { // middle coords.ml = ml; coords.mt = mt; coords.mr = mr; coords.mb = mb; // rotating point coords.mtr = mtr; } return coords; }, /** * Sets corner position coordinates based on current angle, width and height. * See {@link https://github.com/kangax/fabric.js/wiki/When-to-call-setCoords|When-to-call-setCoords} * @param {Boolean} [ignoreZoom] set oCoords with or without the viewport transform. * @param {Boolean} [skipAbsolute] skip calculation of aCoords, usefull in setViewportTransform * @return {fabric.Object} thisArg * @chainable */ setCoords: function(ignoreZoom, skipAbsolute) { this.oCoords = this.calcCoords(ignoreZoom); if (!skipAbsolute) { this.aCoords = this.calcCoords(true); } // set coordinates of the draggable boxes in the corners used to scale/rotate the image ignoreZoom || (this._setCornerCoords && this._setCornerCoords()); return this; }, /** * calculate rotation matrix of an object * @return {Array} rotation matrix for the object */ _calcRotateMatrix: function() { if (this.angle) { var theta = degreesToRadians(this.angle), cos = fabric.util.cos(theta), sin = fabric.util.sin(theta); return [cos, sin, -sin, cos, 0, 0]; } return fabric.iMatrix.concat(); }, /** * calculate the translation matrix for an object transform * @return {Array} rotation matrix for the object */ _calcTranslateMatrix: function() { var center = this.getCenterPoint(); return [1, 0, 0, 1, center.x, center.y]; }, transformMatrixKey: function(skipGroup) { var sep = '_', prefix = ''; if (!skipGroup && this.group) { prefix = this.group.transformMatrixKey(skipGroup) + sep; }; return prefix + this.top + sep + this.left + sep + this.scaleX + sep + this.scaleY + sep + this.skewX + sep + this.skewY + sep + this.angle + sep + this.originX + sep + this.originY + sep + this.width + sep + this.height + sep + this.strokeWidth + this.flipX + this.flipY; }, /** * calculate trasform Matrix that represent current transformation from * object properties. * @param {Boolean} [skipGroup] return transformMatrix for object and not go upward with parents * @return {Array} matrix Transform Matrix for the object */ calcTransformMatrix: function(skipGroup) { if (skipGroup) { return this.calcOwnMatrix(); } var key = this.transformMatrixKey(), cache = this.matrixCache || (this.matrixCache = {}); if (cache.key === key) { return cache.value; } var matrix = this.calcOwnMatrix(); if (this.group) { matrix = multiplyMatrices(this.group.calcTransformMatrix(), matrix); } cache.key = key; cache.value = matrix; return matrix; }, calcOwnMatrix: function() { var key = this.transformMatrixKey(true), cache = this.ownMatrixCache || (this.ownMatrixCache = {}); if (cache.key === key) { return cache.value; } var matrix = this._calcTranslateMatrix(), rotateMatrix, dimensionMatrix = this._calcDimensionsTransformMatrix(this.skewX, this.skewY, true); if (this.angle) { rotateMatrix = this._calcRotateMatrix(); matrix = multiplyMatrices(matrix, rotateMatrix); } matrix = multiplyMatrices(matrix, dimensionMatrix); cache.key = key; cache.value = matrix; return matrix; }, _calcDimensionsTransformMatrix: function(skewX, skewY, flipping) { var skewMatrix, scaleX = this.scaleX * (flipping && this.flipX ? -1 : 1), scaleY = this.scaleY * (flipping && this.flipY ? -1 : 1), scaleMatrix = [scaleX, 0, 0, scaleY, 0, 0]; if (skewX) { skewMatrix = [1, 0, Math.tan(degreesToRadians(skewX)), 1]; scaleMatrix = multiplyMatrices(scaleMatrix, skewMatrix, true); } if (skewY) { skewMatrix = [1, Math.tan(degreesToRadians(skewY)), 0, 1]; scaleMatrix = multiplyMatrices(scaleMatrix, skewMatrix, true); } return scaleMatrix; }, /* * Calculate object dimensions from its properties * @private * @return {Object} .x width dimension * @return {Object} .y height dimension */ _getNonTransformedDimensions: function() { var strokeWidth = this.strokeWidth, w = this.width + strokeWidth, h = this.height + strokeWidth; return { x: w, y: h }; }, /* * Calculate object bounding boxdimensions from its properties scale, skew. * @private * @return {Object} .x width dimension * @return {Object} .y height dimension */ _getTransformedDimensions: function(skewX, skewY) { if (typeof skewX === 'undefined') { skewX = this.skewX; } if (typeof skewY === 'undefined') { skewY = this.skewY; } var dimensions = this._getNonTransformedDimensions(), dimX, dimY, noSkew = skewX === 0 && skewY === 0; if (this.strokeUniform) { dimX = this.width; dimY = this.height; } else { dimX = dimensions.x; dimY = dimensions.y; } if (noSkew) { return this._finalizeDiemensions(dimX * this.scaleX, dimY * this.scaleY); } else { dimX /= 2; dimY /= 2; } var points = [ { x: -dimX, y: -dimY }, { x: dimX, y: -dimY }, { x: -dimX, y: dimY }, { x: dimX, y: dimY }], i, transformMatrix = this._calcDimensionsTransformMatrix(skewX, skewY, false), bbox; for (i = 0; i < points.length; i++) { points[i] = fabric.util.transformPoint(points[i], transformMatrix); } bbox = fabric.util.makeBoundingBoxFromPoints(points); return this._finalizeDiemensions(bbox.width, bbox.height); }, /* * Calculate object bounding boxdimensions from its properties scale, skew. * @param Number width width of the bbox * @param Number height height of the bbox * @private * @return {Object} .x finalized width dimension * @return {Object} .y finalized height dimension */ _finalizeDiemensions: function(width, height) { return this.strokeUniform ? { x: width + this.strokeWidth, y: height + this.strokeWidth } : { x: width, y: height }; }, /* * Calculate object dimensions for controls. include padding and canvas zoom * private */ _calculateCurrentDimensions: function() { var vpt = this.getViewportTransform(), dim = this._getTransformedDimensions(), p = fabric.util.transformPoint(dim, vpt, true); return p.scalarAdd(2 * this.padding); }, }); })(); fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { /** * Moves an object to the bottom of the stack of drawn objects * @return {fabric.Object} thisArg * @chainable */ sendToBack: function() { if (this.group) { fabric.StaticCanvas.prototype.sendToBack.call(this.group, this); } else { this.canvas.sendToBack(this); } return this; }, /** * Moves an object to the top of the stack of drawn objects * @return {fabric.Object} thisArg * @chainable */ bringToFront: function() { if (this.group) { fabric.StaticCanvas.prototype.bringToFront.call(this.group, this); } else { this.canvas.bringToFront(this); } return this; }, /** * Moves an object down in stack of drawn objects * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object * @return {fabric.Object} thisArg * @chainable */ sendBackwards: function(intersecting) { if (this.group) { fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting); } else { this.canvas.sendBackwards(this, intersecting); } return this; }, /** * Moves an object up in stack of drawn objects * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object * @return {fabric.Object} thisArg * @chainable */ bringForward: function(intersecting) { if (this.group) { fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting); } else { this.canvas.bringForward(this, intersecting); } return this; }, /** * Moves an object to specified level in stack of drawn objects * @param {Number} index New position of object * @return {fabric.Object} thisArg * @chainable */ moveTo: function(index) { if (this.group && this.group.type !== 'activeSelection') { fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index); } else { this.canvas.moveTo(this, index); } return this; } }); /* _TO_SVG_START_ */ (function() { function getSvgColorString(prop, value) { if (!value) { return prop + ': none; '; } else if (value.toLive) { return prop + ': url(#SVGID_' + value.id + '); '; } else { var color = new fabric.Color(value), str = prop + ': ' + color.toRgb() + '; ', opacity = color.getAlpha(); if (opacity !== 1) { //change the color in rgb + opacity str += prop + '-opacity: ' + opacity.toString() + '; '; } return str; } } var toFixed = fabric.util.toFixed; fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { /** * Returns styles-string for svg-export * @param {Boolean} skipShadow a boolean to skip shadow filter output * @return {String} */ getSvgStyles: function(skipShadow) { var fillRule = this.fillRule ? this.fillRule : 'nonzero', strokeWidth = this.strokeWidth ? this.strokeWidth : '0', strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : 'none', strokeDashOffset = this.strokeDashOffset ? this.strokeDashOffset : '0', strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt', strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter', strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4', opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1', visibility = this.visible ? '' : ' visibility: hidden;', filter = skipShadow ? '' : this.getSvgFilter(), fill = getSvgColorString('fill', this.fill), stroke = getSvgColorString('stroke', this.stroke); return [ stroke, 'stroke-width: ', strokeWidth, '; ', 'stroke-dasharray: ', strokeDashArray, '; ', 'stroke-linecap: ', strokeLineCap, '; ', 'stroke-dashoffset: ', strokeDashOffset, '; ', 'stroke-linejoin: ', strokeLineJoin, '; ', 'stroke-miterlimit: ', strokeMiterLimit, '; ', fill, 'fill-rule: ', fillRule, '; ', 'opacity: ', opacity, ';', filter, visibility ].join(''); }, /** * Returns styles-string for svg-export * @param {Object} style the object from which to retrieve style properties * @param {Boolean} useWhiteSpace a boolean to include an additional attribute in the style. * @return {String} */ getSvgSpanStyles: function(style, useWhiteSpace) { var term = '; '; var fontFamily = style.fontFamily ? 'font-family: ' + (((style.fontFamily.indexOf('\'') === -1 && style.fontFamily.indexOf('"') === -1) ? '\'' + style.fontFamily + '\'' : style.fontFamily)) + term : ''; var strokeWidth = style.strokeWidth ? 'stroke-width: ' + style.strokeWidth + term : '', fontFamily = fontFamily, fontSize = style.fontSize ? 'font-size: ' + style.fontSize + 'px' + term : '', fontStyle = style.fontStyle ? 'font-style: ' + style.fontStyle + term : '', fontWeight = style.fontWeight ? 'font-weight: ' + style.fontWeight + term : '', fill = style.fill ? getSvgColorString('fill', style.fill) : '', stroke = style.stroke ? getSvgColorString('stroke', style.stroke) : '', textDecoration = this.getSvgTextDecoration(style), deltaY = style.deltaY ? 'baseline-shift: ' + (-style.deltaY) + '; ' : ''; if (textDecoration) { textDecoration = 'text-decoration: ' + textDecoration + term; } return [ stroke, strokeWidth, fontFamily, fontSize, fontStyle, fontWeight, textDecoration, fill, deltaY, useWhiteSpace ? 'white-space: pre; ' : '' ].join(''); }, /** * Returns text-decoration property for svg-export * @param {Object} style the object from which to retrieve style properties * @return {String} */ getSvgTextDecoration: function(style) { if ('overline' in style || 'underline' in style || 'linethrough' in style) { return (style.overline ? 'overline ' : '') + (style.underline ? 'underline ' : '') + (style.linethrough ? 'line-through ' : ''); } return ''; }, /** * Returns filter for svg shadow * @return {String} */ getSvgFilter: function() { return this.shadow ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; }, /** * Returns id attribute for svg output * @return {String} */ getSvgCommons: function() { return [ this.id ? 'id="' + this.id + '" ' : '', this.clipPath ? 'clip-path="url(#' + this.clipPath.clipPathId + ')" ' : '', ].join(''); }, /** * Returns transform-string for svg-export * @param {Boolean} use the full transform or the single object one. * @return {String} */ getSvgTransform: function(full, additionalTransform) { var transform = full ? this.calcTransformMatrix() : this.calcOwnMatrix(), svgTransform = transform.map(function(value) { return toFixed(value, fabric.Object.NUM_FRACTION_DIGITS); }).join(' '); return 'transform="matrix(' + svgTransform + ')' + (additionalTransform || '') + this.getSvgTransformMatrix() + '" '; }, /** * Returns transform-string for svg-export from the transform matrix of single elements * @return {String} */ getSvgTransformMatrix: function() { return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ')' : ''; }, _setSVGBg: function(textBgRects) { if (this.backgroundColor) { var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; textBgRects.push( '\t\t<rect ', this._getFillAttributes(this.backgroundColor), ' x="', toFixed(-this.width / 2, NUM_FRACTION_DIGITS), '" y="', toFixed(-this.height / 2, NUM_FRACTION_DIGITS), '" width="', toFixed(this.width, NUM_FRACTION_DIGITS), '" height="', toFixed(this.height, NUM_FRACTION_DIGITS), '"></rect>\n'); } }, /** * Returns svg representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG: function(reviver) { return this._createBaseSVGMarkup(this._toSVG(), { reviver: reviver }); }, /** * Returns svg clipPath representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toClipPathSVG: function(reviver) { return '\t' + this._createBaseClipPathSVGMarkup(this._toSVG(), { reviver: reviver }); }, /** * @private */ _createBaseClipPathSVGMarkup: function(objectMarkup, options) { options = options || {}; var reviver = options.reviver, additionalTransform = options.additionalTransform || '', commonPieces = [ this.getSvgTransform(true, additionalTransform), this.getSvgCommons(), ].join(''), // insert commons in the markup, style and svgCommons index = objectMarkup.indexOf('COMMON_PARTS'); objectMarkup[index] = commonPieces; return reviver ? reviver(objectMarkup.join('')) : objectMarkup.join(''); }, /** * @private */ _createBaseSVGMarkup: function(objectMarkup, options) { options = options || {}; var noStyle = options.noStyle, withShadow = options.withShadow, reviver = options.reviver, styleInfo = noStyle ? '' : 'style="' + this.getSvgStyles() + '" ', shadowInfo = withShadow ? 'style="' + this.getSvgFilter() + '" ' : '', clipPath = this.clipPath, vectorEffect = this.strokeUniform ? 'vector-effect="non-scaling-stroke" ' : '', absoluteClipPath = this.clipPath && this.clipPath.absolutePositioned, commonPieces, markup = [], clipPathMarkup, // insert commons in the markup, style and svgCommons index = objectMarkup.indexOf('COMMON_PARTS'), additionalTransform = options.additionalTransform; if (clipPath) { clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++; clipPathMarkup = '<clipPath id="' + clipPath.clipPathId + '" >\n' + this.clipPath.toClipPathSVG(reviver) + '</clipPath>\n'; } if (absoluteClipPath) { markup.push( '<g ', shadowInfo, this.getSvgCommons(), ' >\n' ); } markup.push( '<g ', this.getSvgTransform(false), !absoluteClipPath ? shadowInfo + this.getSvgCommons() : '', ' >\n' ); commonPieces = [ styleInfo, vectorEffect, noStyle ? '' : this.addPaintOrder(), ' ', additionalTransform ? 'transform="' + additionalTransform + '" ' : '', ].join(''); objectMarkup[index] = commonPieces; if (this.fill && this.fill.toLive) { markup.push(this.fill.toSVG(this, false)); } if (this.stroke && this.stroke.toLive) { markup.push(this.stroke.toSVG(this, false)); } if (this.shadow) { markup.push(this.shadow.toSVG(this)); } if (clipPath) { markup.push(clipPathMarkup); } markup.push(objectMarkup.join('')); markup.push('</g>\n'); absoluteClipPath && markup.push('</g>\n'); return reviver ? reviver(markup.join('')) : markup.join(''); }, addPaintOrder: function() { return this.paintFirst !== 'fill' ? ' paint-order="' + this.paintFirst + '" ' : ''; } }); })(); /* _TO_SVG_END_ */ (function() { var extend = fabric.util.object.extend, originalSet = 'stateProperties'; /* Depends on `stateProperties` */ function saveProps(origin, destination, props) { var tmpObj = { }, deep = true; props.forEach(function(prop) { tmpObj[prop] = origin[prop]; }); extend(origin[destination], tmpObj, deep); } function _isEqual(origValue, currentValue, firstPass) { if (origValue === currentValue) { // if the objects are identical, return return true; } else if (Array.isArray(origValue)) { if (!Array.isArray(currentValue) || origValue.length !== currentValue.length) { return false; } for (var i = 0, len = origValue.length; i < len; i++) { if (!_isEqual(origValue[i], currentValue[i])) { return false; } } return true; } else if (origValue && typeof origValue === 'object') { var keys = Object.keys(origValue), key; if (!currentValue || typeof currentValue !== 'object' || (!firstPass && keys.length !== Object.keys(currentValue).length) ) { return false; } for (var i = 0, len = keys.length; i < len; i++) { key = keys[i]; // since clipPath is in the statefull cache list and the clipPath objects // would be iterated as an object, this would lead to possible infinite recursion if (key === 'canvas') { continue; } if (!_isEqual(origValue[key], currentValue[key])) { return false; } } return true; } } fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { /** * Returns true if object state (one of its state properties) was changed * @param {String} [propertySet] optional name for the set of property we want to save * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called */ hasStateChanged: function(propertySet) { propertySet = propertySet || originalSet; var dashedPropertySet = '_' + propertySet; if (Object.keys(this[dashedPropertySet]).length < this[propertySet].length) { return true; } return !_isEqual(this[dashedPropertySet], this, true); }, /** * Saves state of an object * @param {Object} [options] Object with additional `stateProperties` array to include when saving state * @return {fabric.Object} thisArg */ saveState: function(options) { var propertySet = options && options.propertySet || originalSet, destination = '_' + propertySet; if (!this[destination]) { return this.setupState(options); } saveProps(this, destination, this[propertySet]); if (options && options.stateProperties) { saveProps(this, destination, options.stateProperties); } return this; }, /** * Setups state of an object * @param {Object} [options] Object with additional `stateProperties` array to include when saving state * @return {fabric.Object} thisArg */ setupState: function(options) { options = options || { }; var propertySet = options.propertySet || originalSet; options.propertySet = propertySet; this['_' + propertySet] = { }; this.saveState(options); return this; } }); })(); (function() { var degreesToRadians = fabric.util.degreesToRadians; fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { /** * The object interactivity controls. * @private */ _controlsVisibility: null, /** * Determines which corner has been clicked * @private * @param {Object} pointer The pointer indicating the mouse position * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found */ _findTargetCorner: function(pointer) { // objects in group, anykind, are not self modificable, // must not return an hovered corner. if (!this.hasControls || this.group || (!this.canvas || this.canvas._activeObject !== this)) { return false; } var ex = pointer.x, ey = pointer.y, xPoints, lines; this.__corner = 0; for (var i in this.oCoords) { if (!this.isControlVisible(i)) { continue; } if (i === 'mtr' && !this.hasRotatingPoint) { continue; } if (this.get('lockUniScaling') && (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) { continue; } lines = this._getImageLines(this.oCoords[i].corner); // debugging // canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2); // canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2); // canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2); // canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2); // canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2); // canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2); // canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2); // canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2); xPoints = this._findCrossPoints({ x: ex, y: ey }, lines); if (xPoints !== 0 && xPoints % 2 === 1) { this.__corner = i; return i; } } return false; }, /** * Sets the coordinates of the draggable boxes in the corners of * the image used to scale/rotate it. * @private */ _setCornerCoords: function() { var coords = this.oCoords, newTheta = degreesToRadians(45 - this.angle), /* Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2, */ /* 0.707106 stands for sqrt(2)/2 */ cornerHypotenuse = this.cornerSize * 0.707106, cosHalfOffset = cornerHypotenuse * fabric.util.cos(newTheta), sinHalfOffset = cornerHypotenuse * fabric.util.sin(newTheta), x, y; for (var point in coords) { x = coords[point].x; y = coords[point].y; coords[point].corner = { tl: { x: x - sinHalfOffset, y: y - cosHalfOffset }, tr: { x: x + cosHalfOffset, y: y - sinHalfOffset }, bl: { x: x - cosHalfOffset, y: y + sinHalfOffset }, br: { x: x + sinHalfOffset, y: y + cosHalfOffset } }; } }, /** * Draws a colored layer behind the object, inside its selection borders. * Requires public options: padding, selectionBackgroundColor * this function is called when the context is transformed * has checks to be skipped when the object is on a staticCanvas * @param {CanvasRenderingContext2D} ctx Context to draw on * @return {fabric.Object} thisArg * @chainable */ drawSelectionBackground: function(ctx) { if (!this.selectionBackgroundColor || (this.canvas && !this.canvas.interactive) || (this.canvas && this.canvas._activeObject !== this) ) { return this; } ctx.save(); var center = this.getCenterPoint(), wh = this._calculateCurrentDimensions(), vpt = this.canvas.viewportTransform; ctx.translate(center.x, center.y); ctx.scale(1 / vpt[0], 1 / vpt[3]); ctx.rotate(degreesToRadians(this.angle)); ctx.fillStyle = this.selectionBackgroundColor; ctx.fillRect(-wh.x / 2, -wh.y / 2, wh.x, wh.y); ctx.restore(); return this; }, /** * Draws borders of an object's bounding box. * Requires public properties: width, height * Requires public options: padding, borderColor * @param {CanvasRenderingContext2D} ctx Context to draw on * @param {Object} styleOverride object to override the object style * @return {fabric.Object} thisArg * @chainable */ drawBorders: function(ctx, styleOverride) { styleOverride = styleOverride || {}; var wh = this._calculateCurrentDimensions(), strokeWidth = 1 / this.borderScaleFactor, width = wh.x + strokeWidth, height = wh.y + strokeWidth, drawRotatingPoint = typeof styleOverride.hasRotatingPoint !== 'undefined' ? styleOverride.hasRotatingPoint : this.hasRotatingPoint, hasControls = typeof styleOverride.hasControls !== 'undefined' ? styleOverride.hasControls : this.hasControls, rotatingPointOffset = typeof styleOverride.rotatingPointOffset !== 'undefined' ? styleOverride.rotatingPointOffset : this.rotatingPointOffset; ctx.save(); ctx.strokeStyle = styleOverride.borderColor || this.borderColor; this._setLineDash(ctx, styleOverride.borderDashArray || this.borderDashArray, null); ctx.strokeRect( -width / 2, -height / 2, width, height ); if (drawRotatingPoint && this.isControlVisible('mtr') && hasControls) { var rotateHeight = -height / 2; ctx.beginPath(); ctx.moveTo(0, rotateHeight); ctx.lineTo(0, rotateHeight - rotatingPointOffset); ctx.stroke(); } ctx.restore(); return this; }, /** * Draws borders of an object's bounding box when it is inside a group. * Requires public properties: width, height * Requires public options: padding, borderColor * @param {CanvasRenderingContext2D} ctx Context to draw on * @param {object} options object representing current object parameters * @param {Object} styleOverride object to override the object style * @return {fabric.Object} thisArg * @chainable */ drawBordersInGroup: function(ctx, options, styleOverride) { styleOverride = styleOverride || {}; var p = this._getNonTransformedDimensions(), matrix = fabric.util.customTransformMatrix(options.scaleX, options.scaleY, options.skewX), wh = fabric.util.transformPoint(p, matrix), strokeWidth = 1 / this.borderScaleFactor, width = wh.x + strokeWidth, height = wh.y + strokeWidth; ctx.save(); this._setLineDash(ctx, styleOverride.borderDashArray || this.borderDashArray, null); ctx.strokeStyle = styleOverride.borderColor || this.borderColor; ctx.strokeRect( -width / 2, -height / 2, width, height ); ctx.restore(); return this; }, /** * Draws corners of an object's bounding box. * Requires public properties: width, height * Requires public options: cornerSize, padding * @param {CanvasRenderingContext2D} ctx Context to draw on * @param {Object} styleOverride object to override the object style * @return {fabric.Object} thisArg * @chainable */ drawControls: function(ctx, styleOverride) { styleOverride = styleOverride || {}; var wh = this._calculateCurrentDimensions(), width = wh.x, height = wh.y, scaleOffset = styleOverride.cornerSize || this.cornerSize, left = -(width + scaleOffset) / 2, top = -(height + scaleOffset) / 2, transparentCorners = typeof styleOverride.transparentCorners !== 'undefined' ? styleOverride.transparentCorners : this.transparentCorners, hasRotatingPoint = typeof styleOverride.hasRotatingPoint !== 'undefined' ? styleOverride.hasRotatingPoint : this.hasRotatingPoint, methodName = transparentCorners ? 'stroke' : 'fill'; ctx.save(); ctx.strokeStyle = ctx.fillStyle = styleOverride.cornerColor || this.cornerColor; if (!this.transparentCorners) { ctx.strokeStyle = styleOverride.cornerStrokeColor || this.cornerStrokeColor; } this._setLineDash(ctx, styleOverride.cornerDashArray || this.cornerDashArray, null); // top-left this._drawControl('tl', ctx, methodName, left, top, styleOverride); // top-right this._drawControl('tr', ctx, methodName, left + width, top, styleOverride); // bottom-left this._drawControl('bl', ctx, methodName, left, top + height, styleOverride); // bottom-right this._drawControl('br', ctx, methodName, left + width, top + height, styleOverride); if (!this.get('lockUniScaling')) { // middle-top this._drawControl('mt', ctx, methodName, left + width / 2, top, styleOverride); // middle-bottom this._drawControl('mb', ctx, methodName, left + width / 2, top + height, styleOverride); // middle-right this._drawControl('mr', ctx, methodName, left + width, top + height / 2, styleOverride); // middle-left this._drawControl('ml', ctx, methodName, left, top + height / 2, styleOverride); } // middle-top-rotate if (hasRotatingPoint) { this._drawControl('mtr', ctx, methodName, left + width / 2, top - this.rotatingPointOffset, styleOverride); } ctx.restore(); return this; }, /** * @private */ _drawControl: function(control, ctx, methodName, left, top, styleOverride) { styleOverride = styleOverride || {}; if (!this.isControlVisible(control)) { return; } var size = this.cornerSize, stroke = !this.transparentCorners && this.cornerStrokeColor; switch (styleOverride.cornerStyle || this.cornerStyle) { case 'circle': ctx.beginPath(); ctx.arc(left + size / 2, top + size / 2, size / 2, 0, 2 * Math.PI, false); ctx[methodName](); if (stroke) { ctx.stroke(); } break; default: this.transparentCorners || ctx.clearRect(left, top, size, size); ctx[methodName + 'Rect'](left, top, size, size); if (stroke) { ctx.strokeRect(left, top, size, size); } } }, /** * Returns true if the specified control is visible, false otherwise. * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. * @returns {Boolean} true if the specified control is visible, false otherwise */ isControlVisible: function(controlName) { return this._getControlsVisibility()[controlName]; }, /** * Sets the visibility of the specified control. * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. * @param {Boolean} visible true to set the specified control visible, false otherwise * @return {fabric.Object} thisArg * @chainable */ setControlVisible: function(controlName, visible) { this._getControlsVisibility()[controlName] = visible; return this; }, /** * Sets the visibility state of object controls. * @param {Object} [options] Options object * @param {Boolean} [options.bl] true to enable the bottom-left control, false to disable it * @param {Boolean} [options.br] true to enable the bottom-right control, false to disable it * @param {Boolean} [options.mb] true to enable the middle-bottom control, false to disable it * @param {Boolean} [options.ml] true to enable the middle-left control, false to disable it * @param {Boolean} [options.mr] true to enable the middle-right control, false to disable it * @param {Boolean} [options.mt] true to enable the middle-top control, false to disable it * @param {Boolean} [options.tl] true to enable the top-left control, false to disable it * @param {Boolean} [options.tr] true to enable the top-right control, false to disable it * @param {Boolean} [options.mtr] true to enable the middle-top-rotate control, false to disable it * @return {fabric.Object} thisArg * @chainable */ setControlsVisibility: function(options) { options || (options = { }); for (var p in options) { this.setControlVisible(p, options[p]); } return this; }, /** * Returns the instance of the control visibility set for this object. * @private * @returns {Object} */ _getControlsVisibility: function() { if (!this._controlsVisibility) { this._controlsVisibility = { tl: true, tr: true, br: true, bl: true, ml: true, mt: true, mr: true, mb: true, mtr: true }; } return this._controlsVisibility; }, /** * This callback function is called every time _discardActiveObject or _setActiveObject * try to to deselect this object. If the function returns true, the process is cancelled * @param {Object} [options] options sent from the upper functions * @param {Event} [options.e] event if the process is generated by an event */ onDeselect: function() { // implemented by sub-classes, as needed. }, /** * This callback function is called every time _discardActiveObject or _setActiveObject * try to to select this object. If the function returns true, the process is cancelled * @param {Object} [options] options sent from the upper functions * @param {Event} [options.e] event if the process is generated by an event */ onSelect: function() { // implemented by sub-classes, as needed. } }); })(); fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { /** * Animation duration (in ms) for fx* methods * @type Number * @default */ FX_DURATION: 500, /** * Centers object horizontally with animation. * @param {fabric.Object} object Object to center * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties * @param {Function} [callbacks.onComplete] Invoked on completion * @param {Function} [callbacks.onChange] Invoked on every step of animation * @return {fabric.Canvas} thisArg * @chainable */ fxCenterObjectH: function (object, callbacks) { callbacks = callbacks || { }; var empty = function() { }, onComplete = callbacks.onComplete || empty, onChange = callbacks.onChange || empty, _this = this; fabric.util.animate({ startValue: object.left, endValue: this.getCenter().left, duration: this.FX_DURATION, onChange: function(value) { object.set('left', value); _this.requestRenderAll(); onChange(); }, onComplete: function() { object.setCoords(); onComplete(); } }); return this; }, /** * Centers object vertically with animation. * @param {fabric.Object} object Object to center * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties * @param {Function} [callbacks.onComplete] Invoked on completion * @param {Function} [callbacks.onChange] Invoked on every step of animation * @return {fabric.Canvas} thisArg * @chainable */ fxCenterObjectV: function (object, callbacks) { callbacks = callbacks || { }; var empty = function() { }, onComplete = callbacks.onComplete || empty, onChange = callbacks.onChange || empty, _this = this; fabric.util.animate({ startValue: object.top, endValue: this.getCenter().top, duration: this.FX_DURATION, onChange: function(value) { object.set('top', value); _this.requestRenderAll(); onChange(); }, onComplete: function() { object.setCoords(); onComplete(); } }); return this; }, /** * Same as `fabric.Canvas#remove` but animated * @param {fabric.Object} object Object to remove * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties * @param {Function} [callbacks.onComplete] Invoked on completion * @param {Function} [callbacks.onChange] Invoked on every step of animation * @return {fabric.Canvas} thisArg * @chainable */ fxRemove: function (object, callbacks) { callbacks = callbacks || { }; var empty = function() { }, onComplete = callbacks.onComplete || empty, onChange = callbacks.onChange || empty, _this = this; fabric.util.animate({ startValue: object.opacity, endValue: 0, duration: this.FX_DURATION, onChange: function(value) { object.set('opacity', value); _this.requestRenderAll(); onChange(); }, onComplete: function () { _this.remove(object); onComplete(); } }); return this; } }); fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { /** * Animates object's properties * @param {String|Object} property Property to animate (if string) or properties to animate (if object) * @param {Number|Object} value Value to animate property to (if string was given first) or options object * @return {fabric.Object} thisArg * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#animation} * @chainable * * As object — multiple properties * * object.animate({ left: ..., top: ... }); * object.animate({ left: ..., top: ... }, { duration: ... }); * * As string — one property * * object.animate('left', ...); * object.animate('left', { duration: ... }); * */ animate: function() { if (arguments[0] && typeof arguments[0] === 'object') { var propsToAnimate = [], prop, skipCallbacks; for (prop in arguments[0]) { propsToAnimate.push(prop); } for (var i = 0, len = propsToAnimate.length; i < len; i++) { prop = propsToAnimate[i]; skipCallbacks = i !== len - 1; this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks); } } else { this._animate.apply(this, arguments); } return this; }, /** * @private * @param {String} property Property to animate * @param {String} to Value to animate to * @param {Object} [options] Options object * @param {Boolean} [skipCallbacks] When true, callbacks like onchange and oncomplete are not invoked */ _animate: function(property, to, options, skipCallbacks) { var _this = this, propPair; to = to.toString(); if (!options) { options = { }; } else { options = fabric.util.object.clone(options); } if (~property.indexOf('.')) { propPair = property.split('.'); } var currentValue = propPair ? this.get(propPair[0])[propPair[1]] : this.get(property); if (!('from' in options)) { options.from = currentValue; } if (~to.indexOf('=')) { to = currentValue + parseFloat(to.replace('=', '')); } else { to = parseFloat(to); } fabric.util.animate({ startValue: options.from, endValue: to, byValue: options.by, easing: options.easing, duration: options.duration, abort: options.abort && function() { return options.abort.call(_this); }, onChange: function(value, valueProgress, timeProgress) { if (propPair) { _this[propPair[0]][propPair[1]] = value; } else { _this.set(property, value); } if (skipCallbacks) { return; } options.onChange && options.onChange(value, valueProgress, timeProgress); }, onComplete: function(value, valueProgress, timeProgress) { if (skipCallbacks) { return; } _this.setCoords(); options.onComplete && options.onComplete(value, valueProgress, timeProgress); } }); } }); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, clone = fabric.util.object.clone, coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 }, supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); if (fabric.Line) { fabric.warn('fabric.Line is already defined'); return; } /** * Line class * @class fabric.Line * @extends fabric.Object * @see {@link fabric.Line#initialize} for constructor definition */ fabric.Line = fabric.util.createClass(fabric.Object, /** @lends fabric.Line.prototype */ { /** * Type of an object * @type String * @default */ type: 'line', /** * x value or first line edge * @type Number * @default */ x1: 0, /** * y value or first line edge * @type Number * @default */ y1: 0, /** * x value or second line edge * @type Number * @default */ x2: 0, /** * y value or second line edge * @type Number * @default */ y2: 0, cacheProperties: fabric.Object.prototype.cacheProperties.concat('x1', 'x2', 'y1', 'y2'), /** * Constructor * @param {Array} [points] Array of points * @param {Object} [options] Options object * @return {fabric.Line} thisArg */ initialize: function(points, options) { if (!points) { points = [0, 0, 0, 0]; } this.callSuper('initialize', options); this.set('x1', points[0]); this.set('y1', points[1]); this.set('x2', points[2]); this.set('y2', points[3]); this._setWidthHeight(options); }, /** * @private * @param {Object} [options] Options */ _setWidthHeight: function(options) { options || (options = { }); this.width = Math.abs(this.x2 - this.x1); this.height = Math.abs(this.y2 - this.y1); this.left = 'left' in options ? options.left : this._getLeftToOriginX(); this.top = 'top' in options ? options.top : this._getTopToOriginY(); }, /** * @private * @param {String} key * @param {*} value */ _set: function(key, value) { this.callSuper('_set', key, value); if (typeof coordProps[key] !== 'undefined') { this._setWidthHeight(); } return this; }, /** * @private * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line. */ _getLeftToOriginX: makeEdgeToOriginGetter( { // property names origin: 'originX', axis1: 'x1', axis2: 'x2', dimension: 'width' }, { // possible values of origin nearest: 'left', center: 'center', farthest: 'right' } ), /** * @private * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line. */ _getTopToOriginY: makeEdgeToOriginGetter( { // property names origin: 'originY', axis1: 'y1', axis2: 'y2', dimension: 'height' }, { // possible values of origin nearest: 'top', center: 'center', farthest: 'bottom' } ), /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { ctx.beginPath(); if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) { // move from center (of virtual box) to its left/top corner // we can't assume x1, y1 is top left and x2, y2 is bottom right var p = this.calcLinePoints(); ctx.moveTo(p.x1, p.y1); ctx.lineTo(p.x2, p.y2); } ctx.lineWidth = this.strokeWidth; // TODO: test this // make sure setting "fill" changes color of a line // (by copying fillStyle to strokeStyle, since line is stroked, not filled) var origStrokeStyle = ctx.strokeStyle; ctx.strokeStyle = this.stroke || ctx.fillStyle; this.stroke && this._renderStroke(ctx); ctx.strokeStyle = origStrokeStyle; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderDashedStroke: function(ctx) { var p = this.calcLinePoints(); ctx.beginPath(); fabric.util.drawDashedLine(ctx, p.x1, p.y1, p.x2, p.y2, this.strokeDashArray); ctx.closePath(); }, /** * This function is an helper for svg import. it returns the center of the object in the svg * untransformed coordinates * @private * @return {Object} center point from element coordinates */ _findCenterFromElement: function() { return { x: (this.x1 + this.x2) / 2, y: (this.y1 + this.y2) / 2, }; }, /** * Returns object representation of an instance * @methd toObject * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { return extend(this.callSuper('toObject', propertiesToInclude), this.calcLinePoints()); }, /* * Calculate object dimensions from its properties * @private */ _getNonTransformedDimensions: function() { var dim = this.callSuper('_getNonTransformedDimensions'); if (this.strokeLineCap === 'butt') { if (this.width === 0) { dim.y -= this.strokeWidth; } if (this.height === 0) { dim.x -= this.strokeWidth; } } return dim; }, /** * Recalculates line points given width and height * @private */ calcLinePoints: function() { var xMult = this.x1 <= this.x2 ? -1 : 1, yMult = this.y1 <= this.y2 ? -1 : 1, x1 = (xMult * this.width * 0.5), y1 = (yMult * this.height * 0.5), x2 = (xMult * this.width * -0.5), y2 = (yMult * this.height * -0.5); return { x1: x1, x2: x2, y1: y1, y2: y2 }; }, /* _TO_SVG_START_ */ /** * Returns svg representation of an instance * @return {Array} an array of strings with the specific svg representation * of the instance */ _toSVG: function() { var p = this.calcLinePoints(); return [ '<line ', 'COMMON_PARTS', 'x1="', p.x1, '" y1="', p.y1, '" x2="', p.x2, '" y2="', p.y2, '" />\n' ]; }, /* _TO_SVG_END_ */ }); /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement}) * @static * @memberOf fabric.Line * @see http://www.w3.org/TR/SVG/shapes.html#LineElement */ fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x1 y1 x2 y2'.split(' ')); /** * Returns fabric.Line instance from an SVG element * @static * @memberOf fabric.Line * @param {SVGElement} element Element to parse * @param {Object} [options] Options object * @param {Function} [callback] callback function invoked after parsing */ fabric.Line.fromElement = function(element, callback, options) { options = options || { }; var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES), points = [ parsedAttributes.x1 || 0, parsedAttributes.y1 || 0, parsedAttributes.x2 || 0, parsedAttributes.y2 || 0 ]; callback(new fabric.Line(points, extend(parsedAttributes, options))); }; /* _FROM_SVG_END_ */ /** * Returns fabric.Line instance from an object representation * @static * @memberOf fabric.Line * @param {Object} object Object to create an instance from * @param {function} [callback] invoked with new instance as first argument */ fabric.Line.fromObject = function(object, callback) { function _callback(instance) { delete instance.points; callback && callback(instance); }; var options = clone(object, true); options.points = [object.x1, object.y1, object.x2, object.y2]; fabric.Object._fromObject('Line', options, _callback, 'points'); }; /** * Produces a function that calculates distance from canvas edge to Line origin. */ function makeEdgeToOriginGetter(propertyNames, originValues) { var origin = propertyNames.origin, axis1 = propertyNames.axis1, axis2 = propertyNames.axis2, dimension = propertyNames.dimension, nearest = originValues.nearest, center = originValues.center, farthest = originValues.farthest; return function() { switch (this.get(origin)) { case nearest: return Math.min(this.get(axis1), this.get(axis2)); case center: return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension)); case farthest: return Math.max(this.get(axis1), this.get(axis2)); } }; } })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), pi = Math.PI; if (fabric.Circle) { fabric.warn('fabric.Circle is already defined.'); return; } /** * Circle class * @class fabric.Circle * @extends fabric.Object * @see {@link fabric.Circle#initialize} for constructor definition */ fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ { /** * Type of an object * @type String * @default */ type: 'circle', /** * Radius of this circle * @type Number * @default */ radius: 0, /** * Start angle of the circle, moving clockwise * deprectated type, this should be in degree, this was an oversight. * probably will change to degrees in next major version * @type Number * @default 0 */ startAngle: 0, /** * End angle of the circle * deprectated type, this should be in degree, this was an oversight. * probably will change to degrees in next major version * @type Number * @default 2Pi */ endAngle: pi * 2, cacheProperties: fabric.Object.prototype.cacheProperties.concat('radius', 'startAngle', 'endAngle'), /** * @private * @param {String} key * @param {*} value * @return {fabric.Circle} thisArg */ _set: function(key, value) { this.callSuper('_set', key, value); if (key === 'radius') { this.setRadius(value); } return this; }, /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { return this.callSuper('toObject', ['radius', 'startAngle', 'endAngle'].concat(propertiesToInclude)); }, /* _TO_SVG_START_ */ /** * Returns svg representation of an instance * @return {Array} an array of strings with the specific svg representation * of the instance */ _toSVG: function() { var svgString, x = 0, y = 0, angle = (this.endAngle - this.startAngle) % ( 2 * pi); if (angle === 0) { svgString = [ '<circle ', 'COMMON_PARTS', 'cx="' + x + '" cy="' + y + '" ', 'r="', this.radius, '" />\n' ]; } else { var startX = fabric.util.cos(this.startAngle) * this.radius, startY = fabric.util.sin(this.startAngle) * this.radius, endX = fabric.util.cos(this.endAngle) * this.radius, endY = fabric.util.sin(this.endAngle) * this.radius, largeFlag = angle > pi ? '1' : '0'; svgString = [ '<path d="M ' + startX + ' ' + startY, ' A ' + this.radius + ' ' + this.radius, ' 0 ', +largeFlag + ' 1', ' ' + endX + ' ' + endY, '" ', 'COMMON_PARTS', ' />\n' ]; } return svgString; }, /* _TO_SVG_END_ */ /** * @private * @param {CanvasRenderingContext2D} ctx context to render on */ _render: function(ctx) { ctx.beginPath(); ctx.arc( 0, 0, this.radius, this.startAngle, this.endAngle, false); this._renderPaintInOrder(ctx); }, /** * Returns horizontal radius of an object (according to how an object is scaled) * @return {Number} */ getRadiusX: function() { return this.get('radius') * this.get('scaleX'); }, /** * Returns vertical radius of an object (according to how an object is scaled) * @return {Number} */ getRadiusY: function() { return this.get('radius') * this.get('scaleY'); }, /** * Sets radius of an object (and updates width accordingly) * @return {fabric.Circle} thisArg */ setRadius: function(value) { this.radius = value; return this.set('width', value * 2).set('height', value * 2); }, }); /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement}) * @static * @memberOf fabric.Circle * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement */ fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' ')); /** * Returns {@link fabric.Circle} instance from an SVG element * @static * @memberOf fabric.Circle * @param {SVGElement} element Element to parse * @param {Function} [callback] Options callback invoked after parsing is finished * @param {Object} [options] Options object * @throws {Error} If value of `r` attribute is missing or invalid */ fabric.Circle.fromElement = function(element, callback) { var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES); if (!isValidRadius(parsedAttributes)) { throw new Error('value of `r` attribute is required and can not be negative'); } parsedAttributes.left = (parsedAttributes.left || 0) - parsedAttributes.radius; parsedAttributes.top = (parsedAttributes.top || 0) - parsedAttributes.radius; callback(new fabric.Circle(parsedAttributes)); }; /** * @private */ function isValidRadius(attributes) { return (('radius' in attributes) && (attributes.radius >= 0)); } /* _FROM_SVG_END_ */ /** * Returns {@link fabric.Circle} instance from an object representation * @static * @memberOf fabric.Circle * @param {Object} object Object to create an instance from * @param {function} [callback] invoked with new instance as first argument * @return {Object} Instance of fabric.Circle */ fabric.Circle.fromObject = function(object, callback) { return fabric.Object._fromObject('Circle', object, callback); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }); if (fabric.Triangle) { fabric.warn('fabric.Triangle is already defined'); return; } /** * Triangle class * @class fabric.Triangle * @extends fabric.Object * @return {fabric.Triangle} thisArg * @see {@link fabric.Triangle#initialize} for constructor definition */ fabric.Triangle = fabric.util.createClass(fabric.Object, /** @lends fabric.Triangle.prototype */ { /** * Type of an object * @type String * @default */ type: 'triangle', /** * Width is set to 100 to compensate the old initialize code that was setting it to 100 * @type Number * @default */ width: 100, /** * Height is set to 100 to compensate the old initialize code that was setting it to 100 * @type Number * @default */ height: 100, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { var widthBy2 = this.width / 2, heightBy2 = this.height / 2; ctx.beginPath(); ctx.moveTo(-widthBy2, heightBy2); ctx.lineTo(0, -heightBy2); ctx.lineTo(widthBy2, heightBy2); ctx.closePath(); this._renderPaintInOrder(ctx); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderDashedStroke: function(ctx) { var widthBy2 = this.width / 2, heightBy2 = this.height / 2; ctx.beginPath(); fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray); fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray); fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray); ctx.closePath(); }, /* _TO_SVG_START_ */ /** * Returns svg representation of an instance * @return {Array} an array of strings with the specific svg representation * of the instance */ _toSVG: function() { var widthBy2 = this.width / 2, heightBy2 = this.height / 2, points = [ -widthBy2 + ' ' + heightBy2, '0 ' + -heightBy2, widthBy2 + ' ' + heightBy2 ].join(','); return [ '<polygon ', 'COMMON_PARTS', 'points="', points, '" />' ]; }, /* _TO_SVG_END_ */ }); /** * Returns {@link fabric.Triangle} instance from an object representation * @static * @memberOf fabric.Triangle * @param {Object} object Object to create an instance from * @param {function} [callback] invoked with new instance as first argument */ fabric.Triangle.fromObject = function(object, callback) { return fabric.Object._fromObject('Triangle', object, callback); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), piBy2 = Math.PI * 2; if (fabric.Ellipse) { fabric.warn('fabric.Ellipse is already defined.'); return; } /** * Ellipse class * @class fabric.Ellipse * @extends fabric.Object * @return {fabric.Ellipse} thisArg * @see {@link fabric.Ellipse#initialize} for constructor definition */ fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @lends fabric.Ellipse.prototype */ { /** * Type of an object * @type String * @default */ type: 'ellipse', /** * Horizontal radius * @type Number * @default */ rx: 0, /** * Vertical radius * @type Number * @default */ ry: 0, cacheProperties: fabric.Object.prototype.cacheProperties.concat('rx', 'ry'), /** * Constructor * @param {Object} [options] Options object * @return {fabric.Ellipse} thisArg */ initialize: function(options) { this.callSuper('initialize', options); this.set('rx', options && options.rx || 0); this.set('ry', options && options.ry || 0); }, /** * @private * @param {String} key * @param {*} value * @return {fabric.Ellipse} thisArg */ _set: function(key, value) { this.callSuper('_set', key, value); switch (key) { case 'rx': this.rx = value; this.set('width', value * 2); break; case 'ry': this.ry = value; this.set('height', value * 2); break; } return this; }, /** * Returns horizontal radius of an object (according to how an object is scaled) * @return {Number} */ getRx: function() { return this.get('rx') * this.get('scaleX'); }, /** * Returns Vertical radius of an object (according to how an object is scaled) * @return {Number} */ getRy: function() { return this.get('ry') * this.get('scaleY'); }, /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { return this.callSuper('toObject', ['rx', 'ry'].concat(propertiesToInclude)); }, /* _TO_SVG_START_ */ /** * Returns svg representation of an instance * @return {Array} an array of strings with the specific svg representation * of the instance */ _toSVG: function() { return [ '<ellipse ', 'COMMON_PARTS', 'cx="0" cy="0" ', 'rx="', this.rx, '" ry="', this.ry, '" />\n' ]; }, /* _TO_SVG_END_ */ /** * @private * @param {CanvasRenderingContext2D} ctx context to render on */ _render: function(ctx) { ctx.beginPath(); ctx.save(); ctx.transform(1, 0, 0, this.ry / this.rx, 0, 0); ctx.arc( 0, 0, this.rx, 0, piBy2, false); ctx.restore(); this._renderPaintInOrder(ctx); }, }); /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement}) * @static * @memberOf fabric.Ellipse * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement */ fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy rx ry'.split(' ')); /** * Returns {@link fabric.Ellipse} instance from an SVG element * @static * @memberOf fabric.Ellipse * @param {SVGElement} element Element to parse * @param {Function} [callback] Options callback invoked after parsing is finished * @return {fabric.Ellipse} */ fabric.Ellipse.fromElement = function(element, callback) { var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES); parsedAttributes.left = (parsedAttributes.left || 0) - parsedAttributes.rx; parsedAttributes.top = (parsedAttributes.top || 0) - parsedAttributes.ry; callback(new fabric.Ellipse(parsedAttributes)); }; /* _FROM_SVG_END_ */ /** * Returns {@link fabric.Ellipse} instance from an object representation * @static * @memberOf fabric.Ellipse * @param {Object} object Object to create an instance from * @param {function} [callback] invoked with new instance as first argument * @return {fabric.Ellipse} */ fabric.Ellipse.fromObject = function(object, callback) { return fabric.Object._fromObject('Ellipse', object, callback); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; if (fabric.Rect) { fabric.warn('fabric.Rect is already defined'); return; } /** * Rectangle class * @class fabric.Rect * @extends fabric.Object * @return {fabric.Rect} thisArg * @see {@link fabric.Rect#initialize} for constructor definition */ fabric.Rect = fabric.util.createClass(fabric.Object, /** @lends fabric.Rect.prototype */ { /** * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged}) * as well as for history (undo/redo) purposes * @type Array */ stateProperties: fabric.Object.prototype.stateProperties.concat('rx', 'ry'), /** * Type of an object * @type String * @default */ type: 'rect', /** * Horizontal border radius * @type Number * @default */ rx: 0, /** * Vertical border radius * @type Number * @default */ ry: 0, cacheProperties: fabric.Object.prototype.cacheProperties.concat('rx', 'ry'), /** * Constructor * @param {Object} [options] Options object * @return {Object} thisArg */ initialize: function(options) { this.callSuper('initialize', options); this._initRxRy(); }, /** * Initializes rx/ry attributes * @private */ _initRxRy: function() { if (this.rx && !this.ry) { this.ry = this.rx; } else if (this.ry && !this.rx) { this.rx = this.ry; } }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { // 1x1 case (used in spray brush) optimization was removed because // with caching and higher zoom level this makes more damage than help var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0, ry = this.ry ? Math.min(this.ry, this.height / 2) : 0, w = this.width, h = this.height, x = -this.width / 2, y = -this.height / 2, isRounded = rx !== 0 || ry !== 0, /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */ k = 1 - 0.5522847498; ctx.beginPath(); ctx.moveTo(x + rx, y); ctx.lineTo(x + w - rx, y); isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry); ctx.lineTo(x + w, y + h - ry); isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h); ctx.lineTo(x + rx, y + h); isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry); ctx.lineTo(x, y + ry); isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y); ctx.closePath(); this._renderPaintInOrder(ctx); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderDashedStroke: function(ctx) { var x = -this.width / 2, y = -this.height / 2, w = this.width, h = this.height; ctx.beginPath(); fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); ctx.closePath(); }, /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { return this.callSuper('toObject', ['rx', 'ry'].concat(propertiesToInclude)); }, /* _TO_SVG_START_ */ /** * Returns svg representation of an instance * @return {Array} an array of strings with the specific svg representation * of the instance */ _toSVG: function() { var x = -this.width / 2, y = -this.height / 2; return [ '<rect ', 'COMMON_PARTS', 'x="', x, '" y="', y, '" rx="', this.rx, '" ry="', this.ry, '" width="', this.width, '" height="', this.height, '" />\n' ]; }, /* _TO_SVG_END_ */ }); /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`) * @static * @memberOf fabric.Rect * @see: http://www.w3.org/TR/SVG/shapes.html#RectElement */ fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y rx ry width height'.split(' ')); /** * Returns {@link fabric.Rect} instance from an SVG element * @static * @memberOf fabric.Rect * @param {SVGElement} element Element to parse * @param {Function} callback callback function invoked after parsing * @param {Object} [options] Options object */ fabric.Rect.fromElement = function(element, callback, options) { if (!element) { return callback(null); } options = options || { }; var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES); parsedAttributes.left = parsedAttributes.left || 0; parsedAttributes.top = parsedAttributes.top || 0; var rect = new fabric.Rect(extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); rect.visible = rect.visible && rect.width > 0 && rect.height > 0; callback(rect); }; /* _FROM_SVG_END_ */ /** * Returns {@link fabric.Rect} instance from an object representation * @static * @memberOf fabric.Rect * @param {Object} object Object to create an instance from * @param {Function} [callback] Callback to invoke when an fabric.Rect instance is created */ fabric.Rect.fromObject = function(object, callback) { return fabric.Object._fromObject('Rect', object, callback); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, min = fabric.util.array.min, max = fabric.util.array.max, toFixed = fabric.util.toFixed; if (fabric.Polyline) { fabric.warn('fabric.Polyline is already defined'); return; } /** * Polyline class * @class fabric.Polyline * @extends fabric.Object * @see {@link fabric.Polyline#initialize} for constructor definition */ fabric.Polyline = fabric.util.createClass(fabric.Object, /** @lends fabric.Polyline.prototype */ { /** * Type of an object * @type String * @default */ type: 'polyline', /** * Points array * @type Array * @default */ points: null, cacheProperties: fabric.Object.prototype.cacheProperties.concat('points'), /** * Constructor * @param {Array} points Array of points (where each point is an object with x and y) * @param {Object} [options] Options object * @return {fabric.Polyline} thisArg * @example * var poly = new fabric.Polyline([ * { x: 10, y: 10 }, * { x: 50, y: 30 }, * { x: 40, y: 70 }, * { x: 60, y: 50 }, * { x: 100, y: 150 }, * { x: 40, y: 100 } * ], { * stroke: 'red', * left: 100, * top: 100 * }); */ initialize: function(points, options) { options = options || {}; this.points = points || []; this.callSuper('initialize', options); var calcDim = this._calcDimensions(); if (typeof options.left === 'undefined') { this.left = calcDim.left; } if (typeof options.top === 'undefined') { this.top = calcDim.top; } this.width = calcDim.width; this.height = calcDim.height; this.pathOffset = { x: calcDim.left + this.width / 2, y: calcDim.top + this.height / 2 }; }, /** * Calculate the polygon min and max point from points array, * returning an object with left, top, widht, height to measure the * polygon size * @return {Object} object.left X coordinate of the polygon leftmost point * @return {Object} object.top Y coordinate of the polygon topmost point * @return {Object} object.width distance between X coordinates of the polygon leftmost and rightmost point * @return {Object} object.height distance between Y coordinates of the polygon topmost and bottommost point * @private */ _calcDimensions: function() { var points = this.points, minX = min(points, 'x') || 0, minY = min(points, 'y') || 0, maxX = max(points, 'x') || 0, maxY = max(points, 'y') || 0, width = (maxX - minX), height = (maxY - minY); return { left: minX, top: minY, width: width, height: height }; }, /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} Object representation of an instance */ toObject: function(propertiesToInclude) { return extend(this.callSuper('toObject', propertiesToInclude), { points: this.points.concat() }); }, /* _TO_SVG_START_ */ /** * Returns svg representation of an instance * @return {Array} an array of strings with the specific svg representation * of the instance */ _toSVG: function() { var points = [], diffX = this.pathOffset.x, diffY = this.pathOffset.y, NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; for (var i = 0, len = this.points.length; i < len; i++) { points.push( toFixed(this.points[i].x - diffX, NUM_FRACTION_DIGITS), ',', toFixed(this.points[i].y - diffY, NUM_FRACTION_DIGITS), ' ' ); } return [ '<' + this.type + ' ', 'COMMON_PARTS', 'points="', points.join(''), '" />\n' ]; }, /* _TO_SVG_END_ */ /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ commonRender: function(ctx) { var point, len = this.points.length, x = this.pathOffset.x, y = this.pathOffset.y; if (!len || isNaN(this.points[len - 1].y)) { // do not draw if no points or odd points // NaN comes from parseFloat of a empty string in parser return false; } ctx.beginPath(); ctx.moveTo(this.points[0].x - x, this.points[0].y - y); for (var i = 0; i < len; i++) { point = this.points[i]; ctx.lineTo(point.x - x, point.y - y); } return true; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { if (!this.commonRender(ctx)) { return; } this._renderPaintInOrder(ctx); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderDashedStroke: function(ctx) { var p1, p2; ctx.beginPath(); for (var i = 0, len = this.points.length; i < len; i++) { p1 = this.points[i]; p2 = this.points[i + 1] || p1; fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); } }, /** * Returns complexity of an instance * @return {Number} complexity of this instance */ complexity: function() { return this.get('points').length; } }); /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement}) * @static * @memberOf fabric.Polyline * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement */ fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); /** * Returns fabric.Polyline instance from an SVG element * @static * @memberOf fabric.Polyline * @param {SVGElement} element Element to parser * @param {Function} callback callback function invoked after parsing * @param {Object} [options] Options object */ fabric.Polyline.fromElement = function(element, callback, options) { if (!element) { return callback(null); } options || (options = { }); var points = fabric.parsePointsAttribute(element.getAttribute('points')), parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES); callback(new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options))); }; /* _FROM_SVG_END_ */ /** * Returns fabric.Polyline instance from an object representation * @static * @memberOf fabric.Polyline * @param {Object} object Object to create an instance from * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created */ fabric.Polyline.fromObject = function(object, callback) { return fabric.Object._fromObject('Polyline', object, callback, 'points'); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; if (fabric.Polygon) { fabric.warn('fabric.Polygon is already defined'); return; } /** * Polygon class * @class fabric.Polygon * @extends fabric.Polyline * @see {@link fabric.Polygon#initialize} for constructor definition */ fabric.Polygon = fabric.util.createClass(fabric.Polyline, /** @lends fabric.Polygon.prototype */ { /** * Type of an object * @type String * @default */ type: 'polygon', /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { if (!this.commonRender(ctx)) { return; } ctx.closePath(); this._renderPaintInOrder(ctx); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderDashedStroke: function(ctx) { this.callSuper('_renderDashedStroke', ctx); ctx.closePath(); }, }); /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`) * @static * @memberOf fabric.Polygon * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement */ fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); /** * Returns {@link fabric.Polygon} instance from an SVG element * @static * @memberOf fabric.Polygon * @param {SVGElement} element Element to parse * @param {Function} callback callback function invoked after parsing * @param {Object} [options] Options object */ fabric.Polygon.fromElement = function(element, callback, options) { if (!element) { return callback(null); } options || (options = { }); var points = fabric.parsePointsAttribute(element.getAttribute('points')), parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES); callback(new fabric.Polygon(points, extend(parsedAttributes, options))); }; /* _FROM_SVG_END_ */ /** * Returns fabric.Polygon instance from an object representation * @static * @memberOf fabric.Polygon * @param {Object} object Object to create an instance from * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created */ fabric.Polygon.fromObject = function(object, callback) { return fabric.Object._fromObject('Polygon', object, callback, 'points'); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), min = fabric.util.array.min, max = fabric.util.array.max, extend = fabric.util.object.extend, _toString = Object.prototype.toString, drawArc = fabric.util.drawArc, toFixed = fabric.util.toFixed, commandLengths = { m: 2, l: 2, h: 1, v: 1, c: 6, s: 4, q: 4, t: 2, a: 7 }, repeatedCommands = { m: 'l', M: 'L' }; if (fabric.Path) { fabric.warn('fabric.Path is already defined'); return; } /** * Path class * @class fabric.Path * @extends fabric.Object * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#path_and_pathgroup} * @see {@link fabric.Path#initialize} for constructor definition */ fabric.Path = fabric.util.createClass(fabric.Object, /** @lends fabric.Path.prototype */ { /** * Type of an object * @type String * @default */ type: 'path', /** * Array of path points * @type Array * @default */ path: null, cacheProperties: fabric.Object.prototype.cacheProperties.concat('path', 'fillRule'), stateProperties: fabric.Object.prototype.stateProperties.concat('path'), /** * Constructor * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) * @param {Object} [options] Options object * @return {fabric.Path} thisArg */ initialize: function(path, options) { options = options || { }; this.callSuper('initialize', options); if (!path) { path = []; } var fromArray = _toString.call(path) === '[object Array]'; this.path = fromArray ? path // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values) : path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi); if (!this.path) { return; } if (!fromArray) { this.path = this._parsePath(); } this._setPositionDimensions(options); }, /** * @private * @param {Object} options Options object */ _setPositionDimensions: function(options) { var calcDim = this._parseDimensions(); this.width = calcDim.width; this.height = calcDim.height; if (typeof options.left === 'undefined') { this.left = calcDim.left; } if (typeof options.top === 'undefined') { this.top = calcDim.top; } this.pathOffset = this.pathOffset || { x: calcDim.left + this.width / 2, y: calcDim.top + this.height / 2 }; }, /** * @private * @param {CanvasRenderingContext2D} ctx context to render path on */ _renderPathCommands: function(ctx) { var current, // current instruction previous = null, subpathStartX = 0, subpathStartY = 0, x = 0, // current x y = 0, // current y controlX = 0, // current control point x controlY = 0, // current control point y tempX, tempY, l = -this.pathOffset.x, t = -this.pathOffset.y; ctx.beginPath(); for (var i = 0, len = this.path.length; i < len; ++i) { current = this.path[i]; switch (current[0]) { // first letter case 'l': // lineto, relative x += current[1]; y += current[2]; ctx.lineTo(x + l, y + t); break; case 'L': // lineto, absolute x = current[1]; y = current[2]; ctx.lineTo(x + l, y + t); break; case 'h': // horizontal lineto, relative x += current[1]; ctx.lineTo(x + l, y + t); break; case 'H': // horizontal lineto, absolute x = current[1]; ctx.lineTo(x + l, y + t); break; case 'v': // vertical lineto, relative y += current[1]; ctx.lineTo(x + l, y + t); break; case 'V': // verical lineto, absolute y = current[1]; ctx.lineTo(x + l, y + t); break; case 'm': // moveTo, relative x += current[1]; y += current[2]; subpathStartX = x; subpathStartY = y; ctx.moveTo(x + l, y + t); break; case 'M': // moveTo, absolute x = current[1]; y = current[2]; subpathStartX = x; subpathStartY = y; ctx.moveTo(x + l, y + t); break; case 'c': // bezierCurveTo, relative tempX = x + current[5]; tempY = y + current[6]; controlX = x + current[3]; controlY = y + current[4]; ctx.bezierCurveTo( x + current[1] + l, // x1 y + current[2] + t, // y1 controlX + l, // x2 controlY + t, // y2 tempX + l, tempY + t ); x = tempX; y = tempY; break; case 'C': // bezierCurveTo, absolute x = current[5]; y = current[6]; controlX = current[3]; controlY = current[4]; ctx.bezierCurveTo( current[1] + l, current[2] + t, controlX + l, controlY + t, x + l, y + t ); break; case 's': // shorthand cubic bezierCurveTo, relative // transform to absolute x,y tempX = x + current[3]; tempY = y + current[4]; if (previous[0].match(/[CcSs]/) === null) { // If there is no previous command or if the previous command was not a C, c, S, or s, // the control point is coincident with the current point controlX = x; controlY = y; } else { // calculate reflection of previous control points controlX = 2 * x - controlX; controlY = 2 * y - controlY; } ctx.bezierCurveTo( controlX + l, controlY + t, x + current[1] + l, y + current[2] + t, tempX + l, tempY + t ); // set control point to 2nd one of this command // "... the first control point is assumed to be // the reflection of the second control point on // the previous command relative to the current point." controlX = x + current[1]; controlY = y + current[2]; x = tempX; y = tempY; break; case 'S': // shorthand cubic bezierCurveTo, absolute tempX = current[3]; tempY = current[4]; if (previous[0].match(/[CcSs]/) === null) { // If there is no previous command or if the previous command was not a C, c, S, or s, // the control point is coincident with the current point controlX = x; controlY = y; } else { // calculate reflection of previous control points controlX = 2 * x - controlX; controlY = 2 * y - controlY; } ctx.bezierCurveTo( controlX + l, controlY + t, current[1] + l, current[2] + t, tempX + l, tempY + t ); x = tempX; y = tempY; // set control point to 2nd one of this command // "... the first control point is assumed to be // the reflection of the second control point on // the previous command relative to the current point." controlX = current[1]; controlY = current[2]; break; case 'q': // quadraticCurveTo, relative // transform to absolute x,y tempX = x + current[3]; tempY = y + current[4]; controlX = x + current[1]; controlY = y + current[2]; ctx.quadraticCurveTo( controlX + l, controlY + t, tempX + l, tempY + t ); x = tempX; y = tempY; break; case 'Q': // quadraticCurveTo, absolute tempX = current[3]; tempY = current[4]; ctx.quadraticCurveTo( current[1] + l, current[2] + t, tempX + l, tempY + t ); x = tempX; y = tempY; controlX = current[1]; controlY = current[2]; break; case 't': // shorthand quadraticCurveTo, relative // transform to absolute x,y tempX = x + current[1]; tempY = y + current[2]; if (previous[0].match(/[QqTt]/) === null) { // If there is no previous command or if the previous command was not a Q, q, T or t, // assume the control point is coincident with the current point controlX = x; controlY = y; } else { // calculate reflection of previous control point controlX = 2 * x - controlX; controlY = 2 * y - controlY; } ctx.quadraticCurveTo( controlX + l, controlY + t, tempX + l, tempY + t ); x = tempX; y = tempY; break; case 'T': tempX = current[1]; tempY = current[2]; if (previous[0].match(/[QqTt]/) === null) { // If there is no previous command or if the previous command was not a Q, q, T or t, // assume the control point is coincident with the current point controlX = x; controlY = y; } else { // calculate reflection of previous control point controlX = 2 * x - controlX; controlY = 2 * y - controlY; } ctx.quadraticCurveTo( controlX + l, controlY + t, tempX + l, tempY + t ); x = tempX; y = tempY; break; case 'a': // TODO: optimize this drawArc(ctx, x + l, y + t, [ current[1], current[2], current[3], current[4], current[5], current[6] + x + l, current[7] + y + t ]); x += current[6]; y += current[7]; break; case 'A': // TODO: optimize this drawArc(ctx, x + l, y + t, [ current[1], current[2], current[3], current[4], current[5], current[6] + l, current[7] + t ]); x = current[6]; y = current[7]; break; case 'z': case 'Z': x = subpathStartX; y = subpathStartY; ctx.closePath(); break; } previous = current; } }, /** * @private * @param {CanvasRenderingContext2D} ctx context to render path on */ _render: function(ctx) { this._renderPathCommands(ctx); this._renderPaintInOrder(ctx); }, /** * Returns string representation of an instance * @return {String} string representation of an instance */ toString: function() { return '#<fabric.Path (' + this.complexity() + '): { "top": ' + this.top + ', "left": ' + this.left + ' }>'; }, /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { var o = extend(this.callSuper('toObject', propertiesToInclude), { path: this.path.map(function(item) { return item.slice(); }), top: this.top, left: this.left, }); return o; }, /** * Returns dataless object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toDatalessObject: function(propertiesToInclude) { var o = this.toObject(['sourcePath'].concat(propertiesToInclude)); if (o.sourcePath) { delete o.path; } return o; }, /* _TO_SVG_START_ */ /** * Returns svg representation of an instance * @return {Array} an array of strings with the specific svg representation * of the instance */ _toSVG: function() { var path = this.path.map(function(path) { return path.join(' '); }).join(' '); return [ '<path ', 'COMMON_PARTS', 'd="', path, '" stroke-linecap="round" ', '/>\n' ]; }, _getOffsetTransform: function() { var digits = fabric.Object.NUM_FRACTION_DIGITS; return ' translate(' + toFixed(-this.pathOffset.x, digits) + ', ' + toFixed(-this.pathOffset.y, digits) + ')'; }, /** * Returns svg clipPath representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toClipPathSVG: function(reviver) { var additionalTransform = this._getOffsetTransform(); return '\t' + this._createBaseClipPathSVGMarkup( this._toSVG(), { reviver: reviver, additionalTransform: additionalTransform } ); }, /** * Returns svg representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG: function(reviver) { var additionalTransform = this._getOffsetTransform(); return this._createBaseSVGMarkup(this._toSVG(), { reviver: reviver, additionalTransform: additionalTransform }); }, /* _TO_SVG_END_ */ /** * Returns number representation of an instance complexity * @return {Number} complexity of this instance */ complexity: function() { return this.path.length; }, /** * @private */ _parsePath: function() { var result = [], coords = [], currentPath, parsed, re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig, match, coordsStr; for (var i = 0, coordsParsed, len = this.path.length; i < len; i++) { currentPath = this.path[i]; coordsStr = currentPath.slice(1).trim(); coords.length = 0; while ((match = re.exec(coordsStr))) { coords.push(match[0]); } coordsParsed = [currentPath.charAt(0)]; for (var j = 0, jlen = coords.length; j < jlen; j++) { parsed = parseFloat(coords[j]); if (!isNaN(parsed)) { coordsParsed.push(parsed); } } var command = coordsParsed[0], commandLength = commandLengths[command.toLowerCase()], repeatedCommand = repeatedCommands[command] || command; if (coordsParsed.length - 1 > commandLength) { for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) { result.push([command].concat(coordsParsed.slice(k, k + commandLength))); command = repeatedCommand; } } else { result.push(coordsParsed); } } return result; }, /** * @private */ _parseDimensions: function() { var aX = [], aY = [], current, // current instruction previous = null, subpathStartX = 0, subpathStartY = 0, x = 0, // current x y = 0, // current y controlX = 0, // current control point x controlY = 0, // current control point y tempX, tempY, bounds; for (var i = 0, len = this.path.length; i < len; ++i) { current = this.path[i]; switch (current[0]) { // first letter case 'l': // lineto, relative x += current[1]; y += current[2]; bounds = []; break; case 'L': // lineto, absolute x = current[1]; y = current[2]; bounds = []; break; case 'h': // horizontal lineto, relative x += current[1]; bounds = []; break; case 'H': // horizontal lineto, absolute x = current[1]; bounds = []; break; case 'v': // vertical lineto, relative y += current[1]; bounds = []; break; case 'V': // verical lineto, absolute y = current[1]; bounds = []; break; case 'm': // moveTo, relative x += current[1]; y += current[2]; subpathStartX = x; subpathStartY = y; bounds = []; break; case 'M': // moveTo, absolute x = current[1]; y = current[2]; subpathStartX = x; subpathStartY = y; bounds = []; break; case 'c': // bezierCurveTo, relative tempX = x + current[5]; tempY = y + current[6]; controlX = x + current[3]; controlY = y + current[4]; bounds = fabric.util.getBoundsOfCurve(x, y, x + current[1], // x1 y + current[2], // y1 controlX, // x2 controlY, // y2 tempX, tempY ); x = tempX; y = tempY; break; case 'C': // bezierCurveTo, absolute controlX = current[3]; controlY = current[4]; bounds = fabric.util.getBoundsOfCurve(x, y, current[1], current[2], controlX, controlY, current[5], current[6] ); x = current[5]; y = current[6]; break; case 's': // shorthand cubic bezierCurveTo, relative // transform to absolute x,y tempX = x + current[3]; tempY = y + current[4]; if (previous[0].match(/[CcSs]/) === null) { // If there is no previous command or if the previous command was not a C, c, S, or s, // the control point is coincident with the current point controlX = x; controlY = y; } else { // calculate reflection of previous control points controlX = 2 * x - controlX; controlY = 2 * y - controlY; } bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, x + current[1], y + current[2], tempX, tempY ); // set control point to 2nd one of this command // "... the first control point is assumed to be // the reflection of the second control point on // the previous command relative to the current point." controlX = x + current[1]; controlY = y + current[2]; x = tempX; y = tempY; break; case 'S': // shorthand cubic bezierCurveTo, absolute tempX = current[3]; tempY = current[4]; if (previous[0].match(/[CcSs]/) === null) { // If there is no previous command or if the previous command was not a C, c, S, or s, // the control point is coincident with the current point controlX = x; controlY = y; } else { // calculate reflection of previous control points controlX = 2 * x - controlX; controlY = 2 * y - controlY; } bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, current[1], current[2], tempX, tempY ); x = tempX; y = tempY; // set control point to 2nd one of this command // "... the first control point is assumed to be // the reflection of the second control point on // the previous command relative to the current point." controlX = current[1]; controlY = current[2]; break; case 'q': // quadraticCurveTo, relative // transform to absolute x,y tempX = x + current[3]; tempY = y + current[4]; controlX = x + current[1]; controlY = y + current[2]; bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, controlX, controlY, tempX, tempY ); x = tempX; y = tempY; break; case 'Q': // quadraticCurveTo, absolute controlX = current[1]; controlY = current[2]; bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, controlX, controlY, current[3], current[4] ); x = current[3]; y = current[4]; break; case 't': // shorthand quadraticCurveTo, relative // transform to absolute x,y tempX = x + current[1]; tempY = y + current[2]; if (previous[0].match(/[QqTt]/) === null) { // If there is no previous command or if the previous command was not a Q, q, T or t, // assume the control point is coincident with the current point controlX = x; controlY = y; } else { // calculate reflection of previous control point controlX = 2 * x - controlX; controlY = 2 * y - controlY; } bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, controlX, controlY, tempX, tempY ); x = tempX; y = tempY; break; case 'T': tempX = current[1]; tempY = current[2]; if (previous[0].match(/[QqTt]/) === null) { // If there is no previous command or if the previous command was not a Q, q, T or t, // assume the control point is coincident with the current point controlX = x; controlY = y; } else { // calculate reflection of previous control point controlX = 2 * x - controlX; controlY = 2 * y - controlY; } bounds = fabric.util.getBoundsOfCurve(x, y, controlX, controlY, controlX, controlY, tempX, tempY ); x = tempX; y = tempY; break; case 'a': // TODO: optimize this bounds = fabric.util.getBoundsOfArc(x, y, current[1], current[2], current[3], current[4], current[5], current[6] + x, current[7] + y ); x += current[6]; y += current[7]; break; case 'A': // TODO: optimize this bounds = fabric.util.getBoundsOfArc(x, y, current[1], current[2], current[3], current[4], current[5], current[6], current[7] ); x = current[6]; y = current[7]; break; case 'z': case 'Z': x = subpathStartX; y = subpathStartY; break; } previous = current; bounds.forEach(function (point) { aX.push(point.x); aY.push(point.y); }); aX.push(x); aY.push(y); } var minX = min(aX) || 0, minY = min(aY) || 0, maxX = max(aX) || 0, maxY = max(aY) || 0, deltaX = maxX - minX, deltaY = maxY - minY, o = { left: minX, top: minY, width: deltaX, height: deltaY }; return o; } }); /** * Creates an instance of fabric.Path from an object * @static * @memberOf fabric.Path * @param {Object} object * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created */ fabric.Path.fromObject = function(object, callback) { if (typeof object.sourcePath === 'string') { var pathUrl = object.sourcePath; fabric.loadSVGFromURL(pathUrl, function (elements) { var path = elements[0]; path.setOptions(object); callback && callback(path); }); } else { fabric.Object._fromObject('Path', object, callback, 'path'); } }; /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`) * @static * @memberOf fabric.Path * @see http://www.w3.org/TR/SVG/paths.html#PathElement */ fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(['d']); /** * Creates an instance of fabric.Path from an SVG <path> element * @static * @memberOf fabric.Path * @param {SVGElement} element to parse * @param {Function} callback Callback to invoke when an fabric.Path instance is created * @param {Object} [options] Options object * @param {Function} [callback] Options callback invoked after parsing is finished */ fabric.Path.fromElement = function(element, callback, options) { var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES); callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options))); }; /* _FROM_SVG_END_ */ })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), min = fabric.util.array.min, max = fabric.util.array.max; if (fabric.Group) { return; } /** * Group class * @class fabric.Group * @extends fabric.Object * @mixes fabric.Collection * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#groups} * @see {@link fabric.Group#initialize} for constructor definition */ fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ { /** * Type of an object * @type String * @default */ type: 'group', /** * Width of stroke * @type Number * @default */ strokeWidth: 0, /** * Indicates if click events should also check for subtargets * @type Boolean * @default */ subTargetCheck: false, /** * Groups are container, do not render anything on theyr own, ence no cache properties * @type Array * @default */ cacheProperties: [], /** * setOnGroup is a method used for TextBox that is no more used since 2.0.0 The behavior is still * available setting this boolean to true. * @type Boolean * @since 2.0.0 * @default */ useSetOnGroup: false, /** * Constructor * @param {Object} objects Group objects * @param {Object} [options] Options object * @param {Boolean} [isAlreadyGrouped] if true, objects have been grouped already. * @return {Object} thisArg */ initialize: function(objects, options, isAlreadyGrouped) { options = options || {}; this._objects = []; // if objects enclosed in a group have been grouped already, // we cannot change properties of objects. // Thus we need to set options to group without objects, isAlreadyGrouped && this.callSuper('initialize', options); this._objects = objects || []; for (var i = this._objects.length; i--; ) { this._objects[i].group = this; } if (!isAlreadyGrouped) { var center = options && options.centerPoint; // we want to set origins before calculating the bounding box. // so that the topleft can be set with that in mind. // if specific top and left are passed, are overwritten later // with the callSuper('initialize', options) if (options.originX !== undefined) { this.originX = options.originX; } if (options.originY !== undefined) { this.originY = options.originY; } // if coming from svg i do not want to calc bounds. // i assume width and height are passed along options center || this._calcBounds(); this._updateObjectsCoords(center); delete options.centerPoint; this.callSuper('initialize', options); } else { this._updateObjectsACoords(); } this.setCoords(); }, /** * @private * @param {Boolean} [skipCoordsChange] if true, coordinates of objects enclosed in a group do not change */ _updateObjectsACoords: function() { var ignoreZoom = true, skipAbsolute = true; for (var i = this._objects.length; i--; ){ this._objects[i].setCoords(ignoreZoom, skipAbsolute); } }, /** * @private * @param {Boolean} [skipCoordsChange] if true, coordinates of objects enclosed in a group do not change */ _updateObjectsCoords: function(center) { var center = center || this.getCenterPoint(); for (var i = this._objects.length; i--; ){ this._updateObjectCoords(this._objects[i], center); } }, /** * @private * @param {Object} object * @param {fabric.Point} center, current center of group. */ _updateObjectCoords: function(object, center) { var objectLeft = object.left, objectTop = object.top, ignoreZoom = true, skipAbsolute = true; object.set({ left: objectLeft - center.x, top: objectTop - center.y }); object.group = this; object.setCoords(ignoreZoom, skipAbsolute); }, /** * Returns string represenation of a group * @return {String} */ toString: function() { return '#<fabric.Group: (' + this.complexity() + ')>'; }, /** * Adds an object to a group; Then recalculates group's dimension, position. * @param {Object} object * @return {fabric.Group} thisArg * @chainable */ addWithUpdate: function(object) { this._restoreObjectsState(); fabric.util.resetObjectTransform(this); if (object) { this._objects.push(object); object.group = this; object._set('canvas', this.canvas); } this._calcBounds(); this._updateObjectsCoords(); this.setCoords(); this.dirty = true; return this; }, /** * Removes an object from a group; Then recalculates group's dimension, position. * @param {Object} object * @return {fabric.Group} thisArg * @chainable */ removeWithUpdate: function(object) { this._restoreObjectsState(); fabric.util.resetObjectTransform(this); this.remove(object); this._calcBounds(); this._updateObjectsCoords(); this.setCoords(); this.dirty = true; return this; }, /** * @private */ _onObjectAdded: function(object) { this.dirty = true; object.group = this; object._set('canvas', this.canvas); }, /** * @private */ _onObjectRemoved: function(object) { this.dirty = true; delete object.group; }, /** * @private */ _set: function(key, value) { var i = this._objects.length; if (this.useSetOnGroup) { while (i--) { this._objects[i].setOnGroup(key, value); } } if (key === 'canvas') { while (i--) { this._objects[i]._set(key, value); } } fabric.Object.prototype._set.call(this, key, value); }, /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { var _includeDefaultValues = this.includeDefaultValues; var objsToObject = this._objects.map(function(obj) { var originalDefaults = obj.includeDefaultValues; obj.includeDefaultValues = _includeDefaultValues; var _obj = obj.toObject(propertiesToInclude); obj.includeDefaultValues = originalDefaults; return _obj; }); var obj = fabric.Object.prototype.toObject.call(this, propertiesToInclude); obj.objects = objsToObject; return obj; }, /** * Returns object representation of an instance, in dataless mode. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toDatalessObject: function(propertiesToInclude) { var objsToObject, sourcePath = this.sourcePath; if (sourcePath) { objsToObject = sourcePath; } else { var _includeDefaultValues = this.includeDefaultValues; objsToObject = this._objects.map(function(obj) { var originalDefaults = obj.includeDefaultValues; obj.includeDefaultValues = _includeDefaultValues; var _obj = obj.toDatalessObject(propertiesToInclude); obj.includeDefaultValues = originalDefaults; return _obj; }); } var obj = fabric.Object.prototype.toDatalessObject.call(this, propertiesToInclude); obj.objects = objsToObject; return obj; }, /** * Renders instance on a given context * @param {CanvasRenderingContext2D} ctx context to render instance on */ render: function(ctx) { this._transformDone = true; this.callSuper('render', ctx); this._transformDone = false; }, /** * Decide if the object should cache or not. Create its own cache level * objectCaching is a global flag, wins over everything * needsItsOwnCache should be used when the object drawing method requires * a cache step. None of the fabric classes requires it. * Generally you do not cache objects in groups because the group outside is cached. * @return {Boolean} */ shouldCache: function() { var ownCache = this.objectCaching && (!this.group || this.needsItsOwnCache() || !this.group.isOnACache()); this.ownCaching = ownCache; if (ownCache) { for (var i = 0, len = this._objects.length; i < len; i++) { if (this._objects[i].willDrawShadow()) { this.ownCaching = false; return false; } } } return ownCache; }, /** * Check if this object or a child object will cast a shadow * @return {Boolean} */ willDrawShadow: function() { if (this.shadow) { return fabric.Object.prototype.willDrawShadow.call(this); } for (var i = 0, len = this._objects.length; i < len; i++) { if (this._objects[i].willDrawShadow()) { return true; } } return false; }, /** * Check if this group or its parent group are caching, recursively up * @return {Boolean} */ isOnACache: function() { return this.ownCaching || (this.group && this.group.isOnACache()); }, /** * Execute the drawing operation for an object on a specified context * @param {CanvasRenderingContext2D} ctx Context to render on */ drawObject: function(ctx) { for (var i = 0, len = this._objects.length; i < len; i++) { this._objects[i].render(ctx); } this._drawClipPath(ctx); }, /** * Check if cache is dirty */ isCacheDirty: function(skipCanvas) { if (this.callSuper('isCacheDirty', skipCanvas)) { return true; } if (!this.statefullCache) { return false; } for (var i = 0, len = this._objects.length; i < len; i++) { if (this._objects[i].isCacheDirty(true)) { if (this._cacheCanvas) { // if this group has not a cache canvas there is nothing to clean var x = this.cacheWidth / this.zoomX, y = this.cacheHeight / this.zoomY; this._cacheContext.clearRect(-x / 2, -y / 2, x, y); } return true; } } return false; }, /** * Retores original state of each of group objects (original state is that which was before group was created). * @private * @return {fabric.Group} thisArg * @chainable */ _restoreObjectsState: function() { this._objects.forEach(this._restoreObjectState, this); return this; }, /** * Realises the transform from this group onto the supplied object * i.e. it tells you what would happen if the supplied object was in * the group, and then the group was destroyed. It mutates the supplied * object. * @param {fabric.Object} object * @return {fabric.Object} transformedObject */ realizeTransform: function(object) { var matrix = object.calcTransformMatrix(), options = fabric.util.qrDecompose(matrix), center = new fabric.Point(options.translateX, options.translateY); object.flipX = false; object.flipY = false; object.set('scaleX', options.scaleX); object.set('scaleY', options.scaleY); object.skewX = options.skewX; object.skewY = options.skewY; object.angle = options.angle; object.setPositionByOrigin(center, 'center', 'center'); return object; }, /** * Restores original state of a specified object in group * @private * @param {fabric.Object} object * @return {fabric.Group} thisArg */ _restoreObjectState: function(object) { this.realizeTransform(object); object.setCoords(); delete object.group; return this; }, /** * Destroys a group (restoring state of its objects) * @return {fabric.Group} thisArg * @chainable */ destroy: function() { // when group is destroyed objects needs to get a repaint to be eventually // displayed on canvas. this._objects.forEach(function(object) { object.set('dirty', true); }); return this._restoreObjectsState(); }, /** * make a group an active selection, remove the group from canvas * the group has to be on canvas for this to work. * @return {fabric.ActiveSelection} thisArg * @chainable */ toActiveSelection: function() { if (!this.canvas) { return; } var objects = this._objects, canvas = this.canvas; this._objects = []; var options = this.toObject(); delete options.objects; var activeSelection = new fabric.ActiveSelection([]); activeSelection.set(options); activeSelection.type = 'activeSelection'; canvas.remove(this); objects.forEach(function(object) { object.group = activeSelection; object.dirty = true; canvas.add(object); }); activeSelection.canvas = canvas; activeSelection._objects = objects; canvas._activeObject = activeSelection; activeSelection.setCoords(); return activeSelection; }, /** * Destroys a group (restoring state of its objects) * @return {fabric.Group} thisArg * @chainable */ ungroupOnCanvas: function() { return this._restoreObjectsState(); }, /** * Sets coordinates of all objects inside group * @return {fabric.Group} thisArg * @chainable */ setObjectsCoords: function() { var ignoreZoom = true, skipAbsolute = true; this.forEachObject(function(object) { object.setCoords(ignoreZoom, skipAbsolute); }); return this; }, /** * @private */ _calcBounds: function(onlyWidthHeight) { var aX = [], aY = [], o, prop, props = ['tr', 'br', 'bl', 'tl'], i = 0, iLen = this._objects.length, j, jLen = props.length, ignoreZoom = true; for ( ; i < iLen; ++i) { o = this._objects[i]; o.setCoords(ignoreZoom); for (j = 0; j < jLen; j++) { prop = props[j]; aX.push(o.oCoords[prop].x); aY.push(o.oCoords[prop].y); } } this._getBounds(aX, aY, onlyWidthHeight); }, /** * @private */ _getBounds: function(aX, aY, onlyWidthHeight) { var minXY = new fabric.Point(min(aX), min(aY)), maxXY = new fabric.Point(max(aX), max(aY)), top = minXY.y || 0, left = minXY.x || 0, width = (maxXY.x - minXY.x) || 0, height = (maxXY.y - minXY.y) || 0; this.width = width; this.height = height; if (!onlyWidthHeight) { // the bounding box always finds the topleft most corner. // whatever is the group origin, we set up here the left/top position. this.setPositionByOrigin({ x: left, y: top }, 'left', 'top'); } }, /* _TO_SVG_START_ */ /** * Returns svg representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG: function(reviver) { var svgString = []; for (var i = 0, len = this._objects.length; i < len; i++) { svgString.push('\t', this._objects[i].toSVG(reviver)); } return this._createBaseSVGMarkup( svgString, { reviver: reviver, noStyle: true, withShadow: true }); }, /** * Returns svg clipPath representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toClipPathSVG: function(reviver) { var svgString = []; for (var i = 0, len = this._objects.length; i < len; i++) { svgString.push('\t', this._objects[i].toClipPathSVG(reviver)); } return this._createBaseClipPathSVGMarkup(svgString, { reviver: reviver }); }, /* _TO_SVG_END_ */ }); /** * Returns {@link fabric.Group} instance from an object representation * @static * @memberOf fabric.Group * @param {Object} object Object to create a group from * @param {Function} [callback] Callback to invoke when an group instance is created */ fabric.Group.fromObject = function(object, callback) { fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) { fabric.util.enlivenObjects([object.clipPath], function(enlivedClipPath) { var options = fabric.util.object.clone(object, true); options.clipPath = enlivedClipPath[0]; delete options.objects; callback && callback(new fabric.Group(enlivenedObjects, options, true)); }); }); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }); if (fabric.ActiveSelection) { return; } /** * Group class * @class fabric.ActiveSelection * @extends fabric.Group * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#groups} * @see {@link fabric.ActiveSelection#initialize} for constructor definition */ fabric.ActiveSelection = fabric.util.createClass(fabric.Group, /** @lends fabric.ActiveSelection.prototype */ { /** * Type of an object * @type String * @default */ type: 'activeSelection', /** * Constructor * @param {Object} objects ActiveSelection objects * @param {Object} [options] Options object * @return {Object} thisArg */ initialize: function(objects, options) { options = options || {}; this._objects = objects || []; for (var i = this._objects.length; i--; ) { this._objects[i].group = this; } if (options.originX) { this.originX = options.originX; } if (options.originY) { this.originY = options.originY; } this._calcBounds(); this._updateObjectsCoords(); fabric.Object.prototype.initialize.call(this, options); this.setCoords(); }, /** * Change te activeSelection to a normal group, * High level function that automatically adds it to canvas as * active object. no events fired. * @since 2.0.0 * @return {fabric.Group} */ toGroup: function() { var objects = this._objects.concat(); this._objects = []; var options = fabric.Object.prototype.toObject.call(this); var newGroup = new fabric.Group([]); delete options.type; newGroup.set(options); objects.forEach(function(object) { object.canvas.remove(object); object.group = newGroup; }); newGroup._objects = objects; if (!this.canvas) { return newGroup; } var canvas = this.canvas; canvas.add(newGroup); canvas._activeObject = newGroup; newGroup.setCoords(); return newGroup; }, /** * If returns true, deselection is cancelled. * @since 2.0.0 * @return {Boolean} [cancel] */ onDeselect: function() { this.destroy(); return false; }, /** * Returns string representation of a group * @return {String} */ toString: function() { return '#<fabric.ActiveSelection: (' + this.complexity() + ')>'; }, /** * Decide if the object should cache or not. Create its own cache level * objectCaching is a global flag, wins over everything * needsItsOwnCache should be used when the object drawing method requires * a cache step. None of the fabric classes requires it. * Generally you do not cache objects in groups because the group outside is cached. * @return {Boolean} */ shouldCache: function() { return false; }, /** * Check if this group or its parent group are caching, recursively up * @return {Boolean} */ isOnACache: function() { return false; }, /** * Renders controls and borders for the object * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Object} [styleOverride] properties to override the object style * @param {Object} [childrenOverride] properties to override the children overrides */ _renderControls: function(ctx, styleOverride, childrenOverride) { ctx.save(); ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; this.callSuper('_renderControls', ctx, styleOverride); childrenOverride = childrenOverride || { }; if (typeof childrenOverride.hasControls === 'undefined') { childrenOverride.hasControls = false; } if (typeof childrenOverride.hasRotatingPoint === 'undefined') { childrenOverride.hasRotatingPoint = false; } childrenOverride.forActiveSelection = true; for (var i = 0, len = this._objects.length; i < len; i++) { this._objects[i]._renderControls(ctx, childrenOverride); } ctx.restore(); }, }); /** * Returns {@link fabric.ActiveSelection} instance from an object representation * @static * @memberOf fabric.ActiveSelection * @param {Object} object Object to create a group from * @param {Function} [callback] Callback to invoke when an ActiveSelection instance is created */ fabric.ActiveSelection.fromObject = function(object, callback) { fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) { delete object.objects; callback && callback(new fabric.ActiveSelection(enlivenedObjects, object, true)); }); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var extend = fabric.util.object.extend; if (!global.fabric) { global.fabric = { }; } if (global.fabric.Image) { fabric.warn('fabric.Image is already defined.'); return; } /** * Image class * @class fabric.Image * @extends fabric.Object * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#images} * @see {@link fabric.Image#initialize} for constructor definition */ fabric.Image = fabric.util.createClass(fabric.Object, /** @lends fabric.Image.prototype */ { /** * Type of an object * @type String * @default */ type: 'image', /** * crossOrigin value (one of "", "anonymous", "use-credentials") * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes * @type String * @default */ crossOrigin: '', /** * Width of a stroke. * For image quality a stroke multiple of 2 gives better results. * @type Number * @default */ strokeWidth: 0, /** * When calling {@link fabric.Image.getSrc}, return value from element src with `element.getAttribute('src')`. * This allows for relative urls as image src. * @since 2.7.0 * @type Boolean * @default */ srcFromAttribute: false, /** * private * contains last value of scaleX to detect * if the Image got resized after the last Render * @type Number */ _lastScaleX: 1, /** * private * contains last value of scaleY to detect * if the Image got resized after the last Render * @type Number */ _lastScaleY: 1, /** * private * contains last value of scaling applied by the apply filter chain * @type Number */ _filterScalingX: 1, /** * private * contains last value of scaling applied by the apply filter chain * @type Number */ _filterScalingY: 1, /** * minimum scale factor under which any resizeFilter is triggered to resize the image * 0 will disable the automatic resize. 1 will trigger automatically always. * number bigger than 1 are not implemented yet. * @type Number */ minimumScaleTrigger: 0.5, /** * List of properties to consider when checking if * state of an object is changed ({@link fabric.Object#hasStateChanged}) * as well as for history (undo/redo) purposes * @type Array */ stateProperties: fabric.Object.prototype.stateProperties.concat('cropX', 'cropY'), /** * key used to retrieve the texture representing this image * @since 2.0.0 * @type String * @default */ cacheKey: '', /** * Image crop in pixels from original image size. * @since 2.0.0 * @type Number * @default */ cropX: 0, /** * Image crop in pixels from original image size. * @since 2.0.0 * @type Number * @default */ cropY: 0, /** * Constructor * @param {HTMLImageElement | String} element Image element * @param {Object} [options] Options object * @param {function} [callback] callback function to call after eventual filters applied. * @return {fabric.Image} thisArg */ initialize: function(element, options) { options || (options = { }); this.filters = []; this.cacheKey = 'texture' + fabric.Object.__uid++; this.callSuper('initialize', options); this._initElement(element, options); }, /** * Returns image element which this instance if based on * @return {HTMLImageElement} Image element */ getElement: function() { return this._element || {}; }, /** * Sets image element for this instance to a specified one. * If filters defined they are applied to new image. * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area. * @param {HTMLImageElement} element * @param {Object} [options] Options object * @return {fabric.Image} thisArg * @chainable */ setElement: function(element, options) { this.removeTexture(this.cacheKey); this.removeTexture(this.cacheKey + '_filtered'); this._element = element; this._originalElement = element; this._initConfig(options); if (this.filters.length !== 0) { this.applyFilters(); } // resizeFilters work on the already filtered copy. // we need to apply resizeFilters AFTER normal filters. // applyResizeFilters is run more often than normal fiters // and is triggered by user interactions rather than dev code if (this.resizeFilter) { this.applyResizeFilters(); } return this; }, /** * Delete a single texture if in webgl mode */ removeTexture: function(key) { var backend = fabric.filterBackend; if (backend && backend.evictCachesForKey) { backend.evictCachesForKey(key); } }, /** * Delete textures, reference to elements and eventually JSDOM cleanup */ dispose: function() { this.removeTexture(this.cacheKey); this.removeTexture(this.cacheKey + '_filtered'); this._cacheContext = undefined; ['_originalElement', '_element', '_filteredEl', '_cacheCanvas'].forEach((function(element) { fabric.util.cleanUpJsdomNode(this[element]); this[element] = undefined; }).bind(this)); }, /** * Sets crossOrigin value (on an instance and corresponding image element) * @return {fabric.Image} thisArg * @chainable */ setCrossOrigin: function(value) { this.crossOrigin = value; this._element.crossOrigin = value; return this; }, /** * Returns original size of an image * @return {Object} Object with "width" and "height" properties */ getOriginalSize: function() { var element = this.getElement(); return { width: element.naturalWidth || element.width, height: element.naturalHeight || element.height }; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _stroke: function(ctx) { if (!this.stroke || this.strokeWidth === 0) { return; } var w = this.width / 2, h = this.height / 2; ctx.beginPath(); ctx.moveTo(-w, -h); ctx.lineTo(w, -h); ctx.lineTo(w, h); ctx.lineTo(-w, h); ctx.lineTo(-w, -h); ctx.closePath(); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderDashedStroke: function(ctx) { var x = -this.width / 2, y = -this.height / 2, w = this.width, h = this.height; ctx.save(); this._setStrokeStyles(ctx, this); ctx.beginPath(); fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); ctx.closePath(); ctx.restore(); }, /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} Object representation of an instance */ toObject: function(propertiesToInclude) { var filters = []; this.filters.forEach(function(filterObj) { if (filterObj) { filters.push(filterObj.toObject()); } }); var object = extend( this.callSuper( 'toObject', ['crossOrigin', 'cropX', 'cropY'].concat(propertiesToInclude) ), { src: this.getSrc(), filters: filters, }); if (this.resizeFilter) { object.resizeFilter = this.resizeFilter.toObject(); } return object; }, /** * Returns true if an image has crop applied, inspecting values of cropX,cropY,width,hight. * @return {Boolean} */ hasCrop: function() { return this.cropX || this.cropY || this.width < this._element.width || this.height < this._element.height; }, /* _TO_SVG_START_ */ /** * Returns svg representation of an instance * @return {Array} an array of strings with the specific svg representation * of the instance */ _toSVG: function() { var svgString = [], imageMarkup = [], strokeSvg, x = -this.width / 2, y = -this.height / 2, clipPath = ''; if (this.hasCrop()) { var clipPathId = fabric.Object.__uid++; svgString.push( '<clipPath id="imageCrop_' + clipPathId + '">\n', '\t<rect x="' + x + '" y="' + y + '" width="' + this.width + '" height="' + this.height + '" />\n', '</clipPath>\n' ); clipPath = ' clip-path="url(#imageCrop_' + clipPathId + ')" '; } imageMarkup.push('\t<image ', 'COMMON_PARTS', 'xlink:href="', this.getSvgSrc(true), '" x="', x - this.cropX, '" y="', y - this.cropY, // we're essentially moving origin of transformation from top/left corner to the center of the shape // by wrapping it in container <g> element with actual transformation, then offsetting object to the top/left // so that object's center aligns with container's left/top '" width="', this._element.width || this._element.naturalWidth, '" height="', this._element.height || this._element.height, '"', clipPath, '></image>\n'); if (this.stroke || this.strokeDashArray) { var origFill = this.fill; this.fill = null; strokeSvg = [ '\t<rect ', 'x="', x, '" y="', y, '" width="', this.width, '" height="', this.height, '" style="', this.getSvgStyles(), '"/>\n' ]; this.fill = origFill; } if (this.paintFirst !== 'fill') { svgString = svgString.concat(strokeSvg, imageMarkup); } else { svgString = svgString.concat(imageMarkup, strokeSvg); } return svgString; }, /* _TO_SVG_END_ */ /** * Returns source of an image * @param {Boolean} filtered indicates if the src is needed for svg * @return {String} Source of an image */ getSrc: function(filtered) { var element = filtered ? this._element : this._originalElement; if (element) { if (element.toDataURL) { return element.toDataURL(); } if (this.srcFromAttribute) { return element.getAttribute('src'); } else { return element.src; } } else { return this.src || ''; } }, /** * Sets source of an image * @param {String} src Source string (URL) * @param {Function} [callback] Callback is invoked when image has been loaded (and all filters have been applied) * @param {Object} [options] Options object * @return {fabric.Image} thisArg * @chainable */ setSrc: function(src, callback, options) { fabric.util.loadImage(src, function(img) { this.setElement(img, options); this._setWidthHeight(); callback && callback(this); }, this, options && options.crossOrigin); return this; }, /** * Returns string representation of an instance * @return {String} String representation of an instance */ toString: function() { return '#<fabric.Image: { src: "' + this.getSrc() + '" }>'; }, applyResizeFilters: function() { var filter = this.resizeFilter, minimumScale = this.minimumScaleTrigger, objectScale = this.getTotalObjectScaling(), scaleX = objectScale.scaleX, scaleY = objectScale.scaleY, elementToFilter = this._filteredEl || this._originalElement; if (this.group) { this.set('dirty', true); } if (!filter || (scaleX > minimumScale && scaleY > minimumScale)) { this._element = elementToFilter; this._filterScalingX = 1; this._filterScalingY = 1; this._lastScaleX = scaleX; this._lastScaleY = scaleY; return; } if (!fabric.filterBackend) { fabric.filterBackend = fabric.initFilterBackend(); } var canvasEl = fabric.util.createCanvasElement(), cacheKey = this._filteredEl ? (this.cacheKey + '_filtered') : this.cacheKey, sourceWidth = elementToFilter.width, sourceHeight = elementToFilter.height; canvasEl.width = sourceWidth; canvasEl.height = sourceHeight; this._element = canvasEl; this._lastScaleX = filter.scaleX = scaleX; this._lastScaleY = filter.scaleY = scaleY; fabric.filterBackend.applyFilters( [filter], elementToFilter, sourceWidth, sourceHeight, this._element, cacheKey); this._filterScalingX = canvasEl.width / this._originalElement.width; this._filterScalingY = canvasEl.height / this._originalElement.height; }, /** * Applies filters assigned to this image (from "filters" array) or from filter param * @method applyFilters * @param {Array} filters to be applied * @param {Boolean} forResizing specify if the filter operation is a resize operation * @return {thisArg} return the fabric.Image object * @chainable */ applyFilters: function(filters) { filters = filters || this.filters || []; filters = filters.filter(function(filter) { return filter && !filter.isNeutralState(); }); this.set('dirty', true); // needs to clear out or WEBGL will not resize correctly this.removeTexture(this.cacheKey + '_filtered'); if (filters.length === 0) { this._element = this._originalElement; this._filteredEl = null; this._filterScalingX = 1; this._filterScalingY = 1; return this; } var imgElement = this._originalElement, sourceWidth = imgElement.naturalWidth || imgElement.width, sourceHeight = imgElement.naturalHeight || imgElement.height; if (this._element === this._originalElement) { // if the element is the same we need to create a new element var canvasEl = fabric.util.createCanvasElement(); canvasEl.width = sourceWidth; canvasEl.height = sourceHeight; this._element = canvasEl; this._filteredEl = canvasEl; } else { // clear the existing element to get new filter data // also dereference the eventual resized _element this._element = this._filteredEl; this._filteredEl.getContext('2d').clearRect(0, 0, sourceWidth, sourceHeight); // we also need to resize again at next renderAll, so remove saved _lastScaleX/Y this._lastScaleX = 1; this._lastScaleY = 1; } if (!fabric.filterBackend) { fabric.filterBackend = fabric.initFilterBackend(); } fabric.filterBackend.applyFilters( filters, this._originalElement, sourceWidth, sourceHeight, this._element, this.cacheKey); if (this._originalElement.width !== this._element.width || this._originalElement.height !== this._element.height) { this._filterScalingX = this._element.width / this._originalElement.width; this._filterScalingY = this._element.height / this._originalElement.height; } return this; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { if (this.isMoving !== true && this.resizeFilter && this._needsResize()) { this.applyResizeFilters(); } this._stroke(ctx); this._renderPaintInOrder(ctx); }, /** * Decide if the object should cache or not. Create its own cache level * objectCaching is a global flag, wins over everything * needsItsOwnCache should be used when the object drawing method requires * a cache step. None of the fabric classes requires it. * Generally you do not cache objects in groups because the group outside is cached. * This is the special image version where we would like to avoid caching where possible. * Essentially images do not benefit from caching. They may require caching, and in that * case we do it. Also caching an image usually ends in a loss of details. * A full performance audit should be done. * @return {Boolean} */ shouldCache: function() { this.ownCaching = this.objectCaching && this.needsItsOwnCache(); return this.ownCaching; }, _renderFill: function(ctx) { var elementToDraw = this._element, w = this.width, h = this.height, sW = Math.min(elementToDraw.naturalWidth || elementToDraw.width, w * this._filterScalingX), sH = Math.min(elementToDraw.naturalHeight || elementToDraw.height, h * this._filterScalingY), x = -w / 2, y = -h / 2, sX = Math.max(0, this.cropX * this._filterScalingX), sY = Math.max(0, this.cropY * this._filterScalingY); elementToDraw && ctx.drawImage(elementToDraw, sX, sY, sW, sH, x, y, w, h); }, /** * @private, needed to check if image needs resize */ _needsResize: function() { var scale = this.getTotalObjectScaling(); return (scale.scaleX !== this._lastScaleX || scale.scaleY !== this._lastScaleY); }, /** * @private */ _resetWidthHeight: function() { this.set(this.getOriginalSize()); }, /** * The Image class's initialization method. This method is automatically * called by the constructor. * @private * @param {HTMLImageElement|String} element The element representing the image * @param {Object} [options] Options object */ _initElement: function(element, options) { this.setElement(fabric.util.getById(element), options); fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS); }, /** * @private * @param {Object} [options] Options object */ _initConfig: function(options) { options || (options = { }); this.setOptions(options); this._setWidthHeight(options); if (this._element && this.crossOrigin) { this._element.crossOrigin = this.crossOrigin; } }, /** * @private * @param {Array} filters to be initialized * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created */ _initFilters: function(filters, callback) { if (filters && filters.length) { fabric.util.enlivenObjects(filters, function(enlivenedObjects) { callback && callback(enlivenedObjects); }, 'fabric.Image.filters'); } else { callback && callback(); } }, /** * @private * Set the width and the height of the image object, using the element or the * options. * @param {Object} [options] Object with width/height properties */ _setWidthHeight: function(options) { options || (options = { }); var el = this.getElement(); this.width = options.width || el.naturalWidth || el.width || 0; this.height = options.height || el.naturalHeight || el.height || 0; }, /** * Calculate offset for center and scale factor for the image in order to respect * the preserveAspectRatio attribute * @private * @return {Object} */ parsePreserveAspectRatioAttribute: function() { var pAR = fabric.util.parsePreserveAspectRatioAttribute(this.preserveAspectRatio || ''), rWidth = this._element.width, rHeight = this._element.height, scaleX = 1, scaleY = 1, offsetLeft = 0, offsetTop = 0, cropX = 0, cropY = 0, offset, pWidth = this.width, pHeight = this.height, parsedAttributes = { width: pWidth, height: pHeight }; if (pAR && (pAR.alignX !== 'none' || pAR.alignY !== 'none')) { if (pAR.meetOrSlice === 'meet') { scaleX = scaleY = fabric.util.findScaleToFit(this._element, parsedAttributes); offset = (pWidth - rWidth * scaleX) / 2; if (pAR.alignX === 'Min') { offsetLeft = -offset; } if (pAR.alignX === 'Max') { offsetLeft = offset; } offset = (pHeight - rHeight * scaleY) / 2; if (pAR.alignY === 'Min') { offsetTop = -offset; } if (pAR.alignY === 'Max') { offsetTop = offset; } } if (pAR.meetOrSlice === 'slice') { scaleX = scaleY = fabric.util.findScaleToCover(this._element, parsedAttributes); offset = rWidth - pWidth / scaleX; if (pAR.alignX === 'Mid') { cropX = offset / 2; } if (pAR.alignX === 'Max') { cropX = offset; } offset = rHeight - pHeight / scaleY; if (pAR.alignY === 'Mid') { cropY = offset / 2; } if (pAR.alignY === 'Max') { cropY = offset; } rWidth = pWidth / scaleX; rHeight = pHeight / scaleY; } } else { scaleX = pWidth / rWidth; scaleY = pHeight / rHeight; } return { width: rWidth, height: rHeight, scaleX: scaleX, scaleY: scaleY, offsetLeft: offsetLeft, offsetTop: offsetTop, cropX: cropX, cropY: cropY }; } }); /** * Default CSS class name for canvas * @static * @type String * @default */ fabric.Image.CSS_CANVAS = 'canvas-img'; /** * Alias for getSrc * @static */ fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc; /** * Creates an instance of fabric.Image from its object representation * @static * @param {Object} object Object to create an instance from * @param {Function} callback Callback to invoke when an image instance is created */ fabric.Image.fromObject = function(_object, callback) { var object = fabric.util.object.clone(_object); fabric.util.loadImage(object.src, function(img, error) { if (error) { callback && callback(null, error); return; } fabric.Image.prototype._initFilters.call(object, object.filters, function(filters) { object.filters = filters || []; fabric.Image.prototype._initFilters.call(object, [object.resizeFilter], function(resizeFilters) { object.resizeFilter = resizeFilters[0]; fabric.util.enlivenObjects([object.clipPath], function(enlivedProps) { object.clipPath = enlivedProps[0]; var image = new fabric.Image(img, object); callback(image); }); }); }); }, null, object.crossOrigin); }; /** * Creates an instance of fabric.Image from an URL string * @static * @param {String} url URL to create an image from * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument) * @param {Object} [imgOptions] Options object */ fabric.Image.fromURL = function(url, callback, imgOptions) { fabric.util.loadImage(url, function(img) { callback && callback(new fabric.Image(img, imgOptions)); }, null, imgOptions && imgOptions.crossOrigin); }; /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement}) * @static * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement} */ fabric.Image.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y width height preserveAspectRatio xlink:href crossOrigin'.split(' ')); /** * Returns {@link fabric.Image} instance from an SVG element * @static * @param {SVGElement} element Element to parse * @param {Object} [options] Options object * @param {Function} callback Callback to execute when fabric.Image object is created * @return {fabric.Image} Instance of fabric.Image */ fabric.Image.fromElement = function(element, callback, options) { var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES); fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); }; /* _FROM_SVG_END_ */ })(typeof exports !== 'undefined' ? exports : this); fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { /** * @private * @return {Number} angle value */ _getAngleValueForStraighten: function() { var angle = this.angle % 360; if (angle > 0) { return Math.round((angle - 1) / 90) * 90; } return Math.round(angle / 90) * 90; }, /** * Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer) * @return {fabric.Object} thisArg * @chainable */ straighten: function() { this.rotate(this._getAngleValueForStraighten()); return this; }, /** * Same as {@link fabric.Object.prototype.straighten} but with animation * @param {Object} callbacks Object with callback functions * @param {Function} [callbacks.onComplete] Invoked on completion * @param {Function} [callbacks.onChange] Invoked on every step of animation * @return {fabric.Object} thisArg * @chainable */ fxStraighten: function(callbacks) { callbacks = callbacks || { }; var empty = function() { }, onComplete = callbacks.onComplete || empty, onChange = callbacks.onChange || empty, _this = this; fabric.util.animate({ startValue: this.get('angle'), endValue: this._getAngleValueForStraighten(), duration: this.FX_DURATION, onChange: function(value) { _this.rotate(value); onChange(); }, onComplete: function() { _this.setCoords(); onComplete(); }, }); return this; } }); fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { /** * Straightens object, then rerenders canvas * @param {fabric.Object} object Object to straighten * @return {fabric.Canvas} thisArg * @chainable */ straightenObject: function (object) { object.straighten(); this.requestRenderAll(); return this; }, /** * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated * @param {fabric.Object} object Object to straighten * @return {fabric.Canvas} thisArg * @chainable */ fxStraightenObject: function (object) { object.fxStraighten({ onChange: this.requestRenderAllBound }); return this; } }); (function() { 'use strict'; /** * Tests if webgl supports certain precision * @param {WebGL} Canvas WebGL context to test on * @param {String} Precision to test can be any of following: 'lowp', 'mediump', 'highp' * @returns {Boolean} Whether the user's browser WebGL supports given precision. */ function testPrecision(gl, precision){ var fragmentSource = 'precision ' + precision + ' float;\nvoid main(){}'; var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentSource); gl.compileShader(fragmentShader); if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { return false; } return true; } /** * Indicate whether this filtering backend is supported by the user's browser. * @param {Number} tileSize check if the tileSize is supported * @returns {Boolean} Whether the user's browser supports WebGL. */ fabric.isWebglSupported = function(tileSize) { if (fabric.isLikelyNode) { return false; } tileSize = tileSize || fabric.WebglFilterBackend.prototype.tileSize; var canvas = document.createElement('canvas'); var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); var isSupported = false; // eslint-disable-next-line if (gl) { fabric.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); isSupported = fabric.maxTextureSize >= tileSize; var precisions = ['highp', 'mediump', 'lowp']; for (var i = 0; i < 3; i++){ if (testPrecision(gl, precisions[i])){ fabric.webGlPrecision = precisions[i]; break; }; } } this.isSupported = isSupported; return isSupported; }; fabric.WebglFilterBackend = WebglFilterBackend; /** * WebGL filter backend. */ function WebglFilterBackend(options) { if (options && options.tileSize) { this.tileSize = options.tileSize; } this.setupGLContext(this.tileSize, this.tileSize); this.captureGPUInfo(); }; WebglFilterBackend.prototype = /** @lends fabric.WebglFilterBackend.prototype */ { tileSize: 2048, /** * Experimental. This object is a sort of repository of help layers used to avoid * of recreating them during frequent filtering. If you are previewing a filter with * a slider you problably do not want to create help layers every filter step. * in this object there will be appended some canvases, created once, resized sometimes * cleared never. Clearing is left to the developer. **/ resources: { }, /** * Setup a WebGL context suitable for filtering, and bind any needed event handlers. */ setupGLContext: function(width, height) { this.dispose(); this.createWebGLCanvas(width, height); // eslint-disable-next-line this.aPosition = new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]); this.chooseFastestCopyGLTo2DMethod(width, height); }, /** * Pick a method to copy data from GL context to 2d canvas. In some browsers using * putImageData is faster than drawImage for that specific operation. */ chooseFastestCopyGLTo2DMethod: function(width, height) { var canMeasurePerf = typeof window.performance !== 'undefined'; var canUseImageData; try { new ImageData(1, 1); canUseImageData = true; } catch (e) { canUseImageData = false; } // eslint-disable-next-line no-undef var canUseArrayBuffer = typeof ArrayBuffer !== 'undefined'; // eslint-disable-next-line no-undef var canUseUint8Clamped = typeof Uint8ClampedArray !== 'undefined'; if (!(canMeasurePerf && canUseImageData && canUseArrayBuffer && canUseUint8Clamped)) { return; } var targetCanvas = fabric.util.createCanvasElement(); // eslint-disable-next-line no-undef var imageBuffer = new ArrayBuffer(width * height * 4); var testContext = { imageBuffer: imageBuffer, destinationWidth: width, destinationHeight: height, targetCanvas: targetCanvas }; var startTime, drawImageTime, putImageDataTime; targetCanvas.width = width; targetCanvas.height = height; startTime = window.performance.now(); copyGLTo2DDrawImage.call(testContext, this.gl, testContext); drawImageTime = window.performance.now() - startTime; startTime = window.performance.now(); copyGLTo2DPutImageData.call(testContext, this.gl, testContext); putImageDataTime = window.performance.now() - startTime; if (drawImageTime > putImageDataTime) { this.imageBuffer = imageBuffer; this.copyGLTo2D = copyGLTo2DPutImageData; } else { this.copyGLTo2D = copyGLTo2DDrawImage; } }, /** * Create a canvas element and associated WebGL context and attaches them as * class properties to the GLFilterBackend class. */ createWebGLCanvas: function(width, height) { var canvas = fabric.util.createCanvasElement(); canvas.width = width; canvas.height = height; var glOptions = { alpha: true, premultipliedAlpha: false, depth: false, stencil: false, antialias: false }, gl = canvas.getContext('webgl', glOptions); if (!gl) { gl = canvas.getContext('experimental-webgl', glOptions); } if (!gl) { return; } gl.clearColor(0, 0, 0, 0); // this canvas can fire webglcontextlost and webglcontextrestored this.canvas = canvas; this.gl = gl; }, /** * Attempts to apply the requested filters to the source provided, drawing the filtered output * to the provided target canvas. * * @param {Array} filters The filters to apply. * @param {HTMLImageElement|HTMLCanvasElement} source The source to be filtered. * @param {Number} width The width of the source input. * @param {Number} height The height of the source input. * @param {HTMLCanvasElement} targetCanvas The destination for filtered output to be drawn. * @param {String|undefined} cacheKey A key used to cache resources related to the source. If * omitted, caching will be skipped. */ applyFilters: function(filters, source, width, height, targetCanvas, cacheKey) { var gl = this.gl; var cachedTexture; if (cacheKey) { cachedTexture = this.getCachedTexture(cacheKey, source); } var pipelineState = { originalWidth: source.width || source.originalWidth, originalHeight: source.height || source.originalHeight, sourceWidth: width, sourceHeight: height, destinationWidth: width, destinationHeight: height, context: gl, sourceTexture: this.createTexture(gl, width, height, !cachedTexture && source), targetTexture: this.createTexture(gl, width, height), originalTexture: cachedTexture || this.createTexture(gl, width, height, !cachedTexture && source), passes: filters.length, webgl: true, aPosition: this.aPosition, programCache: this.programCache, pass: 0, filterBackend: this, targetCanvas: targetCanvas }; var tempFbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, tempFbo); filters.forEach(function(filter) { filter && filter.applyTo(pipelineState); }); resizeCanvasIfNeeded(pipelineState); this.copyGLTo2D(gl, pipelineState); gl.bindTexture(gl.TEXTURE_2D, null); gl.deleteTexture(pipelineState.sourceTexture); gl.deleteTexture(pipelineState.targetTexture); gl.deleteFramebuffer(tempFbo); targetCanvas.getContext('2d').setTransform(1, 0, 0, 1, 0, 0); return pipelineState; }, /** * Detach event listeners, remove references, and clean up caches. */ dispose: function() { if (this.canvas) { this.canvas = null; this.gl = null; } this.clearWebGLCaches(); }, /** * Wipe out WebGL-related caches. */ clearWebGLCaches: function() { this.programCache = {}; this.textureCache = {}; }, /** * Create a WebGL texture object. * * Accepts specific dimensions to initialize the textuer to or a source image. * * @param {WebGLRenderingContext} gl The GL context to use for creating the texture. * @param {Number} width The width to initialize the texture at. * @param {Number} height The height to initialize the texture. * @param {HTMLImageElement|HTMLCanvasElement} textureImageSource A source for the texture data. * @returns {WebGLTexture} */ createTexture: function(gl, width, height, textureImageSource) { var texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); if (textureImageSource) { gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureImageSource); } else { gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); } return texture; }, /** * Can be optionally used to get a texture from the cache array * * If an existing texture is not found, a new texture is created and cached. * * @param {String} uniqueId A cache key to use to find an existing texture. * @param {HTMLImageElement|HTMLCanvasElement} textureImageSource A source to use to create the * texture cache entry if one does not already exist. */ getCachedTexture: function(uniqueId, textureImageSource) { if (this.textureCache[uniqueId]) { return this.textureCache[uniqueId]; } else { var texture = this.createTexture( this.gl, textureImageSource.width, textureImageSource.height, textureImageSource); this.textureCache[uniqueId] = texture; return texture; } }, /** * Clear out cached resources related to a source image that has been * filtered previously. * * @param {String} cacheKey The cache key provided when the source image was filtered. */ evictCachesForKey: function(cacheKey) { if (this.textureCache[cacheKey]) { this.gl.deleteTexture(this.textureCache[cacheKey]); delete this.textureCache[cacheKey]; } }, copyGLTo2D: copyGLTo2DDrawImage, /** * Attempt to extract GPU information strings from a WebGL context. * * Useful information when debugging or blacklisting specific GPUs. * * @returns {Object} A GPU info object with renderer and vendor strings. */ captureGPUInfo: function() { if (this.gpuInfo) { return this.gpuInfo; } var gl = this.gl, gpuInfo = { renderer: '', vendor: '' }; if (!gl) { return gpuInfo; } var ext = gl.getExtension('WEBGL_debug_renderer_info'); if (ext) { var renderer = gl.getParameter(ext.UNMASKED_RENDERER_WEBGL); var vendor = gl.getParameter(ext.UNMASKED_VENDOR_WEBGL); if (renderer) { gpuInfo.renderer = renderer.toLowerCase(); } if (vendor) { gpuInfo.vendor = vendor.toLowerCase(); } } this.gpuInfo = gpuInfo; return gpuInfo; }, }; })(); function resizeCanvasIfNeeded(pipelineState) { var targetCanvas = pipelineState.targetCanvas, width = targetCanvas.width, height = targetCanvas.height, dWidth = pipelineState.destinationWidth, dHeight = pipelineState.destinationHeight; if (width !== dWidth || height !== dHeight) { targetCanvas.width = dWidth; targetCanvas.height = dHeight; } } /** * Copy an input WebGL canvas on to an output 2D canvas. * * The WebGL canvas is assumed to be upside down, with the top-left pixel of the * desired output image appearing in the bottom-left corner of the WebGL canvas. * * @param {WebGLRenderingContext} sourceContext The WebGL context to copy from. * @param {HTMLCanvasElement} targetCanvas The 2D target canvas to copy on to. * @param {Object} pipelineState The 2D target canvas to copy on to. */ function copyGLTo2DDrawImage(gl, pipelineState) { var glCanvas = gl.canvas, targetCanvas = pipelineState.targetCanvas, ctx = targetCanvas.getContext('2d'); ctx.translate(0, targetCanvas.height); // move it down again ctx.scale(1, -1); // vertical flip // where is my image on the big glcanvas? var sourceY = glCanvas.height - targetCanvas.height; ctx.drawImage(glCanvas, 0, sourceY, targetCanvas.width, targetCanvas.height, 0, 0, targetCanvas.width, targetCanvas.height); } /** * Copy an input WebGL canvas on to an output 2D canvas using 2d canvas' putImageData * API. Measurably faster than using ctx.drawImage in Firefox (version 54 on OSX Sierra). * * @param {WebGLRenderingContext} sourceContext The WebGL context to copy from. * @param {HTMLCanvasElement} targetCanvas The 2D target canvas to copy on to. * @param {Object} pipelineState The 2D target canvas to copy on to. */ function copyGLTo2DPutImageData(gl, pipelineState) { var targetCanvas = pipelineState.targetCanvas, ctx = targetCanvas.getContext('2d'), dWidth = pipelineState.destinationWidth, dHeight = pipelineState.destinationHeight, numBytes = dWidth * dHeight * 4; // eslint-disable-next-line no-undef var u8 = new Uint8Array(this.imageBuffer, 0, numBytes); // eslint-disable-next-line no-undef var u8Clamped = new Uint8ClampedArray(this.imageBuffer, 0, numBytes); gl.readPixels(0, 0, dWidth, dHeight, gl.RGBA, gl.UNSIGNED_BYTE, u8); var imgData = new ImageData(u8Clamped, dWidth, dHeight); ctx.putImageData(imgData, 0, 0); } (function() { 'use strict'; var noop = function() {}; fabric.Canvas2dFilterBackend = Canvas2dFilterBackend; /** * Canvas 2D filter backend. */ function Canvas2dFilterBackend() {}; Canvas2dFilterBackend.prototype = /** @lends fabric.Canvas2dFilterBackend.prototype */ { evictCachesForKey: noop, dispose: noop, clearWebGLCaches: noop, /** * Experimental. This object is a sort of repository of help layers used to avoid * of recreating them during frequent filtering. If you are previewing a filter with * a slider you probably do not want to create help layers every filter step. * in this object there will be appended some canvases, created once, resized sometimes * cleared never. Clearing is left to the developer. **/ resources: { }, /** * Apply a set of filters against a source image and draw the filtered output * to the provided destination canvas. * * @param {EnhancedFilter} filters The filter to apply. * @param {HTMLImageElement|HTMLCanvasElement} sourceElement The source to be filtered. * @param {Number} sourceWidth The width of the source input. * @param {Number} sourceHeight The height of the source input. * @param {HTMLCanvasElement} targetCanvas The destination for filtered output to be drawn. */ applyFilters: function(filters, sourceElement, sourceWidth, sourceHeight, targetCanvas) { var ctx = targetCanvas.getContext('2d'); ctx.drawImage(sourceElement, 0, 0, sourceWidth, sourceHeight); var imageData = ctx.getImageData(0, 0, sourceWidth, sourceHeight); var originalImageData = ctx.getImageData(0, 0, sourceWidth, sourceHeight); var pipelineState = { sourceWidth: sourceWidth, sourceHeight: sourceHeight, imageData: imageData, originalEl: sourceElement, originalImageData: originalImageData, canvasEl: targetCanvas, ctx: ctx, filterBackend: this, }; filters.forEach(function(filter) { filter.applyTo(pipelineState); }); if (pipelineState.imageData.width !== sourceWidth || pipelineState.imageData.height !== sourceHeight) { targetCanvas.width = pipelineState.imageData.width; targetCanvas.height = pipelineState.imageData.height; } ctx.putImageData(pipelineState.imageData, 0, 0); return pipelineState; }, }; })(); /** * @namespace fabric.Image.filters * @memberOf fabric.Image * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#image_filters} * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} */ fabric.Image = fabric.Image || { }; fabric.Image.filters = fabric.Image.filters || { }; /** * Root filter class from which all filter classes inherit from * @class fabric.Image.filters.BaseFilter * @memberOf fabric.Image.filters */ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Image.filters.BaseFilter.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'BaseFilter', /** * Array of attributes to send with buffers. do not modify * @private */ vertexSource: 'attribute vec2 aPosition;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vTexCoord = aPosition;\n' + 'gl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0);\n' + '}', fragmentSource: 'precision highp float;\n' + 'varying vec2 vTexCoord;\n' + 'uniform sampler2D uTexture;\n' + 'void main() {\n' + 'gl_FragColor = texture2D(uTexture, vTexCoord);\n' + '}', /** * Constructor * @param {Object} [options] Options object */ initialize: function(options) { if (options) { this.setOptions(options); } }, /** * Sets filter's properties from options * @param {Object} [options] Options object */ setOptions: function(options) { for (var prop in options) { this[prop] = options[prop]; } }, /** * Compile this filter's shader program. * * @param {WebGLRenderingContext} gl The GL canvas context to use for shader compilation. * @param {String} fragmentSource fragmentShader source for compilation * @param {String} vertexSource vertexShader source for compilation */ createProgram: function(gl, fragmentSource, vertexSource) { fragmentSource = fragmentSource || this.fragmentSource; vertexSource = vertexSource || this.vertexSource; if (fabric.webGlPrecision !== 'highp'){ fragmentSource = fragmentSource.replace( /precision highp float/g, 'precision ' + fabric.webGlPrecision + ' float' ); } var vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertexSource); gl.compileShader(vertexShader); if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { throw new Error( // eslint-disable-next-line prefer-template 'Vertex shader compile error for ' + this.type + ': ' + gl.getShaderInfoLog(vertexShader) ); } var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentSource); gl.compileShader(fragmentShader); if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { throw new Error( // eslint-disable-next-line prefer-template 'Fragment shader compile error for ' + this.type + ': ' + gl.getShaderInfoLog(fragmentShader) ); } var program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { throw new Error( // eslint-disable-next-line prefer-template 'Shader link error for "${this.type}" ' + gl.getProgramInfoLog(program) ); } var attributeLocations = this.getAttributeLocations(gl, program); var uniformLocations = this.getUniformLocations(gl, program) || { }; uniformLocations.uStepW = gl.getUniformLocation(program, 'uStepW'); uniformLocations.uStepH = gl.getUniformLocation(program, 'uStepH'); return { program: program, attributeLocations: attributeLocations, uniformLocations: uniformLocations }; }, /** * Return a map of attribute names to WebGLAttributeLocation objects. * * @param {WebGLRenderingContext} gl The canvas context used to compile the shader program. * @param {WebGLShaderProgram} program The shader program from which to take attribute locations. * @returns {Object} A map of attribute names to attribute locations. */ getAttributeLocations: function(gl, program) { return { aPosition: gl.getAttribLocation(program, 'aPosition'), }; }, /** * Return a map of uniform names to WebGLUniformLocation objects. * * Intended to be overridden by subclasses. * * @param {WebGLRenderingContext} gl The canvas context used to compile the shader program. * @param {WebGLShaderProgram} program The shader program from which to take uniform locations. * @returns {Object} A map of uniform names to uniform locations. */ getUniformLocations: function (/* gl, program */) { // in case i do not need any special uniform i need to return an empty object return { }; }, /** * Send attribute data from this filter to its shader program on the GPU. * * @param {WebGLRenderingContext} gl The canvas context used to compile the shader program. * @param {Object} attributeLocations A map of shader attribute names to their locations. */ sendAttributeData: function(gl, attributeLocations, aPositionData) { var attributeLocation = attributeLocations.aPosition; var buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.enableVertexAttribArray(attributeLocation); gl.vertexAttribPointer(attributeLocation, 2, gl.FLOAT, false, 0, 0); gl.bufferData(gl.ARRAY_BUFFER, aPositionData, gl.STATIC_DRAW); }, _setupFrameBuffer: function(options) { var gl = options.context, width, height; if (options.passes > 1) { width = options.destinationWidth; height = options.destinationHeight; if (options.sourceWidth !== width || options.sourceHeight !== height) { gl.deleteTexture(options.targetTexture); options.targetTexture = options.filterBackend.createTexture(gl, width, height); } gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, options.targetTexture, 0); } else { // draw last filter on canvas and not to framebuffer. gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.finish(); } }, _swapTextures: function(options) { options.passes--; options.pass++; var temp = options.targetTexture; options.targetTexture = options.sourceTexture; options.sourceTexture = temp; }, /** * Generic isNeutral implementation for one parameter based filters. * Used only in image applyFilters to discard filters that will not have an effect * on the image * Other filters may need their own verison ( ColorMatrix, HueRotation, gamma, ComposedFilter ) * @param {Object} options **/ isNeutralState: function(/* options */) { var main = this.mainParameter, _class = fabric.Image.filters[this.type].prototype; if (main) { if (Array.isArray(_class[main])) { for (var i = _class[main].length; i--;) { if (this[main][i] !== _class[main][i]) { return false; } } return true; } else { return _class[main] === this[main]; } } else { return false; } }, /** * Apply this filter to the input image data provided. * * Determines whether to use WebGL or Canvas2D based on the options.webgl flag. * * @param {Object} options * @param {Number} options.passes The number of filters remaining to be executed * @param {Boolean} options.webgl Whether to use webgl to render the filter. * @param {WebGLTexture} options.sourceTexture The texture setup as the source to be filtered. * @param {WebGLTexture} options.targetTexture The texture where filtered output should be drawn. * @param {WebGLRenderingContext} options.context The GL context used for rendering. * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. */ applyTo: function(options) { if (options.webgl) { this._setupFrameBuffer(options); this.applyToWebGL(options); this._swapTextures(options); } else { this.applyTo2d(options); } }, /** * Retrieves the cached shader. * @param {Object} options * @param {WebGLRenderingContext} options.context The GL context used for rendering. * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. */ retrieveShader: function(options) { if (!options.programCache.hasOwnProperty(this.type)) { options.programCache[this.type] = this.createProgram(options.context); } return options.programCache[this.type]; }, /** * Apply this filter using webgl. * * @param {Object} options * @param {Number} options.passes The number of filters remaining to be executed * @param {Boolean} options.webgl Whether to use webgl to render the filter. * @param {WebGLTexture} options.originalTexture The texture of the original input image. * @param {WebGLTexture} options.sourceTexture The texture setup as the source to be filtered. * @param {WebGLTexture} options.targetTexture The texture where filtered output should be drawn. * @param {WebGLRenderingContext} options.context The GL context used for rendering. * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. */ applyToWebGL: function(options) { var gl = options.context; var shader = this.retrieveShader(options); if (options.pass === 0 && options.originalTexture) { gl.bindTexture(gl.TEXTURE_2D, options.originalTexture); } else { gl.bindTexture(gl.TEXTURE_2D, options.sourceTexture); } gl.useProgram(shader.program); this.sendAttributeData(gl, shader.attributeLocations, options.aPosition); gl.uniform1f(shader.uniformLocations.uStepW, 1 / options.sourceWidth); gl.uniform1f(shader.uniformLocations.uStepH, 1 / options.sourceHeight); this.sendUniformData(gl, shader.uniformLocations); gl.viewport(0, 0, options.destinationWidth, options.destinationHeight); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); }, bindAdditionalTexture: function(gl, texture, textureUnit) { gl.activeTexture(textureUnit); gl.bindTexture(gl.TEXTURE_2D, texture); // reset active texture to 0 as usual gl.activeTexture(gl.TEXTURE0); }, unbindAdditionalTexture: function(gl, textureUnit) { gl.activeTexture(textureUnit); gl.bindTexture(gl.TEXTURE_2D, null); gl.activeTexture(gl.TEXTURE0); }, getMainParameter: function() { return this[this.mainParameter]; }, setMainParameter: function(value) { this[this.mainParameter] = value; }, /** * Send uniform data from this filter to its shader program on the GPU. * * Intended to be overridden by subclasses. * * @param {WebGLRenderingContext} gl The canvas context used to compile the shader program. * @param {Object} uniformLocations A map of shader uniform names to their locations. */ sendUniformData: function(/* gl, uniformLocations */) { // Intentionally left blank. Override me in subclasses. }, /** * If needed by a 2d filter, this functions can create an helper canvas to be used * remember that options.targetCanvas is available for use till end of chain. */ createHelpLayer: function(options) { if (!options.helpLayer) { var helpLayer = document.createElement('canvas'); helpLayer.width = options.sourceWidth; helpLayer.height = options.sourceHeight; options.helpLayer = helpLayer; } }, /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { var object = { type: this.type }, mainP = this.mainParameter; if (mainP) { object[mainP] = this[mainP]; } return object; }, /** * Returns a JSON representation of an instance * @return {Object} JSON */ toJSON: function() { // delegate, not alias return this.toObject(); } }); fabric.Image.filters.BaseFilter.fromObject = function(object, callback) { var filter = new fabric.Image.filters[object.type](object); callback && callback(filter); return filter; }; (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), filters = fabric.Image.filters, createClass = fabric.util.createClass; /** * Color Matrix filter class * @class fabric.Image.filters.ColorMatrix * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.ColorMatrix#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} * @see {@Link http://www.webwasp.co.uk/tutorials/219/Color_Matrix_Filter.php} * @see {@Link http://phoboslab.org/log/2013/11/fast-image-filters-with-webgl} * @example <caption>Kodachrome filter</caption> * var filter = new fabric.Image.filters.ColorMatrix({ * matrix: [ 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, 0, 0, 0, 1, 0 ] * }); * object.filters.push(filter); * object.applyFilters(); */ filters.ColorMatrix = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.ColorMatrix.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'ColorMatrix', fragmentSource: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'varying vec2 vTexCoord;\n' + 'uniform mat4 uColorMatrix;\n' + 'uniform vec4 uConstants;\n' + 'void main() {\n' + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + 'color *= uColorMatrix;\n' + 'color += uConstants;\n' + 'gl_FragColor = color;\n' + '}', /** * Colormatrix for pixels. * array of 20 floats. Numbers in positions 4, 9, 14, 19 loose meaning * outside the -1, 1 range. * 0.0039215686 is the part of 1 that get translated to 1 in 2d * @param {Array} matrix array of 20 numbers. * @default */ matrix: [ 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0 ], mainParameter: 'matrix', /** * Lock the colormatrix on the color part, skipping alpha, manly for non webgl scenario * to save some calculation */ colorsOnly: true, /** * Constructor * @param {Object} [options] Options object */ initialize: function(options) { this.callSuper('initialize', options); // create a new array instead mutating the prototype with push this.matrix = this.matrix.slice(0); }, /** * Apply the ColorMatrix operation to a Uint8Array representing the pixels of an image. * * @param {Object} options * @param {ImageData} options.imageData The Uint8Array to be filtered. */ applyTo2d: function(options) { var imageData = options.imageData, data = imageData.data, iLen = data.length, m = this.matrix, r, g, b, a, i, colorsOnly = this.colorsOnly; for (i = 0; i < iLen; i += 4) { r = data[i]; g = data[i + 1]; b = data[i + 2]; if (colorsOnly) { data[i] = r * m[0] + g * m[1] + b * m[2] + m[4] * 255; data[i + 1] = r * m[5] + g * m[6] + b * m[7] + m[9] * 255; data[i + 2] = r * m[10] + g * m[11] + b * m[12] + m[14] * 255; } else { a = data[i + 3]; data[i] = r * m[0] + g * m[1] + b * m[2] + a * m[3] + m[4] * 255; data[i + 1] = r * m[5] + g * m[6] + b * m[7] + a * m[8] + m[9] * 255; data[i + 2] = r * m[10] + g * m[11] + b * m[12] + a * m[13] + m[14] * 255; data[i + 3] = r * m[15] + g * m[16] + b * m[17] + a * m[18] + m[19] * 255; } } }, /** * Return WebGL uniform locations for this filter's shader. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {WebGLShaderProgram} program This filter's compiled shader program. */ getUniformLocations: function(gl, program) { return { uColorMatrix: gl.getUniformLocation(program, 'uColorMatrix'), uConstants: gl.getUniformLocation(program, 'uConstants'), }; }, /** * Send data from this filter to its shader program's uniforms. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects */ sendUniformData: function(gl, uniformLocations) { var m = this.matrix, matrix = [ m[0], m[1], m[2], m[3], m[5], m[6], m[7], m[8], m[10], m[11], m[12], m[13], m[15], m[16], m[17], m[18] ], constants = [m[4], m[9], m[14], m[19]]; gl.uniformMatrix4fv(uniformLocations.uColorMatrix, false, matrix); gl.uniform4fv(uniformLocations.uConstants, constants); }, }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @param {function} [callback] function to invoke after filter creation * @return {fabric.Image.filters.ColorMatrix} Instance of fabric.Image.filters.ColorMatrix */ fabric.Image.filters.ColorMatrix.fromObject = fabric.Image.filters.BaseFilter.fromObject; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), filters = fabric.Image.filters, createClass = fabric.util.createClass; /** * Brightness filter class * @class fabric.Image.filters.Brightness * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} * @example * var filter = new fabric.Image.filters.Brightness({ * brightness: 0.05 * }); * object.filters.push(filter); * object.applyFilters(); */ filters.Brightness = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Brightness.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Brightness', /** * Fragment source for the brightness program */ fragmentSource: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform float uBrightness;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + 'color.rgb += uBrightness;\n' + 'gl_FragColor = color;\n' + '}', /** * Brightness value, from -1 to 1. * translated to -255 to 255 for 2d * 0.0039215686 is the part of 1 that get translated to 1 in 2d * @param {Number} brightness * @default */ brightness: 0, /** * Describe the property that is the filter parameter * @param {String} m * @default */ mainParameter: 'brightness', /** * Apply the Brightness operation to a Uint8ClampedArray representing the pixels of an image. * * @param {Object} options * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. */ applyTo2d: function(options) { if (this.brightness === 0) { return; } var imageData = options.imageData, data = imageData.data, i, len = data.length, brightness = Math.round(this.brightness * 255); for (i = 0; i < len; i += 4) { data[i] = data[i] + brightness; data[i + 1] = data[i + 1] + brightness; data[i + 2] = data[i + 2] + brightness; } }, /** * Return WebGL uniform locations for this filter's shader. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {WebGLShaderProgram} program This filter's compiled shader program. */ getUniformLocations: function(gl, program) { return { uBrightness: gl.getUniformLocation(program, 'uBrightness'), }; }, /** * Send data from this filter to its shader program's uniforms. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects */ sendUniformData: function(gl, uniformLocations) { gl.uniform1f(uniformLocations.uBrightness, this.brightness); }, }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @param {function} [callback] to be invoked after filter creation * @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness */ fabric.Image.filters.Brightness.fromObject = fabric.Image.filters.BaseFilter.fromObject; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, filters = fabric.Image.filters, createClass = fabric.util.createClass; /** * Adapted from <a href="http://www.html5rocks.com/en/tutorials/canvas/imagefilters/">html5rocks article</a> * @class fabric.Image.filters.Convolute * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.Convolute#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} * @example <caption>Sharpen filter</caption> * var filter = new fabric.Image.filters.Convolute({ * matrix: [ 0, -1, 0, * -1, 5, -1, * 0, -1, 0 ] * }); * object.filters.push(filter); * object.applyFilters(); * canvas.renderAll(); * @example <caption>Blur filter</caption> * var filter = new fabric.Image.filters.Convolute({ * matrix: [ 1/9, 1/9, 1/9, * 1/9, 1/9, 1/9, * 1/9, 1/9, 1/9 ] * }); * object.filters.push(filter); * object.applyFilters(); * canvas.renderAll(); * @example <caption>Emboss filter</caption> * var filter = new fabric.Image.filters.Convolute({ * matrix: [ 1, 1, 1, * 1, 0.7, -1, * -1, -1, -1 ] * }); * object.filters.push(filter); * object.applyFilters(); * canvas.renderAll(); * @example <caption>Emboss filter with opaqueness</caption> * var filter = new fabric.Image.filters.Convolute({ * opaque: true, * matrix: [ 1, 1, 1, * 1, 0.7, -1, * -1, -1, -1 ] * }); * object.filters.push(filter); * object.applyFilters(); * canvas.renderAll(); */ filters.Convolute = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Convolute.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Convolute', /* * Opaque value (true/false) */ opaque: false, /* * matrix for the filter, max 9x9 */ matrix: [0, 0, 0, 0, 1, 0, 0, 0, 0], /** * Fragment source for the brightness program */ fragmentSource: { Convolute_3_1: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform float uMatrix[9];\n' + 'uniform float uStepW;\n' + 'uniform float uStepH;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vec4 color = vec4(0, 0, 0, 0);\n' + 'for (float h = 0.0; h < 3.0; h+=1.0) {\n' + 'for (float w = 0.0; w < 3.0; w+=1.0) {\n' + 'vec2 matrixPos = vec2(uStepW * (w - 1), uStepH * (h - 1));\n' + 'color += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 3.0 + w)];\n' + '}\n' + '}\n' + 'gl_FragColor = color;\n' + '}', Convolute_3_0: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform float uMatrix[9];\n' + 'uniform float uStepW;\n' + 'uniform float uStepH;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vec4 color = vec4(0, 0, 0, 1);\n' + 'for (float h = 0.0; h < 3.0; h+=1.0) {\n' + 'for (float w = 0.0; w < 3.0; w+=1.0) {\n' + 'vec2 matrixPos = vec2(uStepW * (w - 1.0), uStepH * (h - 1.0));\n' + 'color.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 3.0 + w)];\n' + '}\n' + '}\n' + 'float alpha = texture2D(uTexture, vTexCoord).a;\n' + 'gl_FragColor = color;\n' + 'gl_FragColor.a = alpha;\n' + '}', Convolute_5_1: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform float uMatrix[25];\n' + 'uniform float uStepW;\n' + 'uniform float uStepH;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vec4 color = vec4(0, 0, 0, 0);\n' + 'for (float h = 0.0; h < 5.0; h+=1.0) {\n' + 'for (float w = 0.0; w < 5.0; w+=1.0) {\n' + 'vec2 matrixPos = vec2(uStepW * (w - 2.0), uStepH * (h - 2.0));\n' + 'color += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 5.0 + w)];\n' + '}\n' + '}\n' + 'gl_FragColor = color;\n' + '}', Convolute_5_0: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform float uMatrix[25];\n' + 'uniform float uStepW;\n' + 'uniform float uStepH;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vec4 color = vec4(0, 0, 0, 1);\n' + 'for (float h = 0.0; h < 5.0; h+=1.0) {\n' + 'for (float w = 0.0; w < 5.0; w+=1.0) {\n' + 'vec2 matrixPos = vec2(uStepW * (w - 2.0), uStepH * (h - 2.0));\n' + 'color.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 5.0 + w)];\n' + '}\n' + '}\n' + 'float alpha = texture2D(uTexture, vTexCoord).a;\n' + 'gl_FragColor = color;\n' + 'gl_FragColor.a = alpha;\n' + '}', Convolute_7_1: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform float uMatrix[49];\n' + 'uniform float uStepW;\n' + 'uniform float uStepH;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vec4 color = vec4(0, 0, 0, 0);\n' + 'for (float h = 0.0; h < 7.0; h+=1.0) {\n' + 'for (float w = 0.0; w < 7.0; w+=1.0) {\n' + 'vec2 matrixPos = vec2(uStepW * (w - 3.0), uStepH * (h - 3.0));\n' + 'color += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 7.0 + w)];\n' + '}\n' + '}\n' + 'gl_FragColor = color;\n' + '}', Convolute_7_0: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform float uMatrix[49];\n' + 'uniform float uStepW;\n' + 'uniform float uStepH;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vec4 color = vec4(0, 0, 0, 1);\n' + 'for (float h = 0.0; h < 7.0; h+=1.0) {\n' + 'for (float w = 0.0; w < 7.0; w+=1.0) {\n' + 'vec2 matrixPos = vec2(uStepW * (w - 3.0), uStepH * (h - 3.0));\n' + 'color.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 7.0 + w)];\n' + '}\n' + '}\n' + 'float alpha = texture2D(uTexture, vTexCoord).a;\n' + 'gl_FragColor = color;\n' + 'gl_FragColor.a = alpha;\n' + '}', Convolute_9_1: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform float uMatrix[81];\n' + 'uniform float uStepW;\n' + 'uniform float uStepH;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vec4 color = vec4(0, 0, 0, 0);\n' + 'for (float h = 0.0; h < 9.0; h+=1.0) {\n' + 'for (float w = 0.0; w < 9.0; w+=1.0) {\n' + 'vec2 matrixPos = vec2(uStepW * (w - 4.0), uStepH * (h - 4.0));\n' + 'color += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 9.0 + w)];\n' + '}\n' + '}\n' + 'gl_FragColor = color;\n' + '}', Convolute_9_0: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform float uMatrix[81];\n' + 'uniform float uStepW;\n' + 'uniform float uStepH;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vec4 color = vec4(0, 0, 0, 1);\n' + 'for (float h = 0.0; h < 9.0; h+=1.0) {\n' + 'for (float w = 0.0; w < 9.0; w+=1.0) {\n' + 'vec2 matrixPos = vec2(uStepW * (w - 4.0), uStepH * (h - 4.0));\n' + 'color.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 9.0 + w)];\n' + '}\n' + '}\n' + 'float alpha = texture2D(uTexture, vTexCoord).a;\n' + 'gl_FragColor = color;\n' + 'gl_FragColor.a = alpha;\n' + '}', }, /** * Constructor * @memberOf fabric.Image.filters.Convolute.prototype * @param {Object} [options] Options object * @param {Boolean} [options.opaque=false] Opaque value (true/false) * @param {Array} [options.matrix] Filter matrix */ /** * Retrieves the cached shader. * @param {Object} options * @param {WebGLRenderingContext} options.context The GL context used for rendering. * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. */ retrieveShader: function(options) { var size = Math.sqrt(this.matrix.length); var cacheKey = this.type + '_' + size + '_' + (this.opaque ? 1 : 0); var shaderSource = this.fragmentSource[cacheKey]; if (!options.programCache.hasOwnProperty(cacheKey)) { options.programCache[cacheKey] = this.createProgram(options.context, shaderSource); } return options.programCache[cacheKey]; }, /** * Apply the Brightness operation to a Uint8ClampedArray representing the pixels of an image. * * @param {Object} options * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. */ applyTo2d: function(options) { var imageData = options.imageData, data = imageData.data, weights = this.matrix, side = Math.round(Math.sqrt(weights.length)), halfSide = Math.floor(side / 2), sw = imageData.width, sh = imageData.height, output = options.ctx.createImageData(sw, sh), dst = output.data, // go through the destination image pixels alphaFac = this.opaque ? 1 : 0, r, g, b, a, dstOff, scx, scy, srcOff, wt, x, y, cx, cy; for (y = 0; y < sh; y++) { for (x = 0; x < sw; x++) { dstOff = (y * sw + x) * 4; // calculate the weighed sum of the source image pixels that // fall under the convolution matrix r = 0; g = 0; b = 0; a = 0; for (cy = 0; cy < side; cy++) { for (cx = 0; cx < side; cx++) { scy = y + cy - halfSide; scx = x + cx - halfSide; // eslint-disable-next-line max-depth if (scy < 0 || scy > sh || scx < 0 || scx > sw) { continue; } srcOff = (scy * sw + scx) * 4; wt = weights[cy * side + cx]; r += data[srcOff] * wt; g += data[srcOff + 1] * wt; b += data[srcOff + 2] * wt; // eslint-disable-next-line max-depth if (!alphaFac) { a += data[srcOff + 3] * wt; } } } dst[dstOff] = r; dst[dstOff + 1] = g; dst[dstOff + 2] = b; if (!alphaFac) { dst[dstOff + 3] = a; } else { dst[dstOff + 3] = data[dstOff + 3]; } } } options.imageData = output; }, /** * Return WebGL uniform locations for this filter's shader. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {WebGLShaderProgram} program This filter's compiled shader program. */ getUniformLocations: function(gl, program) { return { uMatrix: gl.getUniformLocation(program, 'uMatrix'), uOpaque: gl.getUniformLocation(program, 'uOpaque'), uHalfSize: gl.getUniformLocation(program, 'uHalfSize'), uSize: gl.getUniformLocation(program, 'uSize'), }; }, /** * Send data from this filter to its shader program's uniforms. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects */ sendUniformData: function(gl, uniformLocations) { gl.uniform1fv(uniformLocations.uMatrix, this.matrix); }, /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { return extend(this.callSuper('toObject'), { opaque: this.opaque, matrix: this.matrix }); } }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @param {function} [callback] to be invoked after filter creation * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute */ fabric.Image.filters.Convolute.fromObject = fabric.Image.filters.BaseFilter.fromObject; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), filters = fabric.Image.filters, createClass = fabric.util.createClass; /** * Grayscale image filter class * @class fabric.Image.filters.Grayscale * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} * @example * var filter = new fabric.Image.filters.Grayscale(); * object.filters.push(filter); * object.applyFilters(); */ filters.Grayscale = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Grayscale.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Grayscale', fragmentSource: { average: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + 'float average = (color.r + color.b + color.g) / 3.0;\n' + 'gl_FragColor = vec4(average, average, average, color.a);\n' + '}', lightness: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform int uMode;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vec4 col = texture2D(uTexture, vTexCoord);\n' + 'float average = (max(max(col.r, col.g),col.b) + min(min(col.r, col.g),col.b)) / 2.0;\n' + 'gl_FragColor = vec4(average, average, average, col.a);\n' + '}', luminosity: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform int uMode;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vec4 col = texture2D(uTexture, vTexCoord);\n' + 'float average = 0.21 * col.r + 0.72 * col.g + 0.07 * col.b;\n' + 'gl_FragColor = vec4(average, average, average, col.a);\n' + '}', }, /** * Grayscale mode, between 'average', 'lightness', 'luminosity' * @param {String} type * @default */ mode: 'average', mainParameter: 'mode', /** * Apply the Grayscale operation to a Uint8Array representing the pixels of an image. * * @param {Object} options * @param {ImageData} options.imageData The Uint8Array to be filtered. */ applyTo2d: function(options) { var imageData = options.imageData, data = imageData.data, i, len = data.length, value, mode = this.mode; for (i = 0; i < len; i += 4) { if (mode === 'average') { value = (data[i] + data[i + 1] + data[i + 2]) / 3; } else if (mode === 'lightness') { value = (Math.min(data[i], data[i + 1], data[i + 2]) + Math.max(data[i], data[i + 1], data[i + 2])) / 2; } else if (mode === 'luminosity') { value = 0.21 * data[i] + 0.72 * data[i + 1] + 0.07 * data[i + 2]; } data[i] = value; data[i + 1] = value; data[i + 2] = value; } }, /** * Retrieves the cached shader. * @param {Object} options * @param {WebGLRenderingContext} options.context The GL context used for rendering. * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. */ retrieveShader: function(options) { var cacheKey = this.type + '_' + this.mode; if (!options.programCache.hasOwnProperty(cacheKey)) { var shaderSource = this.fragmentSource[this.mode]; options.programCache[cacheKey] = this.createProgram(options.context, shaderSource); } return options.programCache[cacheKey]; }, /** * Return WebGL uniform locations for this filter's shader. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {WebGLShaderProgram} program This filter's compiled shader program. */ getUniformLocations: function(gl, program) { return { uMode: gl.getUniformLocation(program, 'uMode'), }; }, /** * Send data from this filter to its shader program's uniforms. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects */ sendUniformData: function(gl, uniformLocations) { // default average mode. var mode = 1; gl.uniform1i(uniformLocations.uMode, mode); }, /** * Grayscale filter isNeutralState implementation * The filter is never neutral * on the image **/ isNeutralState: function() { return false; }, }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @param {function} [callback] to be invoked after filter creation * @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale */ fabric.Image.filters.Grayscale.fromObject = fabric.Image.filters.BaseFilter.fromObject; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), filters = fabric.Image.filters, createClass = fabric.util.createClass; /** * Invert filter class * @class fabric.Image.filters.Invert * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} * @example * var filter = new fabric.Image.filters.Invert(); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); */ filters.Invert = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Invert.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Invert', fragmentSource: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform int uInvert;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + 'if (uInvert == 1) {\n' + 'gl_FragColor = vec4(1.0 - color.r,1.0 -color.g,1.0 -color.b,color.a);\n' + '} else {\n' + 'gl_FragColor = color;\n' + '}\n' + '}', /** * Filter invert. if false, does nothing * @param {Boolean} invert * @default */ invert: true, mainParameter: 'invert', /** * Apply the Invert operation to a Uint8Array representing the pixels of an image. * * @param {Object} options * @param {ImageData} options.imageData The Uint8Array to be filtered. */ applyTo2d: function(options) { var imageData = options.imageData, data = imageData.data, i, len = data.length; for (i = 0; i < len; i += 4) { data[i] = 255 - data[i]; data[i + 1] = 255 - data[i + 1]; data[i + 2] = 255 - data[i + 2]; } }, /** * Invert filter isNeutralState implementation * Used only in image applyFilters to discard filters that will not have an effect * on the image * @param {Object} options **/ isNeutralState: function() { return !this.invert; }, /** * Return WebGL uniform locations for this filter's shader. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {WebGLShaderProgram} program This filter's compiled shader program. */ getUniformLocations: function(gl, program) { return { uInvert: gl.getUniformLocation(program, 'uInvert'), }; }, /** * Send data from this filter to its shader program's uniforms. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects */ sendUniformData: function(gl, uniformLocations) { gl.uniform1i(uniformLocations.uInvert, this.invert); }, }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @param {function} [callback] to be invoked after filter creation * @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert */ fabric.Image.filters.Invert.fromObject = fabric.Image.filters.BaseFilter.fromObject; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, filters = fabric.Image.filters, createClass = fabric.util.createClass; /** * Noise filter class * @class fabric.Image.filters.Noise * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.Noise#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} * @example * var filter = new fabric.Image.filters.Noise({ * noise: 700 * }); * object.filters.push(filter); * object.applyFilters(); * canvas.renderAll(); */ filters.Noise = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Noise.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Noise', /** * Fragment source for the noise program */ fragmentSource: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform float uStepH;\n' + 'uniform float uNoise;\n' + 'uniform float uSeed;\n' + 'varying vec2 vTexCoord;\n' + 'float rand(vec2 co, float seed, float vScale) {\n' + 'return fract(sin(dot(co.xy * vScale ,vec2(12.9898 , 78.233))) * 43758.5453 * (seed + 0.01) / 2.0);\n' + '}\n' + 'void main() {\n' + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + 'color.rgb += (0.5 - rand(vTexCoord, uSeed, 0.1 / uStepH)) * uNoise;\n' + 'gl_FragColor = color;\n' + '}', /** * Describe the property that is the filter parameter * @param {String} m * @default */ mainParameter: 'noise', /** * Noise value, from * @param {Number} noise * @default */ noise: 0, /** * Apply the Brightness operation to a Uint8ClampedArray representing the pixels of an image. * * @param {Object} options * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. */ applyTo2d: function(options) { if (this.noise === 0) { return; } var imageData = options.imageData, data = imageData.data, i, len = data.length, noise = this.noise, rand; for (i = 0, len = data.length; i < len; i += 4) { rand = (0.5 - Math.random()) * noise; data[i] += rand; data[i + 1] += rand; data[i + 2] += rand; } }, /** * Return WebGL uniform locations for this filter's shader. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {WebGLShaderProgram} program This filter's compiled shader program. */ getUniformLocations: function(gl, program) { return { uNoise: gl.getUniformLocation(program, 'uNoise'), uSeed: gl.getUniformLocation(program, 'uSeed'), }; }, /** * Send data from this filter to its shader program's uniforms. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects */ sendUniformData: function(gl, uniformLocations) { gl.uniform1f(uniformLocations.uNoise, this.noise / 255); gl.uniform1f(uniformLocations.uSeed, Math.random()); }, /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { return extend(this.callSuper('toObject'), { noise: this.noise }); } }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @param {Function} [callback] to be invoked after filter creation * @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise */ fabric.Image.filters.Noise.fromObject = fabric.Image.filters.BaseFilter.fromObject; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), filters = fabric.Image.filters, createClass = fabric.util.createClass; /** * Pixelate filter class * @class fabric.Image.filters.Pixelate * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.Pixelate#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} * @example * var filter = new fabric.Image.filters.Pixelate({ * blocksize: 8 * }); * object.filters.push(filter); * object.applyFilters(); */ filters.Pixelate = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Pixelate.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Pixelate', blocksize: 4, mainParameter: 'blocksize', /** * Fragment source for the Pixelate program */ fragmentSource: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform float uBlocksize;\n' + 'uniform float uStepW;\n' + 'uniform float uStepH;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'float blockW = uBlocksize * uStepW;\n' + 'float blockH = uBlocksize * uStepW;\n' + 'int posX = int(vTexCoord.x / blockW);\n' + 'int posY = int(vTexCoord.y / blockH);\n' + 'float fposX = float(posX);\n' + 'float fposY = float(posY);\n' + 'vec2 squareCoords = vec2(fposX * blockW, fposY * blockH);\n' + 'vec4 color = texture2D(uTexture, squareCoords);\n' + 'gl_FragColor = color;\n' + '}', /** * Apply the Pixelate operation to a Uint8ClampedArray representing the pixels of an image. * * @param {Object} options * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. */ applyTo2d: function(options) { var imageData = options.imageData, data = imageData.data, iLen = imageData.height, jLen = imageData.width, index, i, j, r, g, b, a, _i, _j, _iLen, _jLen; for (i = 0; i < iLen; i += this.blocksize) { for (j = 0; j < jLen; j += this.blocksize) { index = (i * 4) * jLen + (j * 4); r = data[index]; g = data[index + 1]; b = data[index + 2]; a = data[index + 3]; _iLen = Math.min(i + this.blocksize, iLen); _jLen = Math.min(j + this.blocksize, jLen); for (_i = i; _i < _iLen; _i++) { for (_j = j; _j < _jLen; _j++) { index = (_i * 4) * jLen + (_j * 4); data[index] = r; data[index + 1] = g; data[index + 2] = b; data[index + 3] = a; } } } } }, /** * Indicate when the filter is not gonna apply changes to the image **/ isNeutralState: function() { return this.blocksize === 1; }, /** * Return WebGL uniform locations for this filter's shader. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {WebGLShaderProgram} program This filter's compiled shader program. */ getUniformLocations: function(gl, program) { return { uBlocksize: gl.getUniformLocation(program, 'uBlocksize'), uStepW: gl.getUniformLocation(program, 'uStepW'), uStepH: gl.getUniformLocation(program, 'uStepH'), }; }, /** * Send data from this filter to its shader program's uniforms. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects */ sendUniformData: function(gl, uniformLocations) { gl.uniform1f(uniformLocations.uBlocksize, this.blocksize); }, }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @param {Function} [callback] to be invoked after filter creation * @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate */ fabric.Image.filters.Pixelate.fromObject = fabric.Image.filters.BaseFilter.fromObject; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, filters = fabric.Image.filters, createClass = fabric.util.createClass; /** * Remove white filter class * @class fabric.Image.filters.RemoveColor * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.RemoveColor#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} * @example * var filter = new fabric.Image.filters.RemoveColor({ * threshold: 0.2, * }); * object.filters.push(filter); * object.applyFilters(); * canvas.renderAll(); */ filters.RemoveColor = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.RemoveColor.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'RemoveColor', /** * Color to remove, in any format understood by fabric.Color. * @param {String} type * @default */ color: '#FFFFFF', /** * Fragment source for the brightness program */ fragmentSource: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform vec4 uLow;\n' + 'uniform vec4 uHigh;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'gl_FragColor = texture2D(uTexture, vTexCoord);\n' + 'if(all(greaterThan(gl_FragColor.rgb,uLow.rgb)) && all(greaterThan(uHigh.rgb,gl_FragColor.rgb))) {\n' + 'gl_FragColor.a = 0.0;\n' + '}\n' + '}', /** * distance to actual color, as value up or down from each r,g,b * between 0 and 1 **/ distance: 0.02, /** * For color to remove inside distance, use alpha channel for a smoother deletion * NOT IMPLEMENTED YET **/ useAlpha: false, /** * Constructor * @memberOf fabric.Image.filters.RemoveWhite.prototype * @param {Object} [options] Options object * @param {Number} [options.color=#RRGGBB] Threshold value * @param {Number} [options.distance=10] Distance value */ /** * Applies filter to canvas element * @param {Object} canvasEl Canvas element to apply filter to */ applyTo2d: function(options) { var imageData = options.imageData, data = imageData.data, i, distance = this.distance * 255, r, g, b, source = new fabric.Color(this.color).getSource(), lowC = [ source[0] - distance, source[1] - distance, source[2] - distance, ], highC = [ source[0] + distance, source[1] + distance, source[2] + distance, ]; for (i = 0; i < data.length; i += 4) { r = data[i]; g = data[i + 1]; b = data[i + 2]; if (r > lowC[0] && g > lowC[1] && b > lowC[2] && r < highC[0] && g < highC[1] && b < highC[2]) { data[i + 3] = 0; } } }, /** * Return WebGL uniform locations for this filter's shader. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {WebGLShaderProgram} program This filter's compiled shader program. */ getUniformLocations: function(gl, program) { return { uLow: gl.getUniformLocation(program, 'uLow'), uHigh: gl.getUniformLocation(program, 'uHigh'), }; }, /** * Send data from this filter to its shader program's uniforms. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects */ sendUniformData: function(gl, uniformLocations) { var source = new fabric.Color(this.color).getSource(), distance = parseFloat(this.distance), lowC = [ 0 + source[0] / 255 - distance, 0 + source[1] / 255 - distance, 0 + source[2] / 255 - distance, 1 ], highC = [ source[0] / 255 + distance, source[1] / 255 + distance, source[2] / 255 + distance, 1 ]; gl.uniform4fv(uniformLocations.uLow, lowC); gl.uniform4fv(uniformLocations.uHigh, highC); }, /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { return extend(this.callSuper('toObject'), { color: this.color, distance: this.distance }); } }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @param {Function} [callback] to be invoked after filter creation * @return {fabric.Image.filters.RemoveColor} Instance of fabric.Image.filters.RemoveWhite */ fabric.Image.filters.RemoveColor.fromObject = fabric.Image.filters.BaseFilter.fromObject; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), filters = fabric.Image.filters, createClass = fabric.util.createClass; var matrices = { Brownie: [ 0.59970,0.34553,-0.27082,0,0.186, -0.03770,0.86095,0.15059,0,-0.1449, 0.24113,-0.07441,0.44972,0,-0.02965, 0,0,0,1,0 ], Vintage: [ 0.62793,0.32021,-0.03965,0,0.03784, 0.02578,0.64411,0.03259,0,0.02926, 0.04660,-0.08512,0.52416,0,0.02023, 0,0,0,1,0 ], Kodachrome: [ 1.12855,-0.39673,-0.03992,0,0.24991, -0.16404,1.08352,-0.05498,0,0.09698, -0.16786,-0.56034,1.60148,0,0.13972, 0,0,0,1,0 ], Technicolor: [ 1.91252,-0.85453,-0.09155,0,0.04624, -0.30878,1.76589,-0.10601,0,-0.27589, -0.23110,-0.75018,1.84759,0,0.12137, 0,0,0,1,0 ], Polaroid: [ 1.438,-0.062,-0.062,0,0, -0.122,1.378,-0.122,0,0, -0.016,-0.016,1.483,0,0, 0,0,0,1,0 ], Sepia: [ 0.393, 0.769, 0.189, 0, 0, 0.349, 0.686, 0.168, 0, 0, 0.272, 0.534, 0.131, 0, 0, 0, 0, 0, 1, 0 ], BlackWhite: [ 1.5, 1.5, 1.5, 0, -1, 1.5, 1.5, 1.5, 0, -1, 1.5, 1.5, 1.5, 0, -1, 0, 0, 0, 1, 0, ] }; for (var key in matrices) { filters[key] = createClass(filters.ColorMatrix, /** @lends fabric.Image.filters.Sepia.prototype */ { /** * Filter type * @param {String} type * @default */ type: key, /** * Colormatrix for the effect * array of 20 floats. Numbers in positions 4, 9, 14, 19 loose meaning * outside the -1, 1 range. * @param {Array} matrix array of 20 numbers. * @default */ matrix: matrices[key], /** * Lock the matrix export for this kind of static, parameter less filters. */ mainParameter: false, /** * Lock the colormatrix on the color part, skipping alpha */ colorsOnly: true, }); fabric.Image.filters[key].fromObject = fabric.Image.filters.BaseFilter.fromObject; } })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric, filters = fabric.Image.filters, createClass = fabric.util.createClass; /** * Color Blend filter class * @class fabric.Image.filter.BlendColor * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @example * var filter = new fabric.Image.filters.BlendColor({ * color: '#000', * mode: 'multiply' * }); * * var filter = new fabric.Image.filters.BlendImage({ * image: fabricImageObject, * mode: 'multiply', * alpha: 0.5 * }); * object.filters.push(filter); * object.applyFilters(); * canvas.renderAll(); */ filters.BlendColor = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Blend.prototype */ { type: 'BlendColor', /** * Color to make the blend operation with. default to a reddish color since black or white * gives always strong result. **/ color: '#F95C63', /** * Blend mode for the filter: one of multiply, add, diff, screen, subtract, * darken, lighten, overlay, exclusion, tint. **/ mode: 'multiply', /** * alpha value. represent the strength of the blend color operation. **/ alpha: 1, /** * Fragment source for the Multiply program */ fragmentSource: { multiply: 'gl_FragColor.rgb *= uColor.rgb;\n', screen: 'gl_FragColor.rgb = 1.0 - (1.0 - gl_FragColor.rgb) * (1.0 - uColor.rgb);\n', add: 'gl_FragColor.rgb += uColor.rgb;\n', diff: 'gl_FragColor.rgb = abs(gl_FragColor.rgb - uColor.rgb);\n', subtract: 'gl_FragColor.rgb -= uColor.rgb;\n', lighten: 'gl_FragColor.rgb = max(gl_FragColor.rgb, uColor.rgb);\n', darken: 'gl_FragColor.rgb = min(gl_FragColor.rgb, uColor.rgb);\n', exclusion: 'gl_FragColor.rgb += uColor.rgb - 2.0 * (uColor.rgb * gl_FragColor.rgb);\n', overlay: 'if (uColor.r < 0.5) {\n' + 'gl_FragColor.r *= 2.0 * uColor.r;\n' + '} else {\n' + 'gl_FragColor.r = 1.0 - 2.0 * (1.0 - gl_FragColor.r) * (1.0 - uColor.r);\n' + '}\n' + 'if (uColor.g < 0.5) {\n' + 'gl_FragColor.g *= 2.0 * uColor.g;\n' + '} else {\n' + 'gl_FragColor.g = 1.0 - 2.0 * (1.0 - gl_FragColor.g) * (1.0 - uColor.g);\n' + '}\n' + 'if (uColor.b < 0.5) {\n' + 'gl_FragColor.b *= 2.0 * uColor.b;\n' + '} else {\n' + 'gl_FragColor.b = 1.0 - 2.0 * (1.0 - gl_FragColor.b) * (1.0 - uColor.b);\n' + '}\n', tint: 'gl_FragColor.rgb *= (1.0 - uColor.a);\n' + 'gl_FragColor.rgb += uColor.rgb;\n', }, /** * build the fragment source for the filters, joining the common part with * the specific one. * @param {String} mode the mode of the filter, a key of this.fragmentSource * @return {String} the source to be compiled * @private */ buildSource: function(mode) { return 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform vec4 uColor;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + 'gl_FragColor = color;\n' + 'if (color.a > 0.0) {\n' + this.fragmentSource[mode] + '}\n' + '}'; }, /** * Retrieves the cached shader. * @param {Object} options * @param {WebGLRenderingContext} options.context The GL context used for rendering. * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. */ retrieveShader: function(options) { var cacheKey = this.type + '_' + this.mode, shaderSource; if (!options.programCache.hasOwnProperty(cacheKey)) { shaderSource = this.buildSource(this.mode); options.programCache[cacheKey] = this.createProgram(options.context, shaderSource); } return options.programCache[cacheKey]; }, /** * Apply the Blend operation to a Uint8ClampedArray representing the pixels of an image. * * @param {Object} options * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. */ applyTo2d: function(options) { var imageData = options.imageData, data = imageData.data, iLen = data.length, tr, tg, tb, r, g, b, source, alpha1 = 1 - this.alpha; source = new fabric.Color(this.color).getSource(); tr = source[0] * this.alpha; tg = source[1] * this.alpha; tb = source[2] * this.alpha; for (var i = 0; i < iLen; i += 4) { r = data[i]; g = data[i + 1]; b = data[i + 2]; switch (this.mode) { case 'multiply': data[i] = r * tr / 255; data[i + 1] = g * tg / 255; data[i + 2] = b * tb / 255; break; case 'screen': data[i] = 255 - (255 - r) * (255 - tr) / 255; data[i + 1] = 255 - (255 - g) * (255 - tg) / 255; data[i + 2] = 255 - (255 - b) * (255 - tb) / 255; break; case 'add': data[i] = r + tr; data[i + 1] = g + tg; data[i + 2] = b + tb; break; case 'diff': case 'difference': data[i] = Math.abs(r - tr); data[i + 1] = Math.abs(g - tg); data[i + 2] = Math.abs(b - tb); break; case 'subtract': data[i] = r - tr; data[i + 1] = g - tg; data[i + 2] = b - tb; break; case 'darken': data[i] = Math.min(r, tr); data[i + 1] = Math.min(g, tg); data[i + 2] = Math.min(b, tb); break; case 'lighten': data[i] = Math.max(r, tr); data[i + 1] = Math.max(g, tg); data[i + 2] = Math.max(b, tb); break; case 'overlay': data[i] = tr < 128 ? (2 * r * tr / 255) : (255 - 2 * (255 - r) * (255 - tr) / 255); data[i + 1] = tg < 128 ? (2 * g * tg / 255) : (255 - 2 * (255 - g) * (255 - tg) / 255); data[i + 2] = tb < 128 ? (2 * b * tb / 255) : (255 - 2 * (255 - b) * (255 - tb) / 255); break; case 'exclusion': data[i] = tr + r - ((2 * tr * r) / 255); data[i + 1] = tg + g - ((2 * tg * g) / 255); data[i + 2] = tb + b - ((2 * tb * b) / 255); break; case 'tint': data[i] = tr + r * alpha1; data[i + 1] = tg + g * alpha1; data[i + 2] = tb + b * alpha1; } } }, /** * Return WebGL uniform locations for this filter's shader. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {WebGLShaderProgram} program This filter's compiled shader program. */ getUniformLocations: function(gl, program) { return { uColor: gl.getUniformLocation(program, 'uColor'), }; }, /** * Send data from this filter to its shader program's uniforms. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects */ sendUniformData: function(gl, uniformLocations) { var source = new fabric.Color(this.color).getSource(); source[0] = this.alpha * source[0] / 255; source[1] = this.alpha * source[1] / 255; source[2] = this.alpha * source[2] / 255; source[3] = this.alpha; gl.uniform4fv(uniformLocations.uColor, source); }, /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { return { type: this.type, color: this.color, mode: this.mode, alpha: this.alpha }; } }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @param {function} [callback] to be invoked after filter creation * @return {fabric.Image.filters.BlendColor} Instance of fabric.Image.filters.BlendColor */ fabric.Image.filters.BlendColor.fromObject = fabric.Image.filters.BaseFilter.fromObject; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric, filters = fabric.Image.filters, createClass = fabric.util.createClass; /** * Image Blend filter class * @class fabric.Image.filter.BlendImage * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @example * var filter = new fabric.Image.filters.BlendColor({ * color: '#000', * mode: 'multiply' * }); * * var filter = new fabric.Image.filters.BlendImage({ * image: fabricImageObject, * mode: 'multiply', * alpha: 0.5 * }); * object.filters.push(filter); * object.applyFilters(); * canvas.renderAll(); */ filters.BlendImage = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.BlendImage.prototype */ { type: 'BlendImage', /** * Color to make the blend operation with. default to a reddish color since black or white * gives always strong result. **/ image: null, /** * Blend mode for the filter: one of multiply, add, diff, screen, subtract, * darken, lighten, overlay, exclusion, tint. **/ mode: 'multiply', /** * alpha value. represent the strength of the blend image operation. * not implemented. **/ alpha: 1, vertexSource: 'attribute vec2 aPosition;\n' + 'varying vec2 vTexCoord;\n' + 'varying vec2 vTexCoord2;\n' + 'uniform mat3 uTransformMatrix;\n' + 'void main() {\n' + 'vTexCoord = aPosition;\n' + 'vTexCoord2 = (uTransformMatrix * vec3(aPosition, 1.0)).xy;\n' + 'gl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0);\n' + '}', /** * Fragment source for the Multiply program */ fragmentSource: { multiply: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform sampler2D uImage;\n' + 'uniform vec4 uColor;\n' + 'varying vec2 vTexCoord;\n' + 'varying vec2 vTexCoord2;\n' + 'void main() {\n' + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + 'vec4 color2 = texture2D(uImage, vTexCoord2);\n' + 'color.rgba *= color2.rgba;\n' + 'gl_FragColor = color;\n' + '}', mask: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform sampler2D uImage;\n' + 'uniform vec4 uColor;\n' + 'varying vec2 vTexCoord;\n' + 'varying vec2 vTexCoord2;\n' + 'void main() {\n' + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + 'vec4 color2 = texture2D(uImage, vTexCoord2);\n' + 'color.a = color2.a;\n' + 'gl_FragColor = color;\n' + '}', }, /** * Retrieves the cached shader. * @param {Object} options * @param {WebGLRenderingContext} options.context The GL context used for rendering. * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. */ retrieveShader: function(options) { var cacheKey = this.type + '_' + this.mode; var shaderSource = this.fragmentSource[this.mode]; if (!options.programCache.hasOwnProperty(cacheKey)) { options.programCache[cacheKey] = this.createProgram(options.context, shaderSource); } return options.programCache[cacheKey]; }, applyToWebGL: function(options) { // load texture to blend. var gl = options.context, texture = this.createTexture(options.filterBackend, this.image); this.bindAdditionalTexture(gl, texture, gl.TEXTURE1); this.callSuper('applyToWebGL', options); this.unbindAdditionalTexture(gl, gl.TEXTURE1); }, createTexture: function(backend, image) { return backend.getCachedTexture(image.cacheKey, image._element); }, /** * Calculate a transformMatrix to adapt the image to blend over * @param {Object} options * @param {WebGLRenderingContext} options.context The GL context used for rendering. * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. */ calculateMatrix: function() { var image = this.image, width = image._element.width, height = image._element.height; return [ 1 / image.scaleX, 0, 0, 0, 1 / image.scaleY, 0, -image.left / width, -image.top / height, 1 ]; }, /** * Apply the Blend operation to a Uint8ClampedArray representing the pixels of an image. * * @param {Object} options * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. */ applyTo2d: function(options) { var imageData = options.imageData, resources = options.filterBackend.resources, data = imageData.data, iLen = data.length, width = imageData.width, height = imageData.height, tr, tg, tb, ta, r, g, b, a, canvas1, context, image = this.image, blendData; if (!resources.blendImage) { resources.blendImage = fabric.util.createCanvasElement(); } canvas1 = resources.blendImage; context = canvas1.getContext('2d'); if (canvas1.width !== width || canvas1.height !== height) { canvas1.width = width; canvas1.height = height; } else { context.clearRect(0, 0, width, height); } context.setTransform(image.scaleX, 0, 0, image.scaleY, image.left, image.top); context.drawImage(image._element, 0, 0, width, height); blendData = context.getImageData(0, 0, width, height).data; for (var i = 0; i < iLen; i += 4) { r = data[i]; g = data[i + 1]; b = data[i + 2]; a = data[i + 3]; tr = blendData[i]; tg = blendData[i + 1]; tb = blendData[i + 2]; ta = blendData[i + 3]; switch (this.mode) { case 'multiply': data[i] = r * tr / 255; data[i + 1] = g * tg / 255; data[i + 2] = b * tb / 255; data[i + 3] = a * ta / 255; break; case 'mask': data[i + 3] = ta; break; } } }, /** * Return WebGL uniform locations for this filter's shader. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {WebGLShaderProgram} program This filter's compiled shader program. */ getUniformLocations: function(gl, program) { return { uTransformMatrix: gl.getUniformLocation(program, 'uTransformMatrix'), uImage: gl.getUniformLocation(program, 'uImage'), }; }, /** * Send data from this filter to its shader program's uniforms. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects */ sendUniformData: function(gl, uniformLocations) { var matrix = this.calculateMatrix(); gl.uniform1i(uniformLocations.uImage, 1); // texture unit 1. gl.uniformMatrix3fv(uniformLocations.uTransformMatrix, false, matrix); }, /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { return { type: this.type, image: this.image && this.image.toObject(), mode: this.mode, alpha: this.alpha }; } }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @param {function} callback to be invoked after filter creation * @return {fabric.Image.filters.BlendImage} Instance of fabric.Image.filters.BlendImage */ fabric.Image.filters.BlendImage.fromObject = function(object, callback) { fabric.Image.fromObject(object.image, function(image) { var options = fabric.util.object.clone(object); options.image = image; callback(new fabric.Image.filters.BlendImage(options)); }); }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), pow = Math.pow, floor = Math.floor, sqrt = Math.sqrt, abs = Math.abs, round = Math.round, sin = Math.sin, ceil = Math.ceil, filters = fabric.Image.filters, createClass = fabric.util.createClass; /** * Resize image filter class * @class fabric.Image.filters.Resize * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} * @example * var filter = new fabric.Image.filters.Resize(); * object.filters.push(filter); * object.applyFilters(canvas.renderAll.bind(canvas)); */ filters.Resize = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Resize.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Resize', /** * Resize type * for webgl resizeType is just lanczos, for canvas2d can be: * bilinear, hermite, sliceHack, lanczos. * @param {String} resizeType * @default */ resizeType: 'hermite', /** * Scale factor for resizing, x axis * @param {Number} scaleX * @default */ scaleX: 1, /** * Scale factor for resizing, y axis * @param {Number} scaleY * @default */ scaleY: 1, /** * LanczosLobes parameter for lanczos filter, valid for resizeType lanczos * @param {Number} lanczosLobes * @default */ lanczosLobes: 3, /** * Return WebGL uniform locations for this filter's shader. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {WebGLShaderProgram} program This filter's compiled shader program. */ getUniformLocations: function(gl, program) { return { uDelta: gl.getUniformLocation(program, 'uDelta'), uTaps: gl.getUniformLocation(program, 'uTaps'), }; }, /** * Send data from this filter to its shader program's uniforms. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects */ sendUniformData: function(gl, uniformLocations) { gl.uniform2fv(uniformLocations.uDelta, this.horizontal ? [1 / this.width, 0] : [0, 1 / this.height]); gl.uniform1fv(uniformLocations.uTaps, this.taps); }, /** * Retrieves the cached shader. * @param {Object} options * @param {WebGLRenderingContext} options.context The GL context used for rendering. * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. */ retrieveShader: function(options) { var filterWindow = this.getFilterWindow(), cacheKey = this.type + '_' + filterWindow; if (!options.programCache.hasOwnProperty(cacheKey)) { var fragmentShader = this.generateShader(filterWindow); options.programCache[cacheKey] = this.createProgram(options.context, fragmentShader); } return options.programCache[cacheKey]; }, getFilterWindow: function() { var scale = this.tempScale; return Math.ceil(this.lanczosLobes / scale); }, getTaps: function() { var lobeFunction = this.lanczosCreate(this.lanczosLobes), scale = this.tempScale, filterWindow = this.getFilterWindow(), taps = new Array(filterWindow); for (var i = 1; i <= filterWindow; i++) { taps[i - 1] = lobeFunction(i * scale); } return taps; }, /** * Generate vertex and shader sources from the necessary steps numbers * @param {Number} filterWindow */ generateShader: function(filterWindow) { var offsets = new Array(filterWindow), fragmentShader = this.fragmentSourceTOP, filterWindow; for (var i = 1; i <= filterWindow; i++) { offsets[i - 1] = i + '.0 * uDelta'; } fragmentShader += 'uniform float uTaps[' + filterWindow + '];\n'; fragmentShader += 'void main() {\n'; fragmentShader += ' vec4 color = texture2D(uTexture, vTexCoord);\n'; fragmentShader += ' float sum = 1.0;\n'; offsets.forEach(function(offset, i) { fragmentShader += ' color += texture2D(uTexture, vTexCoord + ' + offset + ') * uTaps[' + i + '];\n'; fragmentShader += ' color += texture2D(uTexture, vTexCoord - ' + offset + ') * uTaps[' + i + '];\n'; fragmentShader += ' sum += 2.0 * uTaps[' + i + '];\n'; }); fragmentShader += ' gl_FragColor = color / sum;\n'; fragmentShader += '}'; return fragmentShader; }, fragmentSourceTOP: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform vec2 uDelta;\n' + 'varying vec2 vTexCoord;\n', /** * Apply the resize filter to the image * Determines whether to use WebGL or Canvas2D based on the options.webgl flag. * * @param {Object} options * @param {Number} options.passes The number of filters remaining to be executed * @param {Boolean} options.webgl Whether to use webgl to render the filter. * @param {WebGLTexture} options.sourceTexture The texture setup as the source to be filtered. * @param {WebGLTexture} options.targetTexture The texture where filtered output should be drawn. * @param {WebGLRenderingContext} options.context The GL context used for rendering. * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. */ applyTo: function(options) { if (options.webgl) { options.passes++; this.width = options.sourceWidth; this.horizontal = true; this.dW = Math.round(this.width * this.scaleX); this.dH = options.sourceHeight; this.tempScale = this.dW / this.width; this.taps = this.getTaps(); options.destinationWidth = this.dW; this._setupFrameBuffer(options); this.applyToWebGL(options); this._swapTextures(options); options.sourceWidth = options.destinationWidth; this.height = options.sourceHeight; this.horizontal = false; this.dH = Math.round(this.height * this.scaleY); this.tempScale = this.dH / this.height; this.taps = this.getTaps(); options.destinationHeight = this.dH; this._setupFrameBuffer(options); this.applyToWebGL(options); this._swapTextures(options); options.sourceHeight = options.destinationHeight; } else { this.applyTo2d(options); } }, isNeutralState: function() { return this.scaleX === 1 && this.scaleY === 1; }, lanczosCreate: function(lobes) { return function(x) { if (x >= lobes || x <= -lobes) { return 0.0; } if (x < 1.19209290E-07 && x > -1.19209290E-07) { return 1.0; } x *= Math.PI; var xx = x / lobes; return (sin(x) / x) * sin(xx) / xx; }; }, /** * Applies filter to canvas element * @memberOf fabric.Image.filters.Resize.prototype * @param {Object} canvasEl Canvas element to apply filter to * @param {Number} scaleX * @param {Number} scaleY */ applyTo2d: function(options) { var imageData = options.imageData, scaleX = this.scaleX, scaleY = this.scaleY; this.rcpScaleX = 1 / scaleX; this.rcpScaleY = 1 / scaleY; var oW = imageData.width, oH = imageData.height, dW = round(oW * scaleX), dH = round(oH * scaleY), newData; if (this.resizeType === 'sliceHack') { newData = this.sliceByTwo(options, oW, oH, dW, dH); } else if (this.resizeType === 'hermite') { newData = this.hermiteFastResize(options, oW, oH, dW, dH); } else if (this.resizeType === 'bilinear') { newData = this.bilinearFiltering(options, oW, oH, dW, dH); } else if (this.resizeType === 'lanczos') { newData = this.lanczosResize(options, oW, oH, dW, dH); } options.imageData = newData; }, /** * Filter sliceByTwo * @param {Object} canvasEl Canvas element to apply filter to * @param {Number} oW Original Width * @param {Number} oH Original Height * @param {Number} dW Destination Width * @param {Number} dH Destination Height * @returns {ImageData} */ sliceByTwo: function(options, oW, oH, dW, dH) { var imageData = options.imageData, mult = 0.5, doneW = false, doneH = false, stepW = oW * mult, stepH = oH * mult, resources = fabric.filterBackend.resources, tmpCanvas, ctx, sX = 0, sY = 0, dX = oW, dY = 0; if (!resources.sliceByTwo) { resources.sliceByTwo = document.createElement('canvas'); } tmpCanvas = resources.sliceByTwo; if (tmpCanvas.width < oW * 1.5 || tmpCanvas.height < oH) { tmpCanvas.width = oW * 1.5; tmpCanvas.height = oH; } ctx = tmpCanvas.getContext('2d'); ctx.clearRect(0, 0, oW * 1.5, oH); ctx.putImageData(imageData, 0, 0); dW = floor(dW); dH = floor(dH); while (!doneW || !doneH) { oW = stepW; oH = stepH; if (dW < floor(stepW * mult)) { stepW = floor(stepW * mult); } else { stepW = dW; doneW = true; } if (dH < floor(stepH * mult)) { stepH = floor(stepH * mult); } else { stepH = dH; doneH = true; } ctx.drawImage(tmpCanvas, sX, sY, oW, oH, dX, dY, stepW, stepH); sX = dX; sY = dY; dY += stepH; } return ctx.getImageData(sX, sY, dW, dH); }, /** * Filter lanczosResize * @param {Object} canvasEl Canvas element to apply filter to * @param {Number} oW Original Width * @param {Number} oH Original Height * @param {Number} dW Destination Width * @param {Number} dH Destination Height * @returns {ImageData} */ lanczosResize: function(options, oW, oH, dW, dH) { function process(u) { var v, i, weight, idx, a, red, green, blue, alpha, fX, fY; center.x = (u + 0.5) * ratioX; icenter.x = floor(center.x); for (v = 0; v < dH; v++) { center.y = (v + 0.5) * ratioY; icenter.y = floor(center.y); a = 0; red = 0; green = 0; blue = 0; alpha = 0; for (i = icenter.x - range2X; i <= icenter.x + range2X; i++) { if (i < 0 || i >= oW) { continue; } fX = floor(1000 * abs(i - center.x)); if (!cacheLanc[fX]) { cacheLanc[fX] = { }; } for (var j = icenter.y - range2Y; j <= icenter.y + range2Y; j++) { if (j < 0 || j >= oH) { continue; } fY = floor(1000 * abs(j - center.y)); if (!cacheLanc[fX][fY]) { cacheLanc[fX][fY] = lanczos(sqrt(pow(fX * rcpRatioX, 2) + pow(fY * rcpRatioY, 2)) / 1000); } weight = cacheLanc[fX][fY]; if (weight > 0) { idx = (j * oW + i) * 4; a += weight; red += weight * srcData[idx]; green += weight * srcData[idx + 1]; blue += weight * srcData[idx + 2]; alpha += weight * srcData[idx + 3]; } } } idx = (v * dW + u) * 4; destData[idx] = red / a; destData[idx + 1] = green / a; destData[idx + 2] = blue / a; destData[idx + 3] = alpha / a; } if (++u < dW) { return process(u); } else { return destImg; } } var srcData = options.imageData.data, destImg = options.ctx.createImageData(dW, dH), destData = destImg.data, lanczos = this.lanczosCreate(this.lanczosLobes), ratioX = this.rcpScaleX, ratioY = this.rcpScaleY, rcpRatioX = 2 / this.rcpScaleX, rcpRatioY = 2 / this.rcpScaleY, range2X = ceil(ratioX * this.lanczosLobes / 2), range2Y = ceil(ratioY * this.lanczosLobes / 2), cacheLanc = { }, center = { }, icenter = { }; return process(0); }, /** * bilinearFiltering * @param {Object} canvasEl Canvas element to apply filter to * @param {Number} oW Original Width * @param {Number} oH Original Height * @param {Number} dW Destination Width * @param {Number} dH Destination Height * @returns {ImageData} */ bilinearFiltering: function(options, oW, oH, dW, dH) { var a, b, c, d, x, y, i, j, xDiff, yDiff, chnl, color, offset = 0, origPix, ratioX = this.rcpScaleX, ratioY = this.rcpScaleY, w4 = 4 * (oW - 1), img = options.imageData, pixels = img.data, destImage = options.ctx.createImageData(dW, dH), destPixels = destImage.data; for (i = 0; i < dH; i++) { for (j = 0; j < dW; j++) { x = floor(ratioX * j); y = floor(ratioY * i); xDiff = ratioX * j - x; yDiff = ratioY * i - y; origPix = 4 * (y * oW + x); for (chnl = 0; chnl < 4; chnl++) { a = pixels[origPix + chnl]; b = pixels[origPix + 4 + chnl]; c = pixels[origPix + w4 + chnl]; d = pixels[origPix + w4 + 4 + chnl]; color = a * (1 - xDiff) * (1 - yDiff) + b * xDiff * (1 - yDiff) + c * yDiff * (1 - xDiff) + d * xDiff * yDiff; destPixels[offset++] = color; } } } return destImage; }, /** * hermiteFastResize * @param {Object} canvasEl Canvas element to apply filter to * @param {Number} oW Original Width * @param {Number} oH Original Height * @param {Number} dW Destination Width * @param {Number} dH Destination Height * @returns {ImageData} */ hermiteFastResize: function(options, oW, oH, dW, dH) { var ratioW = this.rcpScaleX, ratioH = this.rcpScaleY, ratioWHalf = ceil(ratioW / 2), ratioHHalf = ceil(ratioH / 2), img = options.imageData, data = img.data, img2 = options.ctx.createImageData(dW, dH), data2 = img2.data; for (var j = 0; j < dH; j++) { for (var i = 0; i < dW; i++) { var x2 = (i + j * dW) * 4, weight = 0, weights = 0, weightsAlpha = 0, gxR = 0, gxG = 0, gxB = 0, gxA = 0, centerY = (j + 0.5) * ratioH; for (var yy = floor(j * ratioH); yy < (j + 1) * ratioH; yy++) { var dy = abs(centerY - (yy + 0.5)) / ratioHHalf, centerX = (i + 0.5) * ratioW, w0 = dy * dy; for (var xx = floor(i * ratioW); xx < (i + 1) * ratioW; xx++) { var dx = abs(centerX - (xx + 0.5)) / ratioWHalf, w = sqrt(w0 + dx * dx); /* eslint-disable max-depth */ if (w > 1 && w < -1) { continue; } //hermite filter weight = 2 * w * w * w - 3 * w * w + 1; if (weight > 0) { dx = 4 * (xx + yy * oW); //alpha gxA += weight * data[dx + 3]; weightsAlpha += weight; //colors if (data[dx + 3] < 255) { weight = weight * data[dx + 3] / 250; } gxR += weight * data[dx]; gxG += weight * data[dx + 1]; gxB += weight * data[dx + 2]; weights += weight; } /* eslint-enable max-depth */ } } data2[x2] = gxR / weights; data2[x2 + 1] = gxG / weights; data2[x2 + 2] = gxB / weights; data2[x2 + 3] = gxA / weightsAlpha; } } return img2; }, /** * Returns object representation of an instance * @return {Object} Object representation of an instance */ toObject: function() { return { type: this.type, scaleX: this.scaleX, scaleY: this.scaleY, resizeType: this.resizeType, lanczosLobes: this.lanczosLobes }; } }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @param {Function} [callback] to be invoked after filter creation * @return {fabric.Image.filters.Resize} Instance of fabric.Image.filters.Resize */ fabric.Image.filters.Resize.fromObject = fabric.Image.filters.BaseFilter.fromObject; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), filters = fabric.Image.filters, createClass = fabric.util.createClass; /** * Contrast filter class * @class fabric.Image.filters.Contrast * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.Contrast#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} * @example * var filter = new fabric.Image.filters.Contrast({ * contrast: 40 * }); * object.filters.push(filter); * object.applyFilters(); */ filters.Contrast = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Contrast.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Contrast', fragmentSource: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform float uContrast;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + 'float contrastF = 1.015 * (uContrast + 1.0) / (1.0 * (1.015 - uContrast));\n' + 'color.rgb = contrastF * (color.rgb - 0.5) + 0.5;\n' + 'gl_FragColor = color;\n' + '}', contrast: 0, mainParameter: 'contrast', /** * Constructor * @memberOf fabric.Image.filters.Contrast.prototype * @param {Object} [options] Options object * @param {Number} [options.contrast=0] Value to contrast the image up (-1...1) */ /** * Apply the Contrast operation to a Uint8Array representing the pixels of an image. * * @param {Object} options * @param {ImageData} options.imageData The Uint8Array to be filtered. */ applyTo2d: function(options) { if (this.contrast === 0) { return; } var imageData = options.imageData, i, len, data = imageData.data, len = data.length, contrast = Math.floor(this.contrast * 255), contrastF = 259 * (contrast + 255) / (255 * (259 - contrast)); for (i = 0; i < len; i += 4) { data[i] = contrastF * (data[i] - 128) + 128; data[i + 1] = contrastF * (data[i + 1] - 128) + 128; data[i + 2] = contrastF * (data[i + 2] - 128) + 128; } }, /** * Return WebGL uniform locations for this filter's shader. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {WebGLShaderProgram} program This filter's compiled shader program. */ getUniformLocations: function(gl, program) { return { uContrast: gl.getUniformLocation(program, 'uContrast'), }; }, /** * Send data from this filter to its shader program's uniforms. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects */ sendUniformData: function(gl, uniformLocations) { gl.uniform1f(uniformLocations.uContrast, this.contrast); }, }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @param {function} [callback] to be invoked after filter creation * @return {fabric.Image.filters.Contrast} Instance of fabric.Image.filters.Contrast */ fabric.Image.filters.Contrast.fromObject = fabric.Image.filters.BaseFilter.fromObject; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), filters = fabric.Image.filters, createClass = fabric.util.createClass; /** * Saturate filter class * @class fabric.Image.filters.Saturation * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.Saturation#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} * @example * var filter = new fabric.Image.filters.Saturation({ * saturation: 100 * }); * object.filters.push(filter); * object.applyFilters(); */ filters.Saturation = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Saturation.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Saturation', fragmentSource: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform float uSaturation;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + 'float rgMax = max(color.r, color.g);\n' + 'float rgbMax = max(rgMax, color.b);\n' + 'color.r += rgbMax != color.r ? (rgbMax - color.r) * uSaturation : 0.00;\n' + 'color.g += rgbMax != color.g ? (rgbMax - color.g) * uSaturation : 0.00;\n' + 'color.b += rgbMax != color.b ? (rgbMax - color.b) * uSaturation : 0.00;\n' + 'gl_FragColor = color;\n' + '}', saturation: 0, mainParameter: 'saturation', /** * Constructor * @memberOf fabric.Image.filters.Saturate.prototype * @param {Object} [options] Options object * @param {Number} [options.saturate=0] Value to saturate the image (-1...1) */ /** * Apply the Saturation operation to a Uint8ClampedArray representing the pixels of an image. * * @param {Object} options * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. */ applyTo2d: function(options) { if (this.saturation === 0) { return; } var imageData = options.imageData, data = imageData.data, len = data.length, adjust = -this.saturation, i, max; for (i = 0; i < len; i += 4) { max = Math.max(data[i], data[i + 1], data[i + 2]); data[i] += max !== data[i] ? (max - data[i]) * adjust : 0; data[i + 1] += max !== data[i + 1] ? (max - data[i + 1]) * adjust : 0; data[i + 2] += max !== data[i + 2] ? (max - data[i + 2]) * adjust : 0; } }, /** * Return WebGL uniform locations for this filter's shader. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {WebGLShaderProgram} program This filter's compiled shader program. */ getUniformLocations: function(gl, program) { return { uSaturation: gl.getUniformLocation(program, 'uSaturation'), }; }, /** * Send data from this filter to its shader program's uniforms. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects */ sendUniformData: function(gl, uniformLocations) { gl.uniform1f(uniformLocations.uSaturation, -this.saturation); }, }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @param {Function} [callback] to be invoked after filter creation * @return {fabric.Image.filters.Saturation} Instance of fabric.Image.filters.Saturate */ fabric.Image.filters.Saturation.fromObject = fabric.Image.filters.BaseFilter.fromObject; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), filters = fabric.Image.filters, createClass = fabric.util.createClass; /** * Blur filter class * @class fabric.Image.filters.Blur * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.Blur#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} * @example * var filter = new fabric.Image.filters.Blur({ * blur: 0.5 * }); * object.filters.push(filter); * object.applyFilters(); * canvas.renderAll(); */ filters.Blur = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Blur.prototype */ { type: 'Blur', /* 'gl_FragColor = vec4(0.0);', 'gl_FragColor += texture2D(texture, vTexCoord + -7 * uDelta)*0.0044299121055113265;', 'gl_FragColor += texture2D(texture, vTexCoord + -6 * uDelta)*0.00895781211794;', 'gl_FragColor += texture2D(texture, vTexCoord + -5 * uDelta)*0.0215963866053;', 'gl_FragColor += texture2D(texture, vTexCoord + -4 * uDelta)*0.0443683338718;', 'gl_FragColor += texture2D(texture, vTexCoord + -3 * uDelta)*0.0776744219933;', 'gl_FragColor += texture2D(texture, vTexCoord + -2 * uDelta)*0.115876621105;', 'gl_FragColor += texture2D(texture, vTexCoord + -1 * uDelta)*0.147308056121;', 'gl_FragColor += texture2D(texture, vTexCoord )*0.159576912161;', 'gl_FragColor += texture2D(texture, vTexCoord + 1 * uDelta)*0.147308056121;', 'gl_FragColor += texture2D(texture, vTexCoord + 2 * uDelta)*0.115876621105;', 'gl_FragColor += texture2D(texture, vTexCoord + 3 * uDelta)*0.0776744219933;', 'gl_FragColor += texture2D(texture, vTexCoord + 4 * uDelta)*0.0443683338718;', 'gl_FragColor += texture2D(texture, vTexCoord + 5 * uDelta)*0.0215963866053;', 'gl_FragColor += texture2D(texture, vTexCoord + 6 * uDelta)*0.00895781211794;', 'gl_FragColor += texture2D(texture, vTexCoord + 7 * uDelta)*0.0044299121055113265;', */ /* eslint-disable max-len */ fragmentSource: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform vec2 uDelta;\n' + 'varying vec2 vTexCoord;\n' + 'const float nSamples = 15.0;\n' + 'vec3 v3offset = vec3(12.9898, 78.233, 151.7182);\n' + 'float random(vec3 scale) {\n' + /* use the fragment position for a different seed per-pixel */ 'return fract(sin(dot(gl_FragCoord.xyz, scale)) * 43758.5453);\n' + '}\n' + 'void main() {\n' + 'vec4 color = vec4(0.0);\n' + 'float total = 0.0;\n' + 'float offset = random(v3offset);\n' + 'for (float t = -nSamples; t <= nSamples; t++) {\n' + 'float percent = (t + offset - 0.5) / nSamples;\n' + 'float weight = 1.0 - abs(percent);\n' + 'color += texture2D(uTexture, vTexCoord + uDelta * percent) * weight;\n' + 'total += weight;\n' + '}\n' + 'gl_FragColor = color / total;\n' + '}', /* eslint-enable max-len */ /** * blur value, in percentage of image dimensions. * specific to keep the image blur constant at different resolutions * range bewteen 0 and 1. */ blur: 0, mainParameter: 'blur', applyTo: function(options) { if (options.webgl) { // this aspectRatio is used to give the same blur to vertical and horizontal this.aspectRatio = options.sourceWidth / options.sourceHeight; options.passes++; this._setupFrameBuffer(options); this.horizontal = true; this.applyToWebGL(options); this._swapTextures(options); this._setupFrameBuffer(options); this.horizontal = false; this.applyToWebGL(options); this._swapTextures(options); } else { this.applyTo2d(options); } }, applyTo2d: function(options) { // paint canvasEl with current image data. //options.ctx.putImageData(options.imageData, 0, 0); options.imageData = this.simpleBlur(options); }, simpleBlur: function(options) { var resources = options.filterBackend.resources, canvas1, canvas2, width = options.imageData.width, height = options.imageData.height; if (!resources.blurLayer1) { resources.blurLayer1 = fabric.util.createCanvasElement(); resources.blurLayer2 = fabric.util.createCanvasElement(); } canvas1 = resources.blurLayer1; canvas2 = resources.blurLayer2; if (canvas1.width !== width || canvas1.height !== height) { canvas2.width = canvas1.width = width; canvas2.height = canvas1.height = height; } var ctx1 = canvas1.getContext('2d'), ctx2 = canvas2.getContext('2d'), nSamples = 15, random, percent, j, i, blur = this.blur * 0.06 * 0.5; // load first canvas ctx1.putImageData(options.imageData, 0, 0); ctx2.clearRect(0, 0, width, height); for (i = -nSamples; i <= nSamples; i++) { random = (Math.random() - 0.5) / 4; percent = i / nSamples; j = blur * percent * width + random; ctx2.globalAlpha = 1 - Math.abs(percent); ctx2.drawImage(canvas1, j, random); ctx1.drawImage(canvas2, 0, 0); ctx2.globalAlpha = 1; ctx2.clearRect(0, 0, canvas2.width, canvas2.height); } for (i = -nSamples; i <= nSamples; i++) { random = (Math.random() - 0.5) / 4; percent = i / nSamples; j = blur * percent * height + random; ctx2.globalAlpha = 1 - Math.abs(percent); ctx2.drawImage(canvas1, random, j); ctx1.drawImage(canvas2, 0, 0); ctx2.globalAlpha = 1; ctx2.clearRect(0, 0, canvas2.width, canvas2.height); } options.ctx.drawImage(canvas1, 0, 0); var newImageData = options.ctx.getImageData(0, 0, canvas1.width, canvas1.height); ctx1.globalAlpha = 1; ctx1.clearRect(0, 0, canvas1.width, canvas1.height); return newImageData; }, /** * Return WebGL uniform locations for this filter's shader. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {WebGLShaderProgram} program This filter's compiled shader program. */ getUniformLocations: function(gl, program) { return { delta: gl.getUniformLocation(program, 'uDelta'), }; }, /** * Send data from this filter to its shader program's uniforms. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects */ sendUniformData: function(gl, uniformLocations) { var delta = this.chooseRightDelta(); gl.uniform2fv(uniformLocations.delta, delta); }, /** * choose right value of image percentage to blur with * @returns {Array} a numeric array with delta values */ chooseRightDelta: function() { var blurScale = 1, delta = [0, 0], blur; if (this.horizontal) { if (this.aspectRatio > 1) { // image is wide, i want to shrink radius horizontal blurScale = 1 / this.aspectRatio; } } else { if (this.aspectRatio < 1) { // image is tall, i want to shrink radius vertical blurScale = this.aspectRatio; } } blur = blurScale * this.blur * 0.12; if (this.horizontal) { delta[0] = blur; } else { delta[1] = blur; } return delta; }, }); /** * Deserialize a JSON definition of a BlurFilter into a concrete instance. */ filters.Blur.fromObject = fabric.Image.filters.BaseFilter.fromObject; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), filters = fabric.Image.filters, createClass = fabric.util.createClass; /** * Gamma filter class * @class fabric.Image.filters.Gamma * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.Gamma#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} * @example * var filter = new fabric.Image.filters.Gamma({ * brightness: 200 * }); * object.filters.push(filter); * object.applyFilters(); */ filters.Gamma = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Gamma.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'Gamma', fragmentSource: 'precision highp float;\n' + 'uniform sampler2D uTexture;\n' + 'uniform vec3 uGamma;\n' + 'varying vec2 vTexCoord;\n' + 'void main() {\n' + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + 'vec3 correction = (1.0 / uGamma);\n' + 'color.r = pow(color.r, correction.r);\n' + 'color.g = pow(color.g, correction.g);\n' + 'color.b = pow(color.b, correction.b);\n' + 'gl_FragColor = color;\n' + 'gl_FragColor.rgb *= color.a;\n' + '}', /** * Gamma array value, from 0.01 to 2.2. * @param {Array} gamma * @default */ gamma: [1, 1, 1], /** * Describe the property that is the filter parameter * @param {String} m * @default */ mainParameter: 'gamma', /** * Constructor * @param {Object} [options] Options object */ initialize: function(options) { this.gamma = [1, 1, 1]; filters.BaseFilter.prototype.initialize.call(this, options); }, /** * Apply the Gamma operation to a Uint8Array representing the pixels of an image. * * @param {Object} options * @param {ImageData} options.imageData The Uint8Array to be filtered. */ applyTo2d: function(options) { var imageData = options.imageData, data = imageData.data, gamma = this.gamma, len = data.length, rInv = 1 / gamma[0], gInv = 1 / gamma[1], bInv = 1 / gamma[2], i; if (!this.rVals) { // eslint-disable-next-line this.rVals = new Uint8Array(256); // eslint-disable-next-line this.gVals = new Uint8Array(256); // eslint-disable-next-line this.bVals = new Uint8Array(256); } // This is an optimization - pre-compute a look-up table for each color channel // instead of performing these pow calls for each pixel in the image. for (i = 0, len = 256; i < len; i++) { this.rVals[i] = Math.pow(i / 255, rInv) * 255; this.gVals[i] = Math.pow(i / 255, gInv) * 255; this.bVals[i] = Math.pow(i / 255, bInv) * 255; } for (i = 0, len = data.length; i < len; i += 4) { data[i] = this.rVals[data[i]]; data[i + 1] = this.gVals[data[i + 1]]; data[i + 2] = this.bVals[data[i + 2]]; } }, /** * Return WebGL uniform locations for this filter's shader. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {WebGLShaderProgram} program This filter's compiled shader program. */ getUniformLocations: function(gl, program) { return { uGamma: gl.getUniformLocation(program, 'uGamma'), }; }, /** * Send data from this filter to its shader program's uniforms. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects */ sendUniformData: function(gl, uniformLocations) { gl.uniform3fv(uniformLocations.uGamma, this.gamma); }, }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @param {function} [callback] to be invoked after filter creation * @return {fabric.Image.filters.Gamma} Instance of fabric.Image.filters.Gamma */ fabric.Image.filters.Gamma.fromObject = fabric.Image.filters.BaseFilter.fromObject; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), filters = fabric.Image.filters, createClass = fabric.util.createClass; /** * A container class that knows how to apply a sequence of filters to an input image. */ filters.Composed = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Composed.prototype */ { type: 'Composed', /** * A non sparse array of filters to apply */ subFilters: [], /** * Constructor * @param {Object} [options] Options object */ initialize: function(options) { this.callSuper('initialize', options); // create a new array instead mutating the prototype with push this.subFilters = this.subFilters.slice(0); }, /** * Apply this container's filters to the input image provided. * * @param {Object} options * @param {Number} options.passes The number of filters remaining to be applied. */ applyTo: function(options) { options.passes += this.subFilters.length - 1; this.subFilters.forEach(function(filter) { filter.applyTo(options); }); }, /** * Serialize this filter into JSON. * * @returns {Object} A JSON representation of this filter. */ toObject: function() { return fabric.util.object.extend(this.callSuper('toObject'), { subFilters: this.subFilters.map(function(filter) { return filter.toObject(); }), }); }, isNeutralState: function() { return !this.subFilters.some(function(filter) { return !filter.isNeutralState(); }); } }); /** * Deserialize a JSON definition of a ComposedFilter into a concrete instance. */ fabric.Image.filters.Composed.fromObject = function(object, callback) { var filters = object.subFilters || [], subFilters = filters.map(function(filter) { return new fabric.Image.filters[filter.type](filter); }), instance = new fabric.Image.filters.Composed({ subFilters: subFilters }); callback && callback(instance); return instance; }; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), filters = fabric.Image.filters, createClass = fabric.util.createClass; /** * HueRotation filter class * @class fabric.Image.filters.HueRotation * @memberOf fabric.Image.filters * @extends fabric.Image.filters.BaseFilter * @see {@link fabric.Image.filters.HueRotation#initialize} for constructor definition * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} * @example * var filter = new fabric.Image.filters.HueRotation({ * rotation: -0.5 * }); * object.filters.push(filter); * object.applyFilters(); */ filters.HueRotation = createClass(filters.ColorMatrix, /** @lends fabric.Image.filters.HueRotation.prototype */ { /** * Filter type * @param {String} type * @default */ type: 'HueRotation', /** * HueRotation value, from -1 to 1. * the unit is radians * @param {Number} myParameter * @default */ rotation: 0, /** * Describe the property that is the filter parameter * @param {String} m * @default */ mainParameter: 'rotation', calculateMatrix: function() { var rad = this.rotation * Math.PI, cos = fabric.util.cos(rad), sin = fabric.util.sin(rad), aThird = 1 / 3, aThirdSqtSin = Math.sqrt(aThird) * sin, OneMinusCos = 1 - cos; this.matrix = [ 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0 ]; this.matrix[0] = cos + OneMinusCos / 3; this.matrix[1] = aThird * OneMinusCos - aThirdSqtSin; this.matrix[2] = aThird * OneMinusCos + aThirdSqtSin; this.matrix[5] = aThird * OneMinusCos + aThirdSqtSin; this.matrix[6] = cos + aThird * OneMinusCos; this.matrix[7] = aThird * OneMinusCos - aThirdSqtSin; this.matrix[10] = aThird * OneMinusCos - aThirdSqtSin; this.matrix[11] = aThird * OneMinusCos + aThirdSqtSin; this.matrix[12] = cos + aThird * OneMinusCos; }, /** * HueRotation isNeutralState implementation * Used only in image applyFilters to discard filters that will not have an effect * on the image * @param {Object} options **/ isNeutralState: function(options) { this.calculateMatrix(); return filters.BaseFilter.prototype.isNeutralState.call(this, options); }, /** * Apply this filter to the input image data provided. * * Determines whether to use WebGL or Canvas2D based on the options.webgl flag. * * @param {Object} options * @param {Number} options.passes The number of filters remaining to be executed * @param {Boolean} options.webgl Whether to use webgl to render the filter. * @param {WebGLTexture} options.sourceTexture The texture setup as the source to be filtered. * @param {WebGLTexture} options.targetTexture The texture where filtered output should be drawn. * @param {WebGLRenderingContext} options.context The GL context used for rendering. * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. */ applyTo: function(options) { this.calculateMatrix(); filters.BaseFilter.prototype.applyTo.call(this, options); }, }); /** * Returns filter instance from an object representation * @static * @param {Object} object Object to create an instance from * @param {function} [callback] to be invoked after filter creation * @return {fabric.Image.filters.HueRotation} Instance of fabric.Image.filters.HueRotation */ fabric.Image.filters.HueRotation.fromObject = fabric.Image.filters.BaseFilter.fromObject; })(typeof exports !== 'undefined' ? exports : this); (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), clone = fabric.util.object.clone; if (fabric.Text) { fabric.warn('fabric.Text is already defined'); return; } /** * Text class * @class fabric.Text * @extends fabric.Object * @return {fabric.Text} thisArg * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#text} * @see {@link fabric.Text#initialize} for constructor definition */ fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ { /** * Properties which when set cause object to change dimensions * @type Array * @private */ _dimensionAffectingProps: [ 'fontSize', 'fontWeight', 'fontFamily', 'fontStyle', 'lineHeight', 'text', 'charSpacing', 'textAlign', 'styles', ], /** * @private */ _reNewline: /\r?\n/, /** * Use this regular expression to filter for whitespaces that is not a new line. * Mostly used when text is 'justify' aligned. * @private */ _reSpacesAndTabs: /[ \t\r]/g, /** * Use this regular expression to filter for whitespace that is not a new line. * Mostly used when text is 'justify' aligned. * @private */ _reSpaceAndTab: /[ \t\r]/, /** * Use this regular expression to filter consecutive groups of non spaces. * Mostly used when text is 'justify' aligned. * @private */ _reWords: /\S+/g, /** * Type of an object * @type String * @default */ type: 'text', /** * Font size (in pixels) * @type Number * @default */ fontSize: 40, /** * Font weight (e.g. bold, normal, 400, 600, 800) * @type {(Number|String)} * @default */ fontWeight: 'normal', /** * Font family * @type String * @default */ fontFamily: 'Times New Roman', /** * Text decoration underline. * @type Boolean * @default */ underline: false, /** * Text decoration overline. * @type Boolean * @default */ overline: false, /** * Text decoration linethrough. * @type Boolean * @default */ linethrough: false, /** * Text alignment. Possible values: "left", "center", "right", "justify", * "justify-left", "justify-center" or "justify-right". * @type String * @default */ textAlign: 'left', /** * Font style . Possible values: "", "normal", "italic" or "oblique". * @type String * @default */ fontStyle: 'normal', /** * Line height * @type Number * @default */ lineHeight: 1.16, /** * Superscript schema object (minimum overlap) * @type {Object} * @default */ superscript: { size: 0.60, // fontSize factor baseline: -0.35 // baseline-shift factor (upwards) }, /** * Subscript schema object (minimum overlap) * @type {Object} * @default */ subscript: { size: 0.60, // fontSize factor baseline: 0.11 // baseline-shift factor (downwards) }, /** * Background color of text lines * @type String * @default */ textBackgroundColor: '', /** * List of properties to consider when checking if * state of an object is changed ({@link fabric.Object#hasStateChanged}) * as well as for history (undo/redo) purposes * @type Array */ stateProperties: fabric.Object.prototype.stateProperties.concat('fontFamily', 'fontWeight', 'fontSize', 'text', 'underline', 'overline', 'linethrough', 'textAlign', 'fontStyle', 'lineHeight', 'textBackgroundColor', 'charSpacing', 'styles'), /** * List of properties to consider when checking if cache needs refresh * @type Array */ cacheProperties: fabric.Object.prototype.cacheProperties.concat('fontFamily', 'fontWeight', 'fontSize', 'text', 'underline', 'overline', 'linethrough', 'textAlign', 'fontStyle', 'lineHeight', 'textBackgroundColor', 'charSpacing', 'styles'), /** * When defined, an object is rendered via stroke and this property specifies its color. * <b>Backwards incompatibility note:</b> This property was named "strokeStyle" until v1.1.6 * @type String * @default */ stroke: null, /** * Shadow object representing shadow of this shape. * <b>Backwards incompatibility note:</b> This property was named "textShadow" (String) until v1.2.11 * @type fabric.Shadow * @default */ shadow: null, /** * @private */ _fontSizeFraction: 0.222, /** * @private */ offsets: { underline: 0.10, linethrough: -0.315, overline: -0.88 }, /** * Text Line proportion to font Size (in pixels) * @type Number * @default */ _fontSizeMult: 1.13, /** * additional space between characters * expressed in thousands of em unit * @type Number * @default */ charSpacing: 0, /** * Object containing character styles - top-level properties -> line numbers, * 2nd-level properties - charater numbers * @type Object * @default */ styles: null, /** * Reference to a context to measure text char or couple of chars * the cacheContext of the canvas will be used or a freshly created one if the object is not on canvas * once created it will be referenced on fabric._measuringContext to avoide creating a canvas for every * text object created. * @type {CanvasRenderingContext2D} * @default */ _measuringContext: null, /** * Baseline shift, stlyes only, keep at 0 for the main text object * @type {Number} * @default */ deltaY: 0, /** * Array of properties that define a style unit (of 'styles'). * @type {Array} * @default */ _styleProperties: [ 'stroke', 'strokeWidth', 'fill', 'fontFamily', 'fontSize', 'fontWeight', 'fontStyle', 'underline', 'overline', 'linethrough', 'deltaY', 'textBackgroundColor', ], /** * contains characters bounding boxes */ __charBounds: [], /** * use this size when measuring text. To avoid IE11 rounding errors * @type {Number} * @default * @readonly * @private */ CACHE_FONT_SIZE: 400, /** * contains the min text width to avoid getting 0 * @type {Number} * @default */ MIN_TEXT_WIDTH: 2, /** * Constructor * @param {String} text Text string * @param {Object} [options] Options object * @return {fabric.Text} thisArg */ initialize: function(text, options) { this.styles = options ? (options.styles || { }) : { }; this.text = text; this.__skipDimension = true; this.callSuper('initialize', options); this.__skipDimension = false; this.initDimensions(); this.setCoords(); this.setupState({ propertySet: '_dimensionAffectingProps' }); }, /** * Return a contex for measurement of text string. * if created it gets stored for reuse * @param {String} text Text string * @param {Object} [options] Options object * @return {fabric.Text} thisArg */ getMeasuringContext: function() { // if we did not return we have to measure something. if (!fabric._measuringContext) { fabric._measuringContext = this.canvas && this.canvas.contextCache || fabric.util.createCanvasElement().getContext('2d'); } return fabric._measuringContext; }, /** * @private * Divides text into lines of text and lines of graphemes. */ _splitText: function() { var newLines = this._splitTextIntoLines(this.text); this.textLines = newLines.lines; this._textLines = newLines.graphemeLines; this._unwrappedTextLines = newLines._unwrappedLines; this._text = newLines.graphemeText; return newLines; }, /** * Initialize or update text dimensions. * Updates this.width and this.height with the proper values. * Does not return dimensions. */ initDimensions: function() { if (this.__skipDimension) { return; } this._splitText(); this._clearCache(); this.width = this.calcTextWidth() || this.cursorWidth || this.MIN_TEXT_WIDTH; if (this.textAlign.indexOf('justify') !== -1) { // once text is measured we need to make space fatter to make justified text. this.enlargeSpaces(); } this.height = this.calcTextHeight(); this.saveState({ propertySet: '_dimensionAffectingProps' }); }, /** * Enlarge space boxes and shift the others */ enlargeSpaces: function() { var diffSpace, currentLineWidth, numberOfSpaces, accumulatedSpace, line, charBound, spaces; for (var i = 0, len = this._textLines.length; i < len; i++) { if (this.textAlign !== 'justify' && (i === len - 1 || this.isEndOfWrapping(i))) { continue; } accumulatedSpace = 0; line = this._textLines[i]; currentLineWidth = this.getLineWidth(i); if (currentLineWidth < this.width && (spaces = this.textLines[i].match(this._reSpacesAndTabs))) { numberOfSpaces = spaces.length; diffSpace = (this.width - currentLineWidth) / numberOfSpaces; for (var j = 0, jlen = line.length; j <= jlen; j++) { charBound = this.__charBounds[i][j]; if (this._reSpaceAndTab.test(line[j])) { charBound.width += diffSpace; charBound.kernedWidth += diffSpace; charBound.left += accumulatedSpace; accumulatedSpace += diffSpace; } else { charBound.left += accumulatedSpace; } } } } }, /** * Detect if the text line is ended with an hard break * text and itext do not have wrapping, return false * @return {Boolean} */ isEndOfWrapping: function(lineIndex) { return lineIndex === this._textLines.length - 1; }, /** * Returns string representation of an instance * @return {String} String representation of text object */ toString: function() { return '#<fabric.Text (' + this.complexity() + '): { "text": "' + this.text + '", "fontFamily": "' + this.fontFamily + '" }>'; }, /** * Return the dimension and the zoom level needed to create a cache canvas * big enough to host the object to be cached. * @private * @param {Object} dim.x width of object to be cached * @param {Object} dim.y height of object to be cached * @return {Object}.width width of canvas * @return {Object}.height height of canvas * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache */ _getCacheCanvasDimensions: function() { var dims = this.callSuper('_getCacheCanvasDimensions'); var fontSize = this.fontSize; dims.width += fontSize * dims.zoomX; dims.height += fontSize * dims.zoomY; return dims; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { this._setTextStyles(ctx); this._renderTextLinesBackground(ctx); this._renderTextDecoration(ctx, 'underline'); this._renderText(ctx); this._renderTextDecoration(ctx, 'overline'); this._renderTextDecoration(ctx, 'linethrough'); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderText: function(ctx) { if (this.paintFirst === 'stroke') { this._renderTextStroke(ctx); this._renderTextFill(ctx); } else { this._renderTextFill(ctx); this._renderTextStroke(ctx); } }, /** * Set the font parameter of the context with the object properties or with charStyle * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Object} [charStyle] object with font style properties * @param {String} [charStyle.fontFamily] Font Family * @param {Number} [charStyle.fontSize] Font size in pixels. ( without px suffix ) * @param {String} [charStyle.fontWeight] Font weight * @param {String} [charStyle.fontStyle] Font style (italic|normal) */ _setTextStyles: function(ctx, charStyle, forMeasuring) { ctx.textBaseline = 'alphabetic'; ctx.font = this._getFontDeclaration(charStyle, forMeasuring); }, /** * calculate and return the text Width measuring each line. * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @return {Number} Maximum width of fabric.Text object */ calcTextWidth: function() { var maxWidth = this.getLineWidth(0); for (var i = 1, len = this._textLines.length; i < len; i++) { var currentLineWidth = this.getLineWidth(i); if (currentLineWidth > maxWidth) { maxWidth = currentLineWidth; } } return maxWidth; }, /** * @private * @param {String} method Method name ("fillText" or "strokeText") * @param {CanvasRenderingContext2D} ctx Context to render on * @param {String} line Text to render * @param {Number} left Left position of text * @param {Number} top Top position of text * @param {Number} lineIndex Index of a line in a text */ _renderTextLine: function(method, ctx, line, left, top, lineIndex) { this._renderChars(method, ctx, line, left, top, lineIndex); }, /** * Renders the text background for lines, taking care of style * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderTextLinesBackground: function(ctx) { if (!this.textBackgroundColor && !this.styleHas('textBackgroundColor')) { return; } var lineTopOffset = 0, heightOfLine, lineLeftOffset, originalFill = ctx.fillStyle, line, lastColor, leftOffset = this._getLeftOffset(), topOffset = this._getTopOffset(), boxStart = 0, boxWidth = 0, charBox, currentColor; for (var i = 0, len = this._textLines.length; i < len; i++) { heightOfLine = this.getHeightOfLine(i); if (!this.textBackgroundColor && !this.styleHas('textBackgroundColor', i)) { lineTopOffset += heightOfLine; continue; } line = this._textLines[i]; lineLeftOffset = this._getLineLeftOffset(i); boxWidth = 0; boxStart = 0; lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor'); for (var j = 0, jlen = line.length; j < jlen; j++) { charBox = this.__charBounds[i][j]; currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor'); if (currentColor !== lastColor) { ctx.fillStyle = lastColor; lastColor && ctx.fillRect( leftOffset + lineLeftOffset + boxStart, topOffset + lineTopOffset, boxWidth, heightOfLine / this.lineHeight ); boxStart = charBox.left; boxWidth = charBox.width; lastColor = currentColor; } else { boxWidth += charBox.kernedWidth; } } if (currentColor) { ctx.fillStyle = currentColor; ctx.fillRect( leftOffset + lineLeftOffset + boxStart, topOffset + lineTopOffset, boxWidth, heightOfLine / this.lineHeight ); } lineTopOffset += heightOfLine; } ctx.fillStyle = originalFill; // if there is text background color no // other shadows should be casted this._removeShadow(ctx); }, /** * @private * @param {Object} decl style declaration for cache * @param {String} decl.fontFamily fontFamily * @param {String} decl.fontStyle fontStyle * @param {String} decl.fontWeight fontWeight * @return {Object} reference to cache */ getFontCache: function(decl) { var fontFamily = decl.fontFamily.toLowerCase(); if (!fabric.charWidthsCache[fontFamily]) { fabric.charWidthsCache[fontFamily] = { }; } var cache = fabric.charWidthsCache[fontFamily], cacheProp = decl.fontStyle.toLowerCase() + '_' + (decl.fontWeight + '').toLowerCase(); if (!cache[cacheProp]) { cache[cacheProp] = { }; } return cache[cacheProp]; }, /** * apply all the character style to canvas for rendering * @private * @param {String} _char * @param {Number} lineIndex * @param {Number} charIndex * @param {Object} [decl] */ _applyCharStyles: function(method, ctx, lineIndex, charIndex, styleDeclaration) { this._setFillStyles(ctx, styleDeclaration); this._setStrokeStyles(ctx, styleDeclaration); ctx.font = this._getFontDeclaration(styleDeclaration); }, /** * measure and return the width of a single character. * possibly overridden to accommodate different measure logic or * to hook some external lib for character measurement * @private * @param {String} char to be measured * @param {Object} charStyle style of char to be measured * @param {String} [previousChar] previous char * @param {Object} [prevCharStyle] style of previous char */ _measureChar: function(_char, charStyle, previousChar, prevCharStyle) { // first i try to return from cache var fontCache = this.getFontCache(charStyle), fontDeclaration = this._getFontDeclaration(charStyle), previousFontDeclaration = this._getFontDeclaration(prevCharStyle), couple = previousChar + _char, stylesAreEqual = fontDeclaration === previousFontDeclaration, width, coupleWidth, previousWidth, fontMultiplier = charStyle.fontSize / this.CACHE_FONT_SIZE, kernedWidth; if (previousChar && fontCache[previousChar] !== undefined) { previousWidth = fontCache[previousChar]; } if (fontCache[_char] !== undefined) { kernedWidth = width = fontCache[_char]; } if (stylesAreEqual && fontCache[couple] !== undefined) { coupleWidth = fontCache[couple]; kernedWidth = coupleWidth - previousWidth; } if (width === undefined || previousWidth === undefined || coupleWidth === undefined) { var ctx = this.getMeasuringContext(); // send a TRUE to specify measuring font size CACHE_FONT_SIZE this._setTextStyles(ctx, charStyle, true); } if (width === undefined) { kernedWidth = width = ctx.measureText(_char).width; fontCache[_char] = width; } if (previousWidth === undefined && stylesAreEqual && previousChar) { previousWidth = ctx.measureText(previousChar).width; fontCache[previousChar] = previousWidth; } if (stylesAreEqual && coupleWidth === undefined) { // we can measure the kerning couple and subtract the width of the previous character coupleWidth = ctx.measureText(couple).width; fontCache[couple] = coupleWidth; kernedWidth = coupleWidth - previousWidth; } return { width: width * fontMultiplier, kernedWidth: kernedWidth * fontMultiplier }; }, /** * Computes height of character at given position * @param {Number} line the line number * @param {Number} char the character number * @return {Number} fontSize of the character */ getHeightOfChar: function(line, char) { return this.getValueOfPropertyAt(line, char, 'fontSize'); }, /** * measure a text line measuring all characters. * @param {Number} lineIndex line number * @return {Number} Line width */ measureLine: function(lineIndex) { var lineInfo = this._measureLine(lineIndex); if (this.charSpacing !== 0) { lineInfo.width -= this._getWidthOfCharSpacing(); } if (lineInfo.width < 0) { lineInfo.width = 0; } return lineInfo; }, /** * measure every grapheme of a line, populating __charBounds * @param {Number} lineIndex * @return {Object} object.width total width of characters * @return {Object} object.widthOfSpaces length of chars that match this._reSpacesAndTabs */ _measureLine: function(lineIndex) { var width = 0, i, grapheme, line = this._textLines[lineIndex], prevGrapheme, graphemeInfo, numOfSpaces = 0, lineBounds = new Array(line.length); this.__charBounds[lineIndex] = lineBounds; for (i = 0; i < line.length; i++) { grapheme = line[i]; graphemeInfo = this._getGraphemeBox(grapheme, lineIndex, i, prevGrapheme); lineBounds[i] = graphemeInfo; width += graphemeInfo.kernedWidth; prevGrapheme = grapheme; } // this latest bound box represent the last character of the line // to simplify cursor handling in interactive mode. lineBounds[i] = { left: graphemeInfo ? graphemeInfo.left + graphemeInfo.width : 0, width: 0, kernedWidth: 0, height: this.fontSize }; return { width: width, numOfSpaces: numOfSpaces }; }, /** * Measure and return the info of a single grapheme. * needs the the info of previous graphemes already filled * @private * @param {String} grapheme to be measured * @param {Number} lineIndex index of the line where the char is * @param {Number} charIndex position in the line * @param {String} [prevGrapheme] character preceding the one to be measured */ _getGraphemeBox: function(grapheme, lineIndex, charIndex, prevGrapheme, skipLeft) { var style = this.getCompleteStyleDeclaration(lineIndex, charIndex), prevStyle = prevGrapheme ? this.getCompleteStyleDeclaration(lineIndex, charIndex - 1) : { }, info = this._measureChar(grapheme, style, prevGrapheme, prevStyle), kernedWidth = info.kernedWidth, width = info.width, charSpacing; if (this.charSpacing !== 0) { charSpacing = this._getWidthOfCharSpacing(); width += charSpacing; kernedWidth += charSpacing; } var box = { width: width, left: 0, height: style.fontSize, kernedWidth: kernedWidth, deltaY: style.deltaY, }; if (charIndex > 0 && !skipLeft) { var previousBox = this.__charBounds[lineIndex][charIndex - 1]; box.left = previousBox.left + previousBox.width + info.kernedWidth - info.width; } return box; }, /** * Calculate height of line at 'lineIndex' * @param {Number} lineIndex index of line to calculate * @return {Number} */ getHeightOfLine: function(lineIndex) { if (this.__lineHeights[lineIndex]) { return this.__lineHeights[lineIndex]; } var line = this._textLines[lineIndex], // char 0 is measured before the line cycle because it nneds to char // emptylines maxHeight = this.getHeightOfChar(lineIndex, 0); for (var i = 1, len = line.length; i < len; i++) { maxHeight = Math.max(this.getHeightOfChar(lineIndex, i), maxHeight); } return this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult; }, /** * Calculate text box height */ calcTextHeight: function() { var lineHeight, height = 0; for (var i = 0, len = this._textLines.length; i < len; i++) { lineHeight = this.getHeightOfLine(i); height += (i === len - 1 ? lineHeight / this.lineHeight : lineHeight); } return height; }, /** * @private * @return {Number} Left offset */ _getLeftOffset: function() { return -this.width / 2; }, /** * @private * @return {Number} Top offset */ _getTopOffset: function() { return -this.height / 2; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on * @param {String} method Method name ("fillText" or "strokeText") */ _renderTextCommon: function(ctx, method) { ctx.save(); var lineHeights = 0, left = this._getLeftOffset(), top = this._getTopOffset(), offsets = this._applyPatternGradientTransform(ctx, method === 'fillText' ? this.fill : this.stroke); for (var i = 0, len = this._textLines.length; i < len; i++) { var heightOfLine = this.getHeightOfLine(i), maxHeight = heightOfLine / this.lineHeight, leftOffset = this._getLineLeftOffset(i); this._renderTextLine( method, ctx, this._textLines[i], left + leftOffset - offsets.offsetX, top + lineHeights + maxHeight - offsets.offsetY, i ); lineHeights += heightOfLine; } ctx.restore(); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderTextFill: function(ctx) { if (!this.fill && !this.styleHas('fill')) { return; } this._renderTextCommon(ctx, 'fillText'); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderTextStroke: function(ctx) { if ((!this.stroke || this.strokeWidth === 0) && this.isEmptyStyles()) { return; } if (this.shadow && !this.shadow.affectStroke) { this._removeShadow(ctx); } ctx.save(); this._setLineDash(ctx, this.strokeDashArray); ctx.beginPath(); this._renderTextCommon(ctx, 'strokeText'); ctx.closePath(); ctx.restore(); }, /** * @private * @param {String} method * @param {CanvasRenderingContext2D} ctx Context to render on * @param {String} line Content of the line * @param {Number} left * @param {Number} top * @param {Number} lineIndex * @param {Number} charOffset */ _renderChars: function(method, ctx, line, left, top, lineIndex) { // set proper line offset var lineHeight = this.getHeightOfLine(lineIndex), isJustify = this.textAlign.indexOf('justify') !== -1, actualStyle, nextStyle, charsToRender = '', charBox, boxWidth = 0, timeToRender, shortCut = !isJustify && this.charSpacing === 0 && this.isEmptyStyles(lineIndex); ctx.save(); top -= lineHeight * this._fontSizeFraction / this.lineHeight; if (shortCut) { // render all the line in one pass without checking this._renderChar(method, ctx, lineIndex, 0, this.textLines[lineIndex], left, top, lineHeight); ctx.restore(); return; } for (var i = 0, len = line.length - 1; i <= len; i++) { timeToRender = i === len || this.charSpacing; charsToRender += line[i]; charBox = this.__charBounds[lineIndex][i]; if (boxWidth === 0) { left += charBox.kernedWidth - charBox.width; boxWidth += charBox.width; } else { boxWidth += charBox.kernedWidth; } if (isJustify && !timeToRender) { if (this._reSpaceAndTab.test(line[i])) { timeToRender = true; } } if (!timeToRender) { // if we have charSpacing, we render char by char actualStyle = actualStyle || this.getCompleteStyleDeclaration(lineIndex, i); nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1); timeToRender = this._hasStyleChanged(actualStyle, nextStyle); } if (timeToRender) { this._renderChar(method, ctx, lineIndex, i, charsToRender, left, top, lineHeight); charsToRender = ''; actualStyle = nextStyle; left += boxWidth; boxWidth = 0; } } ctx.restore(); }, /** * @private * @param {String} method * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Number} lineIndex * @param {Number} charIndex * @param {String} _char * @param {Number} left Left coordinate * @param {Number} top Top coordinate * @param {Number} lineHeight Height of the line */ _renderChar: function(method, ctx, lineIndex, charIndex, _char, left, top) { var decl = this._getStyleDeclaration(lineIndex, charIndex), fullDecl = this.getCompleteStyleDeclaration(lineIndex, charIndex), shouldFill = method === 'fillText' && fullDecl.fill, shouldStroke = method === 'strokeText' && fullDecl.stroke && fullDecl.strokeWidth; if (!shouldStroke && !shouldFill) { return; } decl && ctx.save(); this._applyCharStyles(method, ctx, lineIndex, charIndex, fullDecl); if (decl && decl.textBackgroundColor) { this._removeShadow(ctx); } if (decl && decl.deltaY) { top += decl.deltaY; } shouldFill && ctx.fillText(_char, left, top); shouldStroke && ctx.strokeText(_char, left, top); decl && ctx.restore(); }, /** * Turns the character into a 'superior figure' (i.e. 'superscript') * @param {Number} start selection start * @param {Number} end selection end * @returns {fabric.Text} thisArg * @chainable */ setSuperscript: function(start, end) { return this._setScript(start, end, this.superscript); }, /** * Turns the character into an 'inferior figure' (i.e. 'subscript') * @param {Number} start selection start * @param {Number} end selection end * @returns {fabric.Text} thisArg * @chainable */ setSubscript: function(start, end) { return this._setScript(start, end, this.subscript); }, /** * Applies 'schema' at given position * @private * @param {Number} start selection start * @param {Number} end selection end * @param {Number} schema * @returns {fabric.Text} thisArg * @chainable */ _setScript: function(start, end, schema) { var loc = this.get2DCursorLocation(start, true), fontSize = this.getValueOfPropertyAt(loc.lineIndex, loc.charIndex, 'fontSize'), dy = this.getValueOfPropertyAt(loc.lineIndex, loc.charIndex, 'deltaY'), style = { fontSize: fontSize * schema.size, deltaY: dy + fontSize * schema.baseline }; this.setSelectionStyles(style, start, end); return this; }, /** * @private * @param {Object} prevStyle * @param {Object} thisStyle */ _hasStyleChanged: function(prevStyle, thisStyle) { return prevStyle.fill !== thisStyle.fill || prevStyle.stroke !== thisStyle.stroke || prevStyle.strokeWidth !== thisStyle.strokeWidth || prevStyle.fontSize !== thisStyle.fontSize || prevStyle.fontFamily !== thisStyle.fontFamily || prevStyle.fontWeight !== thisStyle.fontWeight || prevStyle.fontStyle !== thisStyle.fontStyle || prevStyle.deltaY !== thisStyle.deltaY; }, /** * @private * @param {Object} prevStyle * @param {Object} thisStyle */ _hasStyleChangedForSvg: function(prevStyle, thisStyle) { return this._hasStyleChanged(prevStyle, thisStyle) || prevStyle.overline !== thisStyle.overline || prevStyle.underline !== thisStyle.underline || prevStyle.linethrough !== thisStyle.linethrough; }, /** * @private * @param {Number} lineIndex index text line * @return {Number} Line left offset */ _getLineLeftOffset: function(lineIndex) { var lineWidth = this.getLineWidth(lineIndex); if (this.textAlign === 'center') { return (this.width - lineWidth) / 2; } if (this.textAlign === 'right') { return this.width - lineWidth; } if (this.textAlign === 'justify-center' && this.isEndOfWrapping(lineIndex)) { return (this.width - lineWidth) / 2; } if (this.textAlign === 'justify-right' && this.isEndOfWrapping(lineIndex)) { return this.width - lineWidth; } return 0; }, /** * @private */ _clearCache: function() { this.__lineWidths = []; this.__lineHeights = []; this.__charBounds = []; }, /** * @private */ _shouldClearDimensionCache: function() { var shouldClear = this._forceClearCache; shouldClear || (shouldClear = this.hasStateChanged('_dimensionAffectingProps')); if (shouldClear) { this.dirty = true; this._forceClearCache = false; } return shouldClear; }, /** * Measure a single line given its index. Used to calculate the initial * text bounding box. The values are calculated and stored in __lineWidths cache. * @private * @param {Number} lineIndex line number * @return {Number} Line width */ getLineWidth: function(lineIndex) { if (this.__lineWidths[lineIndex]) { return this.__lineWidths[lineIndex]; } var width, line = this._textLines[lineIndex], lineInfo; if (line === '') { width = 0; } else { lineInfo = this.measureLine(lineIndex); width = lineInfo.width; } this.__lineWidths[lineIndex] = width; return width; }, _getWidthOfCharSpacing: function() { if (this.charSpacing !== 0) { return this.fontSize * this.charSpacing / 1000; } return 0; }, /** * Retrieves the value of property at given character position * @param {Number} lineIndex the line number * @param {Number} charIndex the charater number * @param {String} property the property name * @returns the value of 'property' */ getValueOfPropertyAt: function(lineIndex, charIndex, property) { var charStyle = this._getStyleDeclaration(lineIndex, charIndex); if (charStyle && typeof charStyle[property] !== 'undefined') { return charStyle[property]; } return this[property]; }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderTextDecoration: function(ctx, type) { if (!this[type] && !this.styleHas(type)) { return; } var heightOfLine, size, _size, lineLeftOffset, dy, _dy, line, lastDecoration, leftOffset = this._getLeftOffset(), topOffset = this._getTopOffset(), top, boxStart, boxWidth, charBox, currentDecoration, maxHeight, currentFill, lastFill, charSpacing = this._getWidthOfCharSpacing(); for (var i = 0, len = this._textLines.length; i < len; i++) { heightOfLine = this.getHeightOfLine(i); if (!this[type] && !this.styleHas(type, i)) { topOffset += heightOfLine; continue; } line = this._textLines[i]; maxHeight = heightOfLine / this.lineHeight; lineLeftOffset = this._getLineLeftOffset(i); boxStart = 0; boxWidth = 0; lastDecoration = this.getValueOfPropertyAt(i, 0, type); lastFill = this.getValueOfPropertyAt(i, 0, 'fill'); top = topOffset + maxHeight * (1 - this._fontSizeFraction); size = this.getHeightOfChar(i, 0); dy = this.getValueOfPropertyAt(i, 0, 'deltaY'); for (var j = 0, jlen = line.length; j < jlen; j++) { charBox = this.__charBounds[i][j]; currentDecoration = this.getValueOfPropertyAt(i, j, type); currentFill = this.getValueOfPropertyAt(i, j, 'fill'); _size = this.getHeightOfChar(i, j); _dy = this.getValueOfPropertyAt(i, j, 'deltaY'); if ((currentDecoration !== lastDecoration || currentFill !== lastFill || _size !== size || _dy !== dy) && boxWidth > 0) { ctx.fillStyle = lastFill; lastDecoration && lastFill && ctx.fillRect( leftOffset + lineLeftOffset + boxStart, top + this.offsets[type] * size + dy, boxWidth, this.fontSize / 15 ); boxStart = charBox.left; boxWidth = charBox.width; lastDecoration = currentDecoration; lastFill = currentFill; size = _size; dy = _dy; } else { boxWidth += charBox.kernedWidth; } } ctx.fillStyle = currentFill; currentDecoration && currentFill && ctx.fillRect( leftOffset + lineLeftOffset + boxStart, top + this.offsets[type] * size + dy, boxWidth - charSpacing, this.fontSize / 15 ); topOffset += heightOfLine; } // if there is text background color no // other shadows should be casted this._removeShadow(ctx); }, /** * return font declaration string for canvas context * @param {Object} [styleObject] object * @returns {String} font declaration formatted for canvas context. */ _getFontDeclaration: function(styleObject, forMeasuring) { var style = styleObject || this, family = this.fontFamily, fontIsGeneric = fabric.Text.genericFonts.indexOf(family.toLowerCase()) > -1; var fontFamily = family === undefined || family.indexOf('\'') > -1 || family.indexOf('"') > -1 || fontIsGeneric ? style.fontFamily : '"' + style.fontFamily + '"'; return [ // node-canvas needs "weight style", while browsers need "style weight" (fabric.isLikelyNode ? style.fontWeight : style.fontStyle), (fabric.isLikelyNode ? style.fontStyle : style.fontWeight), forMeasuring ? this.CACHE_FONT_SIZE + 'px' : style.fontSize + 'px', fontFamily ].join(' '); }, /** * Renders text instance on a specified context * @param {CanvasRenderingContext2D} ctx Context to render on */ render: function(ctx) { // do not render if object is not visible if (!this.visible) { return; } if (this.canvas && this.canvas.skipOffscreen && !this.group && !this.isOnScreen()) { return; } if (this._shouldClearDimensionCache()) { this.initDimensions(); } this.callSuper('render', ctx); }, /** * Returns the text as an array of lines. * @param {String} text text to split * @returns {Array} Lines in the text */ _splitTextIntoLines: function(text) { var lines = text.split(this._reNewline), newLines = new Array(lines.length), newLine = ['\n'], newText = []; for (var i = 0; i < lines.length; i++) { newLines[i] = fabric.util.string.graphemeSplit(lines[i]); newText = newText.concat(newLines[i], newLine); } newText.pop(); return { _unwrappedLines: newLines, lines: lines, graphemeText: newText, graphemeLines: newLines }; }, /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} Object representation of an instance */ toObject: function(propertiesToInclude) { var additionalProperties = [ 'text', 'fontSize', 'fontWeight', 'fontFamily', 'fontStyle', 'lineHeight', 'underline', 'overline', 'linethrough', 'textAlign', 'textBackgroundColor', 'charSpacing', ].concat(propertiesToInclude); var obj = this.callSuper('toObject', additionalProperties); obj.styles = clone(this.styles, true); return obj; }, /** * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. * @param {String|Object} key Property name or object (if object, iterate over the object properties) * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one) * @return {fabric.Object} thisArg * @chainable */ set: function(key, value) { this.callSuper('set', key, value); var needsDims = false; if (typeof key === 'object') { for (var _key in key) { needsDims = needsDims || this._dimensionAffectingProps.indexOf(_key) !== -1; } } else { needsDims = this._dimensionAffectingProps.indexOf(key) !== -1; } if (needsDims) { this.initDimensions(); this.setCoords(); } return this; }, /** * Returns complexity of an instance * @return {Number} complexity */ complexity: function() { return 1; } }); /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement}) * @static * @memberOf fabric.Text * @see: http://www.w3.org/TR/SVG/text.html#TextElement */ fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( 'x y dx dy font-family font-style font-weight font-size letter-spacing text-decoration text-anchor'.split(' ')); /** * Default SVG font size * @static * @memberOf fabric.Text */ fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; /** * Returns fabric.Text instance from an SVG element (<b>not yet implemented</b>) * @static * @memberOf fabric.Text * @param {SVGElement} element Element to parse * @param {Function} callback callback function invoked after parsing * @param {Object} [options] Options object */ fabric.Text.fromElement = function(element, callback, options) { if (!element) { return callback(null); } var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES), parsedAnchor = parsedAttributes.textAnchor || 'left'; options = fabric.util.object.extend((options ? clone(options) : { }), parsedAttributes); options.top = options.top || 0; options.left = options.left || 0; if (parsedAttributes.textDecoration) { var textDecoration = parsedAttributes.textDecoration; if (textDecoration.indexOf('underline') !== -1) { options.underline = true; } if (textDecoration.indexOf('overline') !== -1) { options.overline = true; } if (textDecoration.indexOf('line-through') !== -1) { options.linethrough = true; } delete options.textDecoration; } if ('dx' in parsedAttributes) { options.left += parsedAttributes.dx; } if ('dy' in parsedAttributes) { options.top += parsedAttributes.dy; } if (!('fontSize' in options)) { options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; } var textContent = ''; // The XML is not properly parsed in IE9 so a workaround to get // textContent is through firstChild.data. Another workaround would be // to convert XML loaded from a file to be converted using DOMParser (same way loadSVGFromString() does) if (!('textContent' in element)) { if ('firstChild' in element && element.firstChild !== null) { if ('data' in element.firstChild && element.firstChild.data !== null) { textContent = element.firstChild.data; } } } else { textContent = element.textContent; } textContent = textContent.replace(/^\s+|\s+$|\n+/g, '').replace(/\s+/g, ' '); var originalStrokeWidth = options.strokeWidth; options.strokeWidth = 0; var text = new fabric.Text(textContent, options), textHeightScaleFactor = text.getScaledHeight() / text.height, lineHeightDiff = (text.height + text.strokeWidth) * text.lineHeight - text.height, scaledDiff = lineHeightDiff * textHeightScaleFactor, textHeight = text.getScaledHeight() + scaledDiff, offX = 0; /* Adjust positioning: x/y attributes in SVG correspond to the bottom-left corner of text bounding box fabric output by default at top, left. */ if (parsedAnchor === 'center') { offX = text.getScaledWidth() / 2; } if (parsedAnchor === 'right') { offX = text.getScaledWidth(); } text.set({ left: text.left - offX, top: text.top - (textHeight - text.fontSize * (0.07 + text._fontSizeFraction)) / text.lineHeight, strokeWidth: typeof originalStrokeWidth !== 'undefined' ? originalStrokeWidth : 1, }); callback(text); }; /* _FROM_SVG_END_ */ /** * Returns fabric.Text instance from an object representation * @static * @memberOf fabric.Text * @param {Object} object Object to create an instance from * @param {Function} [callback] Callback to invoke when an fabric.Text instance is created */ fabric.Text.fromObject = function(object, callback) { return fabric.Object._fromObject('Text', object, callback, 'text'); }; fabric.Text.genericFonts = ['sans-serif', 'serif', 'cursive', 'fantasy', 'monospace']; fabric.util.createAccessors && fabric.util.createAccessors(fabric.Text); })(typeof exports !== 'undefined' ? exports : this); (function() { fabric.util.object.extend(fabric.Text.prototype, /** @lends fabric.Text.prototype */ { /** * Returns true if object has no styling or no styling in a line * @param {Number} lineIndex , lineIndex is on wrapped lines. * @return {Boolean} */ isEmptyStyles: function(lineIndex) { if (!this.styles) { return true; } if (typeof lineIndex !== 'undefined' && !this.styles[lineIndex]) { return true; } var obj = typeof lineIndex === 'undefined' ? this.styles : { line: this.styles[lineIndex] }; for (var p1 in obj) { for (var p2 in obj[p1]) { // eslint-disable-next-line no-unused-vars for (var p3 in obj[p1][p2]) { return false; } } } return true; }, /** * Returns true if object has a style property or has it ina specified line * @param {Number} lineIndex * @return {Boolean} */ styleHas: function(property, lineIndex) { if (!this.styles || !property || property === '') { return false; } if (typeof lineIndex !== 'undefined' && !this.styles[lineIndex]) { return false; } var obj = typeof lineIndex === 'undefined' ? this.styles : { line: this.styles[lineIndex] }; // eslint-disable-next-line for (var p1 in obj) { // eslint-disable-next-line for (var p2 in obj[p1]) { if (typeof obj[p1][p2][property] !== 'undefined') { return true; } } } return false; }, /** * Check if characters in a text have a value for a property * whose value matches the textbox's value for that property. If so, * the character-level property is deleted. If the character * has no other properties, then it is also deleted. Finally, * if the line containing that character has no other characters * then it also is deleted. * * @param {string} property The property to compare between characters and text. */ cleanStyle: function(property) { if (!this.styles || !property || property === '') { return false; } var obj = this.styles, stylesCount = 0, letterCount, stylePropertyValue, allStyleObjectPropertiesMatch = true, graphemeCount = 0, styleObject; // eslint-disable-next-line for (var p1 in obj) { letterCount = 0; // eslint-disable-next-line for (var p2 in obj[p1]) { var styleObject = obj[p1][p2], stylePropertyHasBeenSet = styleObject.hasOwnProperty(property); stylesCount++; if (stylePropertyHasBeenSet) { if (!stylePropertyValue) { stylePropertyValue = styleObject[property]; } else if (styleObject[property] !== stylePropertyValue) { allStyleObjectPropertiesMatch = false; } if (styleObject[property] === this[property]) { delete styleObject[property]; } } else { allStyleObjectPropertiesMatch = false; } if (Object.keys(styleObject).length !== 0) { letterCount++; } else { delete obj[p1][p2]; } } if (letterCount === 0) { delete obj[p1]; } } // if every grapheme has the same style set then // delete those styles and set it on the parent for (var i = 0; i < this._textLines.length; i++) { graphemeCount += this._textLines[i].length; } if (allStyleObjectPropertiesMatch && stylesCount === graphemeCount) { this[property] = stylePropertyValue; this.removeStyle(property); } }, /** * Remove a style property or properties from all individual character styles * in a text object. Deletes the character style object if it contains no other style * props. Deletes a line style object if it contains no other character styles. * * @param {String} props The property to remove from character styles. */ removeStyle: function(property) { if (!this.styles || !property || property === '') { return; } var obj = this.styles, line, lineNum, charNum; for (lineNum in obj) { line = obj[lineNum]; for (charNum in line) { delete line[charNum][property]; if (Object.keys(line[charNum]).length === 0) { delete line[charNum]; } } if (Object.keys(line).length === 0) { delete obj[lineNum]; } } }, /** * @private */ _extendStyles: function(index, styles) { var loc = this.get2DCursorLocation(index); if (!this._getLineStyle(loc.lineIndex)) { this._setLineStyle(loc.lineIndex, {}); } if (!this._getStyleDeclaration(loc.lineIndex, loc.charIndex)) { this._setStyleDeclaration(loc.lineIndex, loc.charIndex, {}); } fabric.util.object.extend(this._getStyleDeclaration(loc.lineIndex, loc.charIndex), styles); }, /** * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start) * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used. * @param {Boolean} [skipWrapping] consider the location for unwrapped lines. usefull to manage styles. */ get2DCursorLocation: function(selectionStart, skipWrapping) { if (typeof selectionStart === 'undefined') { selectionStart = this.selectionStart; } var lines = skipWrapping ? this._unwrappedTextLines : this._textLines; var len = lines.length; for (var i = 0; i < len; i++) { if (selectionStart <= lines[i].length) { return { lineIndex: i, charIndex: selectionStart }; } selectionStart -= lines[i].length + 1; } return { lineIndex: i - 1, charIndex: lines[i - 1].length < selectionStart ? lines[i - 1].length : selectionStart }; }, /** * Gets style of a current selection/cursor (at the start position) * if startIndex or endIndex are not provided, slectionStart or selectionEnd will be used. * @param {Number} [startIndex] Start index to get styles at * @param {Number} [endIndex] End index to get styles at, if not specified selectionEnd or startIndex + 1 * @param {Boolean} [complete] get full style or not * @return {Array} styles an array with one, zero or more Style objects */ getSelectionStyles: function(startIndex, endIndex, complete) { if (typeof startIndex === 'undefined') { startIndex = this.selectionStart || 0; } if (typeof endIndex === 'undefined') { endIndex = this.selectionEnd || startIndex; } var styles = []; for (var i = startIndex; i < endIndex; i++) { styles.push(this.getStyleAtPosition(i, complete)); } return styles; }, /** * Gets style of a current selection/cursor position * @param {Number} position to get styles at * @param {Boolean} [complete] full style if true * @return {Object} style Style object at a specified index * @private */ getStyleAtPosition: function(position, complete) { var loc = this.get2DCursorLocation(position), style = complete ? this.getCompleteStyleDeclaration(loc.lineIndex, loc.charIndex) : this._getStyleDeclaration(loc.lineIndex, loc.charIndex); return style || {}; }, /** * Sets style of a current selection, if no selection exist, do not set anything. * @param {Object} [styles] Styles object * @param {Number} [startIndex] Start index to get styles at * @param {Number} [endIndex] End index to get styles at, if not specified selectionEnd or startIndex + 1 * @return {fabric.IText} thisArg * @chainable */ setSelectionStyles: function(styles, startIndex, endIndex) { if (typeof startIndex === 'undefined') { startIndex = this.selectionStart || 0; } if (typeof endIndex === 'undefined') { endIndex = this.selectionEnd || startIndex; } for (var i = startIndex; i < endIndex; i++) { this._extendStyles(i, styles); } /* not included in _extendStyles to avoid clearing cache more than once */ this._forceClearCache = true; return this; }, /** * get the reference, not a clone, of the style object for a given character * @param {Number} lineIndex * @param {Number} charIndex * @return {Object} style object */ _getStyleDeclaration: function(lineIndex, charIndex) { var lineStyle = this.styles && this.styles[lineIndex]; if (!lineStyle) { return null; } return lineStyle[charIndex]; }, /** * return a new object that contains all the style property for a character * the object returned is newly created * @param {Number} lineIndex of the line where the character is * @param {Number} charIndex position of the character on the line * @return {Object} style object */ getCompleteStyleDeclaration: function(lineIndex, charIndex) { var style = this._getStyleDeclaration(lineIndex, charIndex) || { }, styleObject = { }, prop; for (var i = 0; i < this._styleProperties.length; i++) { prop = this._styleProperties[i]; styleObject[prop] = typeof style[prop] === 'undefined' ? this[prop] : style[prop]; } return styleObject; }, /** * @param {Number} lineIndex * @param {Number} charIndex * @param {Object} style * @private */ _setStyleDeclaration: function(lineIndex, charIndex, style) { this.styles[lineIndex][charIndex] = style; }, /** * * @param {Number} lineIndex * @param {Number} charIndex * @private */ _deleteStyleDeclaration: function(lineIndex, charIndex) { delete this.styles[lineIndex][charIndex]; }, /** * @param {Number} lineIndex * @private */ _getLineStyle: function(lineIndex) { return this.styles[lineIndex]; }, /** * @param {Number} lineIndex * @param {Object} style * @private */ _setLineStyle: function(lineIndex, style) { this.styles[lineIndex] = style; }, /** * @param {Number} lineIndex * @private */ _deleteLineStyle: function(lineIndex) { delete this.styles[lineIndex]; } }); })(); (function() { function parseDecoration(object) { if (object.textDecoration) { object.textDecoration.indexOf('underline') > -1 && (object.underline = true); object.textDecoration.indexOf('line-through') > -1 && (object.linethrough = true); object.textDecoration.indexOf('overline') > -1 && (object.overline = true); delete object.textDecoration; } } /** * IText class (introduced in <b>v1.4</b>) Events are also fired with "text:" * prefix when observing canvas. * @class fabric.IText * @extends fabric.Text * @mixes fabric.Observable * * @fires changed * @fires selection:changed * @fires editing:entered * @fires editing:exited * * @return {fabric.IText} thisArg * @see {@link fabric.IText#initialize} for constructor definition * * <p>Supported key combinations:</p> * <pre> * Move cursor: left, right, up, down * Select character: shift + left, shift + right * Select text vertically: shift + up, shift + down * Move cursor by word: alt + left, alt + right * Select words: shift + alt + left, shift + alt + right * Move cursor to line start/end: cmd + left, cmd + right or home, end * Select till start/end of line: cmd + shift + left, cmd + shift + right or shift + home, shift + end * Jump to start/end of text: cmd + up, cmd + down * Select till start/end of text: cmd + shift + up, cmd + shift + down or shift + pgUp, shift + pgDown * Delete character: backspace * Delete word: alt + backspace * Delete line: cmd + backspace * Forward delete: delete * Copy text: ctrl/cmd + c * Paste text: ctrl/cmd + v * Cut text: ctrl/cmd + x * Select entire text: ctrl/cmd + a * Quit editing tab or esc * </pre> * * <p>Supported mouse/touch combination</p> * <pre> * Position cursor: click/touch * Create selection: click/touch & drag * Create selection: click & shift + click * Select word: double click * Select line: triple click * </pre> */ fabric.IText = fabric.util.createClass(fabric.Text, fabric.Observable, /** @lends fabric.IText.prototype */ { /** * Type of an object * @type String * @default */ type: 'i-text', /** * Index where text selection starts (or where cursor is when there is no selection) * @type Number * @default */ selectionStart: 0, /** * Index where text selection ends * @type Number * @default */ selectionEnd: 0, /** * Color of text selection * @type String * @default */ selectionColor: 'rgba(17,119,255,0.3)', /** * Indicates whether text is in editing mode * @type Boolean * @default */ isEditing: false, /** * Indicates whether a text can be edited * @type Boolean * @default */ editable: true, /** * Border color of text object while it's in editing mode * @type String * @default */ editingBorderColor: 'rgba(102,153,255,0.25)', /** * Width of cursor (in px) * @type Number * @default */ cursorWidth: 2, /** * Color of default cursor (when not overwritten by character style) * @type String * @default */ cursorColor: '#333', /** * Delay between cursor blink (in ms) * @type Number * @default */ cursorDelay: 1000, /** * Duration of cursor fadein (in ms) * @type Number * @default */ cursorDuration: 600, /** * Indicates whether internal text char widths can be cached * @type Boolean * @default */ caching: true, /** * @private */ _reSpace: /\s|\n/, /** * @private */ _currentCursorOpacity: 0, /** * @private */ _selectionDirection: null, /** * @private */ _abortCursorAnimation: false, /** * @private */ __widthOfSpace: [], /** * Helps determining when the text is in composition, so that the cursor * rendering is altered. */ inCompositionMode: false, /** * Constructor * @param {String} text Text string * @param {Object} [options] Options object * @return {fabric.IText} thisArg */ initialize: function(text, options) { this.callSuper('initialize', text, options); this.initBehavior(); }, /** * Sets selection start (left boundary of a selection) * @param {Number} index Index to set selection start to */ setSelectionStart: function(index) { index = Math.max(index, 0); this._updateAndFire('selectionStart', index); }, /** * Sets selection end (right boundary of a selection) * @param {Number} index Index to set selection end to */ setSelectionEnd: function(index) { index = Math.min(index, this.text.length); this._updateAndFire('selectionEnd', index); }, /** * @private * @param {String} property 'selectionStart' or 'selectionEnd' * @param {Number} index new position of property */ _updateAndFire: function(property, index) { if (this[property] !== index) { this._fireSelectionChanged(); this[property] = index; } this._updateTextarea(); }, /** * Fires the even of selection changed * @private */ _fireSelectionChanged: function() { this.fire('selection:changed'); this.canvas && this.canvas.fire('text:selection:changed', { target: this }); }, /** * Initialize text dimensions. Render all text on given context * or on a offscreen canvas to get the text width with measureText. * Updates this.width and this.height with the proper values. * Does not return dimensions. * @private */ initDimensions: function() { this.isEditing && this.initDelayedCursor(); this.clearContextTop(); this.callSuper('initDimensions'); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ render: function(ctx) { this.clearContextTop(); this.callSuper('render', ctx); // clear the cursorOffsetCache, so we ensure to calculate once per renderCursor // the correct position but not at every cursor animation. this.cursorOffsetCache = { }; this.renderCursorOrSelection(); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { this.callSuper('_render', ctx); }, /** * Prepare and clean the contextTop */ clearContextTop: function(skipRestore) { if (!this.isEditing) { return; } if (this.canvas && this.canvas.contextTop) { var ctx = this.canvas.contextTop, v = this.canvas.viewportTransform; ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); this.transform(ctx); this.transformMatrix && ctx.transform.apply(ctx, this.transformMatrix); this._clearTextArea(ctx); skipRestore || ctx.restore(); } }, /** * Renders cursor or selection (depending on what exists) */ renderCursorOrSelection: function() { if (!this.isEditing || !this.canvas) { return; } var boundaries = this._getCursorBoundaries(), ctx; if (this.canvas && this.canvas.contextTop) { ctx = this.canvas.contextTop; this.clearContextTop(true); } else { ctx = this.canvas.contextContainer; ctx.save(); } if (this.selectionStart === this.selectionEnd) { this.renderCursor(boundaries, ctx); } else { this.renderSelection(boundaries, ctx); } ctx.restore(); }, _clearTextArea: function(ctx) { // we add 4 pixel, to be sure to do not leave any pixel out var width = this.width + 4, height = this.height + 4; ctx.clearRect(-width / 2, -height / 2, width, height); }, /** * Returns cursor boundaries (left, top, leftOffset, topOffset) * @private * @param {Array} chars Array of characters * @param {String} typeOfBoundaries */ _getCursorBoundaries: function(position) { // left/top are left/top of entire text box // leftOffset/topOffset are offset from that left/top point of a text box if (typeof position === 'undefined') { position = this.selectionStart; } var left = this._getLeftOffset(), top = this._getTopOffset(), offsets = this._getCursorBoundariesOffsets(position); return { left: left, top: top, leftOffset: offsets.left, topOffset: offsets.top }; }, /** * @private */ _getCursorBoundariesOffsets: function(position) { if (this.cursorOffsetCache && 'top' in this.cursorOffsetCache) { return this.cursorOffsetCache; } var lineLeftOffset, lineIndex, charIndex, topOffset = 0, leftOffset = 0, boundaries, cursorPosition = this.get2DCursorLocation(position); charIndex = cursorPosition.charIndex; lineIndex = cursorPosition.lineIndex; for (var i = 0; i < lineIndex; i++) { topOffset += this.getHeightOfLine(i); } lineLeftOffset = this._getLineLeftOffset(lineIndex); var bound = this.__charBounds[lineIndex][charIndex]; bound && (leftOffset = bound.left); if (this.charSpacing !== 0 && charIndex === this._textLines[lineIndex].length) { leftOffset -= this._getWidthOfCharSpacing(); } boundaries = { top: topOffset, left: lineLeftOffset + (leftOffset > 0 ? leftOffset : 0), }; this.cursorOffsetCache = boundaries; return this.cursorOffsetCache; }, /** * Renders cursor * @param {Object} boundaries * @param {CanvasRenderingContext2D} ctx transformed context to draw on */ renderCursor: function(boundaries, ctx) { var cursorLocation = this.get2DCursorLocation(), lineIndex = cursorLocation.lineIndex, charIndex = cursorLocation.charIndex > 0 ? cursorLocation.charIndex - 1 : 0, charHeight = this.getValueOfPropertyAt(lineIndex, charIndex, 'fontSize'), multiplier = this.scaleX * this.canvas.getZoom(), cursorWidth = this.cursorWidth / multiplier, topOffset = boundaries.topOffset, dy = this.getValueOfPropertyAt(lineIndex, charIndex, 'deltaY'); topOffset += (1 - this._fontSizeFraction) * this.getHeightOfLine(lineIndex) / this.lineHeight - charHeight * (1 - this._fontSizeFraction); if (this.inCompositionMode) { this.renderSelection(boundaries, ctx); } ctx.fillStyle = this.getValueOfPropertyAt(lineIndex, charIndex, 'fill'); ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; ctx.fillRect( boundaries.left + boundaries.leftOffset - cursorWidth / 2, topOffset + boundaries.top + dy, cursorWidth, charHeight); }, /** * Renders text selection * @param {Object} boundaries Object with left/top/leftOffset/topOffset * @param {CanvasRenderingContext2D} ctx transformed context to draw on */ renderSelection: function(boundaries, ctx) { var selectionStart = this.inCompositionMode ? this.hiddenTextarea.selectionStart : this.selectionStart, selectionEnd = this.inCompositionMode ? this.hiddenTextarea.selectionEnd : this.selectionEnd, isJustify = this.textAlign.indexOf('justify') !== -1, start = this.get2DCursorLocation(selectionStart), end = this.get2DCursorLocation(selectionEnd), startLine = start.lineIndex, endLine = end.lineIndex, startChar = start.charIndex < 0 ? 0 : start.charIndex, endChar = end.charIndex < 0 ? 0 : end.charIndex; for (var i = startLine; i <= endLine; i++) { var lineOffset = this._getLineLeftOffset(i) || 0, lineHeight = this.getHeightOfLine(i), realLineHeight = 0, boxStart = 0, boxEnd = 0; if (i === startLine) { boxStart = this.__charBounds[startLine][startChar].left; } if (i >= startLine && i < endLine) { boxEnd = isJustify && !this.isEndOfWrapping(i) ? this.width : this.getLineWidth(i) || 5; // WTF is this 5? } else if (i === endLine) { if (endChar === 0) { boxEnd = this.__charBounds[endLine][endChar].left; } else { var charSpacing = this._getWidthOfCharSpacing(); boxEnd = this.__charBounds[endLine][endChar - 1].left + this.__charBounds[endLine][endChar - 1].width - charSpacing; } } realLineHeight = lineHeight; if (this.lineHeight < 1 || (i === endLine && this.lineHeight > 1)) { lineHeight /= this.lineHeight; } if (this.inCompositionMode) { ctx.fillStyle = this.compositionColor || 'black'; ctx.fillRect( boundaries.left + lineOffset + boxStart, boundaries.top + boundaries.topOffset + lineHeight, boxEnd - boxStart, 1); } else { ctx.fillStyle = this.selectionColor; ctx.fillRect( boundaries.left + lineOffset + boxStart, boundaries.top + boundaries.topOffset, boxEnd - boxStart, lineHeight); } boundaries.topOffset += realLineHeight; } }, /** * High level function to know the height of the cursor. * the currentChar is the one that precedes the cursor * Returns fontSize of char at the current cursor * @return {Number} Character font size */ getCurrentCharFontSize: function() { var cp = this._getCurrentCharIndex(); return this.getValueOfPropertyAt(cp.l, cp.c, 'fontSize'); }, /** * High level function to know the color of the cursor. * the currentChar is the one that precedes the cursor * Returns color (fill) of char at the current cursor * @return {String} Character color (fill) */ getCurrentCharColor: function() { var cp = this._getCurrentCharIndex(); return this.getValueOfPropertyAt(cp.l, cp.c, 'fill'); }, /** * Returns the cursor position for the getCurrent.. functions * @private */ _getCurrentCharIndex: function() { var cursorPosition = this.get2DCursorLocation(this.selectionStart, true), charIndex = cursorPosition.charIndex > 0 ? cursorPosition.charIndex - 1 : 0; return { l: cursorPosition.lineIndex, c: charIndex }; } }); /** * Returns fabric.IText instance from an object representation * @static * @memberOf fabric.IText * @param {Object} object Object to create an instance from * @param {function} [callback] invoked with new instance as argument */ fabric.IText.fromObject = function(object, callback) { parseDecoration(object); if (object.styles) { for (var i in object.styles) { for (var j in object.styles[i]) { parseDecoration(object.styles[i][j]); } } } fabric.Object._fromObject('IText', object, callback, 'text'); }; })(); (function() { var clone = fabric.util.object.clone; fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { /** * Initializes all the interactive behavior of IText */ initBehavior: function() { this.initAddedHandler(); this.initRemovedHandler(); this.initCursorSelectionHandlers(); this.initDoubleClickSimulation(); this.mouseMoveHandler = this.mouseMoveHandler.bind(this); }, onDeselect: function() { this.isEditing && this.exitEditing(); this.selected = false; }, /** * Initializes "added" event handler */ initAddedHandler: function() { var _this = this; this.on('added', function() { var canvas = _this.canvas; if (canvas) { if (!canvas._hasITextHandlers) { canvas._hasITextHandlers = true; _this._initCanvasHandlers(canvas); } canvas._iTextInstances = canvas._iTextInstances || []; canvas._iTextInstances.push(_this); } }); }, initRemovedHandler: function() { var _this = this; this.on('removed', function() { var canvas = _this.canvas; if (canvas) { canvas._iTextInstances = canvas._iTextInstances || []; fabric.util.removeFromArray(canvas._iTextInstances, _this); if (canvas._iTextInstances.length === 0) { canvas._hasITextHandlers = false; _this._removeCanvasHandlers(canvas); } } }); }, /** * register canvas event to manage exiting on other instances * @private */ _initCanvasHandlers: function(canvas) { canvas._mouseUpITextHandler = function() { if (canvas._iTextInstances) { canvas._iTextInstances.forEach(function(obj) { obj.__isMousedown = false; }); } }; canvas.on('mouse:up', canvas._mouseUpITextHandler); }, /** * remove canvas event to manage exiting on other instances * @private */ _removeCanvasHandlers: function(canvas) { canvas.off('mouse:up', canvas._mouseUpITextHandler); }, /** * @private */ _tick: function() { this._currentTickState = this._animateCursor(this, 1, this.cursorDuration, '_onTickComplete'); }, /** * @private */ _animateCursor: function(obj, targetOpacity, duration, completeMethod) { var tickState; tickState = { isAborted: false, abort: function() { this.isAborted = true; }, }; obj.animate('_currentCursorOpacity', targetOpacity, { duration: duration, onComplete: function() { if (!tickState.isAborted) { obj[completeMethod](); } }, onChange: function() { // we do not want to animate a selection, only cursor if (obj.canvas && obj.selectionStart === obj.selectionEnd) { obj.renderCursorOrSelection(); } }, abort: function() { return tickState.isAborted; } }); return tickState; }, /** * @private */ _onTickComplete: function() { var _this = this; if (this._cursorTimeout1) { clearTimeout(this._cursorTimeout1); } this._cursorTimeout1 = setTimeout(function() { _this._currentTickCompleteState = _this._animateCursor(_this, 0, this.cursorDuration / 2, '_tick'); }, 100); }, /** * Initializes delayed cursor */ initDelayedCursor: function(restart) { var _this = this, delay = restart ? 0 : this.cursorDelay; this.abortCursorAnimation(); this._currentCursorOpacity = 1; this._cursorTimeout2 = setTimeout(function() { _this._tick(); }, delay); }, /** * Aborts cursor animation and clears all timeouts */ abortCursorAnimation: function() { var shouldClear = this._currentTickState || this._currentTickCompleteState, canvas = this.canvas; this._currentTickState && this._currentTickState.abort(); this._currentTickCompleteState && this._currentTickCompleteState.abort(); clearTimeout(this._cursorTimeout1); clearTimeout(this._cursorTimeout2); this._currentCursorOpacity = 0; // to clear just itext area we need to transform the context // it may not be worth it if (shouldClear && canvas) { canvas.clearContext(canvas.contextTop || canvas.contextContainer); } }, /** * Selects entire text * @return {fabric.IText} thisArg * @chainable */ selectAll: function() { this.selectionStart = 0; this.selectionEnd = this._text.length; this._fireSelectionChanged(); this._updateTextarea(); return this; }, /** * Returns selected text * @return {String} */ getSelectedText: function() { return this._text.slice(this.selectionStart, this.selectionEnd).join(''); }, /** * Find new selection index representing start of current word according to current selection index * @param {Number} startFrom Surrent selection index * @return {Number} New selection index */ findWordBoundaryLeft: function(startFrom) { var offset = 0, index = startFrom - 1; // remove space before cursor first if (this._reSpace.test(this._text[index])) { while (this._reSpace.test(this._text[index])) { offset++; index--; } } while (/\S/.test(this._text[index]) && index > -1) { offset++; index--; } return startFrom - offset; }, /** * Find new selection index representing end of current word according to current selection index * @param {Number} startFrom Current selection index * @return {Number} New selection index */ findWordBoundaryRight: function(startFrom) { var offset = 0, index = startFrom; // remove space after cursor first if (this._reSpace.test(this._text[index])) { while (this._reSpace.test(this._text[index])) { offset++; index++; } } while (/\S/.test(this._text[index]) && index < this.text.length) { offset++; index++; } return startFrom + offset; }, /** * Find new selection index representing start of current line according to current selection index * @param {Number} startFrom Current selection index * @return {Number} New selection index */ findLineBoundaryLeft: function(startFrom) { var offset = 0, index = startFrom - 1; while (!/\n/.test(this._text[index]) && index > -1) { offset++; index--; } return startFrom - offset; }, /** * Find new selection index representing end of current line according to current selection index * @param {Number} startFrom Current selection index * @return {Number} New selection index */ findLineBoundaryRight: function(startFrom) { var offset = 0, index = startFrom; while (!/\n/.test(this._text[index]) && index < this.text.length) { offset++; index++; } return startFrom + offset; }, /** * Finds index corresponding to beginning or end of a word * @param {Number} selectionStart Index of a character * @param {Number} direction 1 or -1 * @return {Number} Index of the beginning or end of a word */ searchWordBoundary: function(selectionStart, direction) { var index = this._reSpace.test(this.text.charAt(selectionStart)) ? selectionStart - 1 : selectionStart, _char = this.text.charAt(index), reNonWord = /[ \n\.,;!\?\-]/; while (!reNonWord.test(_char) && index > 0 && index < this.text.length) { index += direction; _char = this.text.charAt(index); } if (reNonWord.test(_char) && _char !== '\n') { index += direction === 1 ? 0 : 1; } return index; }, /** * Selects a word based on the index * @param {Number} selectionStart Index of a character */ selectWord: function(selectionStart) { selectionStart = selectionStart || this.selectionStart; var newSelectionStart = this.searchWordBoundary(selectionStart, -1), /* search backwards */ newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */ this.selectionStart = newSelectionStart; this.selectionEnd = newSelectionEnd; this._fireSelectionChanged(); this._updateTextarea(); this.renderCursorOrSelection(); }, /** * Selects a line based on the index * @param {Number} selectionStart Index of a character * @return {fabric.IText} thisArg * @chainable */ selectLine: function(selectionStart) { selectionStart = selectionStart || this.selectionStart; var newSelectionStart = this.findLineBoundaryLeft(selectionStart), newSelectionEnd = this.findLineBoundaryRight(selectionStart); this.selectionStart = newSelectionStart; this.selectionEnd = newSelectionEnd; this._fireSelectionChanged(); this._updateTextarea(); return this; }, /** * Enters editing state * @return {fabric.IText} thisArg * @chainable */ enterEditing: function(e) { if (this.isEditing || !this.editable) { return; } if (this.canvas) { this.canvas.calcOffset(); this.exitEditingOnOthers(this.canvas); } this.isEditing = true; this.initHiddenTextarea(e); this.hiddenTextarea.focus(); this.hiddenTextarea.value = this.text; this._updateTextarea(); this._saveEditingProps(); this._setEditingProps(); this._textBeforeEdit = this.text; this._tick(); this.fire('editing:entered'); this._fireSelectionChanged(); if (!this.canvas) { return this; } this.canvas.fire('text:editing:entered', { target: this }); this.initMouseMoveHandler(); this.canvas.requestRenderAll(); return this; }, exitEditingOnOthers: function(canvas) { if (canvas._iTextInstances) { canvas._iTextInstances.forEach(function(obj) { obj.selected = false; if (obj.isEditing) { obj.exitEditing(); } }); } }, /** * Initializes "mousemove" event handler */ initMouseMoveHandler: function() { this.canvas.on('mouse:move', this.mouseMoveHandler); }, /** * @private */ mouseMoveHandler: function(options) { if (!this.__isMousedown || !this.isEditing) { return; } var newSelectionStart = this.getSelectionStartFromPointer(options.e), currentStart = this.selectionStart, currentEnd = this.selectionEnd; if ( (newSelectionStart !== this.__selectionStartOnMouseDown || currentStart === currentEnd) && (currentStart === newSelectionStart || currentEnd === newSelectionStart) ) { return; } if (newSelectionStart > this.__selectionStartOnMouseDown) { this.selectionStart = this.__selectionStartOnMouseDown; this.selectionEnd = newSelectionStart; } else { this.selectionStart = newSelectionStart; this.selectionEnd = this.__selectionStartOnMouseDown; } if (this.selectionStart !== currentStart || this.selectionEnd !== currentEnd) { this.restartCursorIfNeeded(); this._fireSelectionChanged(); this._updateTextarea(); this.renderCursorOrSelection(); } }, /** * @private */ _setEditingProps: function() { this.hoverCursor = 'text'; if (this.canvas) { this.canvas.defaultCursor = this.canvas.moveCursor = 'text'; } this.borderColor = this.editingBorderColor; this.hasControls = this.selectable = false; this.lockMovementX = this.lockMovementY = true; }, /** * convert from textarea to grapheme indexes */ fromStringToGraphemeSelection: function(start, end, text) { var smallerTextStart = text.slice(0, start), graphemeStart = fabric.util.string.graphemeSplit(smallerTextStart).length; if (start === end) { return { selectionStart: graphemeStart, selectionEnd: graphemeStart }; } var smallerTextEnd = text.slice(start, end), graphemeEnd = fabric.util.string.graphemeSplit(smallerTextEnd).length; return { selectionStart: graphemeStart, selectionEnd: graphemeStart + graphemeEnd }; }, /** * convert from fabric to textarea values */ fromGraphemeToStringSelection: function(start, end, _text) { var smallerTextStart = _text.slice(0, start), graphemeStart = smallerTextStart.join('').length; if (start === end) { return { selectionStart: graphemeStart, selectionEnd: graphemeStart }; } var smallerTextEnd = _text.slice(start, end), graphemeEnd = smallerTextEnd.join('').length; return { selectionStart: graphemeStart, selectionEnd: graphemeStart + graphemeEnd }; }, /** * @private */ _updateTextarea: function() { this.cursorOffsetCache = { }; if (!this.hiddenTextarea) { return; } if (!this.inCompositionMode) { var newSelection = this.fromGraphemeToStringSelection(this.selectionStart, this.selectionEnd, this._text); this.hiddenTextarea.selectionStart = newSelection.selectionStart; this.hiddenTextarea.selectionEnd = newSelection.selectionEnd; } this.updateTextareaPosition(); }, /** * @private */ updateFromTextArea: function() { if (!this.hiddenTextarea) { return; } this.cursorOffsetCache = { }; this.text = this.hiddenTextarea.value; if (this._shouldClearDimensionCache()) { this.initDimensions(); this.setCoords(); } var newSelection = this.fromStringToGraphemeSelection( this.hiddenTextarea.selectionStart, this.hiddenTextarea.selectionEnd, this.hiddenTextarea.value); this.selectionEnd = this.selectionStart = newSelection.selectionEnd; if (!this.inCompositionMode) { this.selectionStart = newSelection.selectionStart; } this.updateTextareaPosition(); }, /** * @private */ updateTextareaPosition: function() { if (this.selectionStart === this.selectionEnd) { var style = this._calcTextareaPosition(); this.hiddenTextarea.style.left = style.left; this.hiddenTextarea.style.top = style.top; } }, /** * @private * @return {Object} style contains style for hiddenTextarea */ _calcTextareaPosition: function() { if (!this.canvas) { return { x: 1, y: 1 }; } var desiredPostion = this.inCompositionMode ? this.compositionStart : this.selectionStart, boundaries = this._getCursorBoundaries(desiredPostion), cursorLocation = this.get2DCursorLocation(desiredPostion), lineIndex = cursorLocation.lineIndex, charIndex = cursorLocation.charIndex, charHeight = this.getValueOfPropertyAt(lineIndex, charIndex, 'fontSize') * this.lineHeight, leftOffset = boundaries.leftOffset, m = this.calcTransformMatrix(), p = { x: boundaries.left + leftOffset, y: boundaries.top + boundaries.topOffset + charHeight }, upperCanvas = this.canvas.upperCanvasEl, upperCanvasWidth = upperCanvas.width, upperCanvasHeight = upperCanvas.height, maxWidth = upperCanvasWidth - charHeight, maxHeight = upperCanvasHeight - charHeight, scaleX = upperCanvas.clientWidth / upperCanvasWidth, scaleY = upperCanvas.clientHeight / upperCanvasHeight; p = fabric.util.transformPoint(p, m); p = fabric.util.transformPoint(p, this.canvas.viewportTransform); p.x *= scaleX; p.y *= scaleY; if (p.x < 0) { p.x = 0; } if (p.x > maxWidth) { p.x = maxWidth; } if (p.y < 0) { p.y = 0; } if (p.y > maxHeight) { p.y = maxHeight; } // add canvas offset on document p.x += this.canvas._offset.left; p.y += this.canvas._offset.top; return { left: p.x + 'px', top: p.y + 'px', fontSize: charHeight + 'px', charHeight: charHeight }; }, /** * @private */ _saveEditingProps: function() { this._savedProps = { hasControls: this.hasControls, borderColor: this.borderColor, lockMovementX: this.lockMovementX, lockMovementY: this.lockMovementY, hoverCursor: this.hoverCursor, defaultCursor: this.canvas && this.canvas.defaultCursor, moveCursor: this.canvas && this.canvas.moveCursor }; }, /** * @private */ _restoreEditingProps: function() { if (!this._savedProps) { return; } this.hoverCursor = this._savedProps.hoverCursor; this.hasControls = this._savedProps.hasControls; this.borderColor = this._savedProps.borderColor; this.lockMovementX = this._savedProps.lockMovementX; this.lockMovementY = this._savedProps.lockMovementY; if (this.canvas) { this.canvas.defaultCursor = this._savedProps.defaultCursor; this.canvas.moveCursor = this._savedProps.moveCursor; } }, /** * Exits from editing state * @return {fabric.IText} thisArg * @chainable */ exitEditing: function() { var isTextChanged = (this._textBeforeEdit !== this.text); this.selected = false; this.isEditing = false; this.selectable = true; this.selectionEnd = this.selectionStart; if (this.hiddenTextarea) { this.hiddenTextarea.blur && this.hiddenTextarea.blur(); this.canvas && this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea); this.hiddenTextarea = null; } this.abortCursorAnimation(); this._restoreEditingProps(); this._currentCursorOpacity = 0; if (this._shouldClearDimensionCache()) { this.initDimensions(); this.setCoords(); } this.fire('editing:exited'); isTextChanged && this.fire('modified'); if (this.canvas) { this.canvas.off('mouse:move', this.mouseMoveHandler); this.canvas.fire('text:editing:exited', { target: this }); isTextChanged && this.canvas.fire('object:modified', { target: this }); } return this; }, /** * @private */ _removeExtraneousStyles: function() { for (var prop in this.styles) { if (!this._textLines[prop]) { delete this.styles[prop]; } } }, /** * remove and reflow a style block from start to end. * @param {Number} start linear start position for removal (included in removal) * @param {Number} end linear end position for removal ( excluded from removal ) */ removeStyleFromTo: function(start, end) { var cursorStart = this.get2DCursorLocation(start, true), cursorEnd = this.get2DCursorLocation(end, true), lineStart = cursorStart.lineIndex, charStart = cursorStart.charIndex, lineEnd = cursorEnd.lineIndex, charEnd = cursorEnd.charIndex, i, styleObj; if (lineStart !== lineEnd) { // step1 remove the trailing of lineStart if (this.styles[lineStart]) { for (i = charStart; i < this._unwrappedTextLines[lineStart].length; i++) { delete this.styles[lineStart][i]; } } // step2 move the trailing of lineEnd to lineStart if needed if (this.styles[lineEnd]) { for (i = charEnd; i < this._unwrappedTextLines[lineEnd].length; i++) { styleObj = this.styles[lineEnd][i]; if (styleObj) { this.styles[lineStart] || (this.styles[lineStart] = { }); this.styles[lineStart][charStart + i - charEnd] = styleObj; } } } // step3 detects lines will be completely removed. for (i = lineStart + 1; i <= lineEnd; i++) { delete this.styles[i]; } // step4 shift remaining lines. this.shiftLineStyles(lineEnd, lineStart - lineEnd); } else { // remove and shift left on the same line if (this.styles[lineStart]) { styleObj = this.styles[lineStart]; var diff = charEnd - charStart, numericChar, _char; for (i = charStart; i < charEnd; i++) { delete styleObj[i]; } for (_char in this.styles[lineStart]) { numericChar = parseInt(_char, 10); if (numericChar >= charEnd) { styleObj[numericChar - diff] = styleObj[_char]; delete styleObj[_char]; } } } } }, /** * Shifts line styles up or down * @param {Number} lineIndex Index of a line * @param {Number} offset Can any number? */ shiftLineStyles: function(lineIndex, offset) { // shift all line styles by offset upward or downward // do not clone deep. we need new array, not new style objects var clonedStyles = clone(this.styles); for (var line in this.styles) { var numericLine = parseInt(line, 10); if (numericLine > lineIndex) { this.styles[numericLine + offset] = clonedStyles[numericLine]; if (!clonedStyles[numericLine - offset]) { delete this.styles[numericLine]; } } } }, restartCursorIfNeeded: function() { if (!this._currentTickState || this._currentTickState.isAborted || !this._currentTickCompleteState || this._currentTickCompleteState.isAborted ) { this.initDelayedCursor(); } }, /** * Inserts new style object * @param {Number} lineIndex Index of a line * @param {Number} charIndex Index of a char * @param {Number} qty number of lines to add * @param {Array} copiedStyle Array of objects styles */ insertNewlineStyleObject: function(lineIndex, charIndex, qty, copiedStyle) { var currentCharStyle, newLineStyles = {}, somethingAdded = false; qty || (qty = 1); this.shiftLineStyles(lineIndex, qty); if (this.styles[lineIndex]) { currentCharStyle = this.styles[lineIndex][charIndex === 0 ? charIndex : charIndex - 1]; } // we clone styles of all chars // after cursor onto the current line for (var index in this.styles[lineIndex]) { var numIndex = parseInt(index, 10); if (numIndex >= charIndex) { somethingAdded = true; newLineStyles[numIndex - charIndex] = this.styles[lineIndex][index]; // remove lines from the previous line since they're on a new line now delete this.styles[lineIndex][index]; } } if (somethingAdded) { this.styles[lineIndex + qty] = newLineStyles; } else { delete this.styles[lineIndex + qty]; } // for the other lines // we clone current char style onto the next (otherwise empty) line while (qty > 1) { qty--; if (copiedStyle && copiedStyle[qty]) { this.styles[lineIndex + qty] = { 0: clone(copiedStyle[qty]) }; } else if (currentCharStyle) { this.styles[lineIndex + qty] = { 0: clone(currentCharStyle) }; } else { delete this.styles[lineIndex + qty]; } } this._forceClearCache = true; }, /** * Inserts style object for a given line/char index * @param {Number} lineIndex Index of a line * @param {Number} charIndex Index of a char * @param {Number} quantity number Style object to insert, if given * @param {Array} copiedStyle array of style objecs */ insertCharStyleObject: function(lineIndex, charIndex, quantity, copiedStyle) { if (!this.styles) { this.styles = {}; } var currentLineStyles = this.styles[lineIndex], currentLineStylesCloned = currentLineStyles ? clone(currentLineStyles) : {}; quantity || (quantity = 1); // shift all char styles by quantity forward // 0,1,2,3 -> (charIndex=2) -> 0,1,3,4 -> (insert 2) -> 0,1,2,3,4 for (var index in currentLineStylesCloned) { var numericIndex = parseInt(index, 10); if (numericIndex >= charIndex) { currentLineStyles[numericIndex + quantity] = currentLineStylesCloned[numericIndex]; // only delete the style if there was nothing moved there if (!currentLineStylesCloned[numericIndex - quantity]) { delete currentLineStyles[numericIndex]; } } } this._forceClearCache = true; if (copiedStyle) { while (quantity--) { if (!Object.keys(copiedStyle[quantity]).length) { continue; } if (!this.styles[lineIndex]) { this.styles[lineIndex] = {}; } this.styles[lineIndex][charIndex + quantity] = clone(copiedStyle[quantity]); } return; } if (!currentLineStyles) { return; } var newStyle = currentLineStyles[charIndex ? charIndex - 1 : 1]; while (newStyle && quantity--) { this.styles[lineIndex][charIndex + quantity] = clone(newStyle); } }, /** * Inserts style object(s) * @param {Array} insertedText Characters at the location where style is inserted * @param {Number} start cursor index for inserting style * @param {Array} [copiedStyle] array of style objects to insert. */ insertNewStyleBlock: function(insertedText, start, copiedStyle) { var cursorLoc = this.get2DCursorLocation(start, true), addedLines = [0], linesLenght = 0; for (var i = 0; i < insertedText.length; i++) { if (insertedText[i] === '\n') { linesLenght++; addedLines[linesLenght] = 0; } else { addedLines[linesLenght]++; } } if (addedLines[0] > 0) { this.insertCharStyleObject(cursorLoc.lineIndex, cursorLoc.charIndex, addedLines[0], copiedStyle); copiedStyle = copiedStyle && copiedStyle.slice(addedLines[0] + 1); } linesLenght && this.insertNewlineStyleObject( cursorLoc.lineIndex, cursorLoc.charIndex + addedLines[0], linesLenght); for (var i = 1; i < linesLenght; i++) { if (addedLines[i] > 0) { this.insertCharStyleObject(cursorLoc.lineIndex + i, 0, addedLines[i], copiedStyle); } else if (copiedStyle) { this.styles[cursorLoc.lineIndex + i][0] = copiedStyle[0]; } copiedStyle = copiedStyle && copiedStyle.slice(addedLines[i] + 1); } // we use i outside the loop to get it like linesLength if (addedLines[i] > 0) { this.insertCharStyleObject(cursorLoc.lineIndex + i, 0, addedLines[i], copiedStyle); } }, /** * Set the selectionStart and selectionEnd according to the ne postion of cursor * mimic the key - mouse navigation when shift is pressed. */ setSelectionStartEndWithShift: function(start, end, newSelection) { if (newSelection <= start) { if (end === start) { this._selectionDirection = 'left'; } else if (this._selectionDirection === 'right') { this._selectionDirection = 'left'; this.selectionEnd = start; } this.selectionStart = newSelection; } else if (newSelection > start && newSelection < end) { if (this._selectionDirection === 'right') { this.selectionEnd = newSelection; } else { this.selectionStart = newSelection; } } else { // newSelection is > selection start and end if (end === start) { this._selectionDirection = 'right'; } else if (this._selectionDirection === 'left') { this._selectionDirection = 'right'; this.selectionStart = end; } this.selectionEnd = newSelection; } }, setSelectionInBoundaries: function() { var length = this.text.length; if (this.selectionStart > length) { this.selectionStart = length; } else if (this.selectionStart < 0) { this.selectionStart = 0; } if (this.selectionEnd > length) { this.selectionEnd = length; } else if (this.selectionEnd < 0) { this.selectionEnd = 0; } } }); })(); fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { /** * Initializes "dbclick" event handler */ initDoubleClickSimulation: function() { // for double click this.__lastClickTime = +new Date(); // for triple click this.__lastLastClickTime = +new Date(); this.__lastPointer = { }; this.on('mousedown', this.onMouseDown); }, /** * Default event handler to simulate triple click * @private */ onMouseDown: function(options) { if (!this.canvas) { return; } this.__newClickTime = +new Date(); var newPointer = options.pointer; if (this.isTripleClick(newPointer)) { this.fire('tripleclick', options); this._stopEvent(options.e); } this.__lastLastClickTime = this.__lastClickTime; this.__lastClickTime = this.__newClickTime; this.__lastPointer = newPointer; this.__lastIsEditing = this.isEditing; this.__lastSelected = this.selected; }, isTripleClick: function(newPointer) { return this.__newClickTime - this.__lastClickTime < 500 && this.__lastClickTime - this.__lastLastClickTime < 500 && this.__lastPointer.x === newPointer.x && this.__lastPointer.y === newPointer.y; }, /** * @private */ _stopEvent: function(e) { e.preventDefault && e.preventDefault(); e.stopPropagation && e.stopPropagation(); }, /** * Initializes event handlers related to cursor or selection */ initCursorSelectionHandlers: function() { this.initMousedownHandler(); this.initMouseupHandler(); this.initClicks(); }, /** * Initializes double and triple click event handlers */ initClicks: function() { this.on('mousedblclick', function(options) { this.selectWord(this.getSelectionStartFromPointer(options.e)); }); this.on('tripleclick', function(options) { this.selectLine(this.getSelectionStartFromPointer(options.e)); }); }, /** * Default event handler for the basic functionalities needed on _mouseDown * can be overridden to do something different. * Scope of this implementation is: find the click position, set selectionStart * find selectionEnd, initialize the drawing of either cursor or selection area */ _mouseDownHandler: function(options) { if (!this.canvas || !this.editable || (options.e.button && options.e.button !== 1)) { return; } this.__isMousedown = true; if (this.selected) { this.setCursorByClick(options.e); } if (this.isEditing) { this.__selectionStartOnMouseDown = this.selectionStart; if (this.selectionStart === this.selectionEnd) { this.abortCursorAnimation(); } this.renderCursorOrSelection(); } }, /** * Default event handler for the basic functionalities needed on mousedown:before * can be overridden to do something different. * Scope of this implementation is: verify the object is already selected when mousing down */ _mouseDownHandlerBefore: function(options) { if (!this.canvas || !this.editable || (options.e.button && options.e.button !== 1)) { return; } if (this === this.canvas._activeObject) { this.selected = true; } }, /** * Initializes "mousedown" event handler */ initMousedownHandler: function() { this.on('mousedown', this._mouseDownHandler); this.on('mousedown:before', this._mouseDownHandlerBefore); }, /** * Initializes "mouseup" event handler */ initMouseupHandler: function() { this.on('mouseup', this.mouseUpHandler); }, /** * standard hander for mouse up, overridable * @private */ mouseUpHandler: function(options) { this.__isMousedown = false; if (!this.editable || this.group || (options.transform && options.transform.actionPerformed) || (options.e.button && options.e.button !== 1)) { return; } if (this.canvas) { var currentActive = this.canvas._activeObject; if (currentActive && currentActive !== this) { // avoid running this logic when there is an active object // this because is possible with shift click and fast clicks, // to rapidly deselect and reselect this object and trigger an enterEdit return; } } if (this.__lastSelected && !this.__corner) { this.selected = false; this.__lastSelected = false; this.enterEditing(options.e); if (this.selectionStart === this.selectionEnd) { this.initDelayedCursor(true); } else { this.renderCursorOrSelection(); } } else { this.selected = true; } }, /** * Changes cursor location in a text depending on passed pointer (x/y) object * @param {Event} e Event object */ setCursorByClick: function(e) { var newSelection = this.getSelectionStartFromPointer(e), start = this.selectionStart, end = this.selectionEnd; if (e.shiftKey) { this.setSelectionStartEndWithShift(start, end, newSelection); } else { this.selectionStart = newSelection; this.selectionEnd = newSelection; } if (this.isEditing) { this._fireSelectionChanged(); this._updateTextarea(); } }, /** * Returns index of a character corresponding to where an object was clicked * @param {Event} e Event object * @return {Number} Index of a character */ getSelectionStartFromPointer: function(e) { var mouseOffset = this.getLocalPointer(e), prevWidth = 0, width = 0, height = 0, charIndex = 0, lineIndex = 0, lineLeftOffset, line; for (var i = 0, len = this._textLines.length; i < len; i++) { if (height <= mouseOffset.y) { height += this.getHeightOfLine(i) * this.scaleY; lineIndex = i; if (i > 0) { charIndex += this._textLines[i - 1].length + 1; } } else { break; } } lineLeftOffset = this._getLineLeftOffset(lineIndex); width = lineLeftOffset * this.scaleX; line = this._textLines[lineIndex]; for (var j = 0, jlen = line.length; j < jlen; j++) { prevWidth = width; // i removed something about flipX here, check. width += this.__charBounds[lineIndex][j].kernedWidth * this.scaleX; if (width <= mouseOffset.x) { charIndex++; } else { break; } } return this._getNewSelectionStartFromOffset(mouseOffset, prevWidth, width, charIndex, jlen); }, /** * @private */ _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen) { // we need Math.abs because when width is after the last char, the offset is given as 1, while is 0 var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth, distanceBtwNextCharAndCursor = width - mouseOffset.x, offset = distanceBtwNextCharAndCursor > distanceBtwLastCharAndCursor || distanceBtwNextCharAndCursor < 0 ? 0 : 1, newSelectionStart = index + offset; // if object is horizontally flipped, mirror cursor location from the end if (this.flipX) { newSelectionStart = jlen - newSelectionStart; } if (newSelectionStart > this._text.length) { newSelectionStart = this._text.length; } return newSelectionStart; } }); fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { /** * Initializes hidden textarea (needed to bring up keyboard in iOS) */ initHiddenTextarea: function() { this.hiddenTextarea = fabric.document.createElement('textarea'); this.hiddenTextarea.setAttribute('autocapitalize', 'off'); this.hiddenTextarea.setAttribute('autocorrect', 'off'); this.hiddenTextarea.setAttribute('autocomplete', 'off'); this.hiddenTextarea.setAttribute('spellcheck', 'false'); this.hiddenTextarea.setAttribute('data-fabric-hiddentextarea', ''); this.hiddenTextarea.setAttribute('wrap', 'off'); var style = this._calcTextareaPosition(); // line-height: 1px; was removed from the style to fix this: // https://bugs.chromium.org/p/chromium/issues/detail?id=870966 this.hiddenTextarea.style.cssText = 'position: absolute; top: ' + style.top + '; left: ' + style.left + '; z-index: -999; opacity: 0; width: 1px; height: 1px; font-size: 1px;' + ' paddingーtop: ' + style.fontSize + ';'; fabric.document.body.appendChild(this.hiddenTextarea); fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this)); fabric.util.addListener(this.hiddenTextarea, 'keyup', this.onKeyUp.bind(this)); fabric.util.addListener(this.hiddenTextarea, 'input', this.onInput.bind(this)); fabric.util.addListener(this.hiddenTextarea, 'copy', this.copy.bind(this)); fabric.util.addListener(this.hiddenTextarea, 'cut', this.copy.bind(this)); fabric.util.addListener(this.hiddenTextarea, 'paste', this.paste.bind(this)); fabric.util.addListener(this.hiddenTextarea, 'compositionstart', this.onCompositionStart.bind(this)); fabric.util.addListener(this.hiddenTextarea, 'compositionupdate', this.onCompositionUpdate.bind(this)); fabric.util.addListener(this.hiddenTextarea, 'compositionend', this.onCompositionEnd.bind(this)); if (!this._clickHandlerInitialized && this.canvas) { fabric.util.addListener(this.canvas.upperCanvasEl, 'click', this.onClick.bind(this)); this._clickHandlerInitialized = true; } }, /** * For functionalities on keyDown * Map a special key to a function of the instance/prototype * If you need different behaviour for ESC or TAB or arrows, you have to change * this map setting the name of a function that you build on the fabric.Itext or * your prototype. * the map change will affect all Instances unless you need for only some text Instances * in that case you have to clone this object and assign your Instance. * this.keysMap = fabric.util.object.clone(this.keysMap); * The function must be in fabric.Itext.prototype.myFunction And will receive event as args[0] */ keysMap: { 9: 'exitEditing', 27: 'exitEditing', 33: 'moveCursorUp', 34: 'moveCursorDown', 35: 'moveCursorRight', 36: 'moveCursorLeft', 37: 'moveCursorLeft', 38: 'moveCursorUp', 39: 'moveCursorRight', 40: 'moveCursorDown', }, /** * For functionalities on keyUp + ctrl || cmd */ ctrlKeysMapUp: { 67: 'copy', 88: 'cut' }, /** * For functionalities on keyDown + ctrl || cmd */ ctrlKeysMapDown: { 65: 'selectAll' }, onClick: function() { // No need to trigger click event here, focus is enough to have the keyboard appear on Android this.hiddenTextarea && this.hiddenTextarea.focus(); }, /** * Handles keyup event * @param {Event} e Event object */ onKeyDown: function(e) { if (!this.isEditing || this.inCompositionMode) { return; } if (e.keyCode in this.keysMap) { this[this.keysMap[e.keyCode]](e); } else if ((e.keyCode in this.ctrlKeysMapDown) && (e.ctrlKey || e.metaKey)) { this[this.ctrlKeysMapDown[e.keyCode]](e); } else { return; } e.stopImmediatePropagation(); e.preventDefault(); if (e.keyCode >= 33 && e.keyCode <= 40) { // if i press an arrow key just update selection this.clearContextTop(); this.renderCursorOrSelection(); } else { this.canvas && this.canvas.requestRenderAll(); } }, /** * Handles keyup event * We handle KeyUp because ie11 and edge have difficulties copy/pasting * if a copy/cut event fired, keyup is dismissed * @param {Event} e Event object */ onKeyUp: function(e) { if (!this.isEditing || this._copyDone || this.inCompositionMode) { this._copyDone = false; return; } if ((e.keyCode in this.ctrlKeysMapUp) && (e.ctrlKey || e.metaKey)) { this[this.ctrlKeysMapUp[e.keyCode]](e); } else { return; } e.stopImmediatePropagation(); e.preventDefault(); this.canvas && this.canvas.requestRenderAll(); }, /** * Handles onInput event * @param {Event} e Event object */ onInput: function(e) { var fromPaste = this.fromPaste; this.fromPaste = false; e && e.stopPropagation(); if (!this.isEditing) { return; } // decisions about style changes. var nextText = this._splitTextIntoLines(this.hiddenTextarea.value).graphemeText, charCount = this._text.length, nextCharCount = nextText.length, removedText, insertedText, charDiff = nextCharCount - charCount; if (this.hiddenTextarea.value === '') { this.styles = { }; this.updateFromTextArea(); this.fire('changed'); if (this.canvas) { this.canvas.fire('text:changed', { target: this }); this.canvas.requestRenderAll(); } return; } var textareaSelection = this.fromStringToGraphemeSelection( this.hiddenTextarea.selectionStart, this.hiddenTextarea.selectionEnd, this.hiddenTextarea.value ); var backDelete = this.selectionStart > textareaSelection.selectionStart; if (this.selectionStart !== this.selectionEnd) { removedText = this._text.slice(this.selectionStart, this.selectionEnd); charDiff += this.selectionEnd - this.selectionStart; } else if (nextCharCount < charCount) { if (backDelete) { removedText = this._text.slice(this.selectionEnd + charDiff, this.selectionEnd); } else { removedText = this._text.slice(this.selectionStart, this.selectionStart - charDiff); } } insertedText = nextText.slice(textareaSelection.selectionEnd - charDiff, textareaSelection.selectionEnd); if (removedText && removedText.length) { if (this.selectionStart !== this.selectionEnd) { this.removeStyleFromTo(this.selectionStart, this.selectionEnd); } else if (backDelete) { // detect differencies between forwardDelete and backDelete this.removeStyleFromTo(this.selectionEnd - removedText.length, this.selectionEnd); } else { this.removeStyleFromTo(this.selectionEnd, this.selectionEnd + removedText.length); } } if (insertedText.length) { if (fromPaste && insertedText.join('') === fabric.copiedText) { this.insertNewStyleBlock(insertedText, this.selectionStart, fabric.copiedTextStyle); } else { this.insertNewStyleBlock(insertedText, this.selectionStart); } } this.updateFromTextArea(); this.fire('changed'); if (this.canvas) { this.canvas.fire('text:changed', { target: this }); this.canvas.requestRenderAll(); } }, /** * Composition start */ onCompositionStart: function() { this.inCompositionMode = true; }, /** * Composition end */ onCompositionEnd: function() { this.inCompositionMode = false; }, // /** // * Composition update // */ onCompositionUpdate: function(e) { this.compositionStart = e.target.selectionStart; this.compositionEnd = e.target.selectionEnd; this.updateTextareaPosition(); }, /** * Copies selected text * @param {Event} e Event object */ copy: function() { if (this.selectionStart === this.selectionEnd) { //do not cut-copy if no selection return; } fabric.copiedText = this.getSelectedText(); fabric.copiedTextStyle = this.getSelectionStyles(this.selectionStart, this.selectionEnd, true); this._copyDone = true; }, /** * Pastes text * @param {Event} e Event object */ paste: function() { this.fromPaste = true; }, /** * @private * @param {Event} e Event object * @return {Object} Clipboard data object */ _getClipboardData: function(e) { return (e && e.clipboardData) || fabric.window.clipboardData; }, /** * Finds the width in pixels before the cursor on the same line * @private * @param {Number} lineIndex * @param {Number} charIndex * @return {Number} widthBeforeCursor width before cursor */ _getWidthBeforeCursor: function(lineIndex, charIndex) { var widthBeforeCursor = this._getLineLeftOffset(lineIndex), bound; if (charIndex > 0) { bound = this.__charBounds[lineIndex][charIndex - 1]; widthBeforeCursor += bound.left + bound.width; } return widthBeforeCursor; }, /** * Gets start offset of a selection * @param {Event} e Event object * @param {Boolean} isRight * @return {Number} */ getDownCursorOffset: function(e, isRight) { var selectionProp = this._getSelectionForOffset(e, isRight), cursorLocation = this.get2DCursorLocation(selectionProp), lineIndex = cursorLocation.lineIndex; // if on last line, down cursor goes to end of line if (lineIndex === this._textLines.length - 1 || e.metaKey || e.keyCode === 34) { // move to the end of a text return this._text.length - selectionProp; } var charIndex = cursorLocation.charIndex, widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex), indexOnOtherLine = this._getIndexOnLine(lineIndex + 1, widthBeforeCursor), textAfterCursor = this._textLines[lineIndex].slice(charIndex); return textAfterCursor.length + indexOnOtherLine + 2; }, /** * private * Helps finding if the offset should be counted from Start or End * @param {Event} e Event object * @param {Boolean} isRight * @return {Number} */ _getSelectionForOffset: function(e, isRight) { if (e.shiftKey && this.selectionStart !== this.selectionEnd && isRight) { return this.selectionEnd; } else { return this.selectionStart; } }, /** * @param {Event} e Event object * @param {Boolean} isRight * @return {Number} */ getUpCursorOffset: function(e, isRight) { var selectionProp = this._getSelectionForOffset(e, isRight), cursorLocation = this.get2DCursorLocation(selectionProp), lineIndex = cursorLocation.lineIndex; if (lineIndex === 0 || e.metaKey || e.keyCode === 33) { // if on first line, up cursor goes to start of line return -selectionProp; } var charIndex = cursorLocation.charIndex, widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex), indexOnOtherLine = this._getIndexOnLine(lineIndex - 1, widthBeforeCursor), textBeforeCursor = this._textLines[lineIndex].slice(0, charIndex); // return a negative offset return -this._textLines[lineIndex - 1].length + indexOnOtherLine - textBeforeCursor.length; }, /** * for a given width it founds the matching character. * @private */ _getIndexOnLine: function(lineIndex, width) { var line = this._textLines[lineIndex], lineLeftOffset = this._getLineLeftOffset(lineIndex), widthOfCharsOnLine = lineLeftOffset, indexOnLine = 0, charWidth, foundMatch; for (var j = 0, jlen = line.length; j < jlen; j++) { charWidth = this.__charBounds[lineIndex][j].width; widthOfCharsOnLine += charWidth; if (widthOfCharsOnLine > width) { foundMatch = true; var leftEdge = widthOfCharsOnLine - charWidth, rightEdge = widthOfCharsOnLine, offsetFromLeftEdge = Math.abs(leftEdge - width), offsetFromRightEdge = Math.abs(rightEdge - width); indexOnLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1); break; } } // reached end if (!foundMatch) { indexOnLine = line.length - 1; } return indexOnLine; }, /** * Moves cursor down * @param {Event} e Event object */ moveCursorDown: function(e) { if (this.selectionStart >= this._text.length && this.selectionEnd >= this._text.length) { return; } this._moveCursorUpOrDown('Down', e); }, /** * Moves cursor up * @param {Event} e Event object */ moveCursorUp: function(e) { if (this.selectionStart === 0 && this.selectionEnd === 0) { return; } this._moveCursorUpOrDown('Up', e); }, /** * Moves cursor up or down, fires the events * @param {String} direction 'Up' or 'Down' * @param {Event} e Event object */ _moveCursorUpOrDown: function(direction, e) { // getUpCursorOffset // getDownCursorOffset var action = 'get' + direction + 'CursorOffset', offset = this[action](e, this._selectionDirection === 'right'); if (e.shiftKey) { this.moveCursorWithShift(offset); } else { this.moveCursorWithoutShift(offset); } if (offset !== 0) { this.setSelectionInBoundaries(); this.abortCursorAnimation(); this._currentCursorOpacity = 1; this.initDelayedCursor(); this._fireSelectionChanged(); this._updateTextarea(); } }, /** * Moves cursor with shift * @param {Number} offset */ moveCursorWithShift: function(offset) { var newSelection = this._selectionDirection === 'left' ? this.selectionStart + offset : this.selectionEnd + offset; this.setSelectionStartEndWithShift(this.selectionStart, this.selectionEnd, newSelection); return offset !== 0; }, /** * Moves cursor up without shift * @param {Number} offset */ moveCursorWithoutShift: function(offset) { if (offset < 0) { this.selectionStart += offset; this.selectionEnd = this.selectionStart; } else { this.selectionEnd += offset; this.selectionStart = this.selectionEnd; } return offset !== 0; }, /** * Moves cursor left * @param {Event} e Event object */ moveCursorLeft: function(e) { if (this.selectionStart === 0 && this.selectionEnd === 0) { return; } this._moveCursorLeftOrRight('Left', e); }, /** * @private * @return {Boolean} true if a change happened */ _move: function(e, prop, direction) { var newValue; if (e.altKey) { newValue = this['findWordBoundary' + direction](this[prop]); } else if (e.metaKey || e.keyCode === 35 || e.keyCode === 36 ) { newValue = this['findLineBoundary' + direction](this[prop]); } else { this[prop] += direction === 'Left' ? -1 : 1; return true; } if (typeof newValue !== undefined && this[prop] !== newValue) { this[prop] = newValue; return true; } }, /** * @private */ _moveLeft: function(e, prop) { return this._move(e, prop, 'Left'); }, /** * @private */ _moveRight: function(e, prop) { return this._move(e, prop, 'Right'); }, /** * Moves cursor left without keeping selection * @param {Event} e */ moveCursorLeftWithoutShift: function(e) { var change = true; this._selectionDirection = 'left'; // only move cursor when there is no selection, // otherwise we discard it, and leave cursor on same place if (this.selectionEnd === this.selectionStart && this.selectionStart !== 0) { change = this._moveLeft(e, 'selectionStart'); } this.selectionEnd = this.selectionStart; return change; }, /** * Moves cursor left while keeping selection * @param {Event} e */ moveCursorLeftWithShift: function(e) { if (this._selectionDirection === 'right' && this.selectionStart !== this.selectionEnd) { return this._moveLeft(e, 'selectionEnd'); } else if (this.selectionStart !== 0){ this._selectionDirection = 'left'; return this._moveLeft(e, 'selectionStart'); } }, /** * Moves cursor right * @param {Event} e Event object */ moveCursorRight: function(e) { if (this.selectionStart >= this._text.length && this.selectionEnd >= this._text.length) { return; } this._moveCursorLeftOrRight('Right', e); }, /** * Moves cursor right or Left, fires event * @param {String} direction 'Left', 'Right' * @param {Event} e Event object */ _moveCursorLeftOrRight: function(direction, e) { var actionName = 'moveCursor' + direction + 'With'; this._currentCursorOpacity = 1; if (e.shiftKey) { actionName += 'Shift'; } else { actionName += 'outShift'; } if (this[actionName](e)) { this.abortCursorAnimation(); this.initDelayedCursor(); this._fireSelectionChanged(); this._updateTextarea(); } }, /** * Moves cursor right while keeping selection * @param {Event} e */ moveCursorRightWithShift: function(e) { if (this._selectionDirection === 'left' && this.selectionStart !== this.selectionEnd) { return this._moveRight(e, 'selectionStart'); } else if (this.selectionEnd !== this._text.length) { this._selectionDirection = 'right'; return this._moveRight(e, 'selectionEnd'); } }, /** * Moves cursor right without keeping selection * @param {Event} e Event object */ moveCursorRightWithoutShift: function(e) { var changed = true; this._selectionDirection = 'right'; if (this.selectionStart === this.selectionEnd) { changed = this._moveRight(e, 'selectionStart'); this.selectionEnd = this.selectionStart; } else { this.selectionStart = this.selectionEnd; } return changed; }, /** * Removes characters from start/end * start/end ar per grapheme position in _text array. * * @param {Number} start * @param {Number} end default to start + 1 */ removeChars: function(start, end) { if (typeof end === 'undefined') { end = start + 1; } this.removeStyleFromTo(start, end); this._text.splice(start, end - start); this.text = this._text.join(''); this.set('dirty', true); if (this._shouldClearDimensionCache()) { this.initDimensions(); this.setCoords(); } this._removeExtraneousStyles(); }, /** * insert characters at start position, before start position. * start equal 1 it means the text get inserted between actual grapheme 0 and 1 * if style array is provided, it must be as the same length of text in graphemes * if end is provided and is bigger than start, old text is replaced. * start/end ar per grapheme position in _text array. * * @param {String} text text to insert * @param {Array} style array of style objects * @param {Number} start * @param {Number} end default to start + 1 */ insertChars: function(text, style, start, end) { if (typeof end === 'undefined') { end = start; } if (end > start) { this.removeStyleFromTo(start, end); } var graphemes = fabric.util.string.graphemeSplit(text); this.insertNewStyleBlock(graphemes, start, style); this._text = [].concat(this._text.slice(0, start), graphemes, this._text.slice(end)); this.text = this._text.join(''); this.set('dirty', true); if (this._shouldClearDimensionCache()) { this.initDimensions(); this.setCoords(); } this._removeExtraneousStyles(); }, }); /* _TO_SVG_START_ */ (function() { var toFixed = fabric.util.toFixed, multipleSpacesRegex = / +/g; fabric.util.object.extend(fabric.Text.prototype, /** @lends fabric.Text.prototype */ { /** * Returns SVG representation of an instance * @param {Function} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG: function(reviver) { var offsets = this._getSVGLeftTopOffsets(), textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft), internalMarkup = this._wrapSVGTextAndBg(textAndBg); return this._createBaseSVGMarkup( internalMarkup, { reviver: reviver, noStyle: true, withShadow: true }); }, /** * @private */ _getSVGLeftTopOffsets: function() { return { textLeft: -this.width / 2, textTop: -this.height / 2, lineTop: this.getHeightOfLine(0) }; }, /** * @private */ _wrapSVGTextAndBg: function(textAndBg) { var noShadow = true, textDecoration = this.getSvgTextDecoration(this); return [ textAndBg.textBgRects.join(''), '\t\t<text xml:space="preserve" ', (this.fontFamily ? 'font-family="' + this.fontFamily.replace(/"/g, '\'') + '" ' : ''), (this.fontSize ? 'font-size="' + this.fontSize + '" ' : ''), (this.fontStyle ? 'font-style="' + this.fontStyle + '" ' : ''), (this.fontWeight ? 'font-weight="' + this.fontWeight + '" ' : ''), (textDecoration ? 'text-decoration="' + textDecoration + '" ' : ''), 'style="', this.getSvgStyles(noShadow), '"', this.addPaintOrder(), ' >', textAndBg.textSpans.join(''), '</text>\n' ]; }, /** * @private * @param {Number} textTopOffset Text top offset * @param {Number} textLeftOffset Text left offset * @return {Object} */ _getSVGTextAndBg: function(textTopOffset, textLeftOffset) { var textSpans = [], textBgRects = [], height = textTopOffset, lineOffset; // bounding-box background this._setSVGBg(textBgRects); // text and text-background for (var i = 0, len = this._textLines.length; i < len; i++) { lineOffset = this._getLineLeftOffset(i); if (this.textBackgroundColor || this.styleHas('textBackgroundColor', i)) { this._setSVGTextLineBg(textBgRects, i, textLeftOffset + lineOffset, height); } this._setSVGTextLineText(textSpans, i, textLeftOffset + lineOffset, height); height += this.getHeightOfLine(i); } return { textSpans: textSpans, textBgRects: textBgRects }; }, /** * @private */ _createTextCharSpan: function(_char, styleDecl, left, top) { var shouldUseWhitespace = _char !== _char.trim() || _char.match(multipleSpacesRegex), styleProps = this.getSvgSpanStyles(styleDecl, shouldUseWhitespace), fillStyles = styleProps ? 'style="' + styleProps + '"' : '', dy = styleDecl.deltaY, dySpan = '', NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; if (dy) { dySpan = ' dy="' + toFixed(dy, NUM_FRACTION_DIGITS) + '" '; } return [ '<tspan x="', toFixed(left, NUM_FRACTION_DIGITS), '" y="', toFixed(top, NUM_FRACTION_DIGITS), '" ', dySpan, fillStyles, '>', fabric.util.string.escapeXml(_char), '</tspan>' ].join(''); }, _setSVGTextLineText: function(textSpans, lineIndex, textLeftOffset, textTopOffset) { // set proper line offset var lineHeight = this.getHeightOfLine(lineIndex), isJustify = this.textAlign.indexOf('justify') !== -1, actualStyle, nextStyle, charsToRender = '', charBox, style, boxWidth = 0, line = this._textLines[lineIndex], timeToRender; textTopOffset += lineHeight * (1 - this._fontSizeFraction) / this.lineHeight; for (var i = 0, len = line.length - 1; i <= len; i++) { timeToRender = i === len || this.charSpacing; charsToRender += line[i]; charBox = this.__charBounds[lineIndex][i]; if (boxWidth === 0) { textLeftOffset += charBox.kernedWidth - charBox.width; boxWidth += charBox.width; } else { boxWidth += charBox.kernedWidth; } if (isJustify && !timeToRender) { if (this._reSpaceAndTab.test(line[i])) { timeToRender = true; } } if (!timeToRender) { // if we have charSpacing, we render char by char actualStyle = actualStyle || this.getCompleteStyleDeclaration(lineIndex, i); nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1); timeToRender = this._hasStyleChangedForSvg(actualStyle, nextStyle); } if (timeToRender) { style = this._getStyleDeclaration(lineIndex, i) || { }; textSpans.push(this._createTextCharSpan(charsToRender, style, textLeftOffset, textTopOffset)); charsToRender = ''; actualStyle = nextStyle; textLeftOffset += boxWidth; boxWidth = 0; } } }, _pushTextBgRect: function(textBgRects, color, left, top, width, height) { var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; textBgRects.push( '\t\t<rect ', this._getFillAttributes(color), ' x="', toFixed(left, NUM_FRACTION_DIGITS), '" y="', toFixed(top, NUM_FRACTION_DIGITS), '" width="', toFixed(width, NUM_FRACTION_DIGITS), '" height="', toFixed(height, NUM_FRACTION_DIGITS), '"></rect>\n'); }, _setSVGTextLineBg: function(textBgRects, i, leftOffset, textTopOffset) { var line = this._textLines[i], heightOfLine = this.getHeightOfLine(i) / this.lineHeight, boxWidth = 0, boxStart = 0, charBox, currentColor, lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor'); for (var j = 0, jlen = line.length; j < jlen; j++) { charBox = this.__charBounds[i][j]; currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor'); if (currentColor !== lastColor) { lastColor && this._pushTextBgRect(textBgRects, lastColor, leftOffset + boxStart, textTopOffset, boxWidth, heightOfLine); boxStart = charBox.left; boxWidth = charBox.width; lastColor = currentColor; } else { boxWidth += charBox.kernedWidth; } } currentColor && this._pushTextBgRect(textBgRects, currentColor, leftOffset + boxStart, textTopOffset, boxWidth, heightOfLine); }, /** * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 * * @private * @param {*} value * @return {String} */ _getFillAttributes: function(value) { var fillColor = (value && typeof value === 'string') ? new fabric.Color(value) : ''; if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { return 'fill="' + value + '"'; } return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; }, /** * @private */ _getSVGLineTopOffset: function(lineIndex) { var lineTopOffset = 0, lastHeight = 0; for (var j = 0; j < lineIndex; j++) { lineTopOffset += this.getHeightOfLine(j); } lastHeight = this.getHeightOfLine(j); return { lineTop: lineTopOffset, offset: (this._fontSizeMult - this._fontSizeFraction) * lastHeight / (this.lineHeight * this._fontSizeMult) }; }, /** * Returns styles-string for svg-export * @param {Boolean} skipShadow a boolean to skip shadow filter output * @return {String} */ getSvgStyles: function(skipShadow) { var svgStyle = fabric.Object.prototype.getSvgStyles.call(this, skipShadow); return svgStyle + ' white-space: pre;'; }, }); })(); /* _TO_SVG_END_ */ (function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = {}); /** * Textbox class, based on IText, allows the user to resize the text rectangle * and wraps lines automatically. Textboxes have their Y scaling locked, the * user can only change width. Height is adjusted automatically based on the * wrapping of lines. * @class fabric.Textbox * @extends fabric.IText * @mixes fabric.Observable * @return {fabric.Textbox} thisArg * @see {@link fabric.Textbox#initialize} for constructor definition */ fabric.Textbox = fabric.util.createClass(fabric.IText, fabric.Observable, { /** * Type of an object * @type String * @default */ type: 'textbox', /** * Minimum width of textbox, in pixels. * @type Number * @default */ minWidth: 20, /** * Minimum calculated width of a textbox, in pixels. * fixed to 2 so that an empty textbox cannot go to 0 * and is still selectable without text. * @type Number * @default */ dynamicMinWidth: 2, /** * Cached array of text wrapping. * @type Array */ __cachedLines: null, /** * Override standard Object class values */ lockScalingFlip: true, /** * Override standard Object class values * Textbox needs this on false */ noScaleCache: false, /** * Properties which when set cause object to change dimensions * @type Object * @private */ _dimensionAffectingProps: fabric.Text.prototype._dimensionAffectingProps.concat('width'), /** * Use this regular expression to split strings in breakable lines * @private */ _wordJoiners: /[ \t\r]/, /** * Use this boolean property in order to split strings that have no white space concept. * this is a cheap way to help with chinese/japaense * @type Boolean * @since 2.6.0 */ splitByGrapheme: false, /** * Unlike superclass's version of this function, Textbox does not update * its width. * @private * @override */ initDimensions: function() { if (this.__skipDimension) { return; } this.isEditing && this.initDelayedCursor(); this.clearContextTop(); this._clearCache(); // clear dynamicMinWidth as it will be different after we re-wrap line this.dynamicMinWidth = 0; // wrap lines this._styleMap = this._generateStyleMap(this._splitText()); // if after wrapping, the width is smaller than dynamicMinWidth, change the width and re-wrap if (this.dynamicMinWidth > this.width) { this._set('width', this.dynamicMinWidth); } if (this.textAlign.indexOf('justify') !== -1) { // once text is measured we need to make space fatter to make justified text. this.enlargeSpaces(); } // clear cache and re-calculate height this.height = this.calcTextHeight(); this.saveState({ propertySet: '_dimensionAffectingProps' }); }, /** * Generate an object that translates the style object so that it is * broken up by visual lines (new lines and automatic wrapping). * The original text styles object is broken up by actual lines (new lines only), * which is only sufficient for Text / IText * @private */ _generateStyleMap: function(textInfo) { var realLineCount = 0, realLineCharCount = 0, charCount = 0, map = {}; for (var i = 0; i < textInfo.graphemeLines.length; i++) { if (textInfo.graphemeText[charCount] === '\n' && i > 0) { realLineCharCount = 0; charCount++; realLineCount++; } else if (!this.graphemeSplit && this._reSpaceAndTab.test(textInfo.graphemeText[charCount]) && i > 0) { // this case deals with space's that are removed from end of lines when wrapping realLineCharCount++; charCount++; } map[i] = { line: realLineCount, offset: realLineCharCount }; charCount += textInfo.graphemeLines[i].length; realLineCharCount += textInfo.graphemeLines[i].length; } return map; }, /** * Returns true if object has a style property or has it ina specified line * @param {Number} lineIndex * @return {Boolean} */ styleHas: function(property, lineIndex) { if (this._styleMap && !this.isWrapping) { var map = this._styleMap[lineIndex]; if (map) { lineIndex = map.line; } } return fabric.Text.prototype.styleHas.call(this, property, lineIndex); }, /** * Returns true if object has no styling or no styling in a line * @param {Number} lineIndex , lineIndex is on wrapped lines. * @return {Boolean} */ isEmptyStyles: function(lineIndex) { var offset = 0, nextLineIndex = lineIndex + 1, nextOffset, obj, shouldLimit = false; var map = this._styleMap[lineIndex]; var mapNextLine = this._styleMap[lineIndex + 1]; if (map) { lineIndex = map.line; offset = map.offset; } if (mapNextLine) { nextLineIndex = mapNextLine.line; shouldLimit = nextLineIndex === lineIndex; nextOffset = mapNextLine.offset; } obj = typeof lineIndex === 'undefined' ? this.styles : { line: this.styles[lineIndex] }; for (var p1 in obj) { for (var p2 in obj[p1]) { if (p2 >= offset && (!shouldLimit || p2 < nextOffset)) { // eslint-disable-next-line no-unused-vars for (var p3 in obj[p1][p2]) { return false; } } } } return true; }, /** * @param {Number} lineIndex * @param {Number} charIndex * @private */ _getStyleDeclaration: function(lineIndex, charIndex) { if (this._styleMap && !this.isWrapping) { var map = this._styleMap[lineIndex]; if (!map) { return null; } lineIndex = map.line; charIndex = map.offset + charIndex; } return this.callSuper('_getStyleDeclaration', lineIndex, charIndex); }, /** * @param {Number} lineIndex * @param {Number} charIndex * @param {Object} style * @private */ _setStyleDeclaration: function(lineIndex, charIndex, style) { var map = this._styleMap[lineIndex]; lineIndex = map.line; charIndex = map.offset + charIndex; this.styles[lineIndex][charIndex] = style; }, /** * @param {Number} lineIndex * @param {Number} charIndex * @private */ _deleteStyleDeclaration: function(lineIndex, charIndex) { var map = this._styleMap[lineIndex]; lineIndex = map.line; charIndex = map.offset + charIndex; delete this.styles[lineIndex][charIndex]; }, /** * probably broken need a fix * @param {Number} lineIndex * @private */ _getLineStyle: function(lineIndex) { var map = this._styleMap[lineIndex]; return this.styles[map.line]; }, /** * probably broken need a fix * @param {Number} lineIndex * @param {Object} style * @private */ _setLineStyle: function(lineIndex, style) { var map = this._styleMap[lineIndex]; this.styles[map.line] = style; }, /** * probably broken need a fix * @param {Number} lineIndex * @private */ _deleteLineStyle: function(lineIndex) { var map = this._styleMap[lineIndex]; delete this.styles[map.line]; }, /** * Wraps text using the 'width' property of Textbox. First this function * splits text on newlines, so we preserve newlines entered by the user. * Then it wraps each line using the width of the Textbox by calling * _wrapLine(). * @param {Array} lines The string array of text that is split into lines * @param {Number} desiredWidth width you want to wrap to * @returns {Array} Array of lines */ _wrapText: function(lines, desiredWidth) { var wrapped = [], i; this.isWrapping = true; for (i = 0; i < lines.length; i++) { wrapped = wrapped.concat(this._wrapLine(lines[i], i, desiredWidth)); } this.isWrapping = false; return wrapped; }, /** * Helper function to measure a string of text, given its lineIndex and charIndex offset * it gets called when charBounds are not available yet. * @param {CanvasRenderingContext2D} ctx * @param {String} text * @param {number} lineIndex * @param {number} charOffset * @returns {number} * @private */ _measureWord: function(word, lineIndex, charOffset) { var width = 0, prevGrapheme, skipLeft = true; charOffset = charOffset || 0; for (var i = 0, len = word.length; i < len; i++) { var box = this._getGraphemeBox(word[i], lineIndex, i + charOffset, prevGrapheme, skipLeft); width += box.kernedWidth; prevGrapheme = word[i]; } return width; }, /** * Wraps a line of text using the width of the Textbox and a context. * @param {Array} line The grapheme array that represent the line * @param {Number} lineIndex * @param {Number} desiredWidth width you want to wrap the line to * @param {Number} reservedSpace space to remove from wrapping for custom functionalities * @returns {Array} Array of line(s) into which the given text is wrapped * to. */ _wrapLine: function(_line, lineIndex, desiredWidth, reservedSpace) { var lineWidth = 0, splitByGrapheme = this.splitByGrapheme, graphemeLines = [], line = [], // spaces in different languges? words = splitByGrapheme ? fabric.util.string.graphemeSplit(_line) : _line.split(this._wordJoiners), word = '', offset = 0, infix = splitByGrapheme ? '' : ' ', wordWidth = 0, infixWidth = 0, largestWordWidth = 0, lineJustStarted = true, additionalSpace = splitByGrapheme ? 0 : this._getWidthOfCharSpacing(), reservedSpace = reservedSpace || 0; desiredWidth -= reservedSpace; for (var i = 0; i < words.length; i++) { // i would avoid resplitting the graphemes word = fabric.util.string.graphemeSplit(words[i]); wordWidth = this._measureWord(word, lineIndex, offset); offset += word.length; lineWidth += infixWidth + wordWidth - additionalSpace; if (lineWidth >= desiredWidth && !lineJustStarted) { graphemeLines.push(line); line = []; lineWidth = wordWidth; lineJustStarted = true; } else { lineWidth += additionalSpace; } if (!lineJustStarted && !splitByGrapheme) { line.push(infix); } line = line.concat(word); infixWidth = this._measureWord([infix], lineIndex, offset); offset++; lineJustStarted = false; // keep track of largest word if (wordWidth > largestWordWidth) { largestWordWidth = wordWidth; } } i && graphemeLines.push(line); if (largestWordWidth + reservedSpace > this.dynamicMinWidth) { this.dynamicMinWidth = largestWordWidth - additionalSpace + reservedSpace; } return graphemeLines; }, /** * Detect if the text line is ended with an hard break * text and itext do not have wrapping, return false * @param {Number} lineIndex text to split * @return {Boolean} */ isEndOfWrapping: function(lineIndex) { if (!this._styleMap[lineIndex + 1]) { // is last line, return true; return true; } if (this._styleMap[lineIndex + 1].line !== this._styleMap[lineIndex].line) { // this is last line before a line break, return true; return true; } return false; }, /** * Gets lines of text to render in the Textbox. This function calculates * text wrapping on the fly every time it is called. * @param {String} text text to split * @returns {Array} Array of lines in the Textbox. * @override */ _splitTextIntoLines: function(text) { var newText = fabric.Text.prototype._splitTextIntoLines.call(this, text), graphemeLines = this._wrapText(newText.lines, this.width), lines = new Array(graphemeLines.length); for (var i = 0; i < graphemeLines.length; i++) { lines[i] = graphemeLines[i].join(''); } newText.lines = lines; newText.graphemeLines = graphemeLines; return newText; }, getMinWidth: function() { return Math.max(this.minWidth, this.dynamicMinWidth); }, /** * Returns object representation of an instance * @method toObject * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { return this.callSuper('toObject', ['minWidth', 'splitByGrapheme'].concat(propertiesToInclude)); } }); /** * Returns fabric.Textbox instance from an object representation * @static * @memberOf fabric.Textbox * @param {Object} object Object to create an instance from * @param {Function} [callback] Callback to invoke when an fabric.Textbox instance is created */ fabric.Textbox.fromObject = function(object, callback) { return fabric.Object._fromObject('Textbox', object, callback, 'text'); }; })(typeof exports !== 'undefined' ? exports : this); (function() { /** * Override _setObjectScale and add Textbox specific resizing behavior. Resizing * a Textbox doesn't scale text, it only changes width and makes text wrap automatically. */ var setObjectScaleOverridden = fabric.Canvas.prototype._setObjectScale; fabric.Canvas.prototype._setObjectScale = function(localMouse, transform, lockScalingX, lockScalingY, by, lockScalingFlip, _dim) { var t = transform.target, scaled, scaleX = localMouse.x * t.scaleX / _dim.x, scaleY = localMouse.y * t.scaleY / _dim.y; if (by === 'x' && t instanceof fabric.Textbox) { var tw = t._getTransformedDimensions().x; var w = t.width * (localMouse.x / tw); transform.newScaleX = scaleX; transform.newScaleY = scaleY; if (w >= t.getMinWidth()) { scaled = w !== t.width; t.set('width', w); return scaled; } } else { return setObjectScaleOverridden.call(fabric.Canvas.prototype, localMouse, transform, lockScalingX, lockScalingY, by, lockScalingFlip, _dim); } }; fabric.util.object.extend(fabric.Textbox.prototype, /** @lends fabric.IText.prototype */ { /** * @private */ _removeExtraneousStyles: function() { for (var prop in this._styleMap) { if (!this._textLines[prop]) { delete this.styles[this._styleMap[prop].line]; } } }, }); })(); /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ window.MathJax = { jax : [ "input/TeX", "output/SVG" ], extensions : [ "tex2jax.js" ], skipStartupTypeset : true, showMathMenu : false, showProcessingMessages : false, messageStyle : "none", SVG : { useGlobalCache : false }, TeX : {}, }; // MathJax single file build. Licenses of its components apply /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax.js * * The main support code for the MathJax Hub, including the * Ajax, Callback, Messaging, and Object-Oriented Programming * libraries, as well as the base Jax classes, and startup * processing code. * * --------------------------------------------------------------------- * * Copyright (c) 2009-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // Check if browser can support MathJax (no one fails this nowadays) // if (document.getElementById && document.childNodes && document.createElement) { // // Skip if MathJax is already loaded // if (!(window.MathJax && MathJax.Hub)) { // // Get author configuration from MathJax variable, if any // if (window.MathJax) {window.MathJax = {AuthorConfig: window.MathJax}} else {window.MathJax = {}} // MathJax.isPacked = true; // This line is uncommented by the packer. MathJax.version = "2.7.5"; MathJax.fileversion = "2.7.5"; MathJax.cdnVersion = "2.7.5"; // specifies a revision to break caching MathJax.cdnFileVersions = {}; // can be used to specify revisions for individual files /**********************************************************/ (function (BASENAME) { var BASE = window[BASENAME]; if (!BASE) {BASE = window[BASENAME] = {}} var PROTO = []; // a static object used to indicate when a prototype is being created var OBJECT = function (def) { var obj = def.constructor; if (!obj) {obj = function () {}} for (var id in def) {if (id !== 'constructor' && def.hasOwnProperty(id)) {obj[id] = def[id]}} return obj; }; var CONSTRUCTOR = function () { return function () {return arguments.callee.Init.call(this,arguments)}; }; BASE.Object = OBJECT({ constructor: CONSTRUCTOR(), Subclass: function (def,classdef) { var obj = CONSTRUCTOR(); obj.SUPER = this; obj.Init = this.Init; obj.Subclass = this.Subclass; obj.Augment = this.Augment; obj.protoFunction = this.protoFunction; obj.can = this.can; obj.has = this.has; obj.isa = this.isa; obj.prototype = new this(PROTO); obj.prototype.constructor = obj; // the real constructor obj.Augment(def,classdef); return obj; }, Init: function (args) { var obj = this; if (args.length === 1 && args[0] === PROTO) {return obj} if (!(obj instanceof args.callee)) {obj = new args.callee(PROTO)} return obj.Init.apply(obj,args) || obj; }, Augment: function (def,classdef) { var id; if (def != null) { for (id in def) {if (def.hasOwnProperty(id)) {this.protoFunction(id,def[id])}} // MSIE doesn't list toString even if it is not native so handle it separately if (def.toString !== this.prototype.toString && def.toString !== {}.toString) {this.protoFunction('toString',def.toString)} } if (classdef != null) { for (id in classdef) {if (classdef.hasOwnProperty(id)) {this[id] = classdef[id]}} } return this; }, protoFunction: function (id,def) { this.prototype[id] = def; if (typeof def === "function") {def.SUPER = this.SUPER.prototype} }, prototype: { Init: function () {}, SUPER: function (fn) {return fn.callee.SUPER}, can: function (method) {return typeof(this[method]) === "function"}, has: function (property) {return typeof(this[property]) !== "undefined"}, isa: function (obj) {return (obj instanceof Object) && (this instanceof obj)} }, can: function (method) {return this.prototype.can.call(this,method)}, has: function (property) {return this.prototype.has.call(this,property)}, isa: function (obj) { var constructor = this; while (constructor) { if (constructor === obj) {return true} else {constructor = constructor.SUPER} } return false; }, SimpleSUPER: OBJECT({ constructor: function (def) {return this.SimpleSUPER.define(def)}, define: function (src) { var dst = {}; if (src != null) { for (var id in src) {if (src.hasOwnProperty(id)) {dst[id] = this.wrap(id,src[id])}} // MSIE doesn't list toString even if it is not native so handle it separately if (src.toString !== this.prototype.toString && src.toString !== {}.toString) {dst.toString = this.wrap('toString',src.toString)} } return dst; }, wrap: function (id,f) { if (typeof(f) !== 'function' || !f.toString().match(/\.\s*SUPER\s*\(/)) {return f} var fn = function () { this.SUPER = fn.SUPER[id]; try {var result = f.apply(this,arguments)} catch (err) {delete this.SUPER; throw err} delete this.SUPER; return result; } fn.toString = function () {return f.toString.apply(f,arguments)} return fn; } }) }); BASE.Object.isArray = Array.isArray || function (obj) { return Object.prototype.toString.call(obj) === "[object Array]"; }; BASE.Object.Array = Array; })("MathJax"); /**********************************************************/ /* * Create a callback function from various forms of data: * * MathJax.Callback(fn) -- callback to a function * * MathJax.Callback([fn]) -- callback to function * MathJax.Callback([fn,data...]) * -- callback to function with given data as arguments * MathJax.Callback([object,fn]) * -- call fn with object as "this" * MathJax.Callback([object,fn,data...]) * -- call fn with object as "this" and data as arguments * MathJax.Callback(["method",object]) * -- call method of object wth object as "this" * MathJax.Callback(["method",object,data...]) * -- as above, but with data as arguments to method * * MathJax.Callback({hook: fn, data: [...], object: this}) * -- give function, data, and object to act as "this" explicitly * * MathJax.Callback("code") -- callback that compiles and executes a string * * MathJax.Callback([...],i) * -- use slice of array starting at i and interpret * result as above. (Used for passing "arguments" array * and trimming initial arguments, if any.) */ /* * MathJax.Callback.After([...],cb1,cb2,...) * -- make a callback that isn't called until all the other * ones are called first. I.e., wait for a union of * callbacks to occur before making the given callback. */ /* * MathJax.Callback.Queue([callback,...]) * -- make a synchronized queue of commands that process * sequentially, waiting for those that return uncalled * callbacks. */ /* * MathJax.Callback.Signal(name) * -- finds or creates a names signal, to which listeners * can be attached and are signaled by messages posted * to the signal. Responses can be asynchronous. */ (function (BASENAME) { var BASE = window[BASENAME]; if (!BASE) {BASE = window[BASENAME] = {}} var isArray = BASE.Object.isArray; // // Create a callback from an associative array // var CALLBACK = function (data) { var cb = function () {return arguments.callee.execute.apply(arguments.callee,arguments)}; for (var id in CALLBACK.prototype) { if (CALLBACK.prototype.hasOwnProperty(id)) { if (typeof(data[id]) !== 'undefined') {cb[id] = data[id]} else {cb[id] = CALLBACK.prototype[id]} } } cb.toString = CALLBACK.prototype.toString; return cb; }; CALLBACK.prototype = { isCallback: true, hook: function () {}, data: [], object: window, execute: function () { if (!this.called || this.autoReset) { this.called = !this.autoReset; return this.hook.apply(this.object,this.data.concat([].slice.call(arguments,0))); } }, reset: function () {delete this.called}, toString: function () {return this.hook.toString.apply(this.hook,arguments)} }; var ISCALLBACK = function (f) { return (typeof(f) === "function" && f.isCallback); } // // Evaluate a string in global context // var EVAL = function (code) {return eval.call(window,code)} var TESTEVAL = function () { EVAL("var __TeSt_VaR__ = 1"); // check if it works in global context if (window.__TeSt_VaR__) { try { delete window.__TeSt_VaR__; } // NOTE IE9 throws when in IE7 mode catch (error) { window.__TeSt_VaR__ = null; } } else { if (window.execScript) { // IE EVAL = function (code) { BASE.__code = code; code = "try {"+BASENAME+".__result = eval("+BASENAME+".__code)} catch(err) {"+BASENAME+".__result = err}"; window.execScript(code); var result = BASE.__result; delete BASE.__result; delete BASE.__code; if (result instanceof Error) {throw result} return result; } } else { // Safari2 EVAL = function (code) { BASE.__code = code; code = "try {"+BASENAME+".__result = eval("+BASENAME+".__code)} catch(err) {"+BASENAME+".__result = err}"; var head = (document.getElementsByTagName("head"))[0]; if (!head) {head = document.body} var script = document.createElement("script"); script.appendChild(document.createTextNode(code)); head.appendChild(script); head.removeChild(script); var result = BASE.__result; delete BASE.__result; delete BASE.__code; if (result instanceof Error) {throw result} return result; } } } TESTEVAL = null; }; // // Create a callback from various types of data // var USING = function (args,i) { if (arguments.length > 1) { if (arguments.length === 2 && !(typeof arguments[0] === 'function') && arguments[0] instanceof Object && typeof arguments[1] === 'number') {args = [].slice.call(args,i)} else {args = [].slice.call(arguments,0)} } if (isArray(args) && args.length === 1 && typeof(args[0]) === 'function') {args = args[0]} if (typeof args === 'function') { if (args.execute === CALLBACK.prototype.execute) {return args} return CALLBACK({hook: args}); } else if (isArray(args)) { if (typeof(args[0]) === 'string' && args[1] instanceof Object && typeof args[1][args[0]] === 'function') { return CALLBACK({hook: args[1][args[0]], object: args[1], data: args.slice(2)}); } else if (typeof args[0] === 'function') { return CALLBACK({hook: args[0], data: args.slice(1)}); } else if (typeof args[1] === 'function') { return CALLBACK({hook: args[1], object: args[0], data: args.slice(2)}); } } else if (typeof(args) === 'string') { if (TESTEVAL) TESTEVAL(); return CALLBACK({hook: EVAL, data: [args]}); } else if (args instanceof Object) { return CALLBACK(args); } else if (typeof(args) === 'undefined') { return CALLBACK({}); } throw Error("Can't make callback from given data"); }; // // Wait for a given time to elapse and then perform the callback // var DELAY = function (time,callback) { callback = USING(callback); callback.timeout = setTimeout(callback,time); return callback; }; // // Callback used by AFTER, QUEUE, and SIGNAL to check if calls have completed // var WAITFOR = function (callback,signal) { callback = USING(callback); if (!callback.called) {WAITSIGNAL(callback,signal); signal.pending++} }; var WAITEXECUTE = function () { var signals = this.signal; delete this.signal; this.execute = this.oldExecute; delete this.oldExecute; var result = this.execute.apply(this,arguments); if (ISCALLBACK(result) && !result.called) {WAITSIGNAL(result,signals)} else { for (var i = 0, m = signals.length; i < m; i++) { signals[i].pending--; if (signals[i].pending <= 0) {signals[i].call()} } } }; var WAITSIGNAL = function (callback,signals) { if (!isArray(signals)) {signals = [signals]} if (!callback.signal) { callback.oldExecute = callback.execute; callback.execute = WAITEXECUTE; callback.signal = signals; } else if (signals.length === 1) {callback.signal.push(signals[0])} else {callback.signal = callback.signal.concat(signals)} }; // // Create a callback that is called when a collection of other callbacks have // all been executed. If the callback gets called immediately (i.e., the // others are all already called), check if it returns another callback // and return that instead. // var AFTER = function (callback) { callback = USING(callback); callback.pending = 0; for (var i = 1, m = arguments.length; i < m; i++) {if (arguments[i]) {WAITFOR(arguments[i],callback)}} if (callback.pending === 0) { var result = callback(); if (ISCALLBACK(result)) {callback = result} } return callback; }; // // An array of prioritized hooks that are executed sequentially // with a given set of data. // var HOOKS = MathJax.Object.Subclass({ // // Initialize the array and the auto-reset status // Init: function (reset) { this.hooks = []; this.remove = []; // used when hooks are removed during execution of list this.reset = reset; this.running = false; }, // // Add a callback to the list, in priority order (default priority is 10) // Add: function (hook,priority) { if (priority == null) {priority = 10} if (!ISCALLBACK(hook)) {hook = USING(hook)} hook.priority = priority; var i = this.hooks.length; while (i > 0 && priority < this.hooks[i-1].priority) {i--} this.hooks.splice(i,0,hook); return hook; }, Remove: function (hook) { for (var i = 0, m = this.hooks.length; i < m; i++) { if (this.hooks[i] === hook) { if (this.running) {this.remove.push(i)} else {this.hooks.splice(i,1)} return; } } }, // // Execute the list of callbacks, resetting them if requested. // If any return callbacks, return a callback that will be // executed when they all have completed. // Remove any hooks that requested being removed during processing. // Execute: function () { var callbacks = [{}]; this.running = true; for (var i = 0, m = this.hooks.length; i < m; i++) { if (this.reset) {this.hooks[i].reset()} var result = this.hooks[i].apply(window,arguments); if (ISCALLBACK(result) && !result.called) {callbacks.push(result)} } this.running = false; if (this.remove.length) {this.RemovePending()} if (callbacks.length === 1) {return null} if (callbacks.length === 2) {return callbacks[1]} return AFTER.apply({},callbacks); }, // // Remove hooks that asked to be removed during execution of list // RemovePending: function () { this.remove = this.remove.sort(); for (var i = this.remove.length-1; i >= 0; i--) {this.hooks.splice(i,1)} this.remove = []; } }); // // Run an array of callbacks passing them the given data. // (Legacy function, since this has been replaced by the HOOKS object). // var EXECUTEHOOKS = function (hooks,data,reset) { if (!hooks) {return null} if (!isArray(hooks)) {hooks = [hooks]} if (!isArray(data)) {data = (data == null ? [] : [data])} var handler = HOOKS(reset); for (var i = 0, m = hooks.length; i < m; i++) {handler.Add(hooks[i])} return handler.Execute.apply(handler,data); }; // // Command queue that performs commands in order, waiting when // necessary for commands to complete asynchronousely // var QUEUE = BASE.Object.Subclass({ // // Create the queue and push any commands that are specified // Init: function () { this.pending = this.running = 0; this.queue = []; this.Push.apply(this,arguments); }, // // Add commands to the queue and run them. Adding a callback object // (rather than a callback specification) queues a wait for that callback. // Return the final callback for synchronization purposes. // Push: function () { var callback; for (var i = 0, m = arguments.length; i < m; i++) { callback = USING(arguments[i]); if (callback === arguments[i] && !callback.called) {callback = USING(["wait",this,callback])} this.queue.push(callback); } if (!this.running && !this.pending) {this.Process()} return callback; }, // // Process the command queue if we aren't waiting on another command // Process: function (queue) { while (!this.running && !this.pending && this.queue.length) { var callback = this.queue[0]; queue = this.queue.slice(1); this.queue = []; this.Suspend(); var result = callback(); this.Resume(); if (queue.length) {this.queue = queue.concat(this.queue)} if (ISCALLBACK(result) && !result.called) {WAITFOR(result,this)} } }, // // Suspend/Resume command processing on this queue // Suspend: function () {this.running++}, Resume: function () {if (this.running) {this.running--}}, // // Used by WAITFOR to restart the queue when an action completes // call: function () {this.Process.apply(this,arguments)}, wait: function (callback) {return callback} }); // // Create a named signal that listeners can attach to, to be signaled by // postings made to the signal. Posts are queued if they occur while one // is already in process. // var SIGNAL = QUEUE.Subclass({ Init: function (name) { QUEUE.prototype.Init.call(this); this.name = name; this.posted = []; // the messages posted so far this.listeners = HOOKS(true); // those with interest in this signal this.posting = false; this.callback = null; }, // // Post a message to the signal listeners, with callback for when complete // Post: function (message,callback,forget) { callback = USING(callback); if (this.posting || this.pending) { this.Push(["Post",this,message,callback,forget]); } else { this.callback = callback; callback.reset(); if (!forget) {this.posted.push(message)} this.Suspend(); this.posting = true; var result = this.listeners.Execute(message); if (ISCALLBACK(result) && !result.called) {WAITFOR(result,this)} this.Resume(); this.posting = false; if (!this.pending) {this.call()} } return callback; }, // // Clear the post history (so new listeners won't get old messages) // Clear: function (callback) { callback = USING(callback); if (this.posting || this.pending) { callback = this.Push(["Clear",this,callback]); } else { this.posted = []; callback(); } return callback; }, // // Call the callback (all replies are in) and process the command queue // call: function () {this.callback(this); this.Process()}, // // A listener calls this to register interest in the signal (so it will be called // when posts occur). If ignorePast is true, it will not be sent the post history. // Interest: function (callback,ignorePast,priority) { callback = USING(callback); this.listeners.Add(callback,priority); if (!ignorePast) { for (var i = 0, m = this.posted.length; i < m; i++) { callback.reset(); var result = callback(this.posted[i]); if (ISCALLBACK(result) && i === this.posted.length-1) {WAITFOR(result,this)} } } return callback; }, // // A listener calls this to remove itself from a signal // NoInterest: function (callback) { this.listeners.Remove(callback); }, // // Hook a callback to a particular message on this signal // MessageHook: function (msg,callback,priority) { callback = USING(callback); if (!this.hooks) {this.hooks = {}; this.Interest(["ExecuteHooks",this])} if (!this.hooks[msg]) {this.hooks[msg] = HOOKS(true)} this.hooks[msg].Add(callback,priority); for (var i = 0, m = this.posted.length; i < m; i++) {if (this.posted[i] == msg) {callback.reset(); callback(this.posted[i])}} callback.msg = msg; // keep track so we can remove it return callback; }, // // Execute the message hooks for the given message // ExecuteHooks: function (msg) { var type = (isArray(msg) ? msg[0] : msg); if (!this.hooks[type]) {return null} return this.hooks[type].Execute(msg); }, // // Remove a hook safely // RemoveHook: function (hook) { this.hooks[hook.msg].Remove(hook); } },{ signals: {}, // the named signals find: function (name) { if (!SIGNAL.signals[name]) {SIGNAL.signals[name] = new SIGNAL(name)} return SIGNAL.signals[name]; } }); // // The main entry-points // BASE.Callback = BASE.CallBack = USING; BASE.Callback.Delay = DELAY; BASE.Callback.After = AFTER; BASE.Callback.Queue = QUEUE; BASE.Callback.Signal = SIGNAL.find; BASE.Callback.Hooks = HOOKS; BASE.Callback.ExecuteHooks = EXECUTEHOOKS; })("MathJax"); /**********************************************************/ (function (BASENAME) { var BASE = window[BASENAME]; if (!BASE) {BASE = window[BASENAME] = {}} var isSafari2 = (navigator.vendor === "Apple Computer, Inc." && typeof navigator.vendorSub === "undefined"); var sheets = 0; // used by Safari2 // // Update sheets count and look up the head object // var HEAD = function (head) { if (document.styleSheets && document.styleSheets.length > sheets) {sheets = document.styleSheets.length} if (!head) { head = document.head || ((document.getElementsByTagName("head"))[0]); if (!head) {head = document.body} } return head; }; // // Remove scripts that are completed so they don't clutter up the HEAD. // This runs via setTimeout since IE7 can't remove the script while it is running. // var SCRIPTS = []; // stores scripts to be removed after a delay var REMOVESCRIPTS = function () { for (var i = 0, m = SCRIPTS.length; i < m; i++) {BASE.Ajax.head.removeChild(SCRIPTS[i])} SCRIPTS = []; }; var PATH = {}; PATH[BASENAME] = ""; // empty path gets the root URL PATH.a11y = '[MathJax]/extensions/a11y'; // a11y extensions PATH.Contrib = "https://cdn.mathjax.org/mathjax/contrib"; // the third-party extensions BASE.Ajax = { loaded: {}, // files already loaded loading: {}, // files currently in process of loading loadHooks: {}, // hooks to call when files are loaded timeout: 15*1000, // timeout for loading of files (15 seconds) styleDelay: 1, // delay to use before styles are available config: { root: "", // URL of root directory to load from path: PATH // paths to named URL's (e.g., [MathJax]/...) }, params: {}, // filled in from MathJax.js?... STATUS: { OK: 1, // file is loading or did load OK ERROR: -1 // file timed out during load }, // // Return a complete URL to a file (replacing any root names) // fileURL: function (file) { var match; while ((match = file.match(/^\[([-._a-z0-9]+)\]/i)) && PATH.hasOwnProperty(match[1])) { file = (PATH[match[1]]||this.config.root) + file.substr(match[1].length+2); } return file; }, // // Replace root names if URL includes one // fileName: function (url) { var root = this.config.root; if (url.substr(0,root.length) === root) {url = "["+BASENAME+"]"+url.substr(root.length)} do { var recheck = false; for (var id in PATH) {if (PATH.hasOwnProperty(id) && PATH[id]) { if (url.substr(0,PATH[id].length) === PATH[id]) { url = "["+id+"]"+url.substr(PATH[id].length); recheck = true; break; } }} } while (recheck); return url; }, // // Cache-breaking revision number for file // fileRev: function (file) { var V = BASE.cdnFileVersions[file] || BASE.cdnVersion || ''; if (V) {V = "?V="+V} return V; }, urlRev: function (file) {return this.fileURL(file)+this.fileRev(file)}, // // Load a file if it hasn't been already. // Make sure the file URL is "safe"? // Require: function (file,callback) { callback = BASE.Callback(callback); var type; if (file instanceof Object) { for (var i in file) {if (file.hasOwnProperty(i)) {type = i.toUpperCase(); file = file[i]}} } else {type = file.split(/\./).pop().toUpperCase()} if (this.params.noContrib && file.substr(0,9) === "[Contrib]") { callback(this.STATUS.ERROR); } else { file = this.fileURL(file); // FIXME: check that URL is OK if (this.loaded[file]) { callback(this.loaded[file]); } else { var FILE = {}; FILE[type] = file; this.Load(FILE,callback); } } return callback; }, // // Load a file regardless of where it is and whether it has // already been loaded. // Load: function (file,callback) { callback = BASE.Callback(callback); var type; if (file instanceof Object) { for (var i in file) {if (file.hasOwnProperty(i)) {type = i.toUpperCase(); file = file[i]}} } else {type = file.split(/\./).pop().toUpperCase()} file = this.fileURL(file); if (this.loading[file]) { this.addHook(file,callback); } else { this.head = HEAD(this.head); if (this.loader[type]) {this.loader[type].call(this,file,callback)} else {throw Error("Can't load files of type "+type)} } return callback; }, // // Register a load hook for a particular file (it will be called when // loadComplete() is called for that file) // LoadHook: function (file,callback,priority) { callback = BASE.Callback(callback); if (file instanceof Object) {for (var i in file) {if (file.hasOwnProperty(i)) {file = file[i]}}} file = this.fileURL(file); if (this.loaded[file]) {callback(this.loaded[file])} else {this.addHook(file,callback,priority)} return callback; }, addHook: function (file,callback,priority) { if (!this.loadHooks[file]) {this.loadHooks[file] = MathJax.Callback.Hooks()} this.loadHooks[file].Add(callback,priority); callback.file = file; }, removeHook: function (hook) { if (this.loadHooks[hook.file]) { this.loadHooks[hook.file].Remove(hook); if (!this.loadHooks[hook.file].hooks.length) {delete this.loadHooks[hook.file]} } }, // // Used when files are combined in a preloading configuration file // Preloading: function () { for (var i = 0, m = arguments.length; i < m; i++) { var file = this.fileURL(arguments[i]); if (!this.loading[file]) {this.loading[file] = {preloaded: true}} } }, // // Code used to load the various types of files // (JS for JavaScript, CSS for style sheets) // loader: { // // Create a SCRIPT tag to load the file // JS: function (file,callback) { var name = this.fileName(file); var script = document.createElement("script"); var timeout = BASE.Callback(["loadTimeout",this,file]); this.loading[file] = { callback: callback, timeout: setTimeout(timeout,this.timeout), status: this.STATUS.OK, script: script }; // // Add this to the structure above after it is created to prevent recursion // when loading the initial localization file (before loading message is available) // this.loading[file].message = BASE.Message.File(name); script.onerror = timeout; // doesn't work in IE and no apparent substitute script.type = "text/javascript"; script.src = file+this.fileRev(name); this.head.appendChild(script); }, // // Create a LINK tag to load the style sheet // CSS: function (file,callback) { var name = this.fileName(file); var link = document.createElement("link"); link.rel = "stylesheet"; link.type = "text/css"; link.href = file+this.fileRev(name); this.loading[file] = { callback: callback, message: BASE.Message.File(name), status: this.STATUS.OK }; this.head.appendChild(link); this.timer.create.call(this,[this.timer.file,file],link); } }, // // Timing code for checking when style sheets are available. // timer: { // // Create the timing callback and start the timing loop. // We use a delay because some browsers need it to allow the styles // to be processed. // create: function (callback,node) { callback = BASE.Callback(callback); if (node.nodeName === "STYLE" && node.styleSheet && typeof(node.styleSheet.cssText) !== 'undefined') { callback(this.STATUS.OK); // MSIE processes style immediately, but doesn't set its styleSheet! } else if (window.chrome && node.nodeName === "LINK") { callback(this.STATUS.OK); // Chrome doesn't give access to cssRules for stylesheet in // a link node, so we can't detect when it is loaded. } else if (isSafari2) { this.timer.start(this,[this.timer.checkSafari2,sheets++,callback],this.styleDelay); } else { this.timer.start(this,[this.timer.checkLength,node,callback],this.styleDelay); } return callback; }, // // Start the timer for the given callback checker // start: function (AJAX,check,delay,timeout) { check = BASE.Callback(check); check.execute = this.execute; check.time = this.time; check.STATUS = AJAX.STATUS; check.timeout = timeout || AJAX.timeout; check.delay = check.total = delay || 0; if (delay) {setTimeout(check,delay)} else {check()} }, // // Increment the time total, increase the delay // and test if we are past the timeout time. // time: function (callback) { this.total += this.delay; this.delay = Math.floor(this.delay * 1.05 + 5); if (this.total >= this.timeout) {callback(this.STATUS.ERROR); return 1} return 0; }, // // For JS file loads, call the proper routine according to status // file: function (file,status) { if (status < 0) {BASE.Ajax.loadTimeout(file)} else {BASE.Ajax.loadComplete(file)} }, // // Call the hook with the required data // execute: function () {this.hook.call(this.object,this,this.data[0],this.data[1])}, // // Safari2 doesn't set the link's stylesheet, so we need to look in the // document.styleSheets array for the new sheet when it is created // checkSafari2: function (check,length,callback) { if (check.time(callback)) return; if (document.styleSheets.length > length && document.styleSheets[length].cssRules && document.styleSheets[length].cssRules.length) {callback(check.STATUS.OK)} else {setTimeout(check,check.delay)} }, // // Look for the stylesheets rules and check when they are defined // and no longer of length zero. (This assumes there actually ARE // some rules in the stylesheet.) // checkLength: function (check,node,callback) { if (check.time(callback)) return; var isStyle = 0; var sheet = (node.sheet || node.styleSheet); try {if ((sheet.cssRules||sheet.rules||[]).length > 0) {isStyle = 1}} catch(err) { if (err.message.match(/protected variable|restricted URI/)) {isStyle = 1} else if (err.message.match(/Security error/)) { // Firefox3 gives "Security error" for missing files, so // can't distinguish that from OK files on remote servers. // or OK files in different directory from local files. isStyle = 1; // just say it is OK (can't really tell) } } if (isStyle) { // Opera 9.6 requires this setTimeout setTimeout(BASE.Callback([callback,check.STATUS.OK]),0); } else { setTimeout(check,check.delay); } } }, // // JavaScript code must call this when they are completely initialized // (this allows them to perform asynchronous actions before indicating // that they are complete). // loadComplete: function (file) { file = this.fileURL(file); var loading = this.loading[file]; if (loading && !loading.preloaded) { BASE.Message.Clear(loading.message); clearTimeout(loading.timeout); if (loading.script) { if (SCRIPTS.length === 0) {setTimeout(REMOVESCRIPTS,0)} SCRIPTS.push(loading.script); } this.loaded[file] = loading.status; delete this.loading[file]; this.addHook(file,loading.callback); } else { if (loading) {delete this.loading[file]} this.loaded[file] = this.STATUS.OK; loading = {status: this.STATUS.OK} } if (!this.loadHooks[file]) {return null} return this.loadHooks[file].Execute(loading.status); }, // // If a file fails to load within the timeout period (or the onerror handler // is called), this routine runs to signal the error condition. // loadTimeout: function (file) { if (this.loading[file].timeout) {clearTimeout(this.loading[file].timeout)} this.loading[file].status = this.STATUS.ERROR; this.loadError(file); this.loadComplete(file); }, // // The default error hook for file load failures // loadError: function (file) { BASE.Message.Set(["LoadFailed","File failed to load: %1",file],null,2000); BASE.Hub.signal.Post(["file load error",file]); }, // // Defines a style sheet from a hash of style declarations (key:value pairs // where the key is the style selector and the value is a hash of CSS attributes // and values). // Styles: function (styles,callback) { var styleString = this.StyleString(styles); if (styleString === "") { callback = BASE.Callback(callback); callback(); } else { var style = document.createElement("style"); style.type = "text/css"; this.head = HEAD(this.head); this.head.appendChild(style); if (style.styleSheet && typeof(style.styleSheet.cssText) !== 'undefined') { style.styleSheet.cssText = styleString; } else { style.appendChild(document.createTextNode(styleString)); } callback = this.timer.create.call(this,callback,style); } return callback; }, // // Create a stylesheet string from a style declaration object // StyleString: function (styles) { if (typeof(styles) === 'string') {return styles} var string = "", id, style; for (id in styles) {if (styles.hasOwnProperty(id)) { if (typeof styles[id] === 'string') { string += id + " {"+styles[id]+"}\n"; } else if (BASE.Object.isArray(styles[id])) { for (var i = 0; i < styles[id].length; i++) { style = {}; style[id] = styles[id][i]; string += this.StyleString(style); } } else if (id.substr(0,6) === '@media') { string += id + " {"+this.StyleString(styles[id])+"}\n"; } else if (styles[id] != null) { style = []; for (var name in styles[id]) {if (styles[id].hasOwnProperty(name)) { if (styles[id][name] != null) {style[style.length] = name + ': ' + styles[id][name]} }} string += id +" {"+style.join('; ')+"}\n"; } }} return string; } }; })("MathJax"); /**********************************************************/ MathJax.HTML = { // // Create an HTML element with given attributes and content. // The def parameter is an (optional) object containing key:value pairs // of the attributes and their values, and contents is an (optional) // array of strings to be inserted as text, or arrays of the form // [type,def,contents] that describes an HTML element to be inserted // into the current element. Thus the contents can describe a complete // HTML snippet of arbitrary complexity. E.g.: // // MathJax.HTML.Element("span",{id:"mySpan",style{"font-style":"italic"}},[ // "(See the ",["a",{href:"http://www.mathjax.org"},["MathJax home page"]], // " for more details.)"]); // Element: function (type,def,contents) { var obj = document.createElement(type), id; if (def) { if (def.hasOwnProperty("style")) { var style = def.style; def.style = {}; for (id in style) {if (style.hasOwnProperty(id)) {def.style[id.replace(/-([a-z])/g,this.ucMatch)] = style[id]}} } MathJax.Hub.Insert(obj,def); for (id in def) { if (id === "role" || id.substr(0,5) === "aria-") obj.setAttribute(id,def[id]); } } if (contents) { if (!MathJax.Object.isArray(contents)) {contents = [contents]} for (var i = 0, m = contents.length; i < m; i++) { if (MathJax.Object.isArray(contents[i])) { obj.appendChild(this.Element(contents[i][0],contents[i][1],contents[i][2])); } else if (type === "script") { // IE throws an error if script is added as a text node this.setScript(obj, contents[i]); } else { obj.appendChild(document.createTextNode(contents[i])); } } } return obj; }, ucMatch: function (match,c) {return c.toUpperCase()}, addElement: function (span,type,def,contents) {return span.appendChild(this.Element(type,def,contents))}, TextNode: function (text) {return document.createTextNode(text)}, addText: function (span,text) {return span.appendChild(this.TextNode(text))}, // // Set and get the text of a script // setScript: function (script,text) { if (this.setScriptBug) {script.text = text} else { while (script.firstChild) {script.removeChild(script.firstChild)} this.addText(script,text); } }, getScript: function (script) { var text = (script.text === "" ? script.innerHTML : script.text); return text.replace(/^\s+/,"").replace(/\s+$/,""); }, // // Manage cookies // Cookie: { prefix: "mjx", expires: 365, // // Save an object as a named cookie // Set: function (name,def) { var keys = []; if (def) { for (var id in def) {if (def.hasOwnProperty(id)) { keys.push(id+":"+def[id].toString().replace(/&/g,"&&")); }} } var cookie = this.prefix+"."+name+"="+escape(keys.join('&;')); if (this.expires) { var time = new Date(); time.setDate(time.getDate() + this.expires); cookie += '; expires='+time.toGMTString(); } try {document.cookie = cookie+"; path=/"} catch (err) {} // ignore errors saving cookies }, // // Get the contents of a named cookie and incorporate // it into the given object (or return a fresh one) // Get: function (name,obj) { if (!obj) {obj = {}} var pattern = new RegExp("(?:^|;\\s*)"+this.prefix+"\\."+name+"=([^;]*)(?:;|$)"); var match; try {match = pattern.exec(document.cookie)} catch (err) {}; // ignore errors reading cookies if (match && match[1] !== "") { var keys = unescape(match[1]).split('&;'); for (var i = 0, m = keys.length; i < m; i++) { match = keys[i].match(/([^:]+):(.*)/); var value = match[2].replace(/&&/g,'&'); if (value === "true") {value = true} else if (value === "false") {value = false} else if (value.match(/^-?(\d+(\.\d+)?|\.\d+)$/)) {value = parseFloat(value)} obj[match[1]] = value; } } return obj; } } }; /**********************************************************/ MathJax.Localization = { locale: "en", directory: "[MathJax]/localization", strings: { // Currently, this list is not modified by the MathJax-i18n script. You can // run the following command in MathJax/unpacked/localization to update it: // // find . -name "*.js" | xargs grep menuTitle\: | grep -v qqq | sed 's/^\.\/\(.*\)\/.*\.js\: / "\1"\: \{/' | sed 's/,$/\},/' | sed 's/"English"/"English", isLoaded: true/' > tmp ; sort tmp > tmp2 ; sed '$ s/,$//' tmp2 ; rm tmp* // // This only takes languages with localization data so you must also add // the languages that use a remap but are not translated at all. // "ar": {menuTitle: "\u0627\u0644\u0639\u0631\u0628\u064A\u0629"}, "ast": {menuTitle: "asturianu"}, "bg": {menuTitle: "\u0431\u044A\u043B\u0433\u0430\u0440\u0441\u043A\u0438"}, "bcc": {menuTitle: "\u0628\u0644\u0648\u0686\u06CC"}, "br": {menuTitle: "brezhoneg"}, "ca": {menuTitle: "catal\u00E0"}, "cdo": {menuTitle: "M\u00ECng-d\u0115\u0324ng-ng\u1E73\u0304"}, "cs": {menuTitle: "\u010De\u0161tina"}, "da": {menuTitle: "dansk"}, "de": {menuTitle: "Deutsch"}, "diq": {menuTitle: "Zazaki"}, "en": {menuTitle: "English", isLoaded: true}, "eo": {menuTitle: "Esperanto"}, "es": {menuTitle: "espa\u00F1ol"}, "fa": {menuTitle: "\u0641\u0627\u0631\u0633\u06CC"}, "fi": {menuTitle: "suomi"}, "fr": {menuTitle: "fran\u00E7ais"}, "gl": {menuTitle: "galego"}, "he": {menuTitle: "\u05E2\u05D1\u05E8\u05D9\u05EA"}, "ia": {menuTitle: "interlingua"}, "it": {menuTitle: "italiano"}, "ja": {menuTitle: "\u65E5\u672C\u8A9E"}, "kn": {menuTitle: "\u0C95\u0CA8\u0CCD\u0CA8\u0CA1"}, "ko": {menuTitle: "\uD55C\uAD6D\uC5B4"}, "lb": {menuTitle: "L\u00EBtzebuergesch"}, "lki": {menuTitle: "\u0644\u06D5\u06A9\u06CC"}, "lt": {menuTitle: "lietuvi\u0173"}, "mk": {menuTitle: "\u043C\u0430\u043A\u0435\u0434\u043E\u043D\u0441\u043A\u0438"}, "nl": {menuTitle: "Nederlands"}, "oc": {menuTitle: "occitan"}, "pl": {menuTitle: "polski"}, "pt": {menuTitle: "portugu\u00EAs"}, "pt-br": {menuTitle: "portugu\u00EAs do Brasil"}, "ru": {menuTitle: "\u0440\u0443\u0441\u0441\u043A\u0438\u0439"}, "sco": {menuTitle: "Scots"}, "scn": {menuTitle: "sicilianu"}, "sk": {menuTitle: "sloven\u010Dina"}, "sl": {menuTitle: "sloven\u0161\u010Dina"}, "sv": {menuTitle: "svenska"}, "th": {menuTitle: "\u0E44\u0E17\u0E22"}, "tr": {menuTitle: "T\u00FCrk\u00E7e"}, "uk": {menuTitle: "\u0443\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430"}, "vi": {menuTitle: "Ti\u1EBFng Vi\u1EC7t"}, "zh-hans": {menuTitle: "\u4E2D\u6587\uFF08\u7B80\u4F53\uFF09"}, "zh-hant": {menuTitle: "\u6C49\u8BED"} }, // // The pattern for substitution escapes: // %n or %{n} or %{plural:%n|option1|option1|...} or %c // pattern: /%(\d+|\{\d+\}|\{[a-z]+:\%\d+(?:\|(?:%\{\d+\}|%.|[^\}])*)+\}|.)/g, SPLIT: ("axb".split(/(x)/).length === 3 ? function (string,regex) {return string.split(regex)} : // // IE8 and below don't do split() correctly when the pattern includes // parentheses (the split should include the matched exrepssions). // So implement it by hand here. // function (string,regex) { var result = [], match, last = 0; regex.lastIndex = 0; while ((match = regex.exec(string))) { result.push(string.substr(last,match.index-last)); result.push.apply(result,match.slice(1)); last = match.index + match[0].length; } result.push(string.substr(last)); return result; }), _: function (id,phrase) { if (MathJax.Object.isArray(phrase)) {return this.processSnippet(id,phrase)} return this.processString(this.lookupPhrase(id,phrase),[].slice.call(arguments,2)); }, processString: function (string,args,domain) { // // Process arguments for substitution // If the argument is a snippet (and we are processing snippets) do so, // Otherwise, if it is a number, convert it for the lacale // var i, m, isArray = MathJax.Object.isArray; for (i = 0, m = args.length; i < m; i++) { if (domain && isArray(args[i])) {args[i] = this.processSnippet(domain,args[i])} } // // Split string at escapes and process them individually // var parts = this.SPLIT(string,this.pattern); for (i = 1, m = parts.length; i < m; i += 2) { var c = parts[i].charAt(0); // first char will be { or \d or a char to be kept literally if (c >= "0" && c <= "9") { // %n parts[i] = args[parts[i]-1]; if (typeof parts[i] === "number") parts[i] = this.number(parts[i]); } else if (c === "{") { // %{n} or %{plural:%n|...} c = parts[i].substr(1); if (c >= "0" && c <= "9") { // %{n} parts[i] = args[parts[i].substr(1,parts[i].length-2)-1]; if (typeof parts[i] === "number") parts[i] = this.number(parts[i]); } else { // %{plural:%n|...} var match = parts[i].match(/^\{([a-z]+):%(\d+)\|(.*)\}$/); if (match) { if (match[1] === "plural") { var n = args[match[2]-1]; if (typeof n === "undefined") { parts[i] = "???"; // argument doesn't exist } else { n = this.plural(n) - 1; // index of the form to use var plurals = match[3].replace(/(^|[^%])(%%)*%\|/g,"$1$2%\uEFEF").split(/\|/); // the parts (replacing %| with a special character) if (n >= 0 && n < plurals.length) { parts[i] = this.processString(plurals[n].replace(/\uEFEF/g,"|"),args,domain); } else { parts[i] = "???"; // no string for this index } } } else {parts[i] = "%"+parts[i]} // not "plural", put back the % and leave unchanged } } } if (parts[i] == null) {parts[i] = "???"} } // // If we are not forming a snippet, return the completed string // if (!domain) {return parts.join("")} // // We need to return an HTML snippet, so buld it from the // broken up string with inserted parts (that could be snippets) // var snippet = [], part = ""; for (i = 0; i < m; i++) { part += parts[i]; i++; // add the string and move on to substitution result if (i < m) { if (isArray(parts[i])) { // substitution was a snippet snippet.push(part); // add the accumulated string snippet = snippet.concat(parts[i]); // concatenate the substution snippet part = ""; // start accumulating a new string } else { // substitution was a string part += parts[i]; // add to accumulating string } } } if (part !== "") {snippet.push(part)} // add final string return snippet; }, processSnippet: function (domain,snippet) { var result = []; // the new snippet // // Look through the original snippet for // strings or snippets to translate // for (var i = 0, m = snippet.length; i < m; i++) { if (MathJax.Object.isArray(snippet[i])) { // // This could be a sub-snippet: // ["tag"] or ["tag",{properties}] or ["tag",{properties},snippet] // Or it could be something to translate: // [id,string,args] or [domain,snippet] var data = snippet[i]; if (typeof data[1] === "string") { // [id,string,args] var id = data[0]; if (!MathJax.Object.isArray(id)) {id = [domain,id]} var phrase = this.lookupPhrase(id,data[1]); result = result.concat(this.processMarkdown(phrase,data.slice(2),domain)); } else if (MathJax.Object.isArray(data[1])) { // [domain,snippet] result = result.concat(this.processSnippet.apply(this,data)); } else if (data.length >= 3) { // ["tag",{properties},snippet] result.push([data[0],data[1],this.processSnippet(domain,data[2])]); } else { // ["tag"] or ["tag",{properties}] result.push(snippet[i]); } } else { // a string result.push(snippet[i]); } } return result; }, markdownPattern: /(%.)|(\*{1,3})((?:%.|.)+?)\2|(`+)((?:%.|.)+?)\4|\[((?:%.|.)+?)\]\(([^\s\)]+)\)/, // %c or *bold*, **italics**, ***bold-italics***, or `code`, or [link](url) processMarkdown: function (phrase,args,domain) { var result = [], data; // // Split the string by the Markdown pattern // (the text blocks are separated by // c,stars,star-text,backtics,code-text,link-text,URL). // Start with the first text string from the split. // var parts = phrase.split(this.markdownPattern); var string = parts[0]; // // Loop through the matches and process them // for (var i = 1, m = parts.length; i < m; i += 8) { if (parts[i+1]) { // stars (for bold/italic) // // Select the tag to use by number of stars (three stars requires two tags) // data = this.processString(parts[i+2],args,domain); if (!MathJax.Object.isArray(data)) {data = [data]} data = [["b","i","i"][parts[i+1].length-1],{},data]; // number of stars determines type if (parts[i+1].length === 3) {data = ["b",{},data]} // bold-italic } else if (parts[i+3]) { // backtics (for code) // // Remove one leading or trailing space, and process substitutions // Make a <code> tag // data = this.processString(parts[i+4].replace(/^\s/,"").replace(/\s$/,""),args,domain); if (!MathJax.Object.isArray(data)) {data = [data]} data = ["code",{},data]; } else if (parts[i+5]) { // hyperlink // // Process the link text, and make an <a> tag with the URL // data = this.processString(parts[i+5],args,domain); if (!MathJax.Object.isArray(data)) {data = [data]} data = ["a",{href:this.processString(parts[i+6],args),target:"_blank"},data]; } else { // // Escaped character (%c) gets added into the string. // string += parts[i]; data = null; } // // If there is a tag to insert, // Add any pending string, then push the tag // if (data) { result = this.concatString(result,string,args,domain); result.push(data); string = ""; } // // Process the string that follows matches pattern // if (parts[i+7] !== "") {string += parts[i+7]} }; // // Add any pending string and return the resulting snippet // result = this.concatString(result,string,args,domain); return result; }, concatString: function (result,string,args,domain) { if (string != "") { // // Process the substutions. // If the result is not a snippet, turn it into one. // Then concatenate the snippet to the current one // string = this.processString(string,args,domain); if (!MathJax.Object.isArray(string)) {string = [string]} result = result.concat(string); } return result; }, lookupPhrase: function (id,phrase,domain) { // // Get the domain and messageID // if (!domain) {domain = "_"} if (MathJax.Object.isArray(id)) {domain = (id[0] || "_"); id = (id[1] || "")} // // Check if the data is available and if not, // load it and throw a restart error so the calling // code can wait for the load and try again. // var load = this.loadDomain(domain); if (load) {MathJax.Hub.RestartAfter(load)} // // Look up the message in the localization data // (if not found, the original English is used) // var localeData = this.strings[this.locale]; if (localeData) { if (localeData.domains && domain in localeData.domains) { var domainData = localeData.domains[domain]; if (domainData.strings && id in domainData.strings) {phrase = domainData.strings[id]} } } // // return the translated phrase // return phrase; }, // // Load a langauge data file from the proper // directory and file. // loadFile: function (file,data,callback) { callback = MathJax.Callback(callback); file = (data.file || file); // the data's file name or the default name if (!file.match(/\.js$/)) {file += ".js"} // add .js if needed // // Add the directory if the file doesn't // contain a full URL already. // if (!file.match(/^([a-z]+:|\[MathJax\])/)) { var dir = (this.strings[this.locale].directory || this.directory + "/" + this.locale || "[MathJax]/localization/" + this.locale); file = dir + "/" + file; } // // Load the file and mark the data as loaded (even if it // failed to load, so we don't continue to try to load it // over and over). // var load = MathJax.Ajax.Require(file,function () {data.isLoaded = true; return callback()}); // // Return the callback if needed, otherwise null. // return (load.called ? null : load); }, // // Check to see if the localization data are loaded // for the given domain; if not, load the data file, // and return a callback for the loading operation. // Otherwise return null (data are loaded). // loadDomain: function (domain,callback) { var load, localeData = this.strings[this.locale]; if (localeData) { if (!localeData.isLoaded) { load = this.loadFile(this.locale,localeData); if (load) { return MathJax.Callback.Queue( load,["loadDomain",this,domain] // call again to load domain ).Push(callback||{}); } } if (localeData.domains && domain in localeData.domains) { var domainData = localeData.domains[domain]; if (!domainData.isLoaded) { load = this.loadFile(domain,domainData); if (load) {return MathJax.Callback.Queue(load).Push(callback)} } } } // localization data are loaded, so just do the callback return MathJax.Callback(callback)(); }, // // Perform a function, properly handling // restarts due to localization file loads. // // Note that this may return before the function // has been called successfully, so you should // consider fn as running asynchronously. (Callbacks // can be used to synchronize it with other actions.) // Try: function (fn) { fn = MathJax.Callback(fn); fn.autoReset = true; try {fn()} catch (err) { if (!err.restart) {throw err} MathJax.Callback.After(["Try",this,fn],err.restart); } }, // // Reset the current language // resetLocale: function(locale) { // Selection algorithm: // 1) Downcase locale name (e.g. "en-US" => "en-us") // 2) Try a parent language (e.g. "en-us" => "en") // 3) Try the fallback specified in the data (e.g. "pt" => "pt-br") // 4) Otherwise don't change the locale. if (!locale) return; locale = locale.toLowerCase(); while (!this.strings[locale]) { var dashPos = locale.lastIndexOf("-"); if (dashPos === -1) return; locale = locale.substring(0, dashPos); } var remap = this.strings[locale].remap; this.locale = remap ? remap : locale; MathJax.Callback.Signal("Hub").Post(["Locale Reset", this.locale]); }, // // Set the current language // setLocale: function(locale) { this.resetLocale(locale); if (MathJax.Menu) {this.loadDomain("MathMenu")} }, // // Add or update a language or domain // addTranslation: function (locale,domain,definition) { var data = this.strings[locale], isNew = false; if (!data) {data = this.strings[locale] = {}; isNew = true} if (!data.domains) {data.domains = {}} if (domain) { if (!data.domains[domain]) {data.domains[domain] = {}} data = data.domains[domain]; } MathJax.Hub.Insert(data,definition); if (isNew && MathJax.Menu.menu) {MathJax.Menu.CreateLocaleMenu()} }, // // Set CSS for an element based on font requirements // setCSS: function (div) { var locale = this.strings[this.locale]; if (locale) { if (locale.fontFamily) {div.style.fontFamily = locale.fontFamily} if (locale.fontDirection) { div.style.direction = locale.fontDirection; if (locale.fontDirection === "rtl") {div.style.textAlign = "right"} } } return div; }, // // Get the language's font family or direction // fontFamily: function () { var locale = this.strings[this.locale]; return (locale ? locale.fontFamily : null); }, fontDirection: function () { var locale = this.strings[this.locale]; return (locale ? locale.fontDirection : null); }, // // Get the language's plural index for a number // plural: function (n) { var locale = this.strings[this.locale]; if (locale && locale.plural) {return locale.plural(n)} // default if (n == 1) {return 1} // one return 2; // other }, // // Convert a number to language-specific form // number: function(n) { var locale = this.strings[this.locale]; if (locale && locale.number) {return locale.number(n)} // default return n; } }; /**********************************************************/ MathJax.Message = { ready: false, // used to tell when the styles are available log: [{}], current: null, textNodeBug: (navigator.vendor === "Apple Computer, Inc." && typeof navigator.vendorSub === "undefined") || (window.hasOwnProperty && window.hasOwnProperty("konqueror")), // Konqueror displays some gibberish with text.nodeValue = "..." styles: { "#MathJax_Message": { position: "fixed", left: "1px", bottom: "2px", 'background-color': "#E6E6E6", border: "1px solid #959595", margin: "0px", padding: "2px 8px", 'z-index': "102", color: "black", 'font-size': "80%", width: "auto", 'white-space': "nowrap" }, "#MathJax_MSIE_Frame": { position: "absolute", top:0, left: 0, width: "0px", 'z-index': 101, border: "0px", margin: "0px", padding: "0px" } }, browsers: { MSIE: function (browser) { MathJax.Message.msieFixedPositionBug = ((document.documentMode||0) < 7); if (MathJax.Message.msieFixedPositionBug) {MathJax.Hub.config.styles["#MathJax_Message"].position = "absolute"} MathJax.Message.quirks = (document.compatMode === "BackCompat"); }, Chrome: function (browser) { MathJax.Hub.config.styles["#MathJax_Message"].bottom = "1.5em"; MathJax.Hub.config.styles["#MathJax_Message"].left = "1em"; } }, Init: function (styles) { if (styles) {this.ready = true} if (!document.body || !this.ready) {return false} // // ASCIIMathML replaces the entire page with a copy of itself (@#!#%@!!) // so check that this.div is still part of the page, otherwise look up // the copy and use that. // if (this.div && this.div.parentNode == null) { this.div = document.getElementById("MathJax_Message"); this.text = (this.div ? this.div.firstChild : null); } if (!this.div) { var frame = document.body; if (this.msieFixedPositionBug && window.attachEvent) { frame = this.frame = this.addDiv(document.body); frame.removeAttribute("id"); frame.style.position = "absolute"; frame.style.border = frame.style.margin = frame.style.padding = "0px"; frame.style.zIndex = "101"; frame.style.height = "0px"; frame = this.addDiv(frame); frame.id = "MathJax_MSIE_Frame"; window.attachEvent("onscroll",this.MoveFrame); window.attachEvent("onresize",this.MoveFrame); this.MoveFrame(); } this.div = this.addDiv(frame); this.div.style.display = "none"; } if (!this.text) { this.text = this.div.appendChild(document.createTextNode("")); } return true; }, addDiv: function (parent) { var div = document.createElement("div"); div.id = "MathJax_Message"; if (parent.firstChild) {parent.insertBefore(div,parent.firstChild)} else {parent.appendChild(div)} return div; }, MoveFrame: function () { var body = (MathJax.Message.quirks ? document.body : document.documentElement); var frame = MathJax.Message.frame; frame.style.left = body.scrollLeft + 'px'; frame.style.top = body.scrollTop + 'px'; frame.style.width = body.clientWidth + 'px'; frame = frame.firstChild; frame.style.height = body.clientHeight + 'px'; }, localize: function (message) { return MathJax.Localization._(message,message); }, filterText: function (text,n,id) { if (MathJax.Hub.config.messageStyle === "simple") { if (id === "LoadFile") { if (!this.loading) {this.loading = this.localize("Loading") + " "} text = this.loading; this.loading += "."; } else if (id === "ProcessMath") { if (!this.processing) {this.processing = this.localize("Processing") + " "} text = this.processing; this.processing += "."; } else if (id === "TypesetMath") { if (!this.typesetting) {this.typesetting = this.localize("Typesetting") + " "} text = this.typesetting; this.typesetting += "."; } } return text; }, clearCounts: function () { delete this.loading; delete this.processing; delete this.typesetting; }, Set: function (text,n,clearDelay) { if (n == null) {n = this.log.length; this.log[n] = {}} // // Translate message if it is [id,message,arguments] // var id = ""; if (MathJax.Object.isArray(text)) { id = text[0]; if (MathJax.Object.isArray(id)) {id = id[1]} // // Localization._() will throw a restart error if a localization file // needs to be loaded, so trap that and redo the Set() call // after it is loaded. // try { text = MathJax.Localization._.apply(MathJax.Localization,text); } catch (err) { if (!err.restart) {throw err} if (!err.restart.called) { // // Mark it so we can tell if the Clear() comes before the message is displayed // if (this.log[n].restarted == null) {this.log[n].restarted = 0} this.log[n].restarted++; delete this.log[n].cleared; MathJax.Callback.After(["Set",this,text,n,clearDelay],err.restart); return n; } } } // // Clear the timout timer. // if (this.timer) {clearTimeout(this.timer); delete this.timer} // // Save the message and filtered message. // this.log[n].text = text; this.log[n].filteredText = text = this.filterText(text,n,id); // // Hook the message into the message list so we can tell // what message to put up when this one is removed. // if (typeof(this.log[n].next) === "undefined") { this.log[n].next = this.current; if (this.current != null) {this.log[this.current].prev = n} this.current = n; } // // Show the message if it is the currently active one. // if (this.current === n && MathJax.Hub.config.messageStyle !== "none") { if (this.Init()) { if (this.textNodeBug) {this.div.innerHTML = text} else {this.text.nodeValue = text} this.div.style.display = ""; if (this.status) {window.status = ""; delete this.status} } else { window.status = text; this.status = true; } } // // Check if the message was resetarted to load a localization file // and if it has been cleared in the meanwhile. // if (this.log[n].restarted) { if (this.log[n].cleared) {clearDelay = 0} if (--this.log[n].restarted === 0) {delete this.log[n].cleared} } // // Check if we need to clear the message automatically. // if (clearDelay) {setTimeout(MathJax.Callback(["Clear",this,n]),clearDelay)} else if (clearDelay == 0) {this.Clear(n,0)} // // Return the message number. // return n; }, Clear: function (n,delay) { // // Detatch the message from the active list. // if (this.log[n].prev != null) {this.log[this.log[n].prev].next = this.log[n].next} if (this.log[n].next != null) {this.log[this.log[n].next].prev = this.log[n].prev} // // If it is the current message, get the next one to show. // if (this.current === n) { this.current = this.log[n].next; if (this.text) { if (this.div.parentNode == null) {this.Init()} // see ASCIIMathML comments above if (this.current == null) { // // If there are no more messages, remove the message box. // if (this.timer) {clearTimeout(this.timer); delete this.timer} if (delay == null) {delay = 600} if (delay === 0) {this.Remove()} else {this.timer = setTimeout(MathJax.Callback(["Remove",this]),delay)} } else if (MathJax.Hub.config.messageStyle !== "none") { // // If there is an old message, put it in place // if (this.textNodeBug) {this.div.innerHTML = this.log[this.current].filteredText} else {this.text.nodeValue = this.log[this.current].filteredText} } if (this.status) {window.status = ""; delete this.status} } else if (this.status) { window.status = (this.current == null ? "" : this.log[this.current].text); } } // // Clean up the log data no longer needed // delete this.log[n].next; delete this.log[n].prev; delete this.log[n].filteredText; // // If this is a restarted localization message, mark that it has been cleared // while waiting for the file to load. // if (this.log[n].restarted) {this.log[n].cleared = true} }, Remove: function () { // FIXME: do a fade out or something else interesting? this.text.nodeValue = ""; this.div.style.display = "none"; }, File: function (file) { return this.Set(["LoadFile","Loading %1",file],null,null); }, Log: function () { var strings = []; for (var i = 1, m = this.log.length; i < m; i++) {strings[i] = this.log[i].text} return strings.join("\n"); } }; /**********************************************************/ MathJax.Hub = { config: { root: "", config: [], // list of configuration files to load styleSheets: [], // list of CSS files to load styles: { // styles to generate in-line ".MathJax_Preview": {color: "#888"} }, jax: [], // list of input and output jax to load extensions: [], // list of extensions to load preJax: null, // pattern to remove from before math script tag postJax: null, // pattern to remove from after math script tag displayAlign: 'center', // how to align displayed equations (left, center, right) displayIndent: '0', // indentation for displayed equations (when not centered) preRemoveClass: 'MathJax_Preview', // class of objects to remove preceding math script showProcessingMessages: true, // display "Processing math: nn%" messages or not messageStyle: "normal", // set to "none" or "simple" (for "Loading..." and "Processing...") delayStartupUntil: "none", // set to "onload" to delay setup until the onload handler runs // set to "configured" to delay startup until MathJax.Hub.Configured() is called // set to a Callback to wait for before continuing with the startup skipStartupTypeset: false, // set to true to skip PreProcess and Process during startup elements: [], // array of elements to process when none is given explicitly positionToHash: true, // after initial typeset pass, position to #hash location? showMathMenu: true, // attach math context menu to typeset math? showMathMenuMSIE: true, // separtely determine if MSIE should have math menu // (since the code for that is a bit delicate) menuSettings: { zoom: "None", // when to do MathZoom CTRL: false, // require CTRL for MathZoom? ALT: false, // require Alt or Option? CMD: false, // require CMD? Shift: false, // require Shift? discoverable: false, // make math menu discoverable on hover? zscale: "200%", // the scaling factor for MathZoom renderer: null, // set when Jax are loaded font: "Auto", // what font HTML-CSS should use context: "MathJax", // or "Browser" for pass-through to browser menu locale: null, // the language to use for messages mpContext: false, // true means pass menu events to MathPlayer in IE mpMouse: false, // true means pass mouse events to MathPlayer in IE texHints: true, // include class names for TeXAtom elements FastPreview: null, // use PreviewHTML output as preview? assistiveMML: null, // include hidden MathML for screen readers? inTabOrder: true, // set to false if math elements should be included in the tabindex semantics: false // add semantics tag with original form in MathML output }, errorSettings: { // localized HTML snippet structure for message to use message: ["[",["MathProcessingError","Math Processing Error"],"]"], style: {color: "#CC0000", "font-style":"italic"} // style for message }, ignoreMMLattributes: {} // attributes not to copy to HTML-CSS or SVG output // from MathML input (in addition to the ones in MML.nocopyAttributes). // An id set to true will be ignored, one set to false will // be allowed (even if other criteria normally would prevent // it from being copied); use false carefully! }, preProcessors: MathJax.Callback.Hooks(true), // list of callbacks for preprocessing (initialized by extensions) inputJax: {}, // mime-type mapped to input jax (by registration) outputJax: {order:{}}, // mime-type mapped to output jax list (by registration) processSectionDelay: 50, // pause between input and output phases of processing processUpdateTime: 250, // time between screen updates when processing math (milliseconds) processUpdateDelay: 10, // pause between screen updates to allow other processing (milliseconds) signal: MathJax.Callback.Signal("Hub"), // Signal used for Hub events Config: function (def) { this.Insert(this.config,def); if (this.config.Augment) {this.Augment(this.config.Augment)} }, CombineConfig: function (name,def) { var config = this.config, id, parent; name = name.split(/\./); for (var i = 0, m = name.length; i < m; i++) { id = name[i]; if (!config[id]) {config[id] = {}} parent = config; config = config[id]; } parent[id] = config = this.Insert(def,config); return config; }, Register: { PreProcessor: function () {return MathJax.Hub.preProcessors.Add.apply(MathJax.Hub.preProcessors,arguments)}, MessageHook: function () {return MathJax.Hub.signal.MessageHook.apply(MathJax.Hub.signal,arguments)}, StartupHook: function () {return MathJax.Hub.Startup.signal.MessageHook.apply(MathJax.Hub.Startup.signal,arguments)}, LoadHook: function () {return MathJax.Ajax.LoadHook.apply(MathJax.Ajax,arguments)} }, UnRegister: { PreProcessor: function (hook) {MathJax.Hub.preProcessors.Remove(hook)}, MessageHook: function (hook) {MathJax.Hub.signal.RemoveHook(hook)}, StartupHook: function (hook) {MathJax.Hub.Startup.signal.RemoveHook(hook)}, LoadHook: function (hook) {MathJax.Ajax.removeHook(hook)} }, getAllJax: function (element) { var jax = [], scripts = this.elementScripts(element); for (var i = 0, m = scripts.length; i < m; i++) { if (scripts[i].MathJax && scripts[i].MathJax.elementJax) {jax.push(scripts[i].MathJax.elementJax)} } return jax; }, getJaxByType: function (type,element) { var jax = [], scripts = this.elementScripts(element); for (var i = 0, m = scripts.length; i < m; i++) { if (scripts[i].MathJax && scripts[i].MathJax.elementJax && scripts[i].MathJax.elementJax.mimeType === type) {jax.push(scripts[i].MathJax.elementJax)} } return jax; }, getJaxByInputType: function (type,element) { var jax = [], scripts = this.elementScripts(element); for (var i = 0, m = scripts.length; i < m; i++) { if (scripts[i].MathJax && scripts[i].MathJax.elementJax && scripts[i].type && scripts[i].type.replace(/ *;(.|\s)*/,"") === type) {jax.push(scripts[i].MathJax.elementJax)} } return jax; }, getJaxFor: function (element) { if (typeof(element) === 'string') {element = document.getElementById(element)} if (element && element.MathJax) {return element.MathJax.elementJax} if (this.isMathJaxNode(element)) { if (!element.isMathJax) {element = element.firstChild} // for NativeMML output while (element && !element.jaxID) {element = element.parentNode} if (element) {return MathJax.OutputJax[element.jaxID].getJaxFromMath(element)} } return null; }, isJax: function (element) { if (typeof(element) === 'string') {element = document.getElementById(element)} if (this.isMathJaxNode(element)) {return 1} if (element && (element.tagName||"").toLowerCase() === 'script') { if (element.MathJax) {return (element.MathJax.state === MathJax.ElementJax.STATE.PROCESSED ? 1 : -1)} if (element.type && this.inputJax[element.type.replace(/ *;(.|\s)*/,"")]) {return -1} } return 0; }, isMathJaxNode: function (element) { return !!element && (element.isMathJax || (element.className||"") === "MathJax_MathML"); }, setRenderer: function (renderer,type) { if (!renderer) return; var JAX = MathJax.OutputJax[renderer]; if (!JAX) { MathJax.OutputJax[renderer] = MathJax.OutputJax({id: "unknown", version:"1.0.0", isUnknown: true}); this.config.menuSettings.renderer = ""; var file = "[MathJax]/jax/output/"+renderer+"/config.js"; return MathJax.Ajax.Require(file,["setRenderer",this,renderer,type]); } else { this.config.menuSettings.renderer = renderer; if (type == null) {type = "jax/mml"} if (JAX.isUnknown) JAX.Register(type); var jax = this.outputJax; if (jax[type] && jax[type].length) { if (renderer !== jax[type][0].id) { jax[type].unshift(JAX); return this.signal.Post(["Renderer Selected",renderer]); } } return null; } }, Queue: function () { return this.queue.Push.apply(this.queue,arguments); }, Typeset: function (element,callback) { if (!MathJax.isReady) return null; var ec = this.elementCallback(element,callback); if (ec.count) { var queue = MathJax.Callback.Queue( ["PreProcess",this,ec.elements], ["Process",this,ec.elements] ); } return queue.Push(ec.callback); }, PreProcess: function (element,callback) { var ec = this.elementCallback(element,callback); var queue = MathJax.Callback.Queue(); if (ec.count) { var elements = (ec.count === 1 ? [ec.elements] : ec.elements); queue.Push(["Post",this.signal,["Begin PreProcess",ec.elements]]); for (var i = 0, m = elements.length; i < m; i++) { if (elements[i]) {queue.Push(["Execute",this.preProcessors,elements[i]])} } queue.Push(["Post",this.signal,["End PreProcess",ec.elements]]); } return queue.Push(ec.callback); }, Process: function (element,callback) {return this.takeAction("Process",element,callback)}, Update: function (element,callback) {return this.takeAction("Update",element,callback)}, Reprocess: function (element,callback) {return this.takeAction("Reprocess",element,callback)}, Rerender: function (element,callback) {return this.takeAction("Rerender",element,callback)}, takeAction: function (action,element,callback) { var ec = this.elementCallback(element,callback); var elements = ec.elements; var queue = MathJax.Callback.Queue(["Clear",this.signal]); var state = { scripts: [], // filled in by prepareScripts start: new Date().getTime(), // timer for processing messages i: 0, j: 0, // current script, current jax jax: {}, // scripts grouped by output jax jaxIDs: [] // id's of jax used }; if (ec.count) { var delay = ["Delay",MathJax.Callback,this.processSectionDelay]; if (!delay[2]) {delay = {}} queue.Push( ["clearCounts",MathJax.Message], ["Post",this.signal,["Begin "+action,elements]], ["Post",this.signal,["Begin Math",elements,action]], ["prepareScripts",this,action,elements,state], ["Post",this.signal,["Begin Math Input",elements,action]], ["processInput",this,state], ["Post",this.signal,["End Math Input",elements,action]], delay, ["prepareOutput",this,state,"preProcess"], delay, ["Post",this.signal,["Begin Math Output",elements,action]], ["processOutput",this,state], ["Post",this.signal,["End Math Output",elements,action]], delay, ["prepareOutput",this,state,"postProcess"], delay, ["Post",this.signal,["End Math",elements,action]], ["Post",this.signal,["End "+action,elements]], ["clearCounts",MathJax.Message] ); } return queue.Push(ec.callback); }, scriptAction: { Process: function (script) {}, Update: function (script) { var jax = script.MathJax.elementJax; if (jax && jax.needsUpdate()) {jax.Remove(true); script.MathJax.state = jax.STATE.UPDATE} else {script.MathJax.state = jax.STATE.PROCESSED} }, Reprocess: function (script) { var jax = script.MathJax.elementJax; if (jax) {jax.Remove(true); script.MathJax.state = jax.STATE.UPDATE} }, Rerender: function (script) { var jax = script.MathJax.elementJax; if (jax) {jax.Remove(true); script.MathJax.state = jax.STATE.OUTPUT} } }, prepareScripts: function (action,element,state) { if (arguments.callee.disabled) return; var scripts = this.elementScripts(element); var STATE = MathJax.ElementJax.STATE; for (var i = 0, m = scripts.length; i < m; i++) { var script = scripts[i]; if (script.type && this.inputJax[script.type.replace(/ *;(.|\n)*/,"")]) { if (script.MathJax) { if (script.MathJax.elementJax && script.MathJax.elementJax.hover) { MathJax.Extension.MathEvents.Hover.ClearHover(script.MathJax.elementJax); } if (script.MathJax.state !== STATE.PENDING) {this.scriptAction[action](script)} } if (!script.MathJax) {script.MathJax = {state: STATE.PENDING}} if (script.MathJax.error) delete script.MathJax.error; if (script.MathJax.state !== STATE.PROCESSED) {state.scripts.push(script)} } } }, checkScriptSiblings: function (script) { if (script.MathJax.checked) return; var config = this.config, pre = script.previousSibling; if (pre && pre.nodeName === "#text") { var preJax,postJax, post = script.nextSibling; if (post && post.nodeName !== "#text") {post = null} if (config.preJax) { if (typeof(config.preJax) === "string") {config.preJax = new RegExp(config.preJax+"$")} preJax = pre.nodeValue.match(config.preJax); } if (config.postJax && post) { if (typeof(config.postJax) === "string") {config.postJax = new RegExp("^"+config.postJax)} postJax = post.nodeValue.match(config.postJax); } if (preJax && (!config.postJax || postJax)) { pre.nodeValue = pre.nodeValue.replace (config.preJax,(preJax.length > 1? preJax[1] : "")); pre = null; } if (postJax && (!config.preJax || preJax)) { post.nodeValue = post.nodeValue.replace (config.postJax,(postJax.length > 1? postJax[1] : "")); } if (pre && !pre.nodeValue.match(/\S/)) {pre = pre.previousSibling} } if (config.preRemoveClass && pre && pre.className === config.preRemoveClass) {script.MathJax.preview = pre} script.MathJax.checked = 1; }, processInput: function (state) { var jax, STATE = MathJax.ElementJax.STATE; var script, prev, m = state.scripts.length; try { // // Loop through the scripts // while (state.i < m) { script = state.scripts[state.i]; if (!script) {state.i++; continue} // // Remove previous error marker, if any // prev = script.previousSibling; if (prev && prev.className === "MathJax_Error") {prev.parentNode.removeChild(prev)} // // Check if already processed or needs processing // if (!script.parentNode || !script.MathJax || script.MathJax.state === STATE.PROCESSED) {state.i++; continue}; if (!script.MathJax.elementJax || script.MathJax.state === STATE.UPDATE) { this.checkScriptSiblings(script); // remove preJax/postJax etc. var type = script.type.replace(/ *;(.|\s)*/,""); // the input jax type var input = this.inputJax[type]; // the input jax itself jax = input.Process(script,state); // run the input jax if (typeof jax === 'function') { // if a callback was returned if (jax.called) continue; // go back and call Process() again this.RestartAfter(jax); // wait for the callback } jax = jax.Attach(script,input.id); // register the jax on the script this.saveScript(jax,state,script,STATE); // add script to state this.postInputHooks.Execute(jax,input.id,script); // run global jax filters } else if (script.MathJax.state === STATE.OUTPUT) { this.saveScript(script.MathJax.elementJax,state,script,STATE); // add script to state } // // Go on to the next script, and check if we need to update the processing message // state.i++; var now = new Date().getTime(); if (now - state.start > this.processUpdateTime && state.i < state.scripts.length) {state.start = now; this.RestartAfter(MathJax.Callback.Delay(1))} } } catch (err) {return this.processError(err,state,"Input")} // // Put up final message, reset the state and return // if (state.scripts.length && this.config.showProcessingMessages) {MathJax.Message.Set(["ProcessMath","Processing math: %1%%",100],0)} state.start = new Date().getTime(); state.i = state.j = 0; return null; }, postInputHooks: MathJax.Callback.Hooks(true), // hooks to run after element jax is created saveScript: function (jax,state,script,STATE) { // // Check that output jax exists // if (!this.outputJax[jax.mimeType]) { script.MathJax.state = STATE.UPDATE; throw Error("No output jax registered for "+jax.mimeType); } // // Record the output jax // and put this script in the queue for that jax // jax.outputJax = this.outputJax[jax.mimeType][0].id; if (!state.jax[jax.outputJax]) { if (state.jaxIDs.length === 0) { // use original array until we know there are more (rather than two copies) state.jax[jax.outputJax] = state.scripts; } else { if (state.jaxIDs.length === 1) // get the script so far for the existing jax {state.jax[state.jaxIDs[0]] = state.scripts.slice(0,state.i)} state.jax[jax.outputJax] = []; // start a new array for the new jax } state.jaxIDs.push(jax.outputJax); // save the ID of the jax } if (state.jaxIDs.length > 1) {state.jax[jax.outputJax].push(script)} // // Mark script as needing output // script.MathJax.state = STATE.OUTPUT; }, // // Pre- and post-process scripts by jax // (to get scaling factors, hide/show output, and so on) // Since this can cause the jax to load, we need to trap restarts // prepareOutput: function (state,method) { while (state.j < state.jaxIDs.length) { var id = state.jaxIDs[state.j], JAX = MathJax.OutputJax[id]; if (JAX[method]) { try { var result = JAX[method](state); if (typeof result === 'function') { if (result.called) continue; // go back and try again this.RestartAfter(result); } } catch (err) { if (!err.restart) { MathJax.Message.Set(["PrepError","Error preparing %1 output (%2)",id,method],null,600); MathJax.Hub.lastPrepError = err; state.j++; } return MathJax.Callback.After(["prepareOutput",this,state,method],err.restart); } } state.j++; } return null; }, processOutput: function (state) { var result, STATE = MathJax.ElementJax.STATE, script, m = state.scripts.length; try { // // Loop through the scripts // while (state.i < m) { // // Check that there is an element jax // script = state.scripts[state.i]; if (!script || !script.parentNode || !script.MathJax || script.MathJax.error) {state.i++; continue} var jax = script.MathJax.elementJax; if (!jax) {state.i++; continue} // // Call the output Jax's Process method (which will be its Translate() // method once loaded). Mark it as complete and remove the preview unless // the Process() call returns an explicit false value (in which case, it will // handle this later during the postProcess phase, as HTML-CSS does). // result = MathJax.OutputJax[jax.outputJax].Process(script,state); if (result !== false) { script.MathJax.state = STATE.PROCESSED; if (script.MathJax.preview) { script.MathJax.preview.innerHTML = ""; script.MathJax.preview.style.display = "none"; } // // Signal that new math is available // this.signal.Post(["New Math",jax.inputID]); // FIXME: wait for this? (i.e., restart if returns uncalled callback) } // // Go on to next math expression // state.i++; // // Update the processing message, if needed // var now = new Date().getTime(); if (now - state.start > this.processUpdateTime && state.i < state.scripts.length) {state.start = now; this.RestartAfter(MathJax.Callback.Delay(this.processUpdateDelay))} } } catch (err) {return this.processError(err,state,"Output")} // // Put up the typesetting-complete message // if (state.scripts.length && this.config.showProcessingMessages) { MathJax.Message.Set(["TypesetMath","Typesetting math: %1%%",100],0); MathJax.Message.Clear(0); } state.i = state.j = 0; return null; }, processMessage: function (state,type) { var m = Math.floor(state.i/(state.scripts.length)*100); var message = (type === "Output" ? ["TypesetMath","Typesetting math: %1%%"] : ["ProcessMath","Processing math: %1%%"]); if (this.config.showProcessingMessages) {MathJax.Message.Set(message.concat(m),0)} }, processError: function (err,state,type) { if (!err.restart) { if (!this.config.errorSettings.message) {throw err} this.formatError(state.scripts[state.i],err); state.i++; } this.processMessage(state,type); return MathJax.Callback.After(["process"+type,this,state],err.restart); }, formatError: function (script,err) { var LOCALIZE = function (id,text,arg1,arg2) {return MathJax.Localization._(id,text,arg1,arg2)}; // // Get the error message, URL, and line, and save it for // reporting in the Show Math As Error menu // var message = LOCALIZE("ErrorMessage","Error: %1",err.message)+"\n"; if (err.sourceURL||err.fileName) message += "\n"+LOCALIZE("ErrorFile","file: %1",err.sourceURL||err.fileName); if (err.line||err.lineNumber) message += "\n"+LOCALIZE("ErrorLine","line: %1",err.line||err.lineNumber); message += "\n\n"+LOCALIZE("ErrorTips","Debugging tips: use %1, inspect %2 in the browser console","'unpacked/MathJax.js'","'MathJax.Hub.lastError'"); script.MathJax.error = MathJax.OutputJax.Error.Jax(message,script); if (script.MathJax.elementJax) script.MathJax.error.inputID = script.MathJax.elementJax.inputID; // // Create the [Math Processing Error] span // var errorSettings = this.config.errorSettings; var errorText = LOCALIZE(errorSettings.messageId,errorSettings.message); var error = MathJax.HTML.Element("span", { className:"MathJax_Error", jaxID:"Error", isMathJax:true, id: script.MathJax.error.inputID+"-Frame" },[["span",null,errorText]]); // // Attach the menu events // MathJax.Ajax.Require("[MathJax]/extensions/MathEvents.js",function () { var EVENT = MathJax.Extension.MathEvents.Event, HUB = MathJax.Hub; error.oncontextmenu = EVENT.Menu; error.onmousedown = EVENT.Mousedown; error.onkeydown = EVENT.Keydown; error.tabIndex = HUB.getTabOrder(HUB.getJaxFor(script)); }); // // Insert the error into the page and remove any preview // var node = document.getElementById(error.id); if (node) node.parentNode.removeChild(node); if (script.parentNode) script.parentNode.insertBefore(error,script); if (script.MathJax.preview) { script.MathJax.preview.innerHTML = ""; script.MathJax.preview.style.display = "none"; } // // Save the error for debugging purposes // Report the error as a signal // this.lastError = err; this.signal.Post(["Math Processing Error",script,err]); }, RestartAfter: function (callback) { throw this.Insert(Error("restart"),{restart: MathJax.Callback(callback)}); }, elementCallback: function (element,callback) { if (callback == null && (MathJax.Object.isArray(element) || typeof element === 'function')) {try {MathJax.Callback(element); callback = element; element = null} catch(e) {}} if (element == null) {element = this.config.elements || []} if (this.isHTMLCollection(element)) {element = this.HTMLCollection2Array(element)} if (!MathJax.Object.isArray(element)) {element = [element]} element = [].concat(element); // make a copy so the original isn't changed for (var i = 0, m = element.length; i < m; i++) {if (typeof(element[i]) === 'string') {element[i] = document.getElementById(element[i])}} if (!document.body) {document.body = document.getElementsByTagName("body")[0]} if (element.length == 0) {element.push(document.body)} if (!callback) {callback = {}} return { count: element.length, elements: (element.length === 1 ? element[0] : element), callback: callback }; }, elementScripts: function (element) { var scripts = []; if (MathJax.Object.isArray(element) || this.isHTMLCollection(element)) { for (var i = 0, m = element.length; i < m; i++) { var alreadyDone = 0; for (var j = 0; j < i && !alreadyDone; j++) {alreadyDone = element[j].contains(element[i])} if (!alreadyDone) scripts.push.apply(scripts,this.elementScripts(element[i])); } return scripts; } if (typeof(element) === 'string') {element = document.getElementById(element)} if (!document.body) {document.body = document.getElementsByTagName("body")[0]} if (element == null) {element = document.body} if (element.tagName != null && element.tagName.toLowerCase() === "script") {return [element]} scripts = element.getElementsByTagName("script"); if (this.msieHTMLCollectionBug) {scripts = this.HTMLCollection2Array(scripts)} return scripts; }, // // IE8 fails to check "obj instanceof HTMLCollection" for some values of obj. // isHTMLCollection: function (obj) { return ("HTMLCollection" in window && typeof(obj) === "object" && obj instanceof HTMLCollection); }, // // IE8 doesn't deal with HTMLCollection as an array, so convert to array // HTMLCollection2Array: function (nodes) { if (!this.msieHTMLCollectionBug) {return [].slice.call(nodes)} var NODES = []; for (var i = 0, m = nodes.length; i < m; i++) {NODES[i] = nodes[i]} return NODES; }, Insert: function (dst,src) { for (var id in src) {if (src.hasOwnProperty(id)) { // allow for concatenation of arrays? if (typeof src[id] === 'object' && !(MathJax.Object.isArray(src[id])) && (typeof dst[id] === 'object' || typeof dst[id] === 'function')) { this.Insert(dst[id],src[id]); } else { dst[id] = src[id]; } }} return dst; }, getTabOrder: function(script) { return this.config.menuSettings.inTabOrder ? 0 : -1; }, // Old browsers (e.g. Internet Explorer <= 8) do not support trim(). SplitList: ("trim" in String.prototype ? function (list) {return list.trim().split(/\s+/)} : function (list) {return list.replace(/^\s+/,''). replace(/\s+$/,'').split(/\s+/)}) }; MathJax.Hub.Insert(MathJax.Hub.config.styles,MathJax.Message.styles); MathJax.Hub.Insert(MathJax.Hub.config.styles,{".MathJax_Error":MathJax.Hub.config.errorSettings.style}); // // Storage area for extensions and preprocessors // MathJax.Extension = {}; // // Hub Startup code // MathJax.Hub.Configured = MathJax.Callback({}); // called when configuration is complete MathJax.Hub.Startup = { script: "", // the startup script from the SCRIPT call that loads MathJax.js queue: MathJax.Callback.Queue(), // Queue used for startup actions signal: MathJax.Callback.Signal("Startup"), // Signal used for startup events params: {}, // // Load the configuration files // Config: function () { this.queue.Push(["Post",this.signal,"Begin Config"]); // // Make sure root is set before loading any files // if (MathJax.AuthorConfig && MathJax.AuthorConfig.root) MathJax.Ajax.config.root = MathJax.AuthorConfig.root; // // If a locale is given as a parameter, // set the locale and the default menu value for the locale // if (this.params.locale) { MathJax.Localization.resetLocale(this.params.locale); MathJax.Hub.config.menuSettings.locale = this.params.locale; } // // Run the config files, if any are given in the parameter list // if (this.params.config) { var files = this.params.config.split(/,/); for (var i = 0, m = files.length; i < m; i++) { if (!files[i].match(/\.js$/)) {files[i] += ".js"} this.queue.Push(["Require",MathJax.Ajax,this.URL("config",files[i])]); } } // // Perform author configuration from in-line MathJax = {...} // this.queue.Push(["Config",MathJax.Hub,MathJax.AuthorConfig]); // // Run the deprecated configuration script, if any (ignoring return value) // Wait for the startup delay signal // Run the mathjax-config blocks // Load the files in the configuration's config array // if (this.script.match(/\S/)) {this.queue.Push(this.script+";\n1;")} this.queue.Push( ["ConfigDelay",this], ["ConfigBlocks",this], [function (THIS) {return THIS.loadArray(MathJax.Hub.config.config,"config",null,true)},this], ["Post",this.signal,"End Config"] ); }, // // Return the delay callback // ConfigDelay: function () { var delay = this.params.delayStartupUntil || MathJax.Hub.config.delayStartupUntil; if (delay === "onload") {return this.onload} if (delay === "configured") {return MathJax.Hub.Configured} return delay; }, // // Run the scripts of type=text/x-mathjax-config // ConfigBlocks: function () { var scripts = document.getElementsByTagName("script"); var queue = MathJax.Callback.Queue(); for (var i = 0, m = scripts.length; i < m; i++) { var type = String(scripts[i].type).replace(/ /g,""); if (type.match(/^text\/x-mathjax-config(;.*)?$/) && !type.match(/;executed=true/)) { scripts[i].type += ";executed=true"; queue.Push(scripts[i].innerHTML+";\n1;"); } } return queue.Push(function () {MathJax.Ajax.config.root = MathJax.Hub.config.root}); }, // // Read cookie and set up menu defaults // (set the locale according to the cookie) // (adjust the jax to accommodate renderer preferences) // Cookie: function () { return this.queue.Push( ["Post",this.signal,"Begin Cookie"], ["Get",MathJax.HTML.Cookie,"menu",MathJax.Hub.config.menuSettings], [function (config) { var SETTINGS = config.menuSettings; if (SETTINGS.locale) MathJax.Localization.resetLocale(SETTINGS.locale); var renderer = config.menuSettings.renderer, jax = config.jax; if (renderer) { var name = "output/"+renderer; jax.sort(); for (var i = 0, m = jax.length; i < m; i++) { if (jax[i].substr(0,7) === "output/") break; } if (i == m-1) {jax.pop()} else { while (i < m) {if (jax[i] === name) {jax.splice(i,1); break}; i++} } jax.unshift(name); } if (SETTINGS.CHTMLpreview != null) { if (SETTINGS.FastPreview == null) SETTINGS.FastPreview = SETTINGS.CHTMLpreview; delete SETTINGS.CHTMLpreview; } if (SETTINGS.FastPreview && !MathJax.Extension["fast-preview"]) MathJax.Hub.config.extensions.push("fast-preview.js"); if (config.menuSettings.assistiveMML && !MathJax.Extension.AssistiveMML) MathJax.Hub.config.extensions.push("AssistiveMML.js"); },MathJax.Hub.config], ["Post",this.signal,"End Cookie"] ); }, // // Setup stylesheets and extra styles // Styles: function () { return this.queue.Push( ["Post",this.signal,"Begin Styles"], ["loadArray",this,MathJax.Hub.config.styleSheets,"config"], ["Styles",MathJax.Ajax,MathJax.Hub.config.styles], ["Post",this.signal,"End Styles"] ); }, // // Load the input and output jax // Jax: function () { var config = MathJax.Hub.config, jax = MathJax.Hub.outputJax; // Save the order of the output jax since they are loading asynchronously for (var i = 0, m = config.jax.length, k = 0; i < m; i++) { var name = config.jax[i].substr(7); if (config.jax[i].substr(0,7) === "output/" && jax.order[name] == null) {jax.order[name] = k; k++} } var queue = MathJax.Callback.Queue(); return queue.Push( ["Post",this.signal,"Begin Jax"], ["loadArray",this,config.jax,"jax","config.js"], ["Post",this.signal,"End Jax"] ); }, // // Load the extensions // Extensions: function () { var queue = MathJax.Callback.Queue(); return queue.Push( ["Post",this.signal,"Begin Extensions"], ["loadArray",this,MathJax.Hub.config.extensions,"extensions"], ["Post",this.signal,"End Extensions"] ); }, // // Initialize the Message system // Message: function () { MathJax.Message.Init(true); }, // // Set the math menu renderer, if it isn't already // (this must come after the jax are loaded) // Menu: function () { var menu = MathJax.Hub.config.menuSettings, jax = MathJax.Hub.outputJax, registered; for (var id in jax) {if (jax.hasOwnProperty(id)) { if (jax[id].length) {registered = jax[id]; break} }} if (registered && registered.length) { if (menu.renderer && menu.renderer !== registered[0].id) {registered.unshift(MathJax.OutputJax[menu.renderer])} menu.renderer = registered[0].id; } }, // // Set the location to the designated hash position // Hash: function () { if (MathJax.Hub.config.positionToHash && document.location.hash && document.body && document.body.scrollIntoView) { var name = decodeURIComponent(document.location.hash.substr(1)); var target = document.getElementById(name); if (!target) { var a = document.getElementsByTagName("a"); for (var i = 0, m = a.length; i < m; i++) {if (a[i].name === name) {target = a[i]; break}} } if (target) { while (!target.scrollIntoView) {target = target.parentNode} target = this.HashCheck(target); if (target && target.scrollIntoView) {setTimeout(function () {target.scrollIntoView(true)},1)} } } }, HashCheck: function (target) { var jax = MathJax.Hub.getJaxFor(target); if (jax && MathJax.OutputJax[jax.outputJax].hashCheck) {target = MathJax.OutputJax[jax.outputJax].hashCheck(target)} return target; }, // // Load the Menu and Zoom code, if it hasn't already been loaded. // This is called after the initial typeset, so should no longer be // competing with other page loads, but will make these available // if needed later on. // MenuZoom: function () { if (MathJax.Hub.config.showMathMenu) { if (!MathJax.Extension.MathMenu) { setTimeout( function () { MathJax.Callback.Queue( ["Require",MathJax.Ajax,"[MathJax]/extensions/MathMenu.js",{}], ["loadDomain",MathJax.Localization,"MathMenu"] ) },1000 ); } else { setTimeout( MathJax.Callback(["loadDomain",MathJax.Localization,"MathMenu"]), 1000 ); } if (!MathJax.Extension.MathZoom) { setTimeout( MathJax.Callback(["Require",MathJax.Ajax,"[MathJax]/extensions/MathZoom.js",{}]), 2000 ); } } }, // // Setup the onload callback // onLoad: function () { var onload = this.onload = MathJax.Callback(function () {MathJax.Hub.Startup.signal.Post("onLoad")}); if (document.body && document.readyState) if (MathJax.Hub.Browser.isMSIE) { // IE can change from loading to interactive before // full page is ready, so go with complete (even though // that means we may have to wait longer). if (document.readyState === "complete") {return [onload]} } else if (document.readyState !== "loading") {return [onload]} if (window.addEventListener) { window.addEventListener("load",onload,false); if (!this.params.noDOMContentEvent) {window.addEventListener("DOMContentLoaded",onload,false)} } else if (window.attachEvent) {window.attachEvent("onload",onload)} else {window.onload = onload} return onload; }, // // Perform the initial typesetting (or skip if configuration says to) // Typeset: function (element,callback) { if (MathJax.Hub.config.skipStartupTypeset) {return function () {}} return this.queue.Push( ["Post",this.signal,"Begin Typeset"], ["Typeset",MathJax.Hub,element,callback], ["Post",this.signal,"End Typeset"] ); }, // // Create a URL in the MathJax hierarchy // URL: function (dir,name) { if (!name.match(/^([a-z]+:\/\/|\[|\/)/)) {name = "[MathJax]/"+dir+"/"+name} return name; }, // // Load an array of files, waiting for all of them // to be loaded before going on // loadArray: function (files,dir,name,synchronous) { if (files) { if (!MathJax.Object.isArray(files)) {files = [files]} if (files.length) { var queue = MathJax.Callback.Queue(), callback = {}, file; for (var i = 0, m = files.length; i < m; i++) { file = this.URL(dir,files[i]); if (name) {file += "/" + name} if (synchronous) {queue.Push(["Require",MathJax.Ajax,file,callback])} else {queue.Push(MathJax.Ajax.Require(file,callback))} } return queue.Push({}); // wait for everything to finish } } return null; } }; /**********************************************************/ (function (BASENAME) { var BASE = window[BASENAME], ROOT = "["+BASENAME+"]"; var HUB = BASE.Hub, AJAX = BASE.Ajax, CALLBACK = BASE.Callback; var JAX = MathJax.Object.Subclass({ JAXFILE: "jax.js", require: null, // array of files to load before jax.js is complete config: {}, // // Make a subclass and return an instance of it. // (FIXME: should we replace config with a copy of the constructor's // config? Otherwise all subclasses share the same config structure.) // Init: function (def,cdef) { if (arguments.length === 0) {return this} return (this.constructor.Subclass(def,cdef))(); }, // // Augment by merging with class definition (not replacing) // Augment: function (def,cdef) { var cObject = this.constructor, ndef = {}; if (def != null) { for (var id in def) {if (def.hasOwnProperty(id)) { if (typeof def[id] === "function") {cObject.protoFunction(id,def[id])} else {ndef[id] = def[id]} }} // MSIE doesn't list toString even if it is not native so handle it separately if (def.toString !== cObject.prototype.toString && def.toString !== {}.toString) {cObject.protoFunction('toString',def.toString)} } HUB.Insert(cObject.prototype,ndef); cObject.Augment(null,cdef); return this; }, Translate: function (script,state) { throw Error(this.directory+"/"+this.JAXFILE+" failed to define the Translate() method"); }, Register: function (mimetype) {}, Config: function () { this.config = HUB.CombineConfig(this.id,this.config); if (this.config.Augment) {this.Augment(this.config.Augment)} }, Startup: function () {}, loadComplete: function (file) { if (file === "config.js") { return AJAX.loadComplete(this.directory+"/"+file); } else { var queue = CALLBACK.Queue(); queue.Push( HUB.Register.StartupHook("End Config",{}), // wait until config complete ["Post",HUB.Startup.signal,this.id+" Jax Config"], ["Config",this], ["Post",HUB.Startup.signal,this.id+" Jax Require"], // Config may set the required and extensions array, // so use functions to delay making the reference until needed [function (THIS) {return MathJax.Hub.Startup.loadArray(THIS.require,this.directory)},this], [function (config,id) {return MathJax.Hub.Startup.loadArray(config.extensions,"extensions/"+id)},this.config||{},this.id], ["Post",HUB.Startup.signal,this.id+" Jax Startup"], ["Startup",this], ["Post",HUB.Startup.signal,this.id+" Jax Ready"] ); if (this.copyTranslate) { queue.Push( [function (THIS) { THIS.preProcess = THIS.preTranslate; THIS.Process = THIS.Translate; THIS.postProcess = THIS.postTranslate; },this.constructor.prototype] ); } return queue.Push(["loadComplete",AJAX,this.directory+"/"+file]); } } },{ id: "Jax", version: "2.7.5", directory: ROOT+"/jax", extensionDir: ROOT+"/extensions" }); /***********************************/ BASE.InputJax = JAX.Subclass({ elementJax: "mml", // the element jax to load for this input jax sourceMenuTitle: /*_(MathMenu)*/ ["Original","Original Form"], copyTranslate: true, Process: function (script,state) { var queue = CALLBACK.Queue(), file; // Load any needed element jax var jax = this.elementJax; if (!BASE.Object.isArray(jax)) {jax = [jax]} for (var i = 0, m = jax.length; i < m; i++) { file = BASE.ElementJax.directory+"/"+jax[i]+"/"+this.JAXFILE; if (!this.require) {this.require = []} else if (!BASE.Object.isArray(this.require)) {this.require = [this.require]}; this.require.push(file); // so Startup will wait for it to be loaded queue.Push(AJAX.Require(file)); } // Load the input jax file = this.directory+"/"+this.JAXFILE; var load = queue.Push(AJAX.Require(file)); if (!load.called) { this.constructor.prototype.Process = function () { if (!load.called) {return load} throw Error(file+" failed to load properly"); } } // Load the associated output jax jax = HUB.outputJax["jax/"+jax[0]]; if (jax) {queue.Push(AJAX.Require(jax[0].directory+"/"+this.JAXFILE))} return queue.Push({}); }, needsUpdate: function (jax) { var script = jax.SourceElement(); return (jax.originalText !== BASE.HTML.getScript(script)); }, Register: function (mimetype) { if (!HUB.inputJax) {HUB.inputJax = {}} HUB.inputJax[mimetype] = this; } },{ id: "InputJax", version: "2.7.5", directory: JAX.directory+"/input", extensionDir: JAX.extensionDir }); /***********************************/ BASE.OutputJax = JAX.Subclass({ copyTranslate: true, preProcess: function (state) { var load, file = this.directory+"/"+this.JAXFILE; this.constructor.prototype.preProcess = function (state) { if (!load.called) {return load} throw Error(file+" failed to load properly"); } load = AJAX.Require(file); return load; }, Process: function (state) {throw Error(this.id + " output jax failed to load properly")}, Register: function (mimetype) { var jax = HUB.outputJax; if (!jax[mimetype]) {jax[mimetype] = []} // If the output jax is earlier in the original configuration list, put it first here if (jax[mimetype].length && (this.id === HUB.config.menuSettings.renderer || (jax.order[this.id]||0) < (jax.order[jax[mimetype][0].id]||0))) {jax[mimetype].unshift(this)} else {jax[mimetype].push(this)} // Make sure the element jax is loaded before Startup is called if (!this.require) {this.require = []} else if (!BASE.Object.isArray(this.require)) {this.require = [this.require]}; this.require.push(BASE.ElementJax.directory+"/"+(mimetype.split(/\//)[1])+"/"+this.JAXFILE); }, Remove: function (jax) {} },{ id: "OutputJax", version: "2.7.5", directory: JAX.directory+"/output", extensionDir: JAX.extensionDir, fontDir: ROOT+(BASE.isPacked?"":"/..")+"/fonts", imageDir: ROOT+(BASE.isPacked?"":"/..")+"/images" }); /***********************************/ BASE.ElementJax = JAX.Subclass({ // make a subclass, not an instance Init: function (def,cdef) {return this.constructor.Subclass(def,cdef)}, inputJax: null, outputJax: null, inputID: null, originalText: "", mimeType: "", sourceMenuTitle: /*_(MathMenu)*/ ["MathMLcode","MathML Code"], Text: function (text,callback) { var script = this.SourceElement(); BASE.HTML.setScript(script,text); script.MathJax.state = this.STATE.UPDATE; return HUB.Update(script,callback); }, Reprocess: function (callback) { var script = this.SourceElement(); script.MathJax.state = this.STATE.UPDATE; return HUB.Reprocess(script,callback); }, Update: function (callback) {return this.Rerender(callback)}, Rerender: function (callback) { var script = this.SourceElement(); script.MathJax.state = this.STATE.OUTPUT; return HUB.Process(script,callback); }, Remove: function (keep) { if (this.hover) {this.hover.clear(this)} BASE.OutputJax[this.outputJax].Remove(this); if (!keep) { HUB.signal.Post(["Remove Math",this.inputID]); // wait for this to finish? this.Detach(); } }, needsUpdate: function () { return BASE.InputJax[this.inputJax].needsUpdate(this); }, SourceElement: function () {return document.getElementById(this.inputID)}, Attach: function (script,inputJax) { var jax = script.MathJax.elementJax; if (script.MathJax.state === this.STATE.UPDATE) { jax.Clone(this); } else { jax = script.MathJax.elementJax = this; if (script.id) {this.inputID = script.id} else {script.id = this.inputID = BASE.ElementJax.GetID(); this.newID = 1} } jax.originalText = BASE.HTML.getScript(script); jax.inputJax = inputJax; if (jax.root) {jax.root.inputID = jax.inputID} return jax; }, Detach: function () { var script = this.SourceElement(); if (!script) return; try {delete script.MathJax} catch(err) {script.MathJax = null} if (this.newID) {script.id = ""} }, Clone: function (jax) { var id; for (id in this) { if (!this.hasOwnProperty(id)) continue; if (typeof(jax[id]) === 'undefined' && id !== 'newID') {delete this[id]} } for (id in jax) { if (!jax.hasOwnProperty(id)) continue; if (typeof(this[id]) === 'undefined' || (this[id] !== jax[id] && id !== 'inputID')) {this[id] = jax[id]} } } },{ id: "ElementJax", version: "2.7.5", directory: JAX.directory+"/element", extensionDir: JAX.extensionDir, ID: 0, // jax counter (for IDs) STATE: { PENDING: 1, // script is identified as math but not yet processed PROCESSED: 2, // script has been processed UPDATE: 3, // elementJax should be updated OUTPUT: 4 // output should be updated (input is OK) }, GetID: function () {this.ID++; return "MathJax-Element-"+this.ID}, Subclass: function () { var obj = JAX.Subclass.apply(this,arguments); obj.loadComplete = this.prototype.loadComplete; return obj; } }); BASE.ElementJax.prototype.STATE = BASE.ElementJax.STATE; // // Some "Fake" jax used to allow menu access for "Math Processing Error" messages // BASE.OutputJax.Error = { id: "Error", version: "2.7.5", config: {}, errors: 0, ContextMenu: function () {return BASE.Extension.MathEvents.Event.ContextMenu.apply(BASE.Extension.MathEvents.Event,arguments)}, Mousedown: function () {return BASE.Extension.MathEvents.Event.AltContextMenu.apply(BASE.Extension.MathEvents.Event,arguments)}, getJaxFromMath: function (math) {return (math.nextSibling.MathJax||{}).error}, Jax: function (text,script) { var jax = MathJax.Hub.inputJax[script.type.replace(/ *;(.|\s)*/,"")]; this.errors++; return { inputJax: (jax||{id:"Error"}).id, // Use Error InputJax as fallback outputJax: "Error", inputID: "MathJax-Error-"+this.errors, sourceMenuTitle: /*_(MathMenu)*/ ["ErrorMessage","Error Message"], sourceMenuFormat: "Error", originalText: MathJax.HTML.getScript(script), errorText: text } } }; BASE.InputJax.Error = { id: "Error", version: "2.7.5", config: {}, sourceMenuTitle: /*_(MathMenu)*/ ["Original","Original Form"] }; })("MathJax"); /**********************************************************/ (function (BASENAME) { var BASE = window[BASENAME]; if (!BASE) {BASE = window[BASENAME] = {}} var HUB = BASE.Hub; var STARTUP = HUB.Startup; var CONFIG = HUB.config; var HEAD = document.head || (document.getElementsByTagName("head")[0]); if (!HEAD) {HEAD = document.childNodes[0]}; var scripts = (document.documentElement || document).getElementsByTagName("script"); if (scripts.length === 0 && HEAD.namespaceURI) scripts = document.getElementsByTagNameNS(HEAD.namespaceURI,"script"); var namePattern = new RegExp("(^|/)"+BASENAME+"\\.js(\\?.*)?$"); for (var i = scripts.length-1; i >= 0; i--) { if ((scripts[i].src||"").match(namePattern)) { STARTUP.script = scripts[i].innerHTML; if (RegExp.$2) { var params = RegExp.$2.substr(1).split(/\&/); for (var j = 0, m = params.length; j < m; j++) { var KV = params[j].match(/(.*)=(.*)/); if (KV) {STARTUP.params[unescape(KV[1])] = unescape(KV[2])} else {STARTUP.params[params[j]] = true} } } CONFIG.root = scripts[i].src.replace(/(^|\/)[^\/]*(\?.*)?$/,''); BASE.Ajax.config.root = CONFIG.root; BASE.Ajax.params = STARTUP.params; break; } } var AGENT = navigator.userAgent; var BROWSERS = { isMac: (navigator.platform.substr(0,3) === "Mac"), isPC: (navigator.platform.substr(0,3) === "Win"), isMSIE: ("ActiveXObject" in window && "clipboardData" in window), isEdge: ("MSGestureEvent" in window && "chrome" in window && window.chrome.loadTimes == null), isFirefox: (!!AGENT.match(/Gecko\//) && !AGENT.match(/like Gecko/)), isSafari: (!!AGENT.match(/ (Apple)?WebKit\//) && !AGENT.match(/ like iPhone /) && (!window.chrome || window.chrome.app == null)), isChrome: ("chrome" in window && window.chrome.loadTimes != null), isOpera: ("opera" in window && window.opera.version != null), isKonqueror: ("konqueror" in window && navigator.vendor == "KDE"), versionAtLeast: function (v) { var bv = (this.version).split('.'); v = (new String(v)).split('.'); for (var i = 0, m = v.length; i < m; i++) {if (bv[i] != v[i]) {return parseInt(bv[i]||"0") >= parseInt(v[i])}} return true; }, Select: function (choices) { var browser = choices[HUB.Browser]; if (browser) {return browser(HUB.Browser)} return null; } }; var xAGENT = AGENT .replace(/^Mozilla\/(\d+\.)+\d+ /,"") // remove initial Mozilla, which is never right .replace(/[a-z][-a-z0-9._: ]+\/\d+[^ ]*-[^ ]*\.([a-z][a-z])?\d+ /i,"") // remove linux version .replace(/Gentoo |Ubuntu\/(\d+\.)*\d+ (\([^)]*\) )?/,""); // special case for these HUB.Browser = HUB.Insert(HUB.Insert(new String("Unknown"),{version: "0.0"}),BROWSERS); for (var browser in BROWSERS) {if (BROWSERS.hasOwnProperty(browser)) { if (BROWSERS[browser] && browser.substr(0,2) === "is") { browser = browser.slice(2); if (browser === "Mac" || browser === "PC") continue; HUB.Browser = HUB.Insert(new String(browser),BROWSERS); var VERSION = new RegExp( ".*(Version/| Trident/.*; rv:)((?:\\d+\\.)+\\d+)|" + // for Safari, Opera10, and IE11+ ".*("+browser+")"+(browser == "MSIE" ? " " : "/")+"((?:\\d+\\.)*\\d+)|"+ // for one of the main browsers "(?:^|\\(| )([a-z][-a-z0-9._: ]+|(?:Apple)?WebKit)/((?:\\d+\\.)+\\d+)"); // for unrecognized browser var MATCH = VERSION.exec(xAGENT) || ["","","","unknown","0.0"]; HUB.Browser.name = (MATCH[1] != "" ? browser : (MATCH[3] || MATCH[5])); HUB.Browser.version = MATCH[2] || MATCH[4] || MATCH[6]; break; } }}; // // Initial browser-specific info (e.g., touch up version or name, check for MathPlayer, etc.) // Wrap in try/catch just in case of error (see issue #1155). // try {HUB.Browser.Select({ Safari: function (browser) { var v = parseInt((String(browser.version).split("."))[0]); if (v > 85) {browser.webkit = browser.version} if (v >= 538) {browser.version = "8.0"} else if (v >= 537) {browser.version = "7.0"} else if (v >= 536) {browser.version = "6.0"} else if (v >= 534) {browser.version = "5.1"} else if (v >= 533) {browser.version = "5.0"} else if (v >= 526) {browser.version = "4.0"} else if (v >= 525) {browser.version = "3.1"} else if (v > 500) {browser.version = "3.0"} else if (v > 400) {browser.version = "2.0"} else if (v > 85) {browser.version = "1.0"} browser.webkit = (navigator.appVersion.match(/WebKit\/(\d+)\./))[1]; browser.isMobile = (navigator.appVersion.match(/Mobile/i) != null); browser.noContextMenu = browser.isMobile; }, Firefox: function (browser) { if ((browser.version === "0.0" || AGENT.match(/Firefox/) == null) && navigator.product === "Gecko") { var rv = AGENT.match(/[\/ ]rv:(\d+\.\d.*?)[\) ]/); if (rv) {browser.version = rv[1]} else { var date = (navigator.buildID||navigator.productSub||"0").substr(0,8); if (date >= "20111220") {browser.version = "9.0"} else if (date >= "20111120") {browser.version = "8.0"} else if (date >= "20110927") {browser.version = "7.0"} else if (date >= "20110816") {browser.version = "6.0"} else if (date >= "20110621") {browser.version = "5.0"} else if (date >= "20110320") {browser.version = "4.0"} else if (date >= "20100121") {browser.version = "3.6"} else if (date >= "20090630") {browser.version = "3.5"} else if (date >= "20080617") {browser.version = "3.0"} else if (date >= "20061024") {browser.version = "2.0"} } } browser.isMobile = (navigator.appVersion.match(/Android/i) != null || AGENT.match(/ Fennec\//) != null || AGENT.match(/Mobile/) != null); }, Chrome: function (browser) { browser.noContextMenu = browser.isMobile = !!navigator.userAgent.match(/ Mobile[ \/]/); }, Opera: function (browser) {browser.version = opera.version()}, Edge: function (browser) { browser.isMobile = !!navigator.userAgent.match(/ Phone/); }, MSIE: function (browser) { browser.isMobile = !!navigator.userAgent.match(/ Phone/); browser.isIE9 = !!(document.documentMode && (window.performance || window.msPerformance)); MathJax.HTML.setScriptBug = !browser.isIE9 || document.documentMode < 9; MathJax.Hub.msieHTMLCollectionBug = (document.documentMode < 9); // // MathPlayer doesn't function properly in IE10, and not at all in IE11, // so don't even try to load it. // if (document.documentMode < 10 && !STARTUP.params.NoMathPlayer) { try { new ActiveXObject("MathPlayer.Factory.1"); browser.hasMathPlayer = true; } catch (err) {} try { if (browser.hasMathPlayer) { var mathplayer = document.createElement("object"); mathplayer.id = "mathplayer"; mathplayer.classid = "clsid:32F66A20-7614-11D4-BD11-00104BD3F987"; HEAD.appendChild(mathplayer); document.namespaces.add("m","http://www.w3.org/1998/Math/MathML"); browser.mpNamespace = true; if (document.readyState && (document.readyState === "loading" || document.readyState === "interactive")) { document.write('<?import namespace="m" implementation="#MathPlayer">'); browser.mpImported = true; } } else { // Adding any namespace avoids a crash in IE9 in IE9-standards mode // (any reference to document.namespaces before document.readyState is // "complete" causes an "unspecified error" to be thrown) document.namespaces.add("mjx_IE_fix","http://www.w3.org/1999/xlink"); } } catch (err) {} } } });} catch (err) { console.error(err.message); } MathJax.Ajax.Preloading( "[MathJax]/jax/element/mml/jax.js", "[MathJax]/jax/element/mml/optable/Arrows.js", "[MathJax]/jax/element/mml/optable/MiscMathSymbolsA.js", "[MathJax]/jax/element/mml/optable/Dingbats.js", "[MathJax]/jax/element/mml/optable/GeneralPunctuation.js", "[MathJax]/jax/element/mml/optable/SpacingModLetters.js", "[MathJax]/jax/element/mml/optable/MiscTechnical.js", "[MathJax]/jax/element/mml/optable/SupplementalArrowsA.js", "[MathJax]/jax/element/mml/optable/GreekAndCoptic.js", "[MathJax]/jax/element/mml/optable/LetterlikeSymbols.js", "[MathJax]/jax/element/mml/optable/SupplementalArrowsB.js", "[MathJax]/jax/element/mml/optable/BasicLatin.js", "[MathJax]/jax/element/mml/optable/MiscSymbolsAndArrows.js", "[MathJax]/jax/element/mml/optable/CombDiacritMarks.js", "[MathJax]/jax/element/mml/optable/GeometricShapes.js", "[MathJax]/jax/element/mml/optable/MathOperators.js", "[MathJax]/jax/element/mml/optable/MiscMathSymbolsB.js", "[MathJax]/jax/element/mml/optable/SuppMathOperators.js", "[MathJax]/jax/element/mml/optable/CombDiactForSymbols.js", "[MathJax]/jax/element/mml/optable/Latin1Supplement.js", "[MathJax]/extensions/MathEvents.js", "[MathJax]/extensions/tex2jax.js", "[MathJax]/jax/input/TeX/config.js", "[MathJax]/jax/input/TeX/jax.js", "[MathJax]/jax/output/SVG/config.js", "[MathJax]/jax/output/SVG/jax.js", "[MathJax]/jax/output/SVG/autoload/mtable.js", "[MathJax]/jax/output/SVG/autoload/mglyph.js", "[MathJax]/jax/output/SVG/autoload/mmultiscripts.js", "[MathJax]/jax/output/SVG/autoload/annotation-xml.js", "[MathJax]/jax/output/SVG/autoload/maction.js", "[MathJax]/jax/output/SVG/autoload/multiline.js", "[MathJax]/jax/output/SVG/autoload/menclose.js", "[MathJax]/jax/output/SVG/autoload/ms.js", "[MathJax]/jax/output/SVG/fonts/TeX/fontdata.js", "[MathJax]/jax/output/SVG/fonts/TeX/fontdata-extra.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/Main.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/Arrows.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/BoxDrawing.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/CombDiacritMarks.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/Dingbats.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/EnclosedAlphanum.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/GeneralPunctuation.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/GeometricShapes.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/GreekAndCoptic.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/Latin1Supplement.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/LatinExtendedA.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/LetterlikeSymbols.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/MathOperators.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/MiscMathSymbolsB.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/MiscSymbols.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/MiscTechnical.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/PUA.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/SpacingModLetters.js", "[MathJax]/jax/output/SVG/fonts/TeX/AMS/Regular/SuppMathOperators.js", "[MathJax]/jax/output/SVG/fonts/TeX/Main/Regular/BasicLatin.js", "[MathJax]/jax/output/SVG/fonts/TeX/Main/Regular/CombDiacritMarks.js", "[MathJax]/jax/output/SVG/fonts/TeX/Main/Regular/GeometricShapes.js", "[MathJax]/jax/output/SVG/fonts/TeX/Main/Regular/GreekAndCoptic.js", "[MathJax]/jax/output/SVG/fonts/TeX/Main/Regular/LatinExtendedA.js", "[MathJax]/jax/output/SVG/fonts/TeX/Main/Regular/LatinExtendedB.js", "[MathJax]/jax/output/SVG/fonts/TeX/Main/Regular/LetterlikeSymbols.js", "[MathJax]/jax/output/SVG/fonts/TeX/Main/Regular/MiscSymbols.js", "[MathJax]/jax/output/SVG/fonts/TeX/Main/Regular/SpacingModLetters.js", "[MathJax]/jax/output/SVG/fonts/TeX/Main/Regular/SuppMathOperators.js", "[MathJax]/jax/output/SVG/fonts/TeX/Size1/Regular/Main.js", "[MathJax]/jax/output/SVG/fonts/TeX/Size2/Regular/Main.js", "[MathJax]/jax/output/SVG/fonts/TeX/Size3/Regular/Main.js", "[MathJax]/jax/output/SVG/fonts/TeX/Size4/Regular/Main.js"); MathJax.Hub.Config({"v1.0-compatible":false}); /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/jax/element/mml/jax.js * * Implements the MML ElementJax that holds the internal represetation * of the mathematics on the page. Various InputJax will produce this * format, and the OutputJax will display it in various formats. * * --------------------------------------------------------------------- * * Copyright (c) 2009-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ MathJax.ElementJax.mml = MathJax.ElementJax({ mimeType: "jax/mml" },{ id: "mml", version: "2.7.5", directory: MathJax.ElementJax.directory + "/mml", extensionDir: MathJax.ElementJax.extensionDir + "/mml", optableDir: MathJax.ElementJax.directory + "/mml/optable" }); MathJax.ElementJax.mml.Augment({ Init: function () { if (arguments.length === 1 && arguments[0].type === "math") {this.root = arguments[0]} else {this.root = MathJax.ElementJax.mml.math.apply(this,arguments)} if (this.root.attr && this.root.attr.mode) { if (!this.root.display && this.root.attr.mode === "display") { this.root.display = "block"; this.root.attrNames.push("display"); } delete this.root.attr.mode; for (var i = 0, m = this.root.attrNames.length; i < m; i++) { if (this.root.attrNames[i] === "mode") {this.root.attrNames.splice(i,1); break} } } } },{ INHERIT: "_inherit_", AUTO: "_auto_", SIZE: { INFINITY: "infinity", SMALL: "small", NORMAL: "normal", BIG: "big" }, COLOR: { TRANSPARENT: "transparent" }, VARIANT: { NORMAL: "normal", BOLD: "bold", ITALIC: "italic", BOLDITALIC: "bold-italic", DOUBLESTRUCK: "double-struck", FRAKTUR: "fraktur", BOLDFRAKTUR: "bold-fraktur", SCRIPT: "script", BOLDSCRIPT: "bold-script", SANSSERIF: "sans-serif", BOLDSANSSERIF: "bold-sans-serif", SANSSERIFITALIC: "sans-serif-italic", SANSSERIFBOLDITALIC: "sans-serif-bold-italic", MONOSPACE: "monospace", INITIAL: "initial", TAILED: "tailed", LOOPED: "looped", STRETCHED: "stretched", CALIGRAPHIC: "-tex-caligraphic", OLDSTYLE: "-tex-oldstyle" }, FORM: { PREFIX: "prefix", INFIX: "infix", POSTFIX: "postfix" }, LINEBREAK: { AUTO: "auto", NEWLINE: "newline", NOBREAK: "nobreak", GOODBREAK: "goodbreak", BADBREAK: "badbreak" }, LINEBREAKSTYLE: { BEFORE: "before", AFTER: "after", DUPLICATE: "duplicate", INFIXLINBREAKSTYLE: "infixlinebreakstyle" }, INDENTALIGN: { LEFT: "left", CENTER: "center", RIGHT: "right", AUTO: "auto", ID: "id", INDENTALIGN: "indentalign" }, INDENTSHIFT: { INDENTSHIFT: "indentshift" }, LINETHICKNESS: { THIN: "thin", MEDIUM: "medium", THICK: "thick" }, NOTATION: { LONGDIV: "longdiv", ACTUARIAL: "actuarial", RADICAL: "radical", BOX: "box", ROUNDEDBOX: "roundedbox", CIRCLE: "circle", LEFT: "left", RIGHT: "right", TOP: "top", BOTTOM: "bottom", UPDIAGONALSTRIKE: "updiagonalstrike", DOWNDIAGONALSTRIKE: "downdiagonalstrike", UPDIAGONALARROW: "updiagonalarrow", VERTICALSTRIKE: "verticalstrike", HORIZONTALSTRIKE: "horizontalstrike", PHASORANGLE: "phasorangle", MADRUWB: "madruwb" }, ALIGN: { TOP: "top", BOTTOM: "bottom", CENTER: "center", BASELINE: "baseline", AXIS: "axis", LEFT: "left", RIGHT: "right" }, LINES: { NONE: "none", SOLID: "solid", DASHED: "dashed" }, SIDE: { LEFT: "left", RIGHT: "right", LEFTOVERLAP: "leftoverlap", RIGHTOVERLAP: "rightoverlap" }, WIDTH: { AUTO: "auto", FIT: "fit" }, ACTIONTYPE: { TOGGLE: "toggle", STATUSLINE: "statusline", TOOLTIP: "tooltip", INPUT: "input" }, LENGTH: { VERYVERYTHINMATHSPACE: "veryverythinmathspace", VERYTHINMATHSPACE: "verythinmathspace", THINMATHSPACE: "thinmathspace", MEDIUMMATHSPACE: "mediummathspace", THICKMATHSPACE: "thickmathspace", VERYTHICKMATHSPACE: "verythickmathspace", VERYVERYTHICKMATHSPACE: "veryverythickmathspace", NEGATIVEVERYVERYTHINMATHSPACE: "negativeveryverythinmathspace", NEGATIVEVERYTHINMATHSPACE: "negativeverythinmathspace", NEGATIVETHINMATHSPACE: "negativethinmathspace", NEGATIVEMEDIUMMATHSPACE: "negativemediummathspace", NEGATIVETHICKMATHSPACE: "negativethickmathspace", NEGATIVEVERYTHICKMATHSPACE: "negativeverythickmathspace", NEGATIVEVERYVERYTHICKMATHSPACE: "negativeveryverythickmathspace" }, OVERFLOW: { LINBREAK: "linebreak", SCROLL: "scroll", ELIDE: "elide", TRUNCATE: "truncate", SCALE: "scale" }, UNIT: { EM: "em", EX: "ex", PX: "px", IN: "in", CM: "cm", MM: "mm", PT: "pt", PC: "pc" }, TEXCLASS: { ORD: 0, OP: 1, BIN: 2, REL: 3, OPEN: 4, CLOSE: 5, PUNCT: 6, INNER: 7, VCENTER: 8, NONE: -1 }, TEXCLASSNAMES: ["ORD", "OP", "BIN", "REL", "OPEN", "CLOSE", "PUNCT", "INNER", "VCENTER"], skipAttributes: { texClass:true, useHeight:true, texprimestyle:true }, copyAttributes: { displaystyle:1, scriptlevel:1, open:1, close:1, form:1, actiontype: 1, fontfamily:true, fontsize:true, fontweight:true, fontstyle:true, color:true, background:true, id:true, "class":1, href:true, style:true }, copyAttributeNames: [ "displaystyle", "scriptlevel", "open", "close", "form", // force these to be copied "actiontype", "fontfamily", "fontsize", "fontweight", "fontstyle", "color", "background", "id", "class", "href", "style" ], nocopyAttributes: { fontfamily: true, fontsize: true, fontweight: true, fontstyle: true, color: true, background: true, id: true, 'class': true, href: true, style: true, xmlns: true }, Error: function (message,def) { var mml = this.merror(message), dir = MathJax.Localization.fontDirection(), font = MathJax.Localization.fontFamily(); if (def) {mml = mml.With(def)} if (dir || font) { mml = this.mstyle(mml); if (dir) {mml.dir = dir} if (font) {mml.style.fontFamily = "font-family: "+font} } return mml; } }); (function (MML) { MML.mbase = MathJax.Object.Subclass({ type: "base", isToken: false, defaults: { mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, dir: MML.INHERIT }, noInherit: {}, noInheritAttribute: { texClass: true }, getRemoved: {}, linebreakContainer: false, Init: function () { this.data = []; if (this.inferRow && !(arguments.length === 1 && arguments[0].inferred)) {this.Append(MML.mrow().With({inferred: true, notParent: true}))} this.Append.apply(this,arguments); }, With: function (def) { for (var id in def) {if (def.hasOwnProperty(id)) {this[id] = def[id]}} return this; }, Append: function () { if (this.inferRow && this.data.length) { this.data[0].Append.apply(this.data[0],arguments); } else { for (var i = 0, m = arguments.length; i < m; i++) {this.SetData(this.data.length,arguments[i])} } }, SetData: function (i,item) { if (item != null) { if (!(item instanceof MML.mbase)) {item = (this.isToken || this.isChars ? MML.chars(item) : MML.mtext(item))} item.parent = this; item.setInherit(this.inheritFromMe ? this : this.inherit); } this.data[i] = item; }, Parent: function () { var parent = this.parent; while (parent && parent.notParent) {parent = parent.parent} return parent; }, Get: function (name,nodefault,noself) { if (!noself) { if (this[name] != null) {return this[name]} if (this.attr && this.attr[name] != null) {return this.attr[name]} } // FIXME: should cache these values and get from cache // (clear cache when appended to a new object?) var parent = this.Parent(); if (parent && parent["adjustChild_"+name] != null) { return (parent["adjustChild_"+name])(this.childPosition(),nodefault); } var obj = this.inherit; var root = obj; while (obj) { var value = obj[name]; if (value == null && obj.attr) {value = obj.attr[name]} if (obj.removedStyles && obj.getRemoved[name] && value == null) value = obj.removedStyles[obj.getRemoved[name]]; if (value != null && obj.noInheritAttribute && !obj.noInheritAttribute[name]) { var noInherit = obj.noInherit[this.type]; if (!(noInherit && noInherit[name])) {return value} } root = obj; obj = obj.inherit; } if (!nodefault) { if (this.defaults[name] === MML.AUTO) {return this.autoDefault(name)} if (this.defaults[name] !== MML.INHERIT && this.defaults[name] != null) {return this.defaults[name]} if (root) {return root.defaults[name]} } return null; }, hasValue: function (name) {return (this.Get(name,true) != null)}, getValues: function () { var values = {}; for (var i = 0, m = arguments.length; i < m; i++) {values[arguments[i]] = this.Get(arguments[i])} return values; }, adjustChild_scriptlevel: function (i,nodef) {return this.Get("scriptlevel",nodef)}, // always inherit from parent adjustChild_displaystyle: function (i,nodef) {return this.Get("displaystyle",nodef)}, // always inherit from parent adjustChild_texprimestyle: function (i,nodef) {return this.Get("texprimestyle",nodef)}, // always inherit from parent hasMMLspacing: function () {return false}, childPosition: function () { var child = this, parent = child.parent; while (parent.notParent) {child = parent; parent = child.parent} for (var i = 0, m = parent.data.length; i < m; i++) {if (parent.data[i] === child) {return i}} return null; }, setInherit: function (obj) { if (obj !== this.inherit && this.inherit == null) { this.inherit = obj; for (var i = 0, m = this.data.length; i < m; i++) { if (this.data[i] && this.data[i].setInherit) {this.data[i].setInherit(obj)} } } }, setTeXclass: function (prev) { this.getPrevClass(prev); return (typeof(this.texClass) !== "undefined" ? this : prev); }, getPrevClass: function (prev) { if (prev) { this.prevClass = prev.Get("texClass"); this.prevLevel = prev.Get("scriptlevel"); } }, updateTeXclass: function (core) { if (core) { this.prevClass = core.prevClass; delete core.prevClass; this.prevLevel = core.prevLevel; delete core.prevLevel; this.texClass = core.Get("texClass"); } }, texSpacing: function () { var prev = (this.prevClass != null ? this.prevClass : MML.TEXCLASS.NONE); var tex = (this.Get("texClass") || MML.TEXCLASS.ORD); if (prev === MML.TEXCLASS.NONE || tex === MML.TEXCLASS.NONE) {return ""} if (prev === MML.TEXCLASS.VCENTER) {prev = MML.TEXCLASS.ORD} if (tex === MML.TEXCLASS.VCENTER) {tex = MML.TEXCLASS.ORD} var space = this.TEXSPACE[prev][tex]; if ((this.prevLevel > 0 || this.Get("scriptlevel") > 0) && space >= 0) {return ""} return this.TEXSPACELENGTH[Math.abs(space)]; }, TEXSPACELENGTH:[ "", MML.LENGTH.THINMATHSPACE, MML.LENGTH.MEDIUMMATHSPACE, MML.LENGTH.THICKMATHSPACE ], // See TeXBook Chapter 18 (p. 170) TEXSPACE: [ [ 0,-1, 2, 3, 0, 0, 0, 1], // ORD [-1,-1, 0, 3, 0, 0, 0, 1], // OP [ 2, 2, 0, 0, 2, 0, 0, 2], // BIN [ 3, 3, 0, 0, 3, 0, 0, 3], // REL [ 0, 0, 0, 0, 0, 0, 0, 0], // OPEN [ 0,-1, 2, 3, 0, 0, 0, 1], // CLOSE [ 1, 1, 0, 1, 1, 1, 1, 1], // PUNCT [ 1,-1, 2, 3, 1, 0, 1, 1] // INNER ], autoDefault: function (name) {return ""}, isSpacelike: function () {return false}, isEmbellished: function () {return false}, Core: function () {return this}, CoreMO: function () {return this}, childIndex: function(child) { if (child == null) return; for (var i = 0, m = this.data.length; i < m; i++) if (child === this.data[i]) return i; }, CoreIndex: function () { return (this.inferRow ? this.data[0]||this : this).childIndex(this.Core()); }, hasNewline: function () { if (this.isEmbellished()) {return this.CoreMO().hasNewline()} if (this.isToken || this.linebreakContainer) {return false} for (var i = 0, m = this.data.length; i < m; i++) { if (this.data[i] && this.data[i].hasNewline()) {return true} } return false; }, array: function () {if (this.inferred) {return this.data} else {return [this]}}, toString: function () {return this.type+"("+this.data.join(",")+")"}, getAnnotation: function () {return null} },{ childrenSpacelike: function () { for (var i = 0, m = this.data.length; i < m; i++) {if (!this.data[i].isSpacelike()) {return false}} return true; }, childEmbellished: function () { return (this.data[0] && this.data[0].isEmbellished()); }, childCore: function () {return (this.inferRow && this.data[0] ? this.data[0].Core() : this.data[0])}, childCoreMO: function () {return (this.data[0] ? this.data[0].CoreMO() : null)}, setChildTeXclass: function (prev) { if (this.data[0]) { prev = this.data[0].setTeXclass(prev); this.updateTeXclass(this.data[0]); } return prev; }, setBaseTeXclasses: function (prev) { this.getPrevClass(prev); this.texClass = null; if (this.data[0]) { if (this.isEmbellished() || this.data[0].isa(MML.mi)) { prev = this.data[0].setTeXclass(prev); this.updateTeXclass(this.Core()); } else {this.data[0].setTeXclass(); prev = this} } else {prev = this} for (var i = 1, m = this.data.length; i < m; i++) {if (this.data[i]) {this.data[i].setTeXclass()}} return prev; }, setSeparateTeXclasses: function (prev) { this.getPrevClass(prev); for (var i = 0, m = this.data.length; i < m; i++) {if (this.data[i]) {this.data[i].setTeXclass()}} if (this.isEmbellished()) {this.updateTeXclass(this.Core())} return this; } }); MML.mi = MML.mbase.Subclass({ type: "mi", isToken: true, texClass: MML.TEXCLASS.ORD, defaults: { mathvariant: MML.AUTO, mathsize: MML.INHERIT, mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, dir: MML.INHERIT }, autoDefault: function (name) { if (name === "mathvariant") { var mi = (this.data[0]||"").toString(); return (mi.length === 1 || (mi.length === 2 && mi.charCodeAt(0) >= 0xD800 && mi.charCodeAt(0) < 0xDC00) ? MML.VARIANT.ITALIC : MML.VARIANT.NORMAL); } return ""; }, setTeXclass: function (prev) { this.getPrevClass(prev); var name = this.data.join(""); if (name.length > 1 && name.match(/^[a-z][a-z0-9]*$/i) && this.texClass === MML.TEXCLASS.ORD) { this.texClass = MML.TEXCLASS.OP; this.autoOP = true; } return this; } }); MML.mn = MML.mbase.Subclass({ type: "mn", isToken: true, texClass: MML.TEXCLASS.ORD, defaults: { mathvariant: MML.INHERIT, mathsize: MML.INHERIT, mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, dir: MML.INHERIT } }); MML.mo = MML.mbase.Subclass({ type: "mo", isToken: true, defaults: { mathvariant: MML.INHERIT, mathsize: MML.INHERIT, mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, dir: MML.INHERIT, form: MML.AUTO, fence: MML.AUTO, separator: MML.AUTO, lspace: MML.AUTO, rspace: MML.AUTO, stretchy: MML.AUTO, symmetric: MML.AUTO, maxsize: MML.AUTO, minsize: MML.AUTO, largeop: MML.AUTO, movablelimits: MML.AUTO, accent: MML.AUTO, linebreak: MML.LINEBREAK.AUTO, lineleading: MML.INHERIT, linebreakstyle: MML.AUTO, linebreakmultchar: MML.INHERIT, indentalign: MML.INHERIT, indentshift: MML.INHERIT, indenttarget: MML.INHERIT, indentalignfirst: MML.INHERIT, indentshiftfirst: MML.INHERIT, indentalignlast: MML.INHERIT, indentshiftlast: MML.INHERIT, texClass: MML.AUTO }, defaultDef: { form: MML.FORM.INFIX, fence: false, separator: false, lspace: MML.LENGTH.THICKMATHSPACE, rspace: MML.LENGTH.THICKMATHSPACE, stretchy: false, symmetric: false, maxsize: MML.SIZE.INFINITY, minsize: '0em', //'1em', largeop: false, movablelimits: false, accent: false, linebreak: MML.LINEBREAK.AUTO, lineleading: "1ex", linebreakstyle: "before", indentalign: MML.INDENTALIGN.AUTO, indentshift: "0", indenttarget: "", indentalignfirst: MML.INDENTALIGN.INDENTALIGN, indentshiftfirst: MML.INDENTSHIFT.INDENTSHIFT, indentalignlast: MML.INDENTALIGN.INDENTALIGN, indentshiftlast: MML.INDENTSHIFT.INDENTSHIFT, texClass: MML.TEXCLASS.REL // for MML, but TeX sets ORD explicitly }, SPACE_ATTR: {lspace: 0x01, rspace: 0x02}, useMMLspacing: 0x03, hasMMLspacing: function () { if (this.useMMLspacing) return true; return this.form && (this.OPTABLE[this.form]||{})[this.data.join('')]; }, autoDefault: function (name,nodefault) { var def = this.def; if (!def) { if (name === "form") {return this.getForm()} var mo = this.data.join(""); var forms = [this.Get("form"),MML.FORM.INFIX,MML.FORM.POSTFIX,MML.FORM.PREFIX]; for (var i = 0, m = forms.length; i < m; i++) { var data = this.OPTABLE[forms[i]][mo]; if (data) {def = this.makeDef(data); break} } if (!def) {def = this.CheckRange(mo)} if (!def && nodefault) {def = {}} else { if (!def) {def = MathJax.Hub.Insert({},this.defaultDef)} if (this.parent) {this.def = def} else {def = MathJax.Hub.Insert({},def)} def.form = forms[0]; } } this.useMMLspacing &= ~(this.SPACE_ATTR[name] || 0); if (def[name] != null) {return def[name]} else if (!nodefault) {return this.defaultDef[name]} return ""; }, CheckRange: function (mo) { var n = mo.charCodeAt(0); if (n >= 0xD800 && n < 0xDC00) {n = (((n-0xD800)<<10)+(mo.charCodeAt(1)-0xDC00))+0x10000} for (var i = 0, m = this.RANGES.length; i < m && this.RANGES[i][0] <= n; i++) { if (n <= this.RANGES[i][1]) { if (this.RANGES[i][3]) { var file = MML.optableDir+"/"+this.RANGES[i][3]+".js"; this.RANGES[i][3] = null; MathJax.Hub.RestartAfter(MathJax.Ajax.Require(file)); } var data = MML.TEXCLASSNAMES[this.RANGES[i][2]]; data = this.OPTABLE.infix[mo] = MML.mo.OPTYPES[data === "BIN" ? "BIN3" : data]; return this.makeDef(data); } } return null; }, makeDef: function (data) { if (data[2] == null) {data[2] = this.defaultDef.texClass} if (!data[3]) {data[3] = {}} var def = MathJax.Hub.Insert({},data[3]); def.lspace = this.SPACE[data[0]]; def.rspace = this.SPACE[data[1]]; def.texClass = data[2]; if (def.texClass === MML.TEXCLASS.REL && (this.movablelimits || this.data.join("").match(/^[a-z]+$/i))) {def.texClass = MML.TEXCLASS.OP} // mark named operators as OP return def; }, getForm: function () { var core = this, parent = this.parent, Parent = this.Parent(); while (Parent && Parent.isEmbellished()) {core = parent; parent = Parent.parent; Parent = Parent.Parent()} if (parent && parent.type === "mrow" && parent.NonSpaceLength() !== 1) { if (parent.FirstNonSpace() === core) {return MML.FORM.PREFIX} if (parent.LastNonSpace() === core) {return MML.FORM.POSTFIX} } return MML.FORM.INFIX; }, isEmbellished: function () {return true}, hasNewline: function () {return (this.Get("linebreak") === MML.LINEBREAK.NEWLINE)}, CoreParent: function () { var parent = this; while (parent && parent.isEmbellished() && parent.CoreMO() === this && !parent.isa(MML.math)) {parent = parent.Parent()} return parent; }, CoreText: function (parent) { if (!parent) {return ""} if (parent.isEmbellished()) {return parent.CoreMO().data.join("")} while ((((parent.isa(MML.mrow) || parent.isa(MML.TeXAtom) || parent.isa(MML.mstyle) || parent.isa(MML.mphantom)) && parent.data.length === 1) || parent.isa(MML.munderover)) && parent.data[0]) {parent = parent.data[0]} if (!parent.isToken) {return ""} else {return parent.data.join("")} }, remapChars: { '*':"\u2217", '"':"\u2033", "\u00B0":"\u2218", "\u00B2":"2", "\u00B3":"3", "\u00B4":"\u2032", "\u00B9":"1" }, remap: function (text,map) { text = text.replace(/-/g,"\u2212"); if (map) { text = text.replace(/'/g,"\u2032").replace(/`/g,"\u2035"); if (text.length === 1) {text = map[text]||text} } return text; }, setTeXclass: function (prev) { var values = this.getValues("form","lspace","rspace","fence"); // sets useMMLspacing if (this.hasMMLspacing()) {this.texClass = MML.TEXCLASS.NONE; return this} if (values.fence && !this.texClass) { if (values.form === MML.FORM.PREFIX) {this.texClass = MML.TEXCLASS.OPEN} if (values.form === MML.FORM.POSTFIX) {this.texClass = MML.TEXCLASS.CLOSE} } this.texClass = this.Get("texClass"); if (this.data.join("") === "\u2061") { // force previous node to be texClass OP, and skip this node if (prev) {prev.texClass = MML.TEXCLASS.OP; prev.fnOP = true} this.texClass = this.prevClass = MML.TEXCLASS.NONE; return prev; } return this.adjustTeXclass(prev); }, adjustTeXclass: function (prev) { if (this.texClass === MML.TEXCLASS.NONE) {return prev} if (prev) { if (prev.autoOP && (this.texClass === MML.TEXCLASS.BIN || this.texClass === MML.TEXCLASS.REL)) {prev.texClass = MML.TEXCLASS.ORD} this.prevClass = prev.texClass || MML.TEXCLASS.ORD; this.prevLevel = prev.Get("scriptlevel") } else {this.prevClass = MML.TEXCLASS.NONE} if (this.texClass === MML.TEXCLASS.BIN && (this.prevClass === MML.TEXCLASS.NONE || this.prevClass === MML.TEXCLASS.BIN || this.prevClass === MML.TEXCLASS.OP || this.prevClass === MML.TEXCLASS.REL || this.prevClass === MML.TEXCLASS.OPEN || this.prevClass === MML.TEXCLASS.PUNCT)) { this.texClass = MML.TEXCLASS.ORD; } else if (this.prevClass === MML.TEXCLASS.BIN && (this.texClass === MML.TEXCLASS.REL || this.texClass === MML.TEXCLASS.CLOSE || this.texClass === MML.TEXCLASS.PUNCT)) { prev.texClass = this.prevClass = MML.TEXCLASS.ORD; } else if (this.texClass === MML.TEXCLASS.BIN) { // // Check if node is the last one in its container since the rule // above only takes effect if there is a node that follows. // var child = this, parent = this.parent; while (parent && parent.parent && parent.isEmbellished() && (parent.data.length === 1 || (parent.type !== "mrow" && parent.Core() === child))) // handles msubsup and munderover {child = parent; parent = parent.parent} if (parent.data[parent.data.length-1] === child) this.texClass = MML.TEXCLASS.ORD; } return this; } }); MML.mtext = MML.mbase.Subclass({ type: "mtext", isToken: true, isSpacelike: function () {return true}, texClass: MML.TEXCLASS.ORD, defaults: { mathvariant: MML.INHERIT, mathsize: MML.INHERIT, mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, dir: MML.INHERIT } }); MML.mspace = MML.mbase.Subclass({ type: "mspace", isToken: true, isSpacelike: function () {return true}, defaults: { mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, width: "0em", height: "0ex", depth: "0ex", linebreak: MML.LINEBREAK.AUTO }, hasDimAttr: function () { return (this.hasValue("width") || this.hasValue("height") || this.hasValue("depth")); }, hasNewline: function () { // The MathML spec says that the linebreak attribute should be ignored // if any dimensional attribute is set. return (!this.hasDimAttr() && this.Get("linebreak") === MML.LINEBREAK.NEWLINE); } }); MML.ms = MML.mbase.Subclass({ type: "ms", isToken: true, texClass: MML.TEXCLASS.ORD, defaults: { mathvariant: MML.INHERIT, mathsize: MML.INHERIT, mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, dir: MML.INHERIT, lquote: '"', rquote: '"' } }); MML.mglyph = MML.mbase.Subclass({ type: "mglyph", isToken: true, texClass: MML.TEXCLASS.ORD, defaults: { mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, alt: "", src: "", width: MML.AUTO, height: MML.AUTO, valign: "0em" } }); MML.mrow = MML.mbase.Subclass({ type: "mrow", isSpacelike: MML.mbase.childrenSpacelike, inferred: false, notParent: false, isEmbellished: function () { var isEmbellished = false; for (var i = 0, m = this.data.length; i < m; i++) { if (this.data[i] == null) continue; if (this.data[i].isEmbellished()) { if (isEmbellished) {return false} isEmbellished = true; this.core = i; } else if (!this.data[i].isSpacelike()) {return false} } return isEmbellished; }, NonSpaceLength: function () { var n = 0; for (var i = 0, m = this.data.length; i < m; i++) {if (this.data[i] && !this.data[i].isSpacelike()) {n++}} return n; }, FirstNonSpace: function () { for (var i = 0, m = this.data.length; i < m; i++) {if (this.data[i] && !this.data[i].isSpacelike()) {return this.data[i]}} return null; }, LastNonSpace: function () { for (var i = this.data.length-1; i >= 0; i--) {if (this.data[0] && !this.data[i].isSpacelike()) {return this.data[i]}} return null; }, Core: function () { if (!(this.isEmbellished()) || typeof(this.core) === "undefined") {return this} return this.data[this.core]; }, CoreMO: function () { if (!(this.isEmbellished()) || typeof(this.core) === "undefined") {return this} return this.data[this.core].CoreMO(); }, toString: function () { if (this.inferred) {return '[' + this.data.join(',') + ']'} return this.SUPER(arguments).toString.call(this); }, setTeXclass: function (prev) { var i, m = this.data.length; if ((this.open || this.close) && (!prev || !prev.fnOP)) { // // <mrow> came from \left...\right // so treat as subexpression (tex class INNER) // this.getPrevClass(prev); prev = null; for (i = 0; i < m; i++) {if (this.data[i]) {prev = this.data[i].setTeXclass(prev)}} if (!this.hasOwnProperty("texClass")) this.texClass = MML.TEXCLASS.INNER; return this; } else { // // Normal <mrow>, so treat as // thorugh mrow is not there // for (i = 0; i < m; i++) {if (this.data[i]) {prev = this.data[i].setTeXclass(prev)}} if (this.data[0]) {this.updateTeXclass(this.data[0])} return prev; } }, getAnnotation: function (name) { if (this.data.length != 1) return null; return this.data[0].getAnnotation(name); } }); MML.mfrac = MML.mbase.Subclass({ type: "mfrac", num: 0, den: 1, linebreakContainer: true, isEmbellished: MML.mbase.childEmbellished, Core: MML.mbase.childCore, CoreMO: MML.mbase.childCoreMO, defaults: { mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, linethickness: MML.LINETHICKNESS.MEDIUM, numalign: MML.ALIGN.CENTER, denomalign: MML.ALIGN.CENTER, bevelled: false }, adjustChild_displaystyle: function (n) {return false}, adjustChild_scriptlevel: function (n) { var level = this.Get("scriptlevel"); if (!this.Get("displaystyle") || level > 0) {level++} return level; }, adjustChild_texprimestyle: function (n) { if (n == this.den) {return true} return this.Get("texprimestyle"); }, setTeXclass: MML.mbase.setSeparateTeXclasses }); MML.msqrt = MML.mbase.Subclass({ type: "msqrt", inferRow: true, linebreakContainer: true, texClass: MML.TEXCLASS.ORD, setTeXclass: MML.mbase.setSeparateTeXclasses, adjustChild_texprimestyle: function (n) {return true} }); MML.mroot = MML.mbase.Subclass({ type: "mroot", linebreakContainer: true, texClass: MML.TEXCLASS.ORD, adjustChild_displaystyle: function (n) { if (n === 1) {return false} return this.Get("displaystyle"); }, adjustChild_scriptlevel: function (n) { var level = this.Get("scriptlevel"); if (n === 1) {level += 2} return level; }, adjustChild_texprimestyle: function (n) { if (n === 0) {return true}; return this.Get("texprimestyle"); }, setTeXclass: MML.mbase.setSeparateTeXclasses }); MML.mstyle = MML.mbase.Subclass({ type: "mstyle", isSpacelike: MML.mbase.childrenSpacelike, isEmbellished: MML.mbase.childEmbellished, Core: MML.mbase.childCore, CoreMO: MML.mbase.childCoreMO, inferRow: true, defaults: { scriptlevel: MML.INHERIT, displaystyle: MML.INHERIT, scriptsizemultiplier: Math.sqrt(1/2), scriptminsize: "8pt", mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, dir: MML.INHERIT, infixlinebreakstyle: MML.LINEBREAKSTYLE.BEFORE, decimalseparator: "." }, adjustChild_scriptlevel: function (n) { var level = this.scriptlevel; if (level == null) { level = this.Get("scriptlevel"); } else if (String(level).match(/^ *[-+]/)) { var LEVEL = this.Get("scriptlevel",null,true); level = LEVEL + parseInt(level); } return level; }, inheritFromMe: true, noInherit: { mpadded: {width: true, height: true, depth: true, lspace: true, voffset: true}, mtable: {width: true, height: true, depth: true, align: true} }, getRemoved: {fontfamily:"fontFamily", fontweight:"fontWeight", fontstyle:"fontStyle", fontsize:"fontSize"}, setTeXclass: MML.mbase.setChildTeXclass }); MML.merror = MML.mbase.Subclass({ type: "merror", inferRow: true, linebreakContainer: true, texClass: MML.TEXCLASS.ORD }); MML.mpadded = MML.mbase.Subclass({ type: "mpadded", inferRow: true, isSpacelike: MML.mbase.childrenSpacelike, isEmbellished: MML.mbase.childEmbellished, Core: MML.mbase.childCore, CoreMO: MML.mbase.childCoreMO, defaults: { mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, width: "", height: "", depth: "", lspace: 0, voffset: 0 }, setTeXclass: MML.mbase.setChildTeXclass }); MML.mphantom = MML.mbase.Subclass({ type: "mphantom", texClass: MML.TEXCLASS.ORD, inferRow: true, isSpacelike: MML.mbase.childrenSpacelike, isEmbellished: MML.mbase.childEmbellished, Core: MML.mbase.childCore, CoreMO: MML.mbase.childCoreMO, setTeXclass: MML.mbase.setChildTeXclass }); MML.mfenced = MML.mbase.Subclass({ type: "mfenced", defaults: { mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, open: '(', close: ')', separators: ',' }, addFakeNodes: function () { var values = this.getValues("open","close","separators"); values.open = values.open.replace(/[ \t\n\r]/g,""); values.close = values.close.replace(/[ \t\n\r]/g,""); values.separators = values.separators.replace(/[ \t\n\r]/g,""); // // Create a fake node for the open item // if (values.open !== "") { this.SetData("open",MML.mo(values.open).With({ fence:true, form:MML.FORM.PREFIX, texClass:MML.TEXCLASS.OPEN })); } // // Create fake nodes for the separators // if (values.separators !== "") { while (values.separators.length < this.data.length) {values.separators += values.separators.charAt(values.separators.length-1)} for (var i = 1, m = this.data.length; i < m; i++) { if (this.data[i]) {this.SetData("sep"+i,MML.mo(values.separators.charAt(i-1)).With({separator:true}))} } } // // Create fake node for the close item // if (values.close !== "") { this.SetData("close",MML.mo(values.close).With({ fence:true, form:MML.FORM.POSTFIX, texClass:MML.TEXCLASS.CLOSE })); } }, texClass: MML.TEXCLASS.OPEN, setTeXclass: function (prev) { this.addFakeNodes(); this.getPrevClass(prev); if (this.data.open) {prev = this.data.open.setTeXclass(prev)} if (this.data[0]) {prev = this.data[0].setTeXclass(prev)} for (var i = 1, m = this.data.length; i < m; i++) { if (this.data["sep"+i]) {prev = this.data["sep"+i].setTeXclass(prev)} if (this.data[i]) {prev = this.data[i].setTeXclass(prev)} } if (this.data.close) {prev = this.data.close.setTeXclass(prev)} this.updateTeXclass(this.data.open); this.texClass = MML.TEXCLASS.INNER; return prev; } }); MML.menclose = MML.mbase.Subclass({ type: "menclose", inferRow: true, linebreakContainer: true, defaults: { mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, notation: MML.NOTATION.LONGDIV, texClass: MML.TEXCLASS.ORD }, setTeXclass: MML.mbase.setSeparateTeXclasses }); MML.msubsup = MML.mbase.Subclass({ type: "msubsup", base: 0, sub: 1, sup: 2, isEmbellished: MML.mbase.childEmbellished, Core: MML.mbase.childCore, CoreMO: MML.mbase.childCoreMO, defaults: { mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, subscriptshift: "", superscriptshift: "", texClass: MML.AUTO }, autoDefault: function (name) { if (name === "texClass") {return (this.isEmbellished() ? this.CoreMO().Get(name) : MML.TEXCLASS.ORD)} return 0; }, adjustChild_displaystyle: function (n) { if (n > 0) {return false} return this.Get("displaystyle"); }, adjustChild_scriptlevel: function (n) { var level = this.Get("scriptlevel"); if (n > 0) {level++} return level; }, adjustChild_texprimestyle: function (n) { if (n === this.sub) {return true} return this.Get("texprimestyle"); }, setTeXclass: MML.mbase.setBaseTeXclasses }); MML.msub = MML.msubsup.Subclass({type: "msub"}); MML.msup = MML.msubsup.Subclass({type: "msup", sub:2, sup:1}); MML.mmultiscripts = MML.msubsup.Subclass({ type: "mmultiscripts", adjustChild_texprimestyle: function (n) { if (n % 2 === 1) {return true} return this.Get("texprimestyle"); } }); MML.mprescripts = MML.mbase.Subclass({type: "mprescripts"}); MML.none = MML.mbase.Subclass({type: "none"}); MML.munderover = MML.mbase.Subclass({ type: "munderover", base: 0, under: 1, over: 2, sub: 1, sup: 2, ACCENTS: ["", "accentunder", "accent"], linebreakContainer: true, isEmbellished: MML.mbase.childEmbellished, Core: MML.mbase.childCore, CoreMO: MML.mbase.childCoreMO, defaults: { mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, accent: MML.AUTO, accentunder: MML.AUTO, align: MML.ALIGN.CENTER, texClass: MML.AUTO, subscriptshift: "", // when converted to msubsup by moveablelimits superscriptshift: "" // when converted to msubsup by moveablelimits }, autoDefault: function (name) { if (name === "texClass") {return (this.isEmbellished() ? this.CoreMO().Get(name) : MML.TEXCLASS.ORD)} if (name === "accent" && this.data[this.over]) {return this.data[this.over].CoreMO().Get("accent")} if (name === "accentunder" && this.data[this.under]) {return this.data[this.under].CoreMO().Get("accent")} return false; }, adjustChild_displaystyle: function (n) { if (n > 0) {return false} return this.Get("displaystyle"); }, adjustChild_scriptlevel: function (n) { var level = this.Get("scriptlevel"); var force = (this.data[this.base] && !this.Get("displaystyle") && this.data[this.base].CoreMO().Get("movablelimits")); if (n == this.under && (force || !this.Get("accentunder"))) {level++} if (n == this.over && (force || !this.Get("accent"))) {level++} return level; }, adjustChild_texprimestyle: function (n) { if (n === this.base && this.data[this.over]) {return true} return this.Get("texprimestyle"); }, setTeXclass: MML.mbase.setBaseTeXclasses }); MML.munder = MML.munderover.Subclass({type: "munder"}); MML.mover = MML.munderover.Subclass({ type: "mover", over: 1, under: 2, sup: 1, sub: 2, ACCENTS: ["", "accent", "accentunder"] }); MML.mtable = MML.mbase.Subclass({ type: "mtable", defaults: { mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, align: MML.ALIGN.AXIS, rowalign: MML.ALIGN.BASELINE, columnalign: MML.ALIGN.CENTER, groupalign: "{left}", alignmentscope: true, columnwidth: MML.WIDTH.AUTO, width: MML.WIDTH.AUTO, rowspacing: "1ex", columnspacing: ".8em", rowlines: MML.LINES.NONE, columnlines: MML.LINES.NONE, frame: MML.LINES.NONE, framespacing: "0.4em 0.5ex", equalrows: false, equalcolumns: false, displaystyle: false, side: MML.SIDE.RIGHT, minlabelspacing: "0.8em", texClass: MML.TEXCLASS.ORD, useHeight: 1 }, adjustChild_displaystyle: function () { return (this.displaystyle != null ? this.displaystyle : this.defaults.displaystyle); }, inheritFromMe: true, noInherit: { mover: {align: true}, munder: {align: true}, munderover: {align: true}, mtable: { align: true, rowalign: true, columnalign: true, groupalign: true, alignmentscope: true, columnwidth: true, width: true, rowspacing: true, columnspacing: true, rowlines: true, columnlines: true, frame: true, framespacing: true, equalrows: true, equalcolumns: true, displaystyle: true, side: true, minlabelspacing: true, texClass: true, useHeight: 1 } }, linebreakContainer: true, Append: function () { for (var i = 0, m = arguments.length; i < m; i++) { if (!((arguments[i] instanceof MML.mtr) || (arguments[i] instanceof MML.mlabeledtr))) {arguments[i] = MML.mtr(arguments[i])} } this.SUPER(arguments).Append.apply(this,arguments); }, setTeXclass: MML.mbase.setSeparateTeXclasses }); MML.mtr = MML.mbase.Subclass({ type: "mtr", defaults: { mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, rowalign: MML.INHERIT, columnalign: MML.INHERIT, groupalign: MML.INHERIT }, inheritFromMe: true, noInherit: { mrow: {rowalign: true, columnalign: true, groupalign: true}, mtable: {rowalign: true, columnalign: true, groupalign: true} }, linebreakContainer: true, Append: function () { for (var i = 0, m = arguments.length; i < m; i++) { if (!(arguments[i] instanceof MML.mtd)) {arguments[i] = MML.mtd(arguments[i])} } this.SUPER(arguments).Append.apply(this,arguments); }, setTeXclass: MML.mbase.setSeparateTeXclasses }); MML.mtd = MML.mbase.Subclass({ type: "mtd", inferRow: true, linebreakContainer: true, isEmbellished: MML.mbase.childEmbellished, Core: MML.mbase.childCore, CoreMO: MML.mbase.childCoreMO, defaults: { mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, rowspan: 1, columnspan: 1, rowalign: MML.INHERIT, columnalign: MML.INHERIT, groupalign: MML.INHERIT }, setTeXclass: MML.mbase.setSeparateTeXclasses }); MML.maligngroup = MML.mbase.Subclass({ type: "maligngroup", isSpacelike: function () {return true}, defaults: { mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, groupalign: MML.INHERIT }, inheritFromMe: true, noInherit: { mrow: {groupalign: true}, mtable: {groupalign: true} } }); MML.malignmark = MML.mbase.Subclass({ type: "malignmark", defaults: { mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, edge: MML.SIDE.LEFT }, isSpacelike: function () {return true} }); MML.mlabeledtr = MML.mtr.Subclass({ type: "mlabeledtr" }); MML.maction = MML.mbase.Subclass({ type: "maction", defaults: { mathbackground: MML.INHERIT, mathcolor: MML.INHERIT, actiontype: MML.ACTIONTYPE.TOGGLE, selection: 1 }, selected: function () {return this.data[this.Get("selection")-1] || MML.NULL}, isEmbellished: function () {return this.selected().isEmbellished()}, isSpacelike: function () {return this.selected().isSpacelike()}, Core: function () {return this.selected().Core()}, CoreMO: function () {return this.selected().CoreMO()}, setTeXclass: function (prev) { if (this.Get("actiontype") === MML.ACTIONTYPE.TOOLTIP && this.data[1]) { // Make sure tooltip has proper spacing when typeset (see issue #412) this.data[1].setTeXclass(); } var selected = this.selected(); prev = selected.setTeXclass(prev); this.updateTeXclass(selected); return prev; } }); MML.semantics = MML.mbase.Subclass({ type: "semantics", notParent: true, isEmbellished: MML.mbase.childEmbellished, Core: MML.mbase.childCore, CoreMO: MML.mbase.childCoreMO, defaults: { definitionURL: null, encoding: null }, setTeXclass: MML.mbase.setChildTeXclass, getAnnotation: function (name) { var encodingList = MathJax.Hub.config.MathMenu.semanticsAnnotations[name]; if (encodingList) { for (var i = 0, m = this.data.length; i < m; i++) { var encoding = this.data[i].Get("encoding"); if (encoding) { for (var j = 0, n = encodingList.length; j < n; j++) { if (encodingList[j] === encoding) return this.data[i]; } } } } return null; } }); MML.annotation = MML.mbase.Subclass({ type: "annotation", isChars: true, linebreakContainer: true, defaults: { definitionURL: null, encoding: null, cd: "mathmlkeys", name: "", src: null } }); MML["annotation-xml"] = MML.mbase.Subclass({ type: "annotation-xml", linebreakContainer: true, defaults: { definitionURL: null, encoding: null, cd: "mathmlkeys", name: "", src: null } }); MML.math = MML.mstyle.Subclass({ type: "math", defaults: { mathvariant: MML.VARIANT.NORMAL, mathsize: MML.SIZE.NORMAL, mathcolor: "", // should be "black", but allow it to inherit from surrounding text mathbackground: MML.COLOR.TRANSPARENT, dir: "ltr", scriptlevel: 0, displaystyle: MML.AUTO, display: "inline", maxwidth: "", overflow: MML.OVERFLOW.LINEBREAK, altimg: "", 'altimg-width': "", 'altimg-height': "", 'altimg-valign': "", alttext: "", cdgroup: "", scriptsizemultiplier: Math.sqrt(1/2), scriptminsize: "8px", // should be 8pt, but that's too big infixlinebreakstyle: MML.LINEBREAKSTYLE.BEFORE, lineleading: "1ex", indentshift: "auto", // use user configuration indentalign: MML.INDENTALIGN.AUTO, indentalignfirst: MML.INDENTALIGN.INDENTALIGN, indentshiftfirst: MML.INDENTSHIFT.INDENTSHIFT, indentalignlast: MML.INDENTALIGN.INDENTALIGN, indentshiftlast: MML.INDENTSHIFT.INDENTSHIFT, decimalseparator: ".", texprimestyle: false // is it in TeX's C' style? }, autoDefault: function (name) { if (name === "displaystyle") {return this.Get("display") === "block"} return ""; }, linebreakContainer: true, setTeXclass: MML.mbase.setChildTeXclass, getAnnotation: function (name) { if (this.data.length != 1) return null; return this.data[0].getAnnotation(name); } }); MML.chars = MML.mbase.Subclass({ type: "chars", Append: function () {this.data.push.apply(this.data,arguments)}, value: function () {return this.data.join("")}, toString: function () {return this.data.join("")} }); MML.entity = MML.mbase.Subclass({ type: "entity", Append: function () {this.data.push.apply(this.data,arguments)}, value: function () { if (this.data[0].substr(0,2) === "#x") {return parseInt(this.data[0].substr(2),16)} else if (this.data[0].substr(0,1) === "#") {return parseInt(this.data[0].substr(1))} else {return 0} // FIXME: look up named entities from table }, toString: function () { var n = this.value(); if (n <= 0xFFFF) {return String.fromCharCode(n)} n -= 0x10000; return String.fromCharCode((n>>10)+0xD800) + String.fromCharCode((n&0x3FF)+0xDC00); } }); MML.xml = MML.mbase.Subclass({ type: "xml", Init: function () { this.div = document.createElement("div"); return this.SUPER(arguments).Init.apply(this,arguments); }, Append: function () { for (var i = 0, m = arguments.length; i < m; i++) { var node = this.Import(arguments[i]); this.data.push(node); this.div.appendChild(node); } }, Import: function (node) { if (document.importNode) {return document.importNode(node,true)} // // IE < 9 doesn't have importNode, so fake it. // var nNode, i, m; if (node.nodeType === 1) { // ELEMENT_NODE nNode = document.createElement(node.nodeName); for (i = 0, m = node.attributes.length; i < m; i++) { var attribute = node.attributes[i]; if (attribute.specified && attribute.nodeValue != null && attribute.nodeValue != '') {nNode.setAttribute(attribute.nodeName,attribute.nodeValue)} if (attribute.nodeName === "style") {nNode.style.cssText = attribute.nodeValue} } if (node.className) {nNode.className = node.className} } else if (node.nodeType === 3 || node.nodeType === 4) { // TEXT_NODE or CDATA_SECTION_NODE nNode = document.createTextNode(node.nodeValue); } else if (node.nodeType === 8) { // COMMENT_NODE nNode = document.createComment(node.nodeValue); } else { return document.createTextNode(''); } for (i = 0, m = node.childNodes.length; i < m; i++) {nNode.appendChild(this.Import(node.childNodes[i]))} return nNode; }, value: function () {return this.div}, toString: function () {return this.div.innerHTML} }); MML.TeXAtom = MML.mbase.Subclass({ type: "texatom", linebreakContainer: true, inferRow: true, notParent: true, texClass: MML.TEXCLASS.ORD, Core: MML.mbase.childCore, CoreMO: MML.mbase.childCoreMO, isEmbellished: MML.mbase.childEmbellished, setTeXclass: function (prev) { this.data[0].setTeXclass(); return this.adjustTeXclass(prev); }, adjustTeXclass: MML.mo.prototype.adjustTeXclass }); MML.NULL = MML.mbase().With({type:"null"}); var TEXCLASS = MML.TEXCLASS; var MO = { ORD: [0,0,TEXCLASS.ORD], ORD11: [1,1,TEXCLASS.ORD], ORD21: [2,1,TEXCLASS.ORD], ORD02: [0,2,TEXCLASS.ORD], ORD55: [5,5,TEXCLASS.ORD], OP: [1,2,TEXCLASS.OP,{largeop: true, movablelimits: true, symmetric: true}], OPFIXED: [1,2,TEXCLASS.OP,{largeop: true, movablelimits: true}], INTEGRAL: [0,1,TEXCLASS.OP,{largeop: true, symmetric: true}], INTEGRAL2: [1,2,TEXCLASS.OP,{largeop: true, symmetric: true}], BIN3: [3,3,TEXCLASS.BIN], BIN4: [4,4,TEXCLASS.BIN], BIN01: [0,1,TEXCLASS.BIN], BIN5: [5,5,TEXCLASS.BIN], TALLBIN: [4,4,TEXCLASS.BIN,{stretchy: true}], BINOP: [4,4,TEXCLASS.BIN,{largeop: true, movablelimits: true}], REL: [5,5,TEXCLASS.REL], REL1: [1,1,TEXCLASS.REL,{stretchy: true}], REL4: [4,4,TEXCLASS.REL], RELSTRETCH: [5,5,TEXCLASS.REL,{stretchy: true}], RELACCENT: [5,5,TEXCLASS.REL,{accent: true}], WIDEREL: [5,5,TEXCLASS.REL,{accent: true, stretchy: true}], OPEN: [0,0,TEXCLASS.OPEN,{fence: true, stretchy: true, symmetric: true}], CLOSE: [0,0,TEXCLASS.CLOSE,{fence: true, stretchy: true, symmetric: true}], INNER: [0,0,TEXCLASS.INNER], PUNCT: [0,3,TEXCLASS.PUNCT], ACCENT: [0,0,TEXCLASS.ORD,{accent: true}], WIDEACCENT: [0,0,TEXCLASS.ORD,{accent: true, stretchy: true}] }; MML.mo.Augment({ SPACE: [ '0em', '0.1111em', '0.1667em', '0.2222em', '0.2667em', '0.3333em' ], RANGES: [ [0x20,0x7F,TEXCLASS.REL,"BasicLatin"], [0xA0,0xFF,TEXCLASS.ORD,"Latin1Supplement"], [0x100,0x17F,TEXCLASS.ORD], [0x180,0x24F,TEXCLASS.ORD], [0x2B0,0x2FF,TEXCLASS.ORD,"SpacingModLetters"], [0x300,0x36F,TEXCLASS.ORD,"CombDiacritMarks"], [0x370,0x3FF,TEXCLASS.ORD,"GreekAndCoptic"], [0x1E00,0x1EFF,TEXCLASS.ORD], [0x2000,0x206F,TEXCLASS.PUNCT,"GeneralPunctuation"], [0x2070,0x209F,TEXCLASS.ORD], [0x20A0,0x20CF,TEXCLASS.ORD], [0x20D0,0x20FF,TEXCLASS.ORD,"CombDiactForSymbols"], [0x2100,0x214F,TEXCLASS.ORD,"LetterlikeSymbols"], [0x2150,0x218F,TEXCLASS.ORD], [0x2190,0x21FF,TEXCLASS.REL,"Arrows"], [0x2200,0x22FF,TEXCLASS.BIN,"MathOperators"], [0x2300,0x23FF,TEXCLASS.ORD,"MiscTechnical"], [0x2460,0x24FF,TEXCLASS.ORD], [0x2500,0x259F,TEXCLASS.ORD], [0x25A0,0x25FF,TEXCLASS.ORD,"GeometricShapes"], [0x2700,0x27BF,TEXCLASS.ORD,"Dingbats"], [0x27C0,0x27EF,TEXCLASS.ORD,"MiscMathSymbolsA"], [0x27F0,0x27FF,TEXCLASS.REL,"SupplementalArrowsA"], [0x2900,0x297F,TEXCLASS.REL,"SupplementalArrowsB"], [0x2980,0x29FF,TEXCLASS.ORD,"MiscMathSymbolsB"], [0x2A00,0x2AFF,TEXCLASS.BIN,"SuppMathOperators"], [0x2B00,0x2BFF,TEXCLASS.ORD,"MiscSymbolsAndArrows"], [0x1D400,0x1D7FF,TEXCLASS.ORD] ], OPTABLE: { prefix: { '\u2200': MO.ORD21, // for all '\u2202': MO.ORD21, // partial differential '\u2203': MO.ORD21, // there exists '\u2207': MO.ORD21, // nabla '\u220F': MO.OP, // n-ary product '\u2210': MO.OP, // n-ary coproduct '\u2211': MO.OP, // n-ary summation '\u2212': MO.BIN01, // minus sign '\u2213': MO.BIN01, // minus-or-plus sign '\u221A': [1,1,TEXCLASS.ORD,{stretchy: true}], // square root '\u2220': MO.ORD, // angle '\u222B': MO.INTEGRAL, // integral '\u222E': MO.INTEGRAL, // contour integral '\u22C0': MO.OP, // n-ary logical and '\u22C1': MO.OP, // n-ary logical or '\u22C2': MO.OP, // n-ary intersection '\u22C3': MO.OP, // n-ary union '\u2308': MO.OPEN, // left ceiling '\u230A': MO.OPEN, // left floor '\u27E8': MO.OPEN, // mathematical left angle bracket '\u27EE': MO.OPEN, // mathematical left flattened parenthesis '\u2A00': MO.OP, // n-ary circled dot operator '\u2A01': MO.OP, // n-ary circled plus operator '\u2A02': MO.OP, // n-ary circled times operator '\u2A04': MO.OP, // n-ary union operator with plus '\u2A06': MO.OP, // n-ary square union operator '\u00AC': MO.ORD21, // not sign '\u00B1': MO.BIN01, // plus-minus sign '(': MO.OPEN, // left parenthesis '+': MO.BIN01, // plus sign '-': MO.BIN01, // hyphen-minus '[': MO.OPEN, // left square bracket '{': MO.OPEN, // left curly bracket '|': MO.OPEN // vertical line }, postfix: { '!': [1,0,TEXCLASS.CLOSE], // exclamation mark '&': MO.ORD, // ampersand '\u2032': MO.ORD02, // prime '\u203E': MO.WIDEACCENT, // overline '\u2309': MO.CLOSE, // right ceiling '\u230B': MO.CLOSE, // right floor '\u23DE': MO.WIDEACCENT, // top curly bracket '\u23DF': MO.WIDEACCENT, // bottom curly bracket '\u266D': MO.ORD02, // music flat sign '\u266E': MO.ORD02, // music natural sign '\u266F': MO.ORD02, // music sharp sign '\u27E9': MO.CLOSE, // mathematical right angle bracket '\u27EF': MO.CLOSE, // mathematical right flattened parenthesis '\u02C6': MO.WIDEACCENT, // modifier letter circumflex accent '\u02C7': MO.WIDEACCENT, // caron '\u02C9': MO.WIDEACCENT, // modifier letter macron '\u02CA': MO.ACCENT, // modifier letter acute accent '\u02CB': MO.ACCENT, // modifier letter grave accent '\u02D8': MO.ACCENT, // breve '\u02D9': MO.ACCENT, // dot above '\u02DC': MO.WIDEACCENT, // small tilde '\u0302': MO.WIDEACCENT, // combining circumflex accent '\u00A8': MO.ACCENT, // diaeresis '\u00AF': MO.WIDEACCENT, // macron ')': MO.CLOSE, // right parenthesis ']': MO.CLOSE, // right square bracket '^': MO.WIDEACCENT, // circumflex accent '_': MO.WIDEACCENT, // low line '`': MO.ACCENT, // grave accent '|': MO.CLOSE, // vertical line '}': MO.CLOSE, // right curly bracket '~': MO.WIDEACCENT // tilde }, infix: { '': MO.ORD, // empty <mo> '%': [3,3,TEXCLASS.ORD], // percent sign '\u2022': MO.BIN4, // bullet '\u2026': MO.INNER, // horizontal ellipsis '\u2044': MO.TALLBIN, // fraction slash '\u2061': MO.ORD, // function application '\u2062': MO.ORD, // invisible times '\u2063': [0,0,TEXCLASS.ORD,{linebreakstyle:"after", separator: true}], // invisible separator '\u2064': MO.ORD, // invisible plus '\u2190': MO.WIDEREL, // leftwards arrow '\u2191': MO.RELSTRETCH, // upwards arrow '\u2192': MO.WIDEREL, // rightwards arrow '\u2193': MO.RELSTRETCH, // downwards arrow '\u2194': MO.WIDEREL, // left right arrow '\u2195': MO.RELSTRETCH, // up down arrow '\u2196': MO.RELSTRETCH, // north west arrow '\u2197': MO.RELSTRETCH, // north east arrow '\u2198': MO.RELSTRETCH, // south east arrow '\u2199': MO.RELSTRETCH, // south west arrow '\u21A6': MO.WIDEREL, // rightwards arrow from bar '\u21A9': MO.WIDEREL, // leftwards arrow with hook '\u21AA': MO.WIDEREL, // rightwards arrow with hook '\u21BC': MO.WIDEREL, // leftwards harpoon with barb upwards '\u21BD': MO.WIDEREL, // leftwards harpoon with barb downwards '\u21C0': MO.WIDEREL, // rightwards harpoon with barb upwards '\u21C1': MO.WIDEREL, // rightwards harpoon with barb downwards '\u21CC': MO.WIDEREL, // rightwards harpoon over leftwards harpoon '\u21D0': MO.WIDEREL, // leftwards double arrow '\u21D1': MO.RELSTRETCH, // upwards double arrow '\u21D2': MO.WIDEREL, // rightwards double arrow '\u21D3': MO.RELSTRETCH, // downwards double arrow '\u21D4': MO.WIDEREL, // left right double arrow '\u21D5': MO.RELSTRETCH, // up down double arrow '\u2208': MO.REL, // element of '\u2209': MO.REL, // not an element of '\u220B': MO.REL, // contains as member '\u2212': MO.BIN4, // minus sign '\u2213': MO.BIN4, // minus-or-plus sign '\u2215': MO.TALLBIN, // division slash '\u2216': MO.BIN4, // set minus '\u2217': MO.BIN4, // asterisk operator '\u2218': MO.BIN4, // ring operator '\u2219': MO.BIN4, // bullet operator '\u221D': MO.REL, // proportional to '\u2223': MO.REL, // divides '\u2225': MO.REL, // parallel to '\u2227': MO.BIN4, // logical and '\u2228': MO.BIN4, // logical or '\u2229': MO.BIN4, // intersection '\u222A': MO.BIN4, // union '\u223C': MO.REL, // tilde operator '\u2240': MO.BIN4, // wreath product '\u2243': MO.REL, // asymptotically equal to '\u2245': MO.REL, // approximately equal to '\u2248': MO.REL, // almost equal to '\u224D': MO.REL, // equivalent to '\u2250': MO.REL, // approaches the limit '\u2260': MO.REL, // not equal to '\u2261': MO.REL, // identical to '\u2264': MO.REL, // less-than or equal to '\u2265': MO.REL, // greater-than or equal to '\u226A': MO.REL, // much less-than '\u226B': MO.REL, // much greater-than '\u227A': MO.REL, // precedes '\u227B': MO.REL, // succeeds '\u2282': MO.REL, // subset of '\u2283': MO.REL, // superset of '\u2286': MO.REL, // subset of or equal to '\u2287': MO.REL, // superset of or equal to '\u228E': MO.BIN4, // multiset union '\u2291': MO.REL, // square image of or equal to '\u2292': MO.REL, // square original of or equal to '\u2293': MO.BIN4, // square cap '\u2294': MO.BIN4, // square cup '\u2295': MO.BIN4, // circled plus '\u2296': MO.BIN4, // circled minus '\u2297': MO.BIN4, // circled times '\u2298': MO.BIN4, // circled division slash '\u2299': MO.BIN4, // circled dot operator '\u22A2': MO.REL, // right tack '\u22A3': MO.REL, // left tack '\u22A4': MO.ORD55, // down tack '\u22A5': MO.REL, // up tack '\u22A8': MO.REL, // true '\u22C4': MO.BIN4, // diamond operator '\u22C5': MO.BIN4, // dot operator '\u22C6': MO.BIN4, // star operator '\u22C8': MO.REL, // bowtie '\u22EE': MO.ORD55, // vertical ellipsis '\u22EF': MO.INNER, // midline horizontal ellipsis '\u22F1': [5,5,TEXCLASS.INNER], // down right diagonal ellipsis '\u25B3': MO.BIN4, // white up-pointing triangle '\u25B5': MO.BIN4, // white up-pointing small triangle '\u25B9': MO.BIN4, // white right-pointing small triangle '\u25BD': MO.BIN4, // white down-pointing triangle '\u25BF': MO.BIN4, // white down-pointing small triangle '\u25C3': MO.BIN4, // white left-pointing small triangle '\u2758': MO.REL, // light vertical bar '\u27F5': MO.WIDEREL, // long leftwards arrow '\u27F6': MO.WIDEREL, // long rightwards arrow '\u27F7': MO.WIDEREL, // long left right arrow '\u27F8': MO.WIDEREL, // long leftwards double arrow '\u27F9': MO.WIDEREL, // long rightwards double arrow '\u27FA': MO.WIDEREL, // long left right double arrow '\u27FC': MO.WIDEREL, // long rightwards arrow from bar '\u2A2F': MO.BIN4, // vector or cross product '\u2A3F': MO.BIN4, // amalgamation or coproduct '\u2AAF': MO.REL, // precedes above single-line equals sign '\u2AB0': MO.REL, // succeeds above single-line equals sign '\u00B1': MO.BIN4, // plus-minus sign '\u00B7': MO.BIN4, // middle dot '\u00D7': MO.BIN4, // multiplication sign '\u00F7': MO.BIN4, // division sign '*': MO.BIN3, // asterisk '+': MO.BIN4, // plus sign ',': [0,3,TEXCLASS.PUNCT,{linebreakstyle:"after", separator: true}], // comma '-': MO.BIN4, // hyphen-minus '.': [3,3,TEXCLASS.ORD], // full stop '/': MO.ORD11, // solidus ':': [1,2,TEXCLASS.REL], // colon ';': [0,3,TEXCLASS.PUNCT,{linebreakstyle:"after", separator: true}], // semicolon '<': MO.REL, // less-than sign '=': MO.REL, // equals sign '>': MO.REL, // greater-than sign '?': [1,1,TEXCLASS.CLOSE], // question mark '\\': MO.ORD, // reverse solidus '^': MO.ORD11, // circumflex accent '_': MO.ORD11, // low line '|': [2,2,TEXCLASS.ORD,{fence: true, stretchy: true, symmetric: true}], // vertical line '#': MO.ORD, // # '$': MO.ORD, // $ //'\u002E': [0,3,TEXCLASS.PUNCT,{separator: true}], // \ldotp '\u02B9': MO.ORD, // prime '\u0300': MO.ACCENT, // \grave '\u0301': MO.ACCENT, // \acute '\u0303': MO.WIDEACCENT, // \tilde '\u0304': MO.ACCENT, // \bar '\u0306': MO.ACCENT, // \breve '\u0307': MO.ACCENT, // \dot '\u0308': MO.ACCENT, // \ddot '\u030C': MO.ACCENT, // \check '\u0332': MO.WIDEACCENT, // horizontal line '\u0338': MO.REL4, // \not '\u2015': [0,0,TEXCLASS.ORD,{stretchy: true}], // horizontal line '\u2017': [0,0,TEXCLASS.ORD,{stretchy: true}], // horizontal line '\u2020': MO.BIN3, // \dagger '\u2021': MO.BIN3, // \ddagger '\u20D7': MO.ACCENT, // \vec '\u2111': MO.ORD, // \Im '\u2113': MO.ORD, // \ell '\u2118': MO.ORD, // \wp '\u211C': MO.ORD, // \Re '\u2205': MO.ORD, // \emptyset '\u221E': MO.ORD, // \infty '\u2305': MO.BIN3, // barwedge '\u2306': MO.BIN3, // doublebarwedge '\u2322': MO.REL4, // \frown '\u2323': MO.REL4, // \smile '\u2329': MO.OPEN, // langle '\u232A': MO.CLOSE, // rangle '\u23AA': MO.ORD, // \bracevert '\u23AF': [0,0,TEXCLASS.ORD,{stretchy: true}], // \underline '\u23B0': MO.OPEN, // \lmoustache '\u23B1': MO.CLOSE, // \rmoustache '\u2500': MO.ORD, // horizontal line '\u25EF': MO.BIN3, // \bigcirc '\u2660': MO.ORD, // \spadesuit '\u2661': MO.ORD, // \heartsuit '\u2662': MO.ORD, // \diamondsuit '\u2663': MO.ORD, // \clubsuit '\u3008': MO.OPEN, // langle '\u3009': MO.CLOSE, // rangle '\uFE37': MO.WIDEACCENT, // horizontal brace down '\uFE38': MO.WIDEACCENT // horizontal brace up } } },{ OPTYPES: MO }); // // These are not in the W3C table, but FF works this way, // and it makes sense, so add it here // var OPTABLE = MML.mo.prototype.OPTABLE; OPTABLE.infix["^"] = MO.WIDEREL; OPTABLE.infix["_"] = MO.WIDEREL; OPTABLE.prefix["\u2223"] = MO.OPEN; OPTABLE.prefix["\u2225"] = MO.OPEN; OPTABLE.postfix["\u2223"] = MO.CLOSE; OPTABLE.postfix["\u2225"] = MO.CLOSE; })(MathJax.ElementJax.mml); MathJax.ElementJax.mml.loadComplete("jax.js"); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/Arrows.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { infix: { '\u219A': MO.RELACCENT, // leftwards arrow with stroke '\u219B': MO.RELACCENT, // rightwards arrow with stroke '\u219C': MO.WIDEREL, // leftwards wave arrow '\u219D': MO.WIDEREL, // rightwards wave arrow '\u219E': MO.WIDEREL, // leftwards two headed arrow '\u219F': MO.WIDEREL, // upwards two headed arrow '\u21A0': MO.WIDEREL, // rightwards two headed arrow '\u21A1': MO.RELSTRETCH, // downwards two headed arrow '\u21A2': MO.WIDEREL, // leftwards arrow with tail '\u21A3': MO.WIDEREL, // rightwards arrow with tail '\u21A4': MO.WIDEREL, // leftwards arrow from bar '\u21A5': MO.RELSTRETCH, // upwards arrow from bar '\u21A7': MO.RELSTRETCH, // downwards arrow from bar '\u21A8': MO.RELSTRETCH, // up down arrow with base '\u21AB': MO.WIDEREL, // leftwards arrow with loop '\u21AC': MO.WIDEREL, // rightwards arrow with loop '\u21AD': MO.WIDEREL, // left right wave arrow '\u21AE': MO.RELACCENT, // left right arrow with stroke '\u21AF': MO.RELSTRETCH, // downwards zigzag arrow '\u21B0': MO.RELSTRETCH, // upwards arrow with tip leftwards '\u21B1': MO.RELSTRETCH, // upwards arrow with tip rightwards '\u21B2': MO.RELSTRETCH, // downwards arrow with tip leftwards '\u21B3': MO.RELSTRETCH, // downwards arrow with tip rightwards '\u21B4': MO.RELSTRETCH, // rightwards arrow with corner downwards '\u21B5': MO.RELSTRETCH, // downwards arrow with corner leftwards '\u21B6': MO.RELACCENT, // anticlockwise top semicircle arrow '\u21B7': MO.RELACCENT, // clockwise top semicircle arrow '\u21B8': MO.REL, // north west arrow to long bar '\u21B9': MO.WIDEREL, // leftwards arrow to bar over rightwards arrow to bar '\u21BA': MO.REL, // anticlockwise open circle arrow '\u21BB': MO.REL, // clockwise open circle arrow '\u21BE': MO.RELSTRETCH, // upwards harpoon with barb rightwards '\u21BF': MO.RELSTRETCH, // upwards harpoon with barb leftwards '\u21C2': MO.RELSTRETCH, // downwards harpoon with barb rightwards '\u21C3': MO.RELSTRETCH, // downwards harpoon with barb leftwards '\u21C4': MO.WIDEREL, // rightwards arrow over leftwards arrow '\u21C5': MO.RELSTRETCH, // upwards arrow leftwards of downwards arrow '\u21C6': MO.WIDEREL, // leftwards arrow over rightwards arrow '\u21C7': MO.WIDEREL, // leftwards paired arrows '\u21C8': MO.RELSTRETCH, // upwards paired arrows '\u21C9': MO.WIDEREL, // rightwards paired arrows '\u21CA': MO.RELSTRETCH, // downwards paired arrows '\u21CB': MO.WIDEREL, // leftwards harpoon over rightwards harpoon '\u21CD': MO.RELACCENT, // leftwards double arrow with stroke '\u21CE': MO.RELACCENT, // left right double arrow with stroke '\u21CF': MO.RELACCENT, // rightwards double arrow with stroke '\u21D6': MO.RELSTRETCH, // north west double arrow '\u21D7': MO.RELSTRETCH, // north east double arrow '\u21D8': MO.RELSTRETCH, // south east double arrow '\u21D9': MO.RELSTRETCH, // south west double arrow '\u21DA': MO.WIDEREL, // leftwards triple arrow '\u21DB': MO.WIDEREL, // rightwards triple arrow '\u21DC': MO.WIDEREL, // leftwards squiggle arrow '\u21DD': MO.WIDEREL, // rightwards squiggle arrow '\u21DE': MO.REL, // upwards arrow with double stroke '\u21DF': MO.REL, // downwards arrow with double stroke '\u21E0': MO.WIDEREL, // leftwards dashed arrow '\u21E1': MO.RELSTRETCH, // upwards dashed arrow '\u21E2': MO.WIDEREL, // rightwards dashed arrow '\u21E3': MO.RELSTRETCH, // downwards dashed arrow '\u21E4': MO.WIDEREL, // leftwards arrow to bar '\u21E5': MO.WIDEREL, // rightwards arrow to bar '\u21E6': MO.WIDEREL, // leftwards white arrow '\u21E7': MO.RELSTRETCH, // upwards white arrow '\u21E8': MO.WIDEREL, // rightwards white arrow '\u21E9': MO.RELSTRETCH, // downwards white arrow '\u21EA': MO.RELSTRETCH, // upwards white arrow from bar '\u21EB': MO.RELSTRETCH, // upwards white arrow on pedestal '\u21EC': MO.RELSTRETCH, // upwards white arrow on pedestal with horizontal bar '\u21ED': MO.RELSTRETCH, // upwards white arrow on pedestal with vertical bar '\u21EE': MO.RELSTRETCH, // upwards white double arrow '\u21EF': MO.RELSTRETCH, // upwards white double arrow on pedestal '\u21F0': MO.WIDEREL, // rightwards white arrow from wall '\u21F1': MO.REL, // north west arrow to corner '\u21F2': MO.REL, // south east arrow to corner '\u21F3': MO.RELSTRETCH, // up down white arrow '\u21F4': MO.RELACCENT, // right arrow with small circle '\u21F5': MO.RELSTRETCH, // downwards arrow leftwards of upwards arrow '\u21F6': MO.WIDEREL, // three rightwards arrows '\u21F7': MO.RELACCENT, // leftwards arrow with vertical stroke '\u21F8': MO.RELACCENT, // rightwards arrow with vertical stroke '\u21F9': MO.RELACCENT, // left right arrow with vertical stroke '\u21FA': MO.RELACCENT, // leftwards arrow with double vertical stroke '\u21FB': MO.RELACCENT, // rightwards arrow with double vertical stroke '\u21FC': MO.RELACCENT, // left right arrow with double vertical stroke '\u21FD': MO.WIDEREL, // leftwards open-headed arrow '\u21FE': MO.WIDEREL, // rightwards open-headed arrow '\u21FF': MO.WIDEREL // left right open-headed arrow } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/Arrows.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/MiscMathSymbolsA.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { prefix: { '\u27E6': MO.OPEN, // mathematical left white square bracket '\u27EA': MO.OPEN, // mathematical left double angle bracket '\u27EC': MO.OPEN // mathematical left white tortoise shell bracket }, postfix: { '\u27E7': MO.CLOSE, // mathematical right white square bracket '\u27EB': MO.CLOSE, // mathematical right double angle bracket '\u27ED': MO.CLOSE // mathematical right white tortoise shell bracket } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/MiscMathSymbolsA.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/Dingbats.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { prefix: { '\u2772': MO.OPEN // light left tortoise shell bracket ornament }, postfix: { '\u2773': MO.CLOSE // light right tortoise shell bracket ornament } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/Dingbats.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/GeneralPunctuation.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { prefix: { '\u2016': [0,0,TEXCLASS.ORD,{fence: true, stretchy: true}], // double vertical line '\u2018': [0,0,TEXCLASS.OPEN,{fence: true}], // left single quotation mark '\u201C': [0,0,TEXCLASS.OPEN,{fence: true}] // left double quotation mark }, postfix: { '\u2016': [0,0,TEXCLASS.ORD,{fence: true, stretchy: true}], // double vertical line '\u2019': [0,0,TEXCLASS.CLOSE,{fence: true}], // right single quotation mark '\u201D': [0,0,TEXCLASS.CLOSE,{fence: true}] // right double quotation mark } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/GeneralPunctuation.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/SpacingModLetters.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { postfix: { '\u02CD': MO.WIDEACCENT, // modifier letter low macron '\u02DA': MO.ACCENT, // ring above '\u02DD': MO.ACCENT, // double acute accent '\u02F7': MO.WIDEACCENT // modifier letter low tilde } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/SpacingModLetters.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/MiscTechnical.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { postfix: { '\u23B4': MO.WIDEACCENT, // top square bracket '\u23B5': MO.WIDEACCENT, // bottom square bracket '\u23DC': MO.WIDEACCENT, // top parenthesis '\u23DD': MO.WIDEACCENT, // bottom parenthesis '\u23E0': MO.WIDEACCENT, // top tortoise shell bracket '\u23E1': MO.WIDEACCENT // bottom tortoise shell bracket } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/MiscTechnical.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/SupplementalArrowsA.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { infix: { '\u27F0': MO.RELSTRETCH, // upwards quadruple arrow '\u27F1': MO.RELSTRETCH, // downwards quadruple arrow '\u27FB': MO.WIDEREL, // long leftwards arrow from bar '\u27FD': MO.WIDEREL, // long leftwards double arrow from bar '\u27FE': MO.WIDEREL, // long rightwards double arrow from bar '\u27FF': MO.WIDEREL // long rightwards squiggle arrow } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/SupplementalArrowsA.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/GreekAndCoptic.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { infix: { '\u03F6': MO.REL // greek reversed lunate epsilon symbol } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/GreekAndCoptic.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/LetterlikeSymbols.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { prefix: { '\u2145': MO.ORD21, // double-struck italic capital d '\u2146': [2,0,TEXCLASS.ORD] // double-struck italic small d } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/LetterlikeSymbols.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/SupplementalArrowsB.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { infix: { '\u2900': MO.RELACCENT, // rightwards two-headed arrow with vertical stroke '\u2901': MO.RELACCENT, // rightwards two-headed arrow with double vertical stroke '\u2902': MO.RELACCENT, // leftwards double arrow with vertical stroke '\u2903': MO.RELACCENT, // rightwards double arrow with vertical stroke '\u2904': MO.RELACCENT, // left right double arrow with vertical stroke '\u2905': MO.RELACCENT, // rightwards two-headed arrow from bar '\u2906': MO.RELACCENT, // leftwards double arrow from bar '\u2907': MO.RELACCENT, // rightwards double arrow from bar '\u2908': MO.REL, // downwards arrow with horizontal stroke '\u2909': MO.REL, // upwards arrow with horizontal stroke '\u290A': MO.RELSTRETCH, // upwards triple arrow '\u290B': MO.RELSTRETCH, // downwards triple arrow '\u290C': MO.WIDEREL, // leftwards double dash arrow '\u290D': MO.WIDEREL, // rightwards double dash arrow '\u290E': MO.WIDEREL, // leftwards triple dash arrow '\u290F': MO.WIDEREL, // rightwards triple dash arrow '\u2910': MO.WIDEREL, // rightwards two-headed triple dash arrow '\u2911': MO.RELACCENT, // rightwards arrow with dotted stem '\u2912': MO.RELSTRETCH, // upwards arrow to bar '\u2913': MO.RELSTRETCH, // downwards arrow to bar '\u2914': MO.RELACCENT, // rightwards arrow with tail with vertical stroke '\u2915': MO.RELACCENT, // rightwards arrow with tail with double vertical stroke '\u2916': MO.RELACCENT, // rightwards two-headed arrow with tail '\u2917': MO.RELACCENT, // rightwards two-headed arrow with tail with vertical stroke '\u2918': MO.RELACCENT, // rightwards two-headed arrow with tail with double vertical stroke '\u2919': MO.RELACCENT, // leftwards arrow-tail '\u291A': MO.RELACCENT, // rightwards arrow-tail '\u291B': MO.RELACCENT, // leftwards double arrow-tail '\u291C': MO.RELACCENT, // rightwards double arrow-tail '\u291D': MO.RELACCENT, // leftwards arrow to black diamond '\u291E': MO.RELACCENT, // rightwards arrow to black diamond '\u291F': MO.RELACCENT, // leftwards arrow from bar to black diamond '\u2920': MO.RELACCENT, // rightwards arrow from bar to black diamond '\u2921': MO.RELSTRETCH, // north west and south east arrow '\u2922': MO.RELSTRETCH, // north east and south west arrow '\u2923': MO.REL, // north west arrow with hook '\u2924': MO.REL, // north east arrow with hook '\u2925': MO.REL, // south east arrow with hook '\u2926': MO.REL, // south west arrow with hook '\u2927': MO.REL, // north west arrow and north east arrow '\u2928': MO.REL, // north east arrow and south east arrow '\u2929': MO.REL, // south east arrow and south west arrow '\u292A': MO.REL, // south west arrow and north west arrow '\u292B': MO.REL, // rising diagonal crossing falling diagonal '\u292C': MO.REL, // falling diagonal crossing rising diagonal '\u292D': MO.REL, // south east arrow crossing north east arrow '\u292E': MO.REL, // north east arrow crossing south east arrow '\u292F': MO.REL, // falling diagonal crossing north east arrow '\u2930': MO.REL, // rising diagonal crossing south east arrow '\u2931': MO.REL, // north east arrow crossing north west arrow '\u2932': MO.REL, // north west arrow crossing north east arrow '\u2933': MO.RELACCENT, // wave arrow pointing directly right '\u2934': MO.REL, // arrow pointing rightwards then curving upwards '\u2935': MO.REL, // arrow pointing rightwards then curving downwards '\u2936': MO.REL, // arrow pointing downwards then curving leftwards '\u2937': MO.REL, // arrow pointing downwards then curving rightwards '\u2938': MO.REL, // right-side arc clockwise arrow '\u2939': MO.REL, // left-side arc anticlockwise arrow '\u293A': MO.RELACCENT, // top arc anticlockwise arrow '\u293B': MO.RELACCENT, // bottom arc anticlockwise arrow '\u293C': MO.RELACCENT, // top arc clockwise arrow with minus '\u293D': MO.RELACCENT, // top arc anticlockwise arrow with plus '\u293E': MO.REL, // lower right semicircular clockwise arrow '\u293F': MO.REL, // lower left semicircular anticlockwise arrow '\u2940': MO.REL, // anticlockwise closed circle arrow '\u2941': MO.REL, // clockwise closed circle arrow '\u2942': MO.RELACCENT, // rightwards arrow above short leftwards arrow '\u2943': MO.RELACCENT, // leftwards arrow above short rightwards arrow '\u2944': MO.RELACCENT, // short rightwards arrow above leftwards arrow '\u2945': MO.RELACCENT, // rightwards arrow with plus below '\u2946': MO.RELACCENT, // leftwards arrow with plus below '\u2947': MO.RELACCENT, // rightwards arrow through x '\u2948': MO.RELACCENT, // left right arrow through small circle '\u2949': MO.REL, // upwards two-headed arrow from small circle '\u294A': MO.RELACCENT, // left barb up right barb down harpoon '\u294B': MO.RELACCENT, // left barb down right barb up harpoon '\u294C': MO.REL, // up barb right down barb left harpoon '\u294D': MO.REL, // up barb left down barb right harpoon '\u294E': MO.WIDEREL, // left barb up right barb up harpoon '\u294F': MO.RELSTRETCH, // up barb right down barb right harpoon '\u2950': MO.WIDEREL, // left barb down right barb down harpoon '\u2951': MO.RELSTRETCH, // up barb left down barb left harpoon '\u2952': MO.WIDEREL, // leftwards harpoon with barb up to bar '\u2953': MO.WIDEREL, // rightwards harpoon with barb up to bar '\u2954': MO.RELSTRETCH, // upwards harpoon with barb right to bar '\u2955': MO.RELSTRETCH, // downwards harpoon with barb right to bar '\u2956': MO.RELSTRETCH, // leftwards harpoon with barb down to bar '\u2957': MO.RELSTRETCH, // rightwards harpoon with barb down to bar '\u2958': MO.RELSTRETCH, // upwards harpoon with barb left to bar '\u2959': MO.RELSTRETCH, // downwards harpoon with barb left to bar '\u295A': MO.WIDEREL, // leftwards harpoon with barb up from bar '\u295B': MO.WIDEREL, // rightwards harpoon with barb up from bar '\u295C': MO.RELSTRETCH, // upwards harpoon with barb right from bar '\u295D': MO.RELSTRETCH, // downwards harpoon with barb right from bar '\u295E': MO.WIDEREL, // leftwards harpoon with barb down from bar '\u295F': MO.WIDEREL, // rightwards harpoon with barb down from bar '\u2960': MO.RELSTRETCH, // upwards harpoon with barb left from bar '\u2961': MO.RELSTRETCH, // downwards harpoon with barb left from bar '\u2962': MO.RELACCENT, // leftwards harpoon with barb up above leftwards harpoon with barb down '\u2963': MO.REL, // upwards harpoon with barb left beside upwards harpoon with barb right '\u2964': MO.RELACCENT, // rightwards harpoon with barb up above rightwards harpoon with barb down '\u2965': MO.REL, // downwards harpoon with barb left beside downwards harpoon with barb right '\u2966': MO.RELACCENT, // leftwards harpoon with barb up above rightwards harpoon with barb up '\u2967': MO.RELACCENT, // leftwards harpoon with barb down above rightwards harpoon with barb down '\u2968': MO.RELACCENT, // rightwards harpoon with barb up above leftwards harpoon with barb up '\u2969': MO.RELACCENT, // rightwards harpoon with barb down above leftwards harpoon with barb down '\u296A': MO.RELACCENT, // leftwards harpoon with barb up above long dash '\u296B': MO.RELACCENT, // leftwards harpoon with barb down below long dash '\u296C': MO.RELACCENT, // rightwards harpoon with barb up above long dash '\u296D': MO.RELACCENT, // rightwards harpoon with barb down below long dash '\u296E': MO.RELSTRETCH, // upwards harpoon with barb left beside downwards harpoon with barb right '\u296F': MO.RELSTRETCH, // downwards harpoon with barb left beside upwards harpoon with barb right '\u2970': MO.RELACCENT, // right double arrow with rounded head '\u2971': MO.RELACCENT, // equals sign above rightwards arrow '\u2972': MO.RELACCENT, // tilde operator above rightwards arrow '\u2973': MO.RELACCENT, // leftwards arrow above tilde operator '\u2974': MO.RELACCENT, // rightwards arrow above tilde operator '\u2975': MO.RELACCENT, // rightwards arrow above almost equal to '\u2976': MO.RELACCENT, // less-than above leftwards arrow '\u2977': MO.RELACCENT, // leftwards arrow through less-than '\u2978': MO.RELACCENT, // greater-than above rightwards arrow '\u2979': MO.RELACCENT, // subset above rightwards arrow '\u297A': MO.RELACCENT, // leftwards arrow through subset '\u297B': MO.RELACCENT, // superset above leftwards arrow '\u297C': MO.RELACCENT, // left fish tail '\u297D': MO.RELACCENT, // right fish tail '\u297E': MO.REL, // up fish tail '\u297F': MO.REL // down fish tail } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/SupplementalArrowsB.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/BasicLatin.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { prefix: { '||': [0,0,TEXCLASS.BIN,{fence: true, stretchy: true, symmetric: true}], // multiple character operator: || '|||': [0,0,TEXCLASS.ORD,{fence: true, stretchy: true, symmetric: true}] // multiple character operator: ||| }, postfix: { '!!': [1,0,TEXCLASS.BIN], // multiple character operator: !! '\'': MO.ACCENT, // apostrophe '++': [0,0,TEXCLASS.BIN], // multiple character operator: ++ '--': [0,0,TEXCLASS.BIN], // multiple character operator: -- '..': [0,0,TEXCLASS.BIN], // multiple character operator: .. '...': MO.ORD, // multiple character operator: ... '||': [0,0,TEXCLASS.BIN,{fence: true, stretchy: true, symmetric: true}], // multiple character operator: || '|||': [0,0,TEXCLASS.ORD,{fence: true, stretchy: true, symmetric: true}] // multiple character operator: ||| }, infix: { '!=': MO.BIN4, // multiple character operator: != '&&': MO.BIN4, // multiple character operator: && '**': [1,1,TEXCLASS.BIN], // multiple character operator: ** '*=': MO.BIN4, // multiple character operator: *= '+=': MO.BIN4, // multiple character operator: += '-=': MO.BIN4, // multiple character operator: -= '->': MO.BIN5, // multiple character operator: -> '//': [1,1,TEXCLASS.BIN], // multiple character operator: // '/=': MO.BIN4, // multiple character operator: /= ':=': MO.BIN4, // multiple character operator: := '<=': MO.BIN5, // multiple character operator: <= '<>': [1,1,TEXCLASS.BIN], // multiple character operator: <> '==': MO.BIN4, // multiple character operator: == '>=': MO.BIN5, // multiple character operator: >= '@': MO.ORD11, // commercial at '||': [2,2,TEXCLASS.BIN,{fence: true, stretchy: true, symmetric: true}], // multiple character operator: || '|||': [2,2,TEXCLASS.ORD,{fence: true, stretchy: true, symmetric: true}] // multiple character operator: ||| } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/BasicLatin.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/MiscSymbolsAndArrows.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { infix: { '\u2B45': MO.RELSTRETCH, // leftwards quadruple arrow '\u2B46': MO.RELSTRETCH // rightwards quadruple arrow } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/MiscSymbolsAndArrows.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/CombDiacritMarks.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { postfix: { '\u0311': MO.ACCENT // combining inverted breve } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/CombDiacritMarks.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/GeometricShapes.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { infix: { '\u25A0': MO.BIN3, // black square '\u25A1': MO.BIN3, // white square '\u25AA': MO.BIN3, // black small square '\u25AB': MO.BIN3, // white small square '\u25AD': MO.BIN3, // white rectangle '\u25AE': MO.BIN3, // black vertical rectangle '\u25AF': MO.BIN3, // white vertical rectangle '\u25B0': MO.BIN3, // black parallelogram '\u25B1': MO.BIN3, // white parallelogram '\u25B2': MO.BIN4, // black up-pointing triangle '\u25B4': MO.BIN4, // black up-pointing small triangle '\u25B6': MO.BIN4, // black right-pointing triangle '\u25B7': MO.BIN4, // white right-pointing triangle '\u25B8': MO.BIN4, // black right-pointing small triangle '\u25BC': MO.BIN4, // black down-pointing triangle '\u25BE': MO.BIN4, // black down-pointing small triangle '\u25C0': MO.BIN4, // black left-pointing triangle '\u25C1': MO.BIN4, // white left-pointing triangle '\u25C2': MO.BIN4, // black left-pointing small triangle '\u25C4': MO.BIN4, // black left-pointing pointer '\u25C5': MO.BIN4, // white left-pointing pointer '\u25C6': MO.BIN4, // black diamond '\u25C7': MO.BIN4, // white diamond '\u25C8': MO.BIN4, // white diamond containing black small diamond '\u25C9': MO.BIN4, // fisheye '\u25CC': MO.BIN4, // dotted circle '\u25CD': MO.BIN4, // circle with vertical fill '\u25CE': MO.BIN4, // bullseye '\u25CF': MO.BIN4, // black circle '\u25D6': MO.BIN4, // left half black circle '\u25D7': MO.BIN4, // right half black circle '\u25E6': MO.BIN4 // white bullet } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/GeometricShapes.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/MathOperators.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { prefix: { '\u2204': MO.ORD21, // there does not exist '\u221B': MO.ORD11, // cube root '\u221C': MO.ORD11, // fourth root '\u2221': MO.ORD, // measured angle '\u2222': MO.ORD, // spherical angle '\u222C': MO.INTEGRAL, // double integral '\u222D': MO.INTEGRAL, // triple integral '\u222F': MO.INTEGRAL, // surface integral '\u2230': MO.INTEGRAL, // volume integral '\u2231': MO.INTEGRAL, // clockwise integral '\u2232': MO.INTEGRAL, // clockwise contour integral '\u2233': MO.INTEGRAL // anticlockwise contour integral }, infix: { '\u2201': [1,2,TEXCLASS.ORD], // complement '\u2206': MO.BIN3, // increment '\u220A': MO.REL, // small element of '\u220C': MO.REL, // does not contain as member '\u220D': MO.REL, // small contains as member '\u220E': MO.BIN3, // end of proof '\u2214': MO.BIN4, // dot plus '\u221F': MO.REL, // right angle '\u2224': MO.REL, // does not divide '\u2226': MO.REL, // not parallel to '\u2234': MO.REL, // therefore '\u2235': MO.REL, // because '\u2236': MO.REL, // ratio '\u2237': MO.REL, // proportion '\u2238': MO.BIN4, // dot minus '\u2239': MO.REL, // excess '\u223A': MO.BIN4, // geometric proportion '\u223B': MO.REL, // homothetic '\u223D': MO.REL, // reversed tilde '\u223D\u0331': MO.BIN3, // reversed tilde with underline '\u223E': MO.REL, // inverted lazy s '\u223F': MO.BIN3, // sine wave '\u2241': MO.REL, // not tilde '\u2242': MO.REL, // minus tilde '\u2242\u0338': MO.REL, // minus tilde with slash '\u2244': MO.REL, // not asymptotically equal to '\u2246': MO.REL, // approximately but not actually equal to '\u2247': MO.REL, // neither approximately nor actually equal to '\u2249': MO.REL, // not almost equal to '\u224A': MO.REL, // almost equal or equal to '\u224B': MO.REL, // triple tilde '\u224C': MO.REL, // all equal to '\u224E': MO.REL, // geometrically equivalent to '\u224E\u0338': MO.REL, // geometrically equivalent to with slash '\u224F': MO.REL, // difference between '\u224F\u0338': MO.REL, // difference between with slash '\u2251': MO.REL, // geometrically equal to '\u2252': MO.REL, // approximately equal to or the image of '\u2253': MO.REL, // image of or approximately equal to '\u2254': MO.REL, // colon equals '\u2255': MO.REL, // equals colon '\u2256': MO.REL, // ring in equal to '\u2257': MO.REL, // ring equal to '\u2258': MO.REL, // corresponds to '\u2259': MO.REL, // estimates '\u225A': MO.REL, // equiangular to '\u225C': MO.REL, // delta equal to '\u225D': MO.REL, // equal to by definition '\u225E': MO.REL, // measured by '\u225F': MO.REL, // questioned equal to '\u2262': MO.REL, // not identical to '\u2263': MO.REL, // strictly equivalent to '\u2266': MO.REL, // less-than over equal to '\u2266\u0338': MO.REL, // less-than over equal to with slash '\u2267': MO.REL, // greater-than over equal to '\u2268': MO.REL, // less-than but not equal to '\u2269': MO.REL, // greater-than but not equal to '\u226A\u0338': MO.REL, // much less than with slash '\u226B\u0338': MO.REL, // much greater than with slash '\u226C': MO.REL, // between '\u226D': MO.REL, // not equivalent to '\u226E': MO.REL, // not less-than '\u226F': MO.REL, // not greater-than '\u2270': MO.REL, // neither less-than nor equal to '\u2271': MO.REL, // neither greater-than nor equal to '\u2272': MO.REL, // less-than or equivalent to '\u2273': MO.REL, // greater-than or equivalent to '\u2274': MO.REL, // neither less-than nor equivalent to '\u2275': MO.REL, // neither greater-than nor equivalent to '\u2276': MO.REL, // less-than or greater-than '\u2277': MO.REL, // greater-than or less-than '\u2278': MO.REL, // neither less-than nor greater-than '\u2279': MO.REL, // neither greater-than nor less-than '\u227C': MO.REL, // precedes or equal to '\u227D': MO.REL, // succeeds or equal to '\u227E': MO.REL, // precedes or equivalent to '\u227F': MO.REL, // succeeds or equivalent to '\u227F\u0338': MO.REL, // succeeds or equivalent to with slash '\u2280': MO.REL, // does not precede '\u2281': MO.REL, // does not succeed '\u2282\u20D2': MO.REL, // subset of with vertical line '\u2283\u20D2': MO.REL, // superset of with vertical line '\u2284': MO.REL, // not a subset of '\u2285': MO.REL, // not a superset of '\u2288': MO.REL, // neither a subset of nor equal to '\u2289': MO.REL, // neither a superset of nor equal to '\u228A': MO.REL, // subset of with not equal to '\u228B': MO.REL, // superset of with not equal to '\u228C': MO.BIN4, // multiset '\u228D': MO.BIN4, // multiset multiplication '\u228F': MO.REL, // square image of '\u228F\u0338': MO.REL, // square image of with slash '\u2290': MO.REL, // square original of '\u2290\u0338': MO.REL, // square original of with slash '\u229A': MO.BIN4, // circled ring operator '\u229B': MO.BIN4, // circled asterisk operator '\u229C': MO.BIN4, // circled equals '\u229D': MO.BIN4, // circled dash '\u229E': MO.BIN4, // squared plus '\u229F': MO.BIN4, // squared minus '\u22A0': MO.BIN4, // squared times '\u22A1': MO.BIN4, // squared dot operator '\u22A6': MO.REL, // assertion '\u22A7': MO.REL, // models '\u22A9': MO.REL, // forces '\u22AA': MO.REL, // triple vertical bar right turnstile '\u22AB': MO.REL, // double vertical bar double right turnstile '\u22AC': MO.REL, // does not prove '\u22AD': MO.REL, // not true '\u22AE': MO.REL, // does not force '\u22AF': MO.REL, // negated double vertical bar double right turnstile '\u22B0': MO.REL, // precedes under relation '\u22B1': MO.REL, // succeeds under relation '\u22B2': MO.REL, // normal subgroup of '\u22B3': MO.REL, // contains as normal subgroup '\u22B4': MO.REL, // normal subgroup of or equal to '\u22B5': MO.REL, // contains as normal subgroup or equal to '\u22B6': MO.REL, // original of '\u22B7': MO.REL, // image of '\u22B8': MO.REL, // multimap '\u22B9': MO.REL, // hermitian conjugate matrix '\u22BA': MO.BIN4, // intercalate '\u22BB': MO.BIN4, // xor '\u22BC': MO.BIN4, // nand '\u22BD': MO.BIN4, // nor '\u22BE': MO.BIN3, // right angle with arc '\u22BF': MO.BIN3, // right triangle '\u22C7': MO.BIN4, // division times '\u22C9': MO.BIN4, // left normal factor semidirect product '\u22CA': MO.BIN4, // right normal factor semidirect product '\u22CB': MO.BIN4, // left semidirect product '\u22CC': MO.BIN4, // right semidirect product '\u22CD': MO.REL, // reversed tilde equals '\u22CE': MO.BIN4, // curly logical or '\u22CF': MO.BIN4, // curly logical and '\u22D0': MO.REL, // double subset '\u22D1': MO.REL, // double superset '\u22D2': MO.BIN4, // double intersection '\u22D3': MO.BIN4, // double union '\u22D4': MO.REL, // pitchfork '\u22D5': MO.REL, // equal and parallel to '\u22D6': MO.REL, // less-than with dot '\u22D7': MO.REL, // greater-than with dot '\u22D8': MO.REL, // very much less-than '\u22D9': MO.REL, // very much greater-than '\u22DA': MO.REL, // less-than equal to or greater-than '\u22DB': MO.REL, // greater-than equal to or less-than '\u22DC': MO.REL, // equal to or less-than '\u22DD': MO.REL, // equal to or greater-than '\u22DE': MO.REL, // equal to or precedes '\u22DF': MO.REL, // equal to or succeeds '\u22E0': MO.REL, // does not precede or equal '\u22E1': MO.REL, // does not succeed or equal '\u22E2': MO.REL, // not square image of or equal to '\u22E3': MO.REL, // not square original of or equal to '\u22E4': MO.REL, // square image of or not equal to '\u22E5': MO.REL, // square original of or not equal to '\u22E6': MO.REL, // less-than but not equivalent to '\u22E7': MO.REL, // greater-than but not equivalent to '\u22E8': MO.REL, // precedes but not equivalent to '\u22E9': MO.REL, // succeeds but not equivalent to '\u22EA': MO.REL, // not normal subgroup of '\u22EB': MO.REL, // does not contain as normal subgroup '\u22EC': MO.REL, // not normal subgroup of or equal to '\u22ED': MO.REL, // does not contain as normal subgroup or equal '\u22F0': MO.REL, // up right diagonal ellipsis '\u22F2': MO.REL, // element of with long horizontal stroke '\u22F3': MO.REL, // element of with vertical bar at end of horizontal stroke '\u22F4': MO.REL, // small element of with vertical bar at end of horizontal stroke '\u22F5': MO.REL, // element of with dot above '\u22F6': MO.REL, // element of with overbar '\u22F7': MO.REL, // small element of with overbar '\u22F8': MO.REL, // element of with underbar '\u22F9': MO.REL, // element of with two horizontal strokes '\u22FA': MO.REL, // contains with long horizontal stroke '\u22FB': MO.REL, // contains with vertical bar at end of horizontal stroke '\u22FC': MO.REL, // small contains with vertical bar at end of horizontal stroke '\u22FD': MO.REL, // contains with overbar '\u22FE': MO.REL, // small contains with overbar '\u22FF': MO.REL // z notation bag membership } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/MathOperators.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/MiscMathSymbolsB.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { prefix: { '\u2980': [0,0,TEXCLASS.ORD,{fence: true, stretchy: true}], // triple vertical bar delimiter '\u2983': MO.OPEN, // left white curly bracket '\u2985': MO.OPEN, // left white parenthesis '\u2987': MO.OPEN, // z notation left image bracket '\u2989': MO.OPEN, // z notation left binding bracket '\u298B': MO.OPEN, // left square bracket with underbar '\u298D': MO.OPEN, // left square bracket with tick in top corner '\u298F': MO.OPEN, // left square bracket with tick in bottom corner '\u2991': MO.OPEN, // left angle bracket with dot '\u2993': MO.OPEN, // left arc less-than bracket '\u2995': MO.OPEN, // double left arc greater-than bracket '\u2997': MO.OPEN, // left black tortoise shell bracket '\u29FC': MO.OPEN // left-pointing curved angle bracket }, postfix: { '\u2980': [0,0,TEXCLASS.ORD,{fence: true, stretchy: true}], // triple vertical bar delimiter '\u2984': MO.CLOSE, // right white curly bracket '\u2986': MO.CLOSE, // right white parenthesis '\u2988': MO.CLOSE, // z notation right image bracket '\u298A': MO.CLOSE, // z notation right binding bracket '\u298C': MO.CLOSE, // right square bracket with underbar '\u298E': MO.CLOSE, // right square bracket with tick in bottom corner '\u2990': MO.CLOSE, // right square bracket with tick in top corner '\u2992': MO.CLOSE, // right angle bracket with dot '\u2994': MO.CLOSE, // right arc greater-than bracket '\u2996': MO.CLOSE, // double right arc less-than bracket '\u2998': MO.CLOSE, // right black tortoise shell bracket '\u29FD': MO.CLOSE // right-pointing curved angle bracket }, infix: { '\u2981': MO.BIN3, // z notation spot '\u2982': MO.BIN3, // z notation type colon '\u2999': MO.BIN3, // dotted fence '\u299A': MO.BIN3, // vertical zigzag line '\u299B': MO.BIN3, // measured angle opening left '\u299C': MO.BIN3, // right angle variant with square '\u299D': MO.BIN3, // measured right angle with dot '\u299E': MO.BIN3, // angle with s inside '\u299F': MO.BIN3, // acute angle '\u29A0': MO.BIN3, // spherical angle opening left '\u29A1': MO.BIN3, // spherical angle opening up '\u29A2': MO.BIN3, // turned angle '\u29A3': MO.BIN3, // reversed angle '\u29A4': MO.BIN3, // angle with underbar '\u29A5': MO.BIN3, // reversed angle with underbar '\u29A6': MO.BIN3, // oblique angle opening up '\u29A7': MO.BIN3, // oblique angle opening down '\u29A8': MO.BIN3, // measured angle with open arm ending in arrow pointing up and right '\u29A9': MO.BIN3, // measured angle with open arm ending in arrow pointing up and left '\u29AA': MO.BIN3, // measured angle with open arm ending in arrow pointing down and right '\u29AB': MO.BIN3, // measured angle with open arm ending in arrow pointing down and left '\u29AC': MO.BIN3, // measured angle with open arm ending in arrow pointing right and up '\u29AD': MO.BIN3, // measured angle with open arm ending in arrow pointing left and up '\u29AE': MO.BIN3, // measured angle with open arm ending in arrow pointing right and down '\u29AF': MO.BIN3, // measured angle with open arm ending in arrow pointing left and down '\u29B0': MO.BIN3, // reversed empty set '\u29B1': MO.BIN3, // empty set with overbar '\u29B2': MO.BIN3, // empty set with small circle above '\u29B3': MO.BIN3, // empty set with right arrow above '\u29B4': MO.BIN3, // empty set with left arrow above '\u29B5': MO.BIN3, // circle with horizontal bar '\u29B6': MO.BIN4, // circled vertical bar '\u29B7': MO.BIN4, // circled parallel '\u29B8': MO.BIN4, // circled reverse solidus '\u29B9': MO.BIN4, // circled perpendicular '\u29BA': MO.BIN4, // circle divided by horizontal bar and top half divided by vertical bar '\u29BB': MO.BIN4, // circle with superimposed x '\u29BC': MO.BIN4, // circled anticlockwise-rotated division sign '\u29BD': MO.BIN4, // up arrow through circle '\u29BE': MO.BIN4, // circled white bullet '\u29BF': MO.BIN4, // circled bullet '\u29C0': MO.REL, // circled less-than '\u29C1': MO.REL, // circled greater-than '\u29C2': MO.BIN3, // circle with small circle to the right '\u29C3': MO.BIN3, // circle with two horizontal strokes to the right '\u29C4': MO.BIN4, // squared rising diagonal slash '\u29C5': MO.BIN4, // squared falling diagonal slash '\u29C6': MO.BIN4, // squared asterisk '\u29C7': MO.BIN4, // squared small circle '\u29C8': MO.BIN4, // squared square '\u29C9': MO.BIN3, // two joined squares '\u29CA': MO.BIN3, // triangle with dot above '\u29CB': MO.BIN3, // triangle with underbar '\u29CC': MO.BIN3, // s in triangle '\u29CD': MO.BIN3, // triangle with serifs at bottom '\u29CE': MO.REL, // right triangle above left triangle '\u29CF': MO.REL, // left triangle beside vertical bar '\u29CF\u0338': MO.REL, // left triangle beside vertical bar with slash '\u29D0': MO.REL, // vertical bar beside right triangle '\u29D0\u0338': MO.REL, // vertical bar beside right triangle with slash '\u29D1': MO.REL, // bowtie with left half black '\u29D2': MO.REL, // bowtie with right half black '\u29D3': MO.REL, // black bowtie '\u29D4': MO.REL, // times with left half black '\u29D5': MO.REL, // times with right half black '\u29D6': MO.BIN4, // white hourglass '\u29D7': MO.BIN4, // black hourglass '\u29D8': MO.BIN3, // left wiggly fence '\u29D9': MO.BIN3, // right wiggly fence '\u29DB': MO.BIN3, // right double wiggly fence '\u29DC': MO.BIN3, // incomplete infinity '\u29DD': MO.BIN3, // tie over infinity '\u29DE': MO.REL, // infinity negated with vertical bar '\u29DF': MO.BIN3, // double-ended multimap '\u29E0': MO.BIN3, // square with contoured outline '\u29E1': MO.REL, // increases as '\u29E2': MO.BIN4, // shuffle product '\u29E3': MO.REL, // equals sign and slanted parallel '\u29E4': MO.REL, // equals sign and slanted parallel with tilde above '\u29E5': MO.REL, // identical to and slanted parallel '\u29E6': MO.REL, // gleich stark '\u29E7': MO.BIN3, // thermodynamic '\u29E8': MO.BIN3, // down-pointing triangle with left half black '\u29E9': MO.BIN3, // down-pointing triangle with right half black '\u29EA': MO.BIN3, // black diamond with down arrow '\u29EB': MO.BIN3, // black lozenge '\u29EC': MO.BIN3, // white circle with down arrow '\u29ED': MO.BIN3, // black circle with down arrow '\u29EE': MO.BIN3, // error-barred white square '\u29EF': MO.BIN3, // error-barred black square '\u29F0': MO.BIN3, // error-barred white diamond '\u29F1': MO.BIN3, // error-barred black diamond '\u29F2': MO.BIN3, // error-barred white circle '\u29F3': MO.BIN3, // error-barred black circle '\u29F4': MO.REL, // rule-delayed '\u29F5': MO.BIN4, // reverse solidus operator '\u29F6': MO.BIN4, // solidus with overbar '\u29F7': MO.BIN4, // reverse solidus with horizontal stroke '\u29F8': MO.BIN3, // big solidus '\u29F9': MO.BIN3, // big reverse solidus '\u29FA': MO.BIN3, // double plus '\u29FB': MO.BIN3, // triple plus '\u29FE': MO.BIN4, // tiny '\u29FF': MO.BIN4 // miny } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/MiscMathSymbolsB.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/SuppMathOperators.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { prefix: { '\u2A03': MO.OP, // n-ary union operator with dot '\u2A05': MO.OP, // n-ary square intersection operator '\u2A07': MO.OP, // two logical and operator '\u2A08': MO.OP, // two logical or operator '\u2A09': MO.OP, // n-ary times operator '\u2A0A': MO.OP, // modulo two sum '\u2A0B': MO.INTEGRAL2, // summation with integral '\u2A0C': MO.INTEGRAL, // quadruple integral operator '\u2A0D': MO.INTEGRAL2, // finite part integral '\u2A0E': MO.INTEGRAL2, // integral with double stroke '\u2A0F': MO.INTEGRAL2, // integral average with slash '\u2A10': MO.OP, // circulation function '\u2A11': MO.OP, // anticlockwise integration '\u2A12': MO.OP, // line integration with rectangular path around pole '\u2A13': MO.OP, // line integration with semicircular path around pole '\u2A14': MO.OP, // line integration not including the pole '\u2A15': MO.INTEGRAL2, // integral around a point operator '\u2A16': MO.INTEGRAL2, // quaternion integral operator '\u2A17': MO.INTEGRAL2, // integral with leftwards arrow with hook '\u2A18': MO.INTEGRAL2, // integral with times sign '\u2A19': MO.INTEGRAL2, // integral with intersection '\u2A1A': MO.INTEGRAL2, // integral with union '\u2A1B': MO.INTEGRAL2, // integral with overbar '\u2A1C': MO.INTEGRAL2, // integral with underbar '\u2AFC': MO.OP, // large triple vertical bar operator '\u2AFF': MO.OP // n-ary white vertical bar }, infix: { '\u2A1D': MO.BIN3, // join '\u2A1E': MO.BIN3, // large left triangle operator '\u2A1F': MO.BIN3, // z notation schema composition '\u2A20': MO.BIN3, // z notation schema piping '\u2A21': MO.BIN3, // z notation schema projection '\u2A22': MO.BIN4, // plus sign with small circle above '\u2A23': MO.BIN4, // plus sign with circumflex accent above '\u2A24': MO.BIN4, // plus sign with tilde above '\u2A25': MO.BIN4, // plus sign with dot below '\u2A26': MO.BIN4, // plus sign with tilde below '\u2A27': MO.BIN4, // plus sign with subscript two '\u2A28': MO.BIN4, // plus sign with black triangle '\u2A29': MO.BIN4, // minus sign with comma above '\u2A2A': MO.BIN4, // minus sign with dot below '\u2A2B': MO.BIN4, // minus sign with falling dots '\u2A2C': MO.BIN4, // minus sign with rising dots '\u2A2D': MO.BIN4, // plus sign in left half circle '\u2A2E': MO.BIN4, // plus sign in right half circle '\u2A30': MO.BIN4, // multiplication sign with dot above '\u2A31': MO.BIN4, // multiplication sign with underbar '\u2A32': MO.BIN4, // semidirect product with bottom closed '\u2A33': MO.BIN4, // smash product '\u2A34': MO.BIN4, // multiplication sign in left half circle '\u2A35': MO.BIN4, // multiplication sign in right half circle '\u2A36': MO.BIN4, // circled multiplication sign with circumflex accent '\u2A37': MO.BIN4, // multiplication sign in double circle '\u2A38': MO.BIN4, // circled division sign '\u2A39': MO.BIN4, // plus sign in triangle '\u2A3A': MO.BIN4, // minus sign in triangle '\u2A3B': MO.BIN4, // multiplication sign in triangle '\u2A3C': MO.BIN4, // interior product '\u2A3D': MO.BIN4, // righthand interior product '\u2A3E': MO.BIN4, // z notation relational composition '\u2A40': MO.BIN4, // intersection with dot '\u2A41': MO.BIN4, // union with minus sign '\u2A42': MO.BIN4, // union with overbar '\u2A43': MO.BIN4, // intersection with overbar '\u2A44': MO.BIN4, // intersection with logical and '\u2A45': MO.BIN4, // union with logical or '\u2A46': MO.BIN4, // union above intersection '\u2A47': MO.BIN4, // intersection above union '\u2A48': MO.BIN4, // union above bar above intersection '\u2A49': MO.BIN4, // intersection above bar above union '\u2A4A': MO.BIN4, // union beside and joined with union '\u2A4B': MO.BIN4, // intersection beside and joined with intersection '\u2A4C': MO.BIN4, // closed union with serifs '\u2A4D': MO.BIN4, // closed intersection with serifs '\u2A4E': MO.BIN4, // double square intersection '\u2A4F': MO.BIN4, // double square union '\u2A50': MO.BIN4, // closed union with serifs and smash product '\u2A51': MO.BIN4, // logical and with dot above '\u2A52': MO.BIN4, // logical or with dot above '\u2A53': MO.BIN4, // double logical and '\u2A54': MO.BIN4, // double logical or '\u2A55': MO.BIN4, // two intersecting logical and '\u2A56': MO.BIN4, // two intersecting logical or '\u2A57': MO.BIN4, // sloping large or '\u2A58': MO.BIN4, // sloping large and '\u2A59': MO.REL, // logical or overlapping logical and '\u2A5A': MO.BIN4, // logical and with middle stem '\u2A5B': MO.BIN4, // logical or with middle stem '\u2A5C': MO.BIN4, // logical and with horizontal dash '\u2A5D': MO.BIN4, // logical or with horizontal dash '\u2A5E': MO.BIN4, // logical and with double overbar '\u2A5F': MO.BIN4, // logical and with underbar '\u2A60': MO.BIN4, // logical and with double underbar '\u2A61': MO.BIN4, // small vee with underbar '\u2A62': MO.BIN4, // logical or with double overbar '\u2A63': MO.BIN4, // logical or with double underbar '\u2A64': MO.BIN4, // z notation domain antirestriction '\u2A65': MO.BIN4, // z notation range antirestriction '\u2A66': MO.REL, // equals sign with dot below '\u2A67': MO.REL, // identical with dot above '\u2A68': MO.REL, // triple horizontal bar with double vertical stroke '\u2A69': MO.REL, // triple horizontal bar with triple vertical stroke '\u2A6A': MO.REL, // tilde operator with dot above '\u2A6B': MO.REL, // tilde operator with rising dots '\u2A6C': MO.REL, // similar minus similar '\u2A6D': MO.REL, // congruent with dot above '\u2A6E': MO.REL, // equals with asterisk '\u2A6F': MO.REL, // almost equal to with circumflex accent '\u2A70': MO.REL, // approximately equal or equal to '\u2A71': MO.BIN4, // equals sign above plus sign '\u2A72': MO.BIN4, // plus sign above equals sign '\u2A73': MO.REL, // equals sign above tilde operator '\u2A74': MO.REL, // double colon equal '\u2A75': MO.REL, // two consecutive equals signs '\u2A76': MO.REL, // three consecutive equals signs '\u2A77': MO.REL, // equals sign with two dots above and two dots below '\u2A78': MO.REL, // equivalent with four dots above '\u2A79': MO.REL, // less-than with circle inside '\u2A7A': MO.REL, // greater-than with circle inside '\u2A7B': MO.REL, // less-than with question mark above '\u2A7C': MO.REL, // greater-than with question mark above '\u2A7D': MO.REL, // less-than or slanted equal to '\u2A7D\u0338': MO.REL, // less-than or slanted equal to with slash '\u2A7E': MO.REL, // greater-than or slanted equal to '\u2A7E\u0338': MO.REL, // greater-than or slanted equal to with slash '\u2A7F': MO.REL, // less-than or slanted equal to with dot inside '\u2A80': MO.REL, // greater-than or slanted equal to with dot inside '\u2A81': MO.REL, // less-than or slanted equal to with dot above '\u2A82': MO.REL, // greater-than or slanted equal to with dot above '\u2A83': MO.REL, // less-than or slanted equal to with dot above right '\u2A84': MO.REL, // greater-than or slanted equal to with dot above left '\u2A85': MO.REL, // less-than or approximate '\u2A86': MO.REL, // greater-than or approximate '\u2A87': MO.REL, // less-than and single-line not equal to '\u2A88': MO.REL, // greater-than and single-line not equal to '\u2A89': MO.REL, // less-than and not approximate '\u2A8A': MO.REL, // greater-than and not approximate '\u2A8B': MO.REL, // less-than above double-line equal above greater-than '\u2A8C': MO.REL, // greater-than above double-line equal above less-than '\u2A8D': MO.REL, // less-than above similar or equal '\u2A8E': MO.REL, // greater-than above similar or equal '\u2A8F': MO.REL, // less-than above similar above greater-than '\u2A90': MO.REL, // greater-than above similar above less-than '\u2A91': MO.REL, // less-than above greater-than above double-line equal '\u2A92': MO.REL, // greater-than above less-than above double-line equal '\u2A93': MO.REL, // less-than above slanted equal above greater-than above slanted equal '\u2A94': MO.REL, // greater-than above slanted equal above less-than above slanted equal '\u2A95': MO.REL, // slanted equal to or less-than '\u2A96': MO.REL, // slanted equal to or greater-than '\u2A97': MO.REL, // slanted equal to or less-than with dot inside '\u2A98': MO.REL, // slanted equal to or greater-than with dot inside '\u2A99': MO.REL, // double-line equal to or less-than '\u2A9A': MO.REL, // double-line equal to or greater-than '\u2A9B': MO.REL, // double-line slanted equal to or less-than '\u2A9C': MO.REL, // double-line slanted equal to or greater-than '\u2A9D': MO.REL, // similar or less-than '\u2A9E': MO.REL, // similar or greater-than '\u2A9F': MO.REL, // similar above less-than above equals sign '\u2AA0': MO.REL, // similar above greater-than above equals sign '\u2AA1': MO.REL, // double nested less-than '\u2AA1\u0338': MO.REL, // double nested less-than with slash '\u2AA2': MO.REL, // double nested greater-than '\u2AA2\u0338': MO.REL, // double nested greater-than with slash '\u2AA3': MO.REL, // double nested less-than with underbar '\u2AA4': MO.REL, // greater-than overlapping less-than '\u2AA5': MO.REL, // greater-than beside less-than '\u2AA6': MO.REL, // less-than closed by curve '\u2AA7': MO.REL, // greater-than closed by curve '\u2AA8': MO.REL, // less-than closed by curve above slanted equal '\u2AA9': MO.REL, // greater-than closed by curve above slanted equal '\u2AAA': MO.REL, // smaller than '\u2AAB': MO.REL, // larger than '\u2AAC': MO.REL, // smaller than or equal to '\u2AAD': MO.REL, // larger than or equal to '\u2AAE': MO.REL, // equals sign with bumpy above '\u2AAF\u0338': MO.REL, // precedes above single-line equals sign with slash '\u2AB0\u0338': MO.REL, // succeeds above single-line equals sign with slash '\u2AB1': MO.REL, // precedes above single-line not equal to '\u2AB2': MO.REL, // succeeds above single-line not equal to '\u2AB3': MO.REL, // precedes above equals sign '\u2AB4': MO.REL, // succeeds above equals sign '\u2AB5': MO.REL, // precedes above not equal to '\u2AB6': MO.REL, // succeeds above not equal to '\u2AB7': MO.REL, // precedes above almost equal to '\u2AB8': MO.REL, // succeeds above almost equal to '\u2AB9': MO.REL, // precedes above not almost equal to '\u2ABA': MO.REL, // succeeds above not almost equal to '\u2ABB': MO.REL, // double precedes '\u2ABC': MO.REL, // double succeeds '\u2ABD': MO.REL, // subset with dot '\u2ABE': MO.REL, // superset with dot '\u2ABF': MO.REL, // subset with plus sign below '\u2AC0': MO.REL, // superset with plus sign below '\u2AC1': MO.REL, // subset with multiplication sign below '\u2AC2': MO.REL, // superset with multiplication sign below '\u2AC3': MO.REL, // subset of or equal to with dot above '\u2AC4': MO.REL, // superset of or equal to with dot above '\u2AC5': MO.REL, // subset of above equals sign '\u2AC6': MO.REL, // superset of above equals sign '\u2AC7': MO.REL, // subset of above tilde operator '\u2AC8': MO.REL, // superset of above tilde operator '\u2AC9': MO.REL, // subset of above almost equal to '\u2ACA': MO.REL, // superset of above almost equal to '\u2ACB': MO.REL, // subset of above not equal to '\u2ACC': MO.REL, // superset of above not equal to '\u2ACD': MO.REL, // square left open box operator '\u2ACE': MO.REL, // square right open box operator '\u2ACF': MO.REL, // closed subset '\u2AD0': MO.REL, // closed superset '\u2AD1': MO.REL, // closed subset or equal to '\u2AD2': MO.REL, // closed superset or equal to '\u2AD3': MO.REL, // subset above superset '\u2AD4': MO.REL, // superset above subset '\u2AD5': MO.REL, // subset above subset '\u2AD6': MO.REL, // superset above superset '\u2AD7': MO.REL, // superset beside subset '\u2AD8': MO.REL, // superset beside and joined by dash with subset '\u2AD9': MO.REL, // element of opening downwards '\u2ADA': MO.REL, // pitchfork with tee top '\u2ADB': MO.REL, // transversal intersection '\u2ADC': MO.REL, // forking '\u2ADD': MO.REL, // nonforking '\u2ADE': MO.REL, // short left tack '\u2ADF': MO.REL, // short down tack '\u2AE0': MO.REL, // short up tack '\u2AE1': MO.REL, // perpendicular with s '\u2AE2': MO.REL, // vertical bar triple right turnstile '\u2AE3': MO.REL, // double vertical bar left turnstile '\u2AE4': MO.REL, // vertical bar double left turnstile '\u2AE5': MO.REL, // double vertical bar double left turnstile '\u2AE6': MO.REL, // long dash from left member of double vertical '\u2AE7': MO.REL, // short down tack with overbar '\u2AE8': MO.REL, // short up tack with underbar '\u2AE9': MO.REL, // short up tack above short down tack '\u2AEA': MO.REL, // double down tack '\u2AEB': MO.REL, // double up tack '\u2AEC': MO.REL, // double stroke not sign '\u2AED': MO.REL, // reversed double stroke not sign '\u2AEE': MO.REL, // does not divide with reversed negation slash '\u2AEF': MO.REL, // vertical line with circle above '\u2AF0': MO.REL, // vertical line with circle below '\u2AF1': MO.REL, // down tack with circle below '\u2AF2': MO.REL, // parallel with horizontal stroke '\u2AF3': MO.REL, // parallel with tilde operator '\u2AF4': MO.BIN4, // triple vertical bar binary relation '\u2AF5': MO.BIN4, // triple vertical bar with horizontal stroke '\u2AF6': MO.BIN4, // triple colon operator '\u2AF7': MO.REL, // triple nested less-than '\u2AF8': MO.REL, // triple nested greater-than '\u2AF9': MO.REL, // double-line slanted less-than or equal to '\u2AFA': MO.REL, // double-line slanted greater-than or equal to '\u2AFB': MO.BIN4, // triple solidus binary relation '\u2AFD': MO.BIN4, // double solidus operator '\u2AFE': MO.BIN3 // white vertical bar } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/SuppMathOperators.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/CombDiactForSymbols.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { postfix: { '\u20DB': MO.ACCENT, // combining three dots above '\u20DC': MO.ACCENT // combining four dots above } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/CombDiactForSymbols.js"); })(MathJax.ElementJax.mml); /************************************************************* * * MathJax/jax/output/HTML-CSS/optable/Latin1Supplement.js * * Copyright (c) 2010-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (MML) { var MO = MML.mo.OPTYPES; var TEXCLASS = MML.TEXCLASS; MathJax.Hub.Insert(MML.mo.prototype,{ OPTABLE: { postfix: { '\u00B0': MO.ORD, // degree sign '\u00B4': MO.ACCENT, // acute accent '\u00B8': MO.ACCENT // cedilla } } }); MathJax.Ajax.loadComplete(MML.optableDir+"/Latin1Supplement.js"); })(MathJax.ElementJax.mml); /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/extensions/MathEvents.js * * Implements the event handlers needed by the output jax to perform * menu, hover, and other events. * * --------------------------------------------------------------------- * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ (function (HUB,HTML,AJAX,CALLBACK,LOCALE,OUTPUT,INPUT) { var VERSION = "2.7.5"; var EXTENSION = MathJax.Extension; var ME = EXTENSION.MathEvents = {version: VERSION}; var SETTINGS = HUB.config.menuSettings; var CONFIG = { hover: 500, // time required to be considered a hover frame: { x: 3.5, y: 5, // frame padding and bwidth: 1, // frame border width (in pixels) bcolor: "#A6D", // frame border color hwidth: "15px", // haze width hcolor: "#83A" // haze color }, button: { x: -6, y: -3, // menu button offsets wx: -2 // button offset for full-width equations }, fadeinInc: .2, // increment for fade-in fadeoutInc: .05, // increment for fade-out fadeDelay: 50, // delay between fade-in or fade-out steps fadeoutStart: 400, // delay before fade-out after mouseout fadeoutDelay: 15*1000, // delay before automatic fade-out styles: { ".MathJax_Hover_Frame": { "border-radius": ".25em", // Opera 10.5 and IE9 "-webkit-border-radius": ".25em", // Safari and Chrome "-moz-border-radius": ".25em", // Firefox "-khtml-border-radius": ".25em", // Konqueror "box-shadow": "0px 0px 15px #83A", // Opera 10.5 and IE9 "-webkit-box-shadow": "0px 0px 15px #83A", // Safari and Chrome "-moz-box-shadow": "0px 0px 15px #83A", // Forefox "-khtml-box-shadow": "0px 0px 15px #83A", // Konqueror border: "1px solid #A6D ! important", display: "inline-block", position:"absolute" }, ".MathJax_Menu_Button .MathJax_Hover_Arrow": { position:"absolute", cursor:"pointer", display:"inline-block", border:"2px solid #AAA", "border-radius":"4px", "-webkit-border-radius": "4px", // Safari and Chrome "-moz-border-radius": "4px", // Firefox "-khtml-border-radius": "4px", // Konqueror "font-family":"'Courier New',Courier", "font-size":"9px", color:"#F0F0F0" }, ".MathJax_Menu_Button .MathJax_Hover_Arrow span": { display:"block", "background-color":"#AAA", border:"1px solid", "border-radius":"3px", "line-height":0, padding:"4px" }, ".MathJax_Hover_Arrow:hover": { color:"white!important", border:"2px solid #CCC!important" }, ".MathJax_Hover_Arrow:hover span": { "background-color":"#CCC!important" } } }; // // Common event-handling code // var EVENT = ME.Event = { LEFTBUTTON: 0, // the event.button value for left button RIGHTBUTTON: 2, // the event.button value for right button MENUKEY: "altKey", // the event value for alternate context menu /*************************************************************/ /* * Enum element for key codes. */ KEY: { RETURN: 13, ESCAPE: 27, SPACE: 32, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40 }, Mousedown: function (event) {return EVENT.Handler(event,"Mousedown",this)}, Mouseup: function (event) {return EVENT.Handler(event,"Mouseup",this)}, Mousemove: function (event) {return EVENT.Handler(event,"Mousemove",this)}, Mouseover: function (event) {return EVENT.Handler(event,"Mouseover",this)}, Mouseout: function (event) {return EVENT.Handler(event,"Mouseout",this)}, Click: function (event) {return EVENT.Handler(event,"Click",this)}, DblClick: function (event) {return EVENT.Handler(event,"DblClick",this)}, Menu: function (event) {return EVENT.Handler(event,"ContextMenu",this)}, // // Call the output jax's event handler or the zoom handler // Handler: function (event,type,math) { if (AJAX.loadingMathMenu) {return EVENT.False(event)} var jax = OUTPUT[math.jaxID]; if (!event) {event = window.event} event.isContextMenu = (type === "ContextMenu"); if (jax[type]) {return jax[type](event,math)} if (EXTENSION.MathZoom) {return EXTENSION.MathZoom.HandleEvent(event,type,math)} }, // // Try to cancel the event in every way we can // False: function (event) { if (!event) {event = window.event} if (event) { if (event.preventDefault) {event.preventDefault()} else {event.returnValue = false} if (event.stopPropagation) {event.stopPropagation()} event.cancelBubble = true; } return false; }, // // Keydown event handler. Should only fire on Space key. // Keydown: function (event, math) { if (!event) event = window.event; if (event.keyCode === EVENT.KEY.SPACE) { EVENT.ContextMenu(event, this); }; }, // // Load the contextual menu code, if needed, and post the menu // ContextMenu: function (event,math,force) { // // Check if we are showing menus // var JAX = OUTPUT[math.jaxID], jax = JAX.getJaxFromMath(math); var show = (JAX.config.showMathMenu != null ? JAX : HUB).config.showMathMenu; if (!show || (SETTINGS.context !== "MathJax" && !force)) return; // // Remove selections, remove hover fades // if (ME.msieEventBug) {event = window.event || event} EVENT.ClearSelection(); HOVER.ClearHoverTimer(); if (jax.hover) { if (jax.hover.remove) {clearTimeout(jax.hover.remove); delete jax.hover.remove} jax.hover.nofade = true; } // // If the menu code is loaded, // Check if localization needs loading; // If not, post the menu, and return. // Otherwise wait for the localization to load // Otherwse load the menu code. // Try again after the file is loaded. // var MENU = MathJax.Menu; var load, fn; if (MENU) { if (MENU.loadingDomain) {return EVENT.False(event)} load = LOCALE.loadDomain("MathMenu"); if (!load) { MENU.jax = jax; var source = MENU.menu.Find("Show Math As").submenu; source.items[0].name = jax.sourceMenuTitle; source.items[0].format = (jax.sourceMenuFormat||"MathML"); source.items[1].name = INPUT[jax.inputJax].sourceMenuTitle; source.items[5].disabled = !INPUT[jax.inputJax].annotationEncoding; // // Try and find each known annotation format and enable the menu // items accordingly. // var annotations = source.items[2]; annotations.disabled = true; var annotationItems = annotations.submenu.items; annotationList = MathJax.Hub.Config.semanticsAnnotations; for (var i = 0, m = annotationItems.length; i < m; i++) { var name = annotationItems[i].name[1] if (jax.root && jax.root.getAnnotation(name) !== null) { annotations.disabled = false; annotationItems[i].hidden = false; } else { annotationItems[i].hidden = true; } } var MathPlayer = MENU.menu.Find("Math Settings","MathPlayer"); MathPlayer.hidden = !(jax.outputJax === "NativeMML" && HUB.Browser.hasMathPlayer); return MENU.menu.Post(event); } MENU.loadingDomain = true; fn = function () {delete MENU.loadingDomain}; } else { if (AJAX.loadingMathMenu) {return EVENT.False(event)} AJAX.loadingMathMenu = true; load = AJAX.Require("[MathJax]/extensions/MathMenu.js"); fn = function () { delete AJAX.loadingMathMenu; if (!MathJax.Menu) {MathJax.Menu = {}} } } var ev = { pageX:event.pageX, pageY:event.pageY, clientX:event.clientX, clientY:event.clientY }; CALLBACK.Queue( load, fn, // load the file and delete the marker when done ["ContextMenu",EVENT,ev,math,force] // call this function again ); return EVENT.False(event); }, // // Mousedown handler for alternate means of accessing menu // AltContextMenu: function (event,math) { var JAX = OUTPUT[math.jaxID]; var show = (JAX.config.showMathMenu != null ? JAX : HUB).config.showMathMenu; if (show) { show = (JAX.config.showMathMenuMSIE != null ? JAX : HUB).config.showMathMenuMSIE; if (SETTINGS.context === "MathJax" && !SETTINGS.mpContext && show) { if (!ME.noContextMenuBug || event.button !== EVENT.RIGHTBUTTON) return; } else { if (!event[EVENT.MENUKEY] || event.button !== EVENT.LEFTBUTTON) return; } return JAX.ContextMenu(event,math,true); } }, ClearSelection: function () { if (ME.safariContextMenuBug) {setTimeout("window.getSelection().empty()",0)} if (document.selection) {setTimeout("document.selection.empty()",0)} }, getBBox: function (span) { span.appendChild(ME.topImg); var h = ME.topImg.offsetTop, d = span.offsetHeight-h, w = span.offsetWidth; span.removeChild(ME.topImg); return {w:w, h:h, d:d}; } }; // // Handle hover "discoverability" // var HOVER = ME.Hover = { // // Check if we are moving from a non-MathJax element to a MathJax one // and either start fading in again (if it is fading out) or start the // timer for the hover // Mouseover: function (event,math) { if (SETTINGS.discoverable || SETTINGS.zoom === "Hover") { var from = event.fromElement || event.relatedTarget, to = event.toElement || event.target; if (from && to && (HUB.isMathJaxNode(from) !== HUB.isMathJaxNode(to) || HUB.getJaxFor(from) !== HUB.getJaxFor(to))) { var jax = this.getJaxFromMath(math); if (jax.hover) {HOVER.ReHover(jax)} else {HOVER.HoverTimer(jax,math)} return EVENT.False(event); } } }, // // Check if we are moving from a MathJax element to a non-MathJax one // and either start fading out, or clear the timer if we haven't // hovered yet // Mouseout: function (event,math) { if (SETTINGS.discoverable || SETTINGS.zoom === "Hover") { var from = event.fromElement || event.relatedTarget, to = event.toElement || event.target; if (from && to && (HUB.isMathJaxNode(from) !== HUB.isMathJaxNode(to) || HUB.getJaxFor(from) !== HUB.getJaxFor(to))) { var jax = this.getJaxFromMath(math); if (jax.hover) {HOVER.UnHover(jax)} else {HOVER.ClearHoverTimer()} return EVENT.False(event); } } }, // // Restart hover timer if the mouse moves // Mousemove: function (event,math) { if (SETTINGS.discoverable || SETTINGS.zoom === "Hover") { var jax = this.getJaxFromMath(math); if (jax.hover) return; if (HOVER.lastX == event.clientX && HOVER.lastY == event.clientY) return; HOVER.lastX = event.clientX; HOVER.lastY = event.clientY; HOVER.HoverTimer(jax,math); return EVENT.False(event); } }, // // Clear the old timer and start a new one // HoverTimer: function (jax,math) { this.ClearHoverTimer(); this.hoverTimer = setTimeout(CALLBACK(["Hover",this,jax,math]),CONFIG.hover); }, ClearHoverTimer: function () { if (this.hoverTimer) {clearTimeout(this.hoverTimer); delete this.hoverTimer} }, // // Handle putting up the hover frame // Hover: function (jax,math) { // // Check if Zoom handles the hover event // if (EXTENSION.MathZoom && EXTENSION.MathZoom.Hover({},math)) return; // // Get the hover data // var JAX = OUTPUT[jax.outputJax], span = JAX.getHoverSpan(jax,math), bbox = JAX.getHoverBBox(jax,span,math), show = (JAX.config.showMathMenu != null ? JAX : HUB).config.showMathMenu; var dx = CONFIG.frame.x, dy = CONFIG.frame.y, dd = CONFIG.frame.bwidth; // frame size if (ME.msieBorderWidthBug) {dd = 0} jax.hover = {opacity:0, id:jax.inputID+"-Hover"}; // // The frame and menu button // var frame = HTML.Element("span",{ id:jax.hover.id, isMathJax: true, style:{display:"inline-block", width:0, height:0, position:"relative"} },[["span",{ className:"MathJax_Hover_Frame", isMathJax: true, style:{ display:"inline-block", position:"absolute", top:this.Px(-bbox.h-dy-dd-(bbox.y||0)), left:this.Px(-dx-dd+(bbox.x||0)), width:this.Px(bbox.w+2*dx), height:this.Px(bbox.h+bbox.d+2*dy), opacity:0, filter:"alpha(opacity=0)" }} ]] ); var button = HTML.Element("span",{ isMathJax: true, id:jax.hover.id+"Menu", className:"MathJax_Menu_Button", style:{display:"inline-block", "z-index": 1, width:0, height:0, position:"relative"} },[["span",{ className: "MathJax_Hover_Arrow", isMathJax: true, math: math, onclick: this.HoverMenu, jax:JAX.id, style: { left:this.Px(bbox.w+dx+dd+(bbox.x||0)+CONFIG.button.x), top:this.Px(-bbox.h-dy-dd-(bbox.y||0)-CONFIG.button.y), opacity:0, filter:"alpha(opacity=0)" } },[["span",{isMathJax:true},"\u25BC"]]]] ); if (bbox.width) { frame.style.width = button.style.width = bbox.width; frame.style.marginRight = button.style.marginRight = "-"+bbox.width; frame.firstChild.style.width = bbox.width; button.firstChild.style.left = ""; button.firstChild.style.right = this.Px(CONFIG.button.wx); } // // Add the frame and button // span.parentNode.insertBefore(frame,span); if (show) {span.parentNode.insertBefore(button,span)} if (span.style) {span.style.position = "relative"} // so math is on top of hover frame // // Start the hover fade-in // this.ReHover(jax); }, // // Restart the hover fade in and fade-out timers // ReHover: function (jax) { if (jax.hover.remove) {clearTimeout(jax.hover.remove)} jax.hover.remove = setTimeout(CALLBACK(["UnHover",this,jax]),CONFIG.fadeoutDelay); this.HoverFadeTimer(jax,CONFIG.fadeinInc); }, // // Start the fade-out // UnHover: function (jax) { if (!jax.hover.nofade) {this.HoverFadeTimer(jax,-CONFIG.fadeoutInc,CONFIG.fadeoutStart)} }, // // Handle the fade-in and fade-out // HoverFade: function (jax) { delete jax.hover.timer; jax.hover.opacity = Math.max(0,Math.min(1,jax.hover.opacity + jax.hover.inc)); jax.hover.opacity = Math.floor(1000*jax.hover.opacity)/1000; var frame = document.getElementById(jax.hover.id), button = document.getElementById(jax.hover.id+"Menu"); frame.firstChild.style.opacity = jax.hover.opacity; frame.firstChild.style.filter = "alpha(opacity="+Math.floor(100*jax.hover.opacity)+")"; if (button) { button.firstChild.style.opacity = jax.hover.opacity; button.firstChild.style.filter = frame.style.filter; } if (jax.hover.opacity === 1) {return} if (jax.hover.opacity > 0) {this.HoverFadeTimer(jax,jax.hover.inc); return} frame.parentNode.removeChild(frame); if (button) {button.parentNode.removeChild(button)} if (jax.hover.remove) {clearTimeout(jax.hover.remove)} delete jax.hover; }, // // Set the fade to in or out (via inc) and start the timer, if needed // HoverFadeTimer: function (jax,inc,delay) { jax.hover.inc = inc; if (!jax.hover.timer) { jax.hover.timer = setTimeout(CALLBACK(["HoverFade",this,jax]),(delay||CONFIG.fadeDelay)); } }, // // Handle a click on the menu button // HoverMenu: function (event) { if (!event) {event = window.event} return OUTPUT[this.jax].ContextMenu(event,this.math,true); }, // // Clear all hover timers // ClearHover: function (jax) { if (jax.hover.remove) {clearTimeout(jax.hover.remove)} if (jax.hover.timer) {clearTimeout(jax.hover.timer)} HOVER.ClearHoverTimer(); delete jax.hover; }, // // Make a measurement in pixels // Px: function (m) { if (Math.abs(m) < .006) {return "0px"} return m.toFixed(2).replace(/\.?0+$/,"") + "px"; }, // // Preload images so they show up with the menu // getImages: function () { if (SETTINGS.discoverable) { var menu = new Image(); menu.src = CONFIG.button.src; } } }; // // Handle touch events. // // Use double-tap-and-hold as a replacement for context menu event. // Use double-tap as a replacement for double click. // var TOUCH = ME.Touch = { last: 0, // time of last tap event delay: 500, // delay time for double-click // // Check if this is a double-tap, and if so, start the timer // for the double-tap and hold (to trigger the contextual menu) // start: function (event) { var now = new Date().getTime(); var dblTap = (now - TOUCH.last < TOUCH.delay && TOUCH.up); TOUCH.last = now; TOUCH.up = false; if (dblTap) { TOUCH.timeout = setTimeout(TOUCH.menu,TOUCH.delay,event,this); event.preventDefault(); } }, // // Check if there is a timeout pending, i.e., we have a // double-tap and were waiting to see if it is held long // enough for the menu. Since we got the end before the // timeout, it is a double-click, not a double-tap-and-hold. // Prevent the default action and issue a double click. // end: function (event) { var now = new Date().getTime(); TOUCH.up = (now - TOUCH.last < TOUCH.delay); if (TOUCH.timeout) { clearTimeout(TOUCH.timeout); delete TOUCH.timeout; TOUCH.last = 0; TOUCH.up = false; event.preventDefault(); return EVENT.Handler((event.touches[0]||event.touch),"DblClick",this); } }, // // If the timeout passes without an end event, we issue // the contextual menu event. // menu: function (event,math) { delete TOUCH.timeout; TOUCH.last = 0; TOUCH.up = false; return EVENT.Handler((event.touches[0]||event.touch),"ContextMenu",math); } }; /* * // * // Mobile screens are small, so use larger version of arrow * // * if (HUB.Browser.isMobile) { * var arrow = CONFIG.styles[".MathJax_Hover_Arrow"]; * arrow.width = "25px"; arrow.height = "18px"; * CONFIG.button.x = -6; * } */ // // Set up browser-specific values // HUB.Browser.Select({ MSIE: function (browser) { var mode = (document.documentMode || 0); var isIE8 = browser.versionAtLeast("8.0"); ME.msieBorderWidthBug = (document.compatMode === "BackCompat"); // borders are inside offsetWidth/Height ME.msieEventBug = browser.isIE9; // must get event from window even though event is passed ME.msieAlignBug = (!isIE8 || mode < 8); // inline-block spans don't rest on baseline if (mode < 9) {EVENT.LEFTBUTTON = 1} // IE < 9 has wrong event.button values }, Safari: function (browser) { ME.safariContextMenuBug = true; // selection can be started by contextmenu event }, Opera: function (browser) { ME.operaPositionBug = true; // position is wrong unless border is used }, Konqueror: function (browser) { ME.noContextMenuBug = true; // doesn't produce contextmenu event } }); // // Used in measuring zoom and hover positions // ME.topImg = (ME.msieAlignBug ? HTML.Element("img",{style:{width:0,height:0,position:"relative"},src:"about:blank"}) : HTML.Element("span",{style:{width:0,height:0,display:"inline-block"}}) ); if (ME.operaPositionBug) {ME.topImg.style.border="1px solid"} // // Get configuration from user // ME.config = CONFIG = HUB.CombineConfig("MathEvents",CONFIG); var SETFRAME = function () { var haze = CONFIG.styles[".MathJax_Hover_Frame"]; haze.border = CONFIG.frame.bwidth+"px solid "+CONFIG.frame.bcolor+" ! important"; haze["box-shadow"] = haze["-webkit-box-shadow"] = haze["-moz-box-shadow"] = haze["-khtml-box-shadow"] = "0px 0px "+CONFIG.frame.hwidth+" "+CONFIG.frame.hcolor; }; // // Queue the events needed for startup // CALLBACK.Queue( HUB.Register.StartupHook("End Config",{}), // wait until config is complete [SETFRAME], ["getImages",HOVER], ["Styles",AJAX,CONFIG.styles], ["Post",HUB.Startup.signal,"MathEvents Ready"], ["loadComplete",AJAX,"[MathJax]/extensions/MathEvents.js"] ); })(MathJax.Hub,MathJax.HTML,MathJax.Ajax,MathJax.Callback, MathJax.Localization,MathJax.OutputJax,MathJax.InputJax); /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/extensions/tex2jax.js * * Implements the TeX to Jax preprocessor that locates TeX code * within the text of a document and replaces it with SCRIPT tags * for processing by MathJax. * * --------------------------------------------------------------------- * * Copyright (c) 2009-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ MathJax.Extension.tex2jax = { version: "2.7.5", config: { inlineMath: [ // The start/stop pairs for in-line math // ['$','$'], // (comment out any you don't want, or add your own, but ['\\(','\\)'] // be sure that you don't have an extra comma at the end) ], displayMath: [ // The start/stop pairs for display math ['$$','$$'], // (comment out any you don't want, or add your own, but ['\\[','\\]'] // be sure that you don't have an extra comma at the end) ], skipTags: ["script","noscript","style","textarea","pre","code","annotation","annotation-xml"], // The names of the tags whose contents will not be // scanned for math delimiters ignoreClass: "tex2jax_ignore", // the class name of elements whose contents should // NOT be processed by tex2jax. Note that this // is a regular expression, so be sure to quote any // regexp special characters processClass: "tex2jax_process", // the class name of elements whose contents SHOULD // be processed when they appear inside ones that // are ignored. Note that this is a regular expression, // so be sure to quote any regexp special characters processEscapes: false, // set to true to allow \$ to produce a dollar without // starting in-line math mode processEnvironments: true, // set to true to process \begin{xxx}...\end{xxx} outside // of math mode, false to prevent that processRefs: true, // set to true to process \ref{...} outside of math mode preview: "TeX" // set to "none" to not insert MathJax_Preview spans // or set to an array specifying an HTML snippet // to use the same preview for every equation. }, // // Tags to ignore when searching for TeX in the page // ignoreTags: { br: (MathJax.Hub.Browser.isMSIE && document.documentMode < 9 ? "\n" : " "), wbr: "", "#comment": "" }, PreProcess: function (element) { if (!this.configured) { this.config = MathJax.Hub.CombineConfig("tex2jax",this.config); if (this.config.Augment) {MathJax.Hub.Insert(this,this.config.Augment)} if (typeof(this.config.previewTeX) !== "undefined" && !this.config.previewTeX) {this.config.preview = "none"} // backward compatibility for previewTeX parameter this.configured = true; } if (typeof(element) === "string") {element = document.getElementById(element)} if (!element) {element = document.body} if (this.createPatterns()) {this.scanElement(element,element.nextSibling)} }, createPatterns: function () { var starts = [], parts = [], i, m, config = this.config; this.match = {}; for (i = 0, m = config.inlineMath.length; i < m; i++) { starts.push(this.patternQuote(config.inlineMath[i][0])); this.match[config.inlineMath[i][0]] = { mode: "", end: config.inlineMath[i][1], pattern: this.endPattern(config.inlineMath[i][1]) }; } for (i = 0, m = config.displayMath.length; i < m; i++) { starts.push(this.patternQuote(config.displayMath[i][0])); this.match[config.displayMath[i][0]] = { mode: "; mode=display", end: config.displayMath[i][1], pattern: this.endPattern(config.displayMath[i][1]) }; } if (starts.length) {parts.push(starts.sort(this.sortLength).join("|"))} if (config.processEnvironments) {parts.push("\\\\begin\\{([^}]*)\\}")} if (config.processEscapes) {parts.push("\\\\*\\\\\\\$")} if (config.processRefs) {parts.push("\\\\(eq)?ref\\{[^}]*\\}")} this.start = new RegExp(parts.join("|"),"g"); this.skipTags = new RegExp("^("+config.skipTags.join("|")+")$","i"); var ignore = []; if (MathJax.Hub.config.preRemoveClass) {ignore.push(MathJax.Hub.config.preRemoveClass)}; if (config.ignoreClass) {ignore.push(config.ignoreClass)} this.ignoreClass = (ignore.length ? new RegExp("(^| )("+ignore.join("|")+")( |$)") : /^$/); this.processClass = new RegExp("(^| )("+config.processClass+")( |$)"); return (parts.length > 0); }, patternQuote: function (s) {return s.replace(/([\^$(){}+*?\-|\[\]\:\\])/g,'\\$1')}, endPattern: function (end) { return new RegExp(this.patternQuote(end)+"|\\\\.|[{}]","g"); }, sortLength: function (a,b) { if (a.length !== b.length) {return b.length - a.length} return (a == b ? 0 : (a < b ? -1 : 1)); }, scanElement: function (element,stop,ignore) { var cname, tname, ignoreChild, process; while (element && element != stop) { if (element.nodeName.toLowerCase() === '#text') { if (!ignore) {element = this.scanText(element)} } else { cname = (typeof(element.className) === "undefined" ? "" : element.className); tname = (typeof(element.tagName) === "undefined" ? "" : element.tagName); if (typeof(cname) !== "string") {cname = String(cname)} // jsxgraph uses non-string class names! process = this.processClass.exec(cname); if (element.firstChild && !cname.match(/(^| )MathJax/) && (process || !this.skipTags.exec(tname))) { ignoreChild = (ignore || this.ignoreClass.exec(cname)) && !process; this.scanElement(element.firstChild,stop,ignoreChild); } } if (element) {element = element.nextSibling} } }, scanText: function (element) { if (element.nodeValue.replace(/\s+/,'') == '') {return element} var match, prev, pos = 0, rescan; this.search = {start: true}; this.pattern = this.start; while (element) { rescan = null; this.pattern.lastIndex = pos; pos = 0; while (element && element.nodeName.toLowerCase() === '#text' && (match = this.pattern.exec(element.nodeValue))) { if (this.search.start) {element = this.startMatch(match,element)} else {element = this.endMatch(match,element)} } if (this.search.matched) element = this.encloseMath(element); else if (!this.search.start) rescan = this.search; if (element) { do {prev = element; element = element.nextSibling} while (element && this.ignoreTags[element.nodeName.toLowerCase()] != null); if (!element || element.nodeName !== '#text') { if (!rescan) return (this.search.close ? this.prevEndMatch() : prev); element = rescan.open; pos = rescan.opos + rescan.olen + (rescan.blen || 0); this.search = {start: true}; this.pattern = this.start; } } } return element; }, startMatch: function (match,element) { var delim = this.match[match[0]]; if (delim != null) { // a start delimiter this.search = { end: delim.end, mode: delim.mode, pcount: 0, open: element, olen: match[0].length, opos: this.pattern.lastIndex - match[0].length }; this.switchPattern(delim.pattern); } else if (match[0].substr(0,6) === "\\begin") { // \begin{...} this.search = { end: "\\end{"+match[1]+"}", mode: "; mode=display", pcount: 0, open: element, olen: 0, opos: this.pattern.lastIndex - match[0].length, blen: match[1].length + 3, isBeginEnd: true }; this.switchPattern(this.endPattern(this.search.end)); } else if (match[0].substr(0,4) === "\\ref" || match[0].substr(0,6) === "\\eqref") { this.search = { mode: "", end: "", open: element, pcount: 0, olen: 0, opos: this.pattern.lastIndex - match[0].length } return this.endMatch([""],element); } else { // escaped dollar signs // put $ in a span so it doesn't get processed again // split off backslashes so they don't get removed later var slashes = match[0].substr(0,match[0].length-1), n, span; if (slashes.length % 2 === 0) {span = [slashes.replace(/\\\\/g,"\\")]; n = 1} else {span = [slashes.substr(1).replace(/\\\\/g,"\\"),"$"]; n = 0} span = MathJax.HTML.Element("span",null,span); var text = MathJax.HTML.TextNode(element.nodeValue.substr(0,match.index)); element.nodeValue = element.nodeValue.substr(match.index + match[0].length - n); element.parentNode.insertBefore(span,element); element.parentNode.insertBefore(text,span); this.pattern.lastIndex = n; } return element; }, endMatch: function (match,element) { var search = this.search; if (match[0] == search.end) { if (!search.close || search.pcount === 0) { search.close = element; search.cpos = this.pattern.lastIndex; search.clen = (search.isBeginEnd ? 0 : match[0].length); } if (search.pcount === 0) { search.matched = true; element = this.encloseMath(element); this.switchPattern(this.start); } } else if (match[0] === "{") {search.pcount++} else if (match[0] === "}" && search.pcount) {search.pcount--} return element; }, prevEndMatch: function () { this.search.matched = true; var element = this.encloseMath(this.search.close); this.switchPattern(this.start); return element; }, switchPattern: function (pattern) { pattern.lastIndex = this.pattern.lastIndex; this.pattern = pattern; this.search.start = (pattern === this.start); }, encloseMath: function (element) { var search = this.search, close = search.close, CLOSE, math, next; if (search.cpos === close.length) {close = close.nextSibling} else {close = close.splitText(search.cpos)} if (!close) {CLOSE = close = MathJax.HTML.addText(search.close.parentNode,"")} search.close = close; math = (search.opos ? search.open.splitText(search.opos) : search.open); while ((next = math.nextSibling) && next !== close) { if (next.nodeValue !== null) { if (next.nodeName === "#comment") { math.nodeValue += next.nodeValue.replace(/^\[CDATA\[((.|\n|\r)*)\]\]$/,"$1"); } else { math.nodeValue += next.nodeValue; } } else { var ignore = this.ignoreTags[next.nodeName.toLowerCase()]; math.nodeValue += (ignore == null ? " " : ignore); } math.parentNode.removeChild(next); } var TeX = math.nodeValue.substr(search.olen,math.nodeValue.length-search.olen-search.clen); math.parentNode.removeChild(math); if (this.config.preview !== "none") {this.createPreview(search.mode,TeX)} math = this.createMathTag(search.mode,TeX); this.search = {}; this.pattern.lastIndex = 0; if (CLOSE) {CLOSE.parentNode.removeChild(CLOSE)} return math; }, insertNode: function (node) { var search = this.search; search.close.parentNode.insertBefore(node,search.close); }, createPreview: function (mode,tex) { var previewClass = MathJax.Hub.config.preRemoveClass; var preview = this.config.preview; if (preview === "none") return; if ((this.search.close.previousSibling||{}).className === previewClass) return; if (preview === "TeX") {preview = [this.filterPreview(tex)]} if (preview) { preview = MathJax.HTML.Element("span",{className:previewClass},preview); this.insertNode(preview); } }, createMathTag: function (mode,tex) { var script = document.createElement("script"); script.type = "math/tex" + mode; MathJax.HTML.setScript(script,tex); this.insertNode(script); return script; }, filterPreview: function (tex) {return tex} }; // We register the preprocessors with the following priorities: // - mml2jax.js: 5 // - jsMath2jax.js: 8 // - asciimath2jax.js, tex2jax.js: 10 (default) // See issues 18 and 484 and the other *2jax.js files. MathJax.Hub.Register.PreProcessor(["PreProcess",MathJax.Extension.tex2jax]); MathJax.Ajax.loadComplete("[MathJax]/extensions/tex2jax.js"); /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/jax/input/TeX/config.js * * Initializes the TeX InputJax (the main definition is in * MathJax/jax/input/TeX/jax.js, which is loaded when needed). * * --------------------------------------------------------------------- * * Copyright (c) 2009-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ MathJax.InputJax.TeX = MathJax.InputJax({ id: "TeX", version: "2.7.5", directory: MathJax.InputJax.directory + "/TeX", extensionDir: MathJax.InputJax.extensionDir + "/TeX", config: { TagSide: "right", TagIndent: "0.8em", MultLineWidth: "85%", equationNumbers: { autoNumber: "none", // "AMS" for standard AMS numbering, // or "all" for all displayed equations formatNumber: function (n) {return n}, formatTag: function (n) {return '('+n+')'}, formatID: function (n) {return 'mjx-eqn-'+String(n).replace(/\s/g,"_")}, formatURL: function (id,base) {return base+'#'+encodeURIComponent(id)}, useLabelIds: true } }, resetEquationNumbers: function () {} // filled in by AMSmath extension }); MathJax.InputJax.TeX.Register("math/tex"); MathJax.InputJax.TeX.loadComplete("config.js"); /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/jax/input/TeX/jax.js * * Implements the TeX InputJax that reads mathematics in * TeX and LaTeX format and converts it to the MML ElementJax * internal format. * * --------------------------------------------------------------------- * * Copyright (c) 2009-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ (function (TEX,HUB,AJAX) { var MML, NBSP = "\u00A0"; var _ = function (id) { return MathJax.Localization._.apply(MathJax.Localization, [["TeX", id]].concat([].slice.call(arguments,1))); }; var isArray = MathJax.Object.isArray; var STACK = MathJax.Object.Subclass({ Init: function (env,inner) { this.global = {isInner: inner}; this.data = [STACKITEM.start(this.global)]; if (env) {this.data[0].env = env} this.env = this.data[0].env; }, Push: function () { var i, m, item, top; for (i = 0, m = arguments.length; i < m; i++) { item = arguments[i]; if (!item) continue; if (item instanceof MML.mbase) {item = STACKITEM.mml(item)} item.global = this.global; top = (this.data.length ? this.Top().checkItem(item) : true); if (top instanceof Array) {this.Pop(); this.Push.apply(this,top)} else if (top instanceof STACKITEM) {this.Pop(); this.Push(top)} else if (top) { this.data.push(item); if (item.env) { if (item.copyEnv !== false) { for (var id in this.env) {if (this.env.hasOwnProperty(id)) {item.env[id] = this.env[id]}} } this.env = item.env; } else {item.env = this.env} } } }, Pop: function () { var item = this.data.pop(); if (!item.isOpen) {delete item.env} this.env = (this.data.length ? this.Top().env : {}); return item; }, Top: function (n) { if (n == null) {n = 1} if (this.data.length < n) {return null} return this.data[this.data.length-n]; }, Prev: function (noPop) { var top = this.Top(); if (noPop) {return top.data[top.data.length-1]} else {return top.Pop()} }, toString: function () {return "stack[\n "+this.data.join("\n ")+"\n]"} }); var STACKITEM = STACK.Item = MathJax.Object.Subclass({ type: "base", endError: /*_()*/ ["ExtraOpenMissingClose","Extra open brace or missing close brace"], closeError: /*_()*/ ["ExtraCloseMissingOpen","Extra close brace or missing open brace"], rightError: /*_()*/ ["MissingLeftExtraRight","Missing \\left or extra \\right"], Init: function () { if (this.isOpen) {this.env = {}} this.data = []; this.Push.apply(this,arguments); }, Push: function () {this.data.push.apply(this.data,arguments)}, Pop: function () {return this.data.pop()}, mmlData: function (inferred,forceRow) { if (inferred == null) {inferred = true} if (this.data.length === 1 && !forceRow) {return this.data[0]} return MML.mrow.apply(MML,this.data).With((inferred ? {inferred: true}: {})); }, checkItem: function (item) { if (item.type === "over" && this.isOpen) {item.num = this.mmlData(false); this.data = []} if (item.type === "cell" && this.isOpen) { if (item.linebreak) {return false} TEX.Error(["Misplaced","Misplaced %1",item.name]); } if (item.isClose && this[item.type+"Error"]) {TEX.Error(this[item.type+"Error"])} if (!item.isNotStack) {return true} this.Push(item.data[0]); return false; }, With: function (def) { for (var id in def) {if (def.hasOwnProperty(id)) {this[id] = def[id]}} return this; }, toString: function () {return this.type+"["+this.data.join("; ")+"]"} }); STACKITEM.start = STACKITEM.Subclass({ type: "start", isOpen: true, Init: function (global) { this.SUPER(arguments).Init.call(this); this.global = global; }, checkItem: function (item) { if (item.type === "stop") {return STACKITEM.mml(this.mmlData())} return this.SUPER(arguments).checkItem.call(this,item); } }); STACKITEM.stop = STACKITEM.Subclass({ type: "stop", isClose: true }); STACKITEM.open = STACKITEM.Subclass({ type: "open", isOpen: true, stopError: /*_()*/ ["ExtraOpenMissingClose","Extra open brace or missing close brace"], checkItem: function (item) { if (item.type === "close") { var mml = this.mmlData(); return STACKITEM.mml(MML.TeXAtom(mml)); // TeXAtom make it an ORD to prevent spacing (FIXME: should be another way) } return this.SUPER(arguments).checkItem.call(this,item); } }); STACKITEM.close = STACKITEM.Subclass({ type: "close", isClose: true }); STACKITEM.prime = STACKITEM.Subclass({ type: "prime", checkItem: function (item) { if (this.data[0].type !== "msubsup") {return [MML.msup(this.data[0],this.data[1]),item]} this.data[0].SetData(this.data[0].sup,this.data[1]); return [this.data[0],item]; } }); STACKITEM.subsup = STACKITEM.Subclass({ type: "subsup", stopError: /*_()*/ ["MissingScript","Missing superscript or subscript argument"], supError: /*_()*/ ["MissingOpenForSup","Missing open brace for superscript"], subError: /*_()*/ ["MissingOpenForSub","Missing open brace for subscript"], checkItem: function (item) { if (item.type === "open" || item.type === "left") {return true} if (item.type === "mml") { if (this.primes) { if (this.position !== 2) {this.data[0].SetData(2,this.primes)} else {item.data[0] = MML.mrow(this.primes.With({variantForm:true}),item.data[0])} } this.data[0].SetData(this.position,item.data[0]); if (this.movesupsub != null) {this.data[0].movesupsub = this.movesupsub} return STACKITEM.mml(this.data[0]); } if (this.SUPER(arguments).checkItem.call(this,item)) {TEX.Error(this[["","subError","supError"][this.position]])} }, Pop: function () {} }); STACKITEM.over = STACKITEM.Subclass({ type: "over", isClose: true, name: "\\over", checkItem: function (item,stack) { if (item.type === "over") {TEX.Error(["AmbiguousUseOf","Ambiguous use of %1",item.name])} if (item.isClose) { var mml = MML.mfrac(this.num,this.mmlData(false)); if (this.thickness != null) {mml.linethickness = this.thickness} if (this.open || this.close) { mml.texWithDelims = true; mml = TEX.fixedFence(this.open,mml,this.close); } return [STACKITEM.mml(mml), item]; } return this.SUPER(arguments).checkItem.call(this,item); }, toString: function () {return "over["+this.num+" / "+this.data.join("; ")+"]"} }); STACKITEM.left = STACKITEM.Subclass({ type: "left", isOpen: true, delim: '(', stopError: /*_()*/ ["ExtraLeftMissingRight", "Extra \\left or missing \\right"], checkItem: function (item) { if (item.type === "right") {return STACKITEM.mml(TEX.fenced(this.delim,this.mmlData(),item.delim))} return this.SUPER(arguments).checkItem.call(this,item); } }); STACKITEM.right = STACKITEM.Subclass({ type: "right", isClose: true, delim: ')' }); STACKITEM.begin = STACKITEM.Subclass({ type: "begin", isOpen: true, checkItem: function (item) { if (item.type === "end") { if (item.name !== this.name) {TEX.Error(["EnvBadEnd","\\begin{%1} ended with \\end{%2}",this.name,item.name])} if (!this.end) {return STACKITEM.mml(this.mmlData())} return this.parse[this.end].call(this.parse,this,this.data); } if (item.type === "stop") {TEX.Error(["EnvMissingEnd","Missing \\end{%1}",this.name])} return this.SUPER(arguments).checkItem.call(this,item); } }); STACKITEM.end = STACKITEM.Subclass({ type: "end", isClose: true }); STACKITEM.style = STACKITEM.Subclass({ type: "style", checkItem: function (item) { if (!item.isClose) {return this.SUPER(arguments).checkItem.call(this,item)} var mml = MML.mstyle.apply(MML,this.data).With(this.styles); return [STACKITEM.mml(mml),item]; } }); STACKITEM.position = STACKITEM.Subclass({ type: "position", checkItem: function (item) { if (item.isClose) {TEX.Error(["MissingBoxFor","Missing box for %1",this.name])} if (item.isNotStack) { var mml = item.mmlData(); switch (this.move) { case 'vertical': mml = MML.mpadded(mml).With({height: this.dh, depth: this.dd, voffset: this.dh}); return [STACKITEM.mml(mml)]; case 'horizontal': return [STACKITEM.mml(this.left),item,STACKITEM.mml(this.right)]; } } return this.SUPER(arguments).checkItem.call(this,item); } }); STACKITEM.array = STACKITEM.Subclass({ type: "array", isOpen: true, copyEnv: false, arraydef: {}, Init: function () { this.table = []; this.row = []; this.frame = []; this.hfill = []; this.SUPER(arguments).Init.apply(this,arguments); }, checkItem: function (item) { if (item.isClose && item.type !== "over") { if (item.isEntry) {this.EndEntry(); this.clearEnv(); return false} if (item.isCR) {this.EndEntry(); this.EndRow(); this.clearEnv(); return false} this.EndTable(); this.clearEnv(); var scriptlevel = this.arraydef.scriptlevel; delete this.arraydef.scriptlevel; var mml = MML.mtable.apply(MML,this.table).With(this.arraydef); if (this.frame.length === 4) { mml.frame = (this.frame.dashed ? "dashed" : "solid"); } else if (this.frame.length) { mml.hasFrame = true; if (this.arraydef.rowlines) {this.arraydef.rowlines = this.arraydef.rowlines.replace(/none( none)+$/,"none")} mml = MML.menclose(mml).With({notation: this.frame.join(" "), isFrame: true}); if ((this.arraydef.columnlines||"none") != "none" || (this.arraydef.rowlines||"none") != "none") {mml.padding = 0} // HTML-CSS jax implements this } if (scriptlevel) {mml = MML.mstyle(mml).With({scriptlevel: scriptlevel})} if (this.open || this.close) {mml = TEX.fenced(this.open,mml,this.close)} mml = STACKITEM.mml(mml); if (this.requireClose) { if (item.type === 'close') {return mml} TEX.Error(["MissingCloseBrace","Missing close brace"]); } return [mml,item]; } return this.SUPER(arguments).checkItem.call(this,item); }, EndEntry: function () { var mtd = MML.mtd.apply(MML,this.data); if (this.hfill.length) { if (this.hfill[0] === 0) mtd.columnalign = "right"; if (this.hfill[this.hfill.length-1] === this.data.length) mtd.columnalign = (mtd.columnalign ? "center" : "left"); } this.row.push(mtd); this.data = []; this.hfill = []; }, EndRow: function () { var mtr = MML.mtr; if (this.isNumbered && this.row.length === 3) { this.row.unshift(this.row.pop()); // move equation number to first position mtr = MML.mlabeledtr; } this.table.push(mtr.apply(MML,this.row)); this.row = []; }, EndTable: function () { if (this.data.length || this.row.length) {this.EndEntry(); this.EndRow()} this.checkLines(); }, checkLines: function () { if (this.arraydef.rowlines) { var lines = this.arraydef.rowlines.split(/ /); if (lines.length === this.table.length) { this.frame.push("bottom"); lines.pop(); this.arraydef.rowlines = lines.join(' '); } else if (lines.length < this.table.length-1) { this.arraydef.rowlines += " none"; } } if (this.rowspacing) { var rows = this.arraydef.rowspacing.split(/ /); while (rows.length < this.table.length) {rows.push(this.rowspacing+"em")} this.arraydef.rowspacing = rows.join(' '); } }, clearEnv: function () { for (var id in this.env) {if (this.env.hasOwnProperty(id)) {delete this.env[id]}} } }); STACKITEM.cell = STACKITEM.Subclass({ type: "cell", isClose: true }); STACKITEM.mml = STACKITEM.Subclass({ type: "mml", isNotStack: true, Add: function () {this.data.push.apply(this.data,arguments); return this} }); STACKITEM.fn = STACKITEM.Subclass({ type: "fn", checkItem: function (item) { if (this.data[0]) { if (item.isOpen) {return true} if (item.type !== "fn") { if (item.type !== "mml" || !item.data[0]) {return [this.data[0],item]} if (item.data[0].isa(MML.mspace)) {return [this.data[0],item]} var mml = item.data[0]; if (mml.isEmbellished()) {mml = mml.CoreMO()} if ([0,0,1,1,0,1,1,0,0,0][mml.Get("texClass")]) {return [this.data[0],item]} } return [this.data[0],MML.mo(MML.entity("#x2061")).With({texClass:MML.TEXCLASS.NONE}),item]; } return this.SUPER(arguments).checkItem.apply(this,arguments); } }); STACKITEM.not = STACKITEM.Subclass({ type: "not", checkItem: function (item) { var mml, c; if (item.type === "open" || item.type === "left") {return true} if (item.type === "mml" && item.data[0].type.match(/^(mo|mi|mtext)$/)) { mml = item.data[0], c = mml.data.join(""); if (c.length === 1 && !mml.movesupsub && mml.data.length === 1) { c = STACKITEM.not.remap[c.charCodeAt(0)]; if (c) {mml.SetData(0,MML.chars(String.fromCharCode(c)))} else {mml.Append(MML.chars("\u0338"))} return item; } } // \mathrel{\rlap{\notChar}} mml = MML.mpadded(MML.mtext("\u29F8")).With({width:0}); mml = MML.TeXAtom(mml).With({texClass:MML.TEXCLASS.REL}); return [mml,item]; } }); STACKITEM.not.remap = { 0x2190:0x219A, 0x2192:0x219B, 0x2194:0x21AE, 0x21D0:0x21CD, 0x21D2:0x21CF, 0x21D4:0x21CE, 0x2208:0x2209, 0x220B:0x220C, 0x2223:0x2224, 0x2225:0x2226, 0x223C:0x2241, 0x007E:0x2241, 0x2243:0x2244, 0x2245:0x2247, 0x2248:0x2249, 0x224D:0x226D, 0x003D:0x2260, 0x2261:0x2262, 0x003C:0x226E, 0x003E:0x226F, 0x2264:0x2270, 0x2265:0x2271, 0x2272:0x2274, 0x2273:0x2275, 0x2276:0x2278, 0x2277:0x2279, 0x227A:0x2280, 0x227B:0x2281, 0x2282:0x2284, 0x2283:0x2285, 0x2286:0x2288, 0x2287:0x2289, 0x22A2:0x22AC, 0x22A8:0x22AD, 0x22A9:0x22AE, 0x22AB:0x22AF, 0x227C:0x22E0, 0x227D:0x22E1, 0x2291:0x22E2, 0x2292:0x22E3, 0x22B2:0x22EA, 0x22B3:0x22EB, 0x22B4:0x22EC, 0x22B5:0x22ED, 0x2203:0x2204 }; STACKITEM.dots = STACKITEM.Subclass({ type: "dots", checkItem: function (item) { if (item.type === "open" || item.type === "left") {return true} var dots = this.ldots; if (item.type === "mml" && item.data[0].isEmbellished()) { var tclass = item.data[0].CoreMO().Get("texClass"); if (tclass === MML.TEXCLASS.BIN || tclass === MML.TEXCLASS.REL) {dots = this.cdots} } return [dots,item]; } }); var TEXDEF = { // // Add new definitions without overriding user-defined ones // Add: function (src,dst,nouser) { if (!dst) {dst = this} for (var id in src) {if (src.hasOwnProperty(id)) { if (typeof src[id] === 'object' && !isArray(src[id]) && (typeof dst[id] === 'object' || typeof dst[id] === 'function')) {this.Add(src[id],dst[id],src[id],nouser)} else if (!dst[id] || !dst[id].isUser || !nouser) {dst[id] = src[id]} }} return dst; } }; var STARTUP = function () { MML = MathJax.ElementJax.mml; HUB.Insert(TEXDEF,{ // patterns for letters and numbers letter: /[a-z]/i, digit: /[0-9.]/, number: /^(?:[0-9]+(?:\{,\}[0-9]{3})*(?:\.[0-9]*)*|\.[0-9]+)/, special: { '\\': 'ControlSequence', '{': 'Open', '}': 'Close', '~': 'Tilde', '^': 'Superscript', '_': 'Subscript', ' ': 'Space', "\t": 'Space', "\r": 'Space', "\n": 'Space', "'": 'Prime', '%': 'Comment', '&': 'Entry', '#': 'Hash', '\u00A0': 'Space', '\u2019': 'Prime' }, remap: { '-': '2212', '*': '2217', '`': '2018' // map ` to back quote }, mathchar0mi: { // Lower-case greek alpha: '03B1', beta: '03B2', gamma: '03B3', delta: '03B4', epsilon: '03F5', zeta: '03B6', eta: '03B7', theta: '03B8', iota: '03B9', kappa: '03BA', lambda: '03BB', mu: '03BC', nu: '03BD', xi: '03BE', omicron: '03BF', // added for completeness pi: '03C0', rho: '03C1', sigma: '03C3', tau: '03C4', upsilon: '03C5', phi: '03D5', chi: '03C7', psi: '03C8', omega: '03C9', varepsilon: '03B5', vartheta: '03D1', varpi: '03D6', varrho: '03F1', varsigma: '03C2', varphi: '03C6', // Ord symbols S: ['00A7',{mathvariant: MML.VARIANT.NORMAL}], aleph: ['2135',{mathvariant: MML.VARIANT.NORMAL}], hbar: ['210F',{variantForm:true}], imath: '0131', jmath: '0237', ell: '2113', wp: ['2118',{mathvariant: MML.VARIANT.NORMAL}], Re: ['211C',{mathvariant: MML.VARIANT.NORMAL}], Im: ['2111',{mathvariant: MML.VARIANT.NORMAL}], partial: ['2202',{mathvariant: MML.VARIANT.NORMAL}], infty: ['221E',{mathvariant: MML.VARIANT.NORMAL}], prime: ['2032',{mathvariant: MML.VARIANT.NORMAL, variantForm:true}], emptyset: ['2205',{mathvariant: MML.VARIANT.NORMAL}], nabla: ['2207',{mathvariant: MML.VARIANT.NORMAL}], top: ['22A4',{mathvariant: MML.VARIANT.NORMAL}], bot: ['22A5',{mathvariant: MML.VARIANT.NORMAL}], angle: ['2220',{mathvariant: MML.VARIANT.NORMAL}], triangle: ['25B3',{mathvariant: MML.VARIANT.NORMAL}], backslash: ['2216',{mathvariant: MML.VARIANT.NORMAL, variantForm:true}], forall: ['2200',{mathvariant: MML.VARIANT.NORMAL}], exists: ['2203',{mathvariant: MML.VARIANT.NORMAL}], neg: ['00AC',{mathvariant: MML.VARIANT.NORMAL}], lnot: ['00AC',{mathvariant: MML.VARIANT.NORMAL}], flat: ['266D',{mathvariant: MML.VARIANT.NORMAL}], natural: ['266E',{mathvariant: MML.VARIANT.NORMAL}], sharp: ['266F',{mathvariant: MML.VARIANT.NORMAL}], clubsuit: ['2663',{mathvariant: MML.VARIANT.NORMAL}], diamondsuit: ['2662',{mathvariant: MML.VARIANT.NORMAL}], heartsuit: ['2661',{mathvariant: MML.VARIANT.NORMAL}], spadesuit: ['2660',{mathvariant: MML.VARIANT.NORMAL}] }, mathchar0mo: { surd: '221A', // big ops coprod: ['2210',{texClass: MML.TEXCLASS.OP, movesupsub:true}], bigvee: ['22C1',{texClass: MML.TEXCLASS.OP, movesupsub:true}], bigwedge: ['22C0',{texClass: MML.TEXCLASS.OP, movesupsub:true}], biguplus: ['2A04',{texClass: MML.TEXCLASS.OP, movesupsub:true}], bigcap: ['22C2',{texClass: MML.TEXCLASS.OP, movesupsub:true}], bigcup: ['22C3',{texClass: MML.TEXCLASS.OP, movesupsub:true}], 'int': ['222B',{texClass: MML.TEXCLASS.OP}], intop: ['222B',{texClass: MML.TEXCLASS.OP, movesupsub:true, movablelimits:true}], iint: ['222C',{texClass: MML.TEXCLASS.OP}], iiint: ['222D',{texClass: MML.TEXCLASS.OP}], prod: ['220F',{texClass: MML.TEXCLASS.OP, movesupsub:true}], sum: ['2211',{texClass: MML.TEXCLASS.OP, movesupsub:true}], bigotimes: ['2A02',{texClass: MML.TEXCLASS.OP, movesupsub:true}], bigoplus: ['2A01',{texClass: MML.TEXCLASS.OP, movesupsub:true}], bigodot: ['2A00',{texClass: MML.TEXCLASS.OP, movesupsub:true}], oint: ['222E',{texClass: MML.TEXCLASS.OP}], bigsqcup: ['2A06',{texClass: MML.TEXCLASS.OP, movesupsub:true}], smallint: ['222B',{largeop:false}], // binary operations triangleleft: '25C3', triangleright: '25B9', bigtriangleup: '25B3', bigtriangledown: '25BD', wedge: '2227', land: '2227', vee: '2228', lor: '2228', cap: '2229', cup: '222A', ddagger: '2021', dagger: '2020', sqcap: '2293', sqcup: '2294', uplus: '228E', amalg: '2A3F', diamond: '22C4', bullet: '2219', wr: '2240', div: '00F7', odot: ['2299',{largeop: false}], oslash: ['2298',{largeop: false}], otimes: ['2297',{largeop: false}], ominus: ['2296',{largeop: false}], oplus: ['2295',{largeop: false}], mp: '2213', pm: '00B1', circ: '2218', bigcirc: '25EF', setminus: ['2216',{variantForm:true}], cdot: '22C5', ast: '2217', times: '00D7', star: '22C6', // Relations propto: '221D', sqsubseteq: '2291', sqsupseteq: '2292', parallel: '2225', mid: '2223', dashv: '22A3', vdash: '22A2', leq: '2264', le: '2264', geq: '2265', ge: '2265', lt: '003C', gt: '003E', succ: '227B', prec: '227A', approx: '2248', succeq: '2AB0', // or '227C', preceq: '2AAF', // or '227D', supset: '2283', subset: '2282', supseteq: '2287', subseteq: '2286', 'in': '2208', ni: '220B', notin: '2209', owns: '220B', gg: '226B', ll: '226A', sim: '223C', simeq: '2243', perp: '22A5', equiv: '2261', asymp: '224D', smile: '2323', frown: '2322', ne: '2260', neq: '2260', cong: '2245', doteq: '2250', bowtie: '22C8', models: '22A8', notChar: '29F8', // Arrows Leftrightarrow: '21D4', Leftarrow: '21D0', Rightarrow: '21D2', leftrightarrow: '2194', leftarrow: '2190', gets: '2190', rightarrow: '2192', to: '2192', mapsto: '21A6', leftharpoonup: '21BC', leftharpoondown: '21BD', rightharpoonup: '21C0', rightharpoondown: '21C1', nearrow: '2197', searrow: '2198', nwarrow: '2196', swarrow: '2199', rightleftharpoons: '21CC', hookrightarrow: '21AA', hookleftarrow: '21A9', longleftarrow: '27F5', Longleftarrow: '27F8', longrightarrow: '27F6', Longrightarrow: '27F9', Longleftrightarrow: '27FA', longleftrightarrow: '27F7', longmapsto: '27FC', // Misc. ldots: '2026', cdots: '22EF', vdots: '22EE', ddots: '22F1', dotsc: '2026', // dots with commas dotsb: '22EF', // dots with binary ops and relations dotsm: '22EF', // dots with multiplication dotsi: '22EF', // dots with integrals dotso: '2026', // other dots ldotp: ['002E', {texClass: MML.TEXCLASS.PUNCT}], cdotp: ['22C5', {texClass: MML.TEXCLASS.PUNCT}], colon: ['003A', {texClass: MML.TEXCLASS.PUNCT}] }, mathchar7: { Gamma: '0393', Delta: '0394', Theta: '0398', Lambda: '039B', Xi: '039E', Pi: '03A0', Sigma: '03A3', Upsilon: '03A5', Phi: '03A6', Psi: '03A8', Omega: '03A9', '_': '005F', '#': '0023', '$': '0024', '%': '0025', '&': '0026', And: '0026' }, delimiter: { '(': '(', ')': ')', '[': '[', ']': ']', '<': '27E8', '>': '27E9', '\\lt': '27E8', '\\gt': '27E9', '/': '/', '|': ['|',{texClass:MML.TEXCLASS.ORD}], '.': '', '\\\\': '\\', '\\lmoustache': '23B0', // non-standard '\\rmoustache': '23B1', // non-standard '\\lgroup': '27EE', // non-standard '\\rgroup': '27EF', // non-standard '\\arrowvert': '23D0', '\\Arrowvert': '2016', '\\bracevert': '23AA', // non-standard '\\Vert': ['2016',{texClass:MML.TEXCLASS.ORD}], '\\|': ['2016',{texClass:MML.TEXCLASS.ORD}], '\\vert': ['|',{texClass:MML.TEXCLASS.ORD}], '\\uparrow': '2191', '\\downarrow': '2193', '\\updownarrow': '2195', '\\Uparrow': '21D1', '\\Downarrow': '21D3', '\\Updownarrow': '21D5', '\\backslash': '\\', '\\rangle': '27E9', '\\langle': '27E8', '\\rbrace': '}', '\\lbrace': '{', '\\}': '}', '\\{': '{', '\\rceil': '2309', '\\lceil': '2308', '\\rfloor': '230B', '\\lfloor': '230A', '\\lbrack': '[', '\\rbrack': ']' }, macros: { displaystyle: ['SetStyle','D',true,0], textstyle: ['SetStyle','T',false,0], scriptstyle: ['SetStyle','S',false,1], scriptscriptstyle: ['SetStyle','SS',false,2], rm: ['SetFont',MML.VARIANT.NORMAL], mit: ['SetFont',MML.VARIANT.ITALIC], oldstyle: ['SetFont',MML.VARIANT.OLDSTYLE], cal: ['SetFont',MML.VARIANT.CALIGRAPHIC], it: ['SetFont',"-tex-mathit"], // needs special handling bf: ['SetFont',MML.VARIANT.BOLD], bbFont: ['SetFont',MML.VARIANT.DOUBLESTRUCK], scr: ['SetFont',MML.VARIANT.SCRIPT], frak: ['SetFont',MML.VARIANT.FRAKTUR], sf: ['SetFont',MML.VARIANT.SANSSERIF], tt: ['SetFont',MML.VARIANT.MONOSPACE], // font: tiny: ['SetSize',0.5], Tiny: ['SetSize',0.6], // non-standard scriptsize: ['SetSize',0.7], small: ['SetSize',0.85], normalsize: ['SetSize',1.0], large: ['SetSize',1.2], Large: ['SetSize',1.44], LARGE: ['SetSize',1.73], huge: ['SetSize',2.07], Huge: ['SetSize',2.49], arcsin: ['NamedFn'], arccos: ['NamedFn'], arctan: ['NamedFn'], arg: ['NamedFn'], cos: ['NamedFn'], cosh: ['NamedFn'], cot: ['NamedFn'], coth: ['NamedFn'], csc: ['NamedFn'], deg: ['NamedFn'], det: 'NamedOp', dim: ['NamedFn'], exp: ['NamedFn'], gcd: 'NamedOp', hom: ['NamedFn'], inf: 'NamedOp', ker: ['NamedFn'], lg: ['NamedFn'], lim: 'NamedOp', liminf: ['NamedOp','lim inf'], limsup: ['NamedOp','lim sup'], ln: ['NamedFn'], log: ['NamedFn'], max: 'NamedOp', min: 'NamedOp', Pr: 'NamedOp', sec: ['NamedFn'], sin: ['NamedFn'], sinh: ['NamedFn'], sup: 'NamedOp', tan: ['NamedFn'], tanh: ['NamedFn'], limits: ['Limits',1], nolimits: ['Limits',0], overline: ['UnderOver','00AF',null,1], underline: ['UnderOver','005F'], overbrace: ['UnderOver','23DE',1], underbrace: ['UnderOver','23DF',1], overparen: ['UnderOver','23DC'], underparen: ['UnderOver','23DD'], overrightarrow: ['UnderOver','2192'], underrightarrow: ['UnderOver','2192'], overleftarrow: ['UnderOver','2190'], underleftarrow: ['UnderOver','2190'], overleftrightarrow: ['UnderOver','2194'], underleftrightarrow: ['UnderOver','2194'], overset: 'Overset', underset: 'Underset', stackrel: ['Macro','\\mathrel{\\mathop{#2}\\limits^{#1}}',2], over: 'Over', overwithdelims: 'Over', atop: 'Over', atopwithdelims: 'Over', above: 'Over', abovewithdelims: 'Over', brace: ['Over','{','}'], brack: ['Over','[',']'], choose: ['Over','(',')'], frac: 'Frac', sqrt: 'Sqrt', root: 'Root', uproot: ['MoveRoot','upRoot'], leftroot: ['MoveRoot','leftRoot'], left: 'LeftRight', right: 'LeftRight', middle: 'Middle', llap: 'Lap', rlap: 'Lap', raise: 'RaiseLower', lower: 'RaiseLower', moveleft: 'MoveLeftRight', moveright: 'MoveLeftRight', ',': ['Spacer',MML.LENGTH.THINMATHSPACE], ':': ['Spacer',MML.LENGTH.MEDIUMMATHSPACE], // for LaTeX '>': ['Spacer',MML.LENGTH.MEDIUMMATHSPACE], ';': ['Spacer',MML.LENGTH.THICKMATHSPACE], '!': ['Spacer',MML.LENGTH.NEGATIVETHINMATHSPACE], enspace: ['Spacer',".5em"], quad: ['Spacer',"1em"], qquad: ['Spacer',"2em"], thinspace: ['Spacer',MML.LENGTH.THINMATHSPACE], negthinspace: ['Spacer',MML.LENGTH.NEGATIVETHINMATHSPACE], hskip: 'Hskip', hspace: 'Hskip', kern: 'Hskip', mskip: 'Hskip', mspace: 'Hskip', mkern: 'Hskip', rule: 'rule', Rule: ['Rule'], Space: ['Rule','blank'], big: ['MakeBig',MML.TEXCLASS.ORD,0.85], Big: ['MakeBig',MML.TEXCLASS.ORD,1.15], bigg: ['MakeBig',MML.TEXCLASS.ORD,1.45], Bigg: ['MakeBig',MML.TEXCLASS.ORD,1.75], bigl: ['MakeBig',MML.TEXCLASS.OPEN,0.85], Bigl: ['MakeBig',MML.TEXCLASS.OPEN,1.15], biggl: ['MakeBig',MML.TEXCLASS.OPEN,1.45], Biggl: ['MakeBig',MML.TEXCLASS.OPEN,1.75], bigr: ['MakeBig',MML.TEXCLASS.CLOSE,0.85], Bigr: ['MakeBig',MML.TEXCLASS.CLOSE,1.15], biggr: ['MakeBig',MML.TEXCLASS.CLOSE,1.45], Biggr: ['MakeBig',MML.TEXCLASS.CLOSE,1.75], bigm: ['MakeBig',MML.TEXCLASS.REL,0.85], Bigm: ['MakeBig',MML.TEXCLASS.REL,1.15], biggm: ['MakeBig',MML.TEXCLASS.REL,1.45], Biggm: ['MakeBig',MML.TEXCLASS.REL,1.75], mathord: ['TeXAtom',MML.TEXCLASS.ORD], mathop: ['TeXAtom',MML.TEXCLASS.OP], mathopen: ['TeXAtom',MML.TEXCLASS.OPEN], mathclose: ['TeXAtom',MML.TEXCLASS.CLOSE], mathbin: ['TeXAtom',MML.TEXCLASS.BIN], mathrel: ['TeXAtom',MML.TEXCLASS.REL], mathpunct: ['TeXAtom',MML.TEXCLASS.PUNCT], mathinner: ['TeXAtom',MML.TEXCLASS.INNER], vcenter: ['TeXAtom',MML.TEXCLASS.VCENTER], mathchoice: ['Extension','mathchoice'], buildrel: 'BuildRel', hbox: ['HBox',0], text: 'HBox', mbox: ['HBox',0], fbox: 'FBox', strut: 'Strut', mathstrut: ['Macro','\\vphantom{(}'], phantom: 'Phantom', vphantom: ['Phantom',1,0], hphantom: ['Phantom',0,1], smash: 'Smash', acute: ['Accent', "00B4"], // or 0301 or 02CA grave: ['Accent', "0060"], // or 0300 or 02CB ddot: ['Accent', "00A8"], // or 0308 tilde: ['Accent', "007E"], // or 0303 or 02DC bar: ['Accent', "00AF"], // or 0304 or 02C9 breve: ['Accent', "02D8"], // or 0306 check: ['Accent', "02C7"], // or 030C hat: ['Accent', "005E"], // or 0302 or 02C6 vec: ['Accent', "2192"], // or 20D7 dot: ['Accent', "02D9"], // or 0307 widetilde: ['Accent', "007E",1], // or 0303 or 02DC widehat: ['Accent', "005E",1], // or 0302 or 02C6 matrix: 'Matrix', array: 'Matrix', pmatrix: ['Matrix','(',')'], cases: ['Matrix','{','',"left left",null,".1em",null,true], eqalign: ['Matrix',null,null,"right left",MML.LENGTH.THICKMATHSPACE,".5em",'D'], displaylines: ['Matrix',null,null,"center",null,".5em",'D'], cr: 'Cr', '\\': 'CrLaTeX', newline: 'Cr', hline: ['HLine','solid'], hdashline: ['HLine','dashed'], // noalign: 'HandleNoAlign', eqalignno: ['Matrix',null,null,"right left",MML.LENGTH.THICKMATHSPACE,".5em",'D',null,"right"], leqalignno: ['Matrix',null,null,"right left",MML.LENGTH.THICKMATHSPACE,".5em",'D',null,"left"], hfill: 'HFill', hfil: 'HFill', // \hfil treated as \hfill for now hfilll: 'HFill', // \hfilll treated as \hfill for now // TeX substitution macros bmod: ['Macro','\\mmlToken{mo}[lspace="thickmathspace" rspace="thickmathspace"]{mod}'], pmod: ['Macro','\\pod{\\mmlToken{mi}{mod}\\kern 6mu #1}',1], mod: ['Macro','\\mathchoice{\\kern18mu}{\\kern12mu}{\\kern12mu}{\\kern12mu}\\mmlToken{mi}{mod}\\,\\,#1',1], pod: ['Macro','\\mathchoice{\\kern18mu}{\\kern8mu}{\\kern8mu}{\\kern8mu}(#1)',1], iff: ['Macro','\\;\\Longleftrightarrow\\;'], skew: ['Macro','{{#2{#3\\mkern#1mu}\\mkern-#1mu}{}}',3], mathcal: ['Macro','{\\cal #1}',1], mathscr: ['Macro','{\\scr #1}',1], mathrm: ['Macro','{\\rm #1}',1], mathbf: ['Macro','{\\bf #1}',1], mathbb: ['Macro','{\\bbFont #1}',1], Bbb: ['Macro','{\\bbFont #1}',1], mathit: ['Macro','{\\it #1}',1], mathfrak: ['Macro','{\\frak #1}',1], mathsf: ['Macro','{\\sf #1}',1], mathtt: ['Macro','{\\tt #1}',1], textrm: ['Macro','\\mathord{\\rm\\text{#1}}',1], textit: ['Macro','\\mathord{\\it\\text{#1}}',1], textbf: ['Macro','\\mathord{\\bf\\text{#1}}',1], textsf: ['Macro','\\mathord{\\sf\\text{#1}}',1], texttt: ['Macro','\\mathord{\\tt\\text{#1}}',1], pmb: ['Macro','\\rlap{#1}\\kern1px{#1}',1], TeX: ['Macro','T\\kern-.14em\\lower.5ex{E}\\kern-.115em X'], LaTeX: ['Macro','L\\kern-.325em\\raise.21em{\\scriptstyle{A}}\\kern-.17em\\TeX'], ' ': ['Macro','\\text{ }'], // Specially handled not: 'Not', dots: 'Dots', space: 'Tilde', '\u00A0': 'Tilde', // LaTeX begin: 'BeginEnd', end: 'BeginEnd', newcommand: ['Extension','newcommand'], renewcommand: ['Extension','newcommand'], newenvironment: ['Extension','newcommand'], renewenvironment: ['Extension','newcommand'], def: ['Extension','newcommand'], 'let': ['Extension','newcommand'], verb: ['Extension','verb'], boldsymbol: ['Extension','boldsymbol'], tag: ['Extension','AMSmath'], notag: ['Extension','AMSmath'], label: ['Extension','AMSmath'], ref: ['Extension','AMSmath'], eqref: ['Extension','AMSmath'], nonumber: ['Macro','\\notag'], // Extensions to TeX unicode: ['Extension','unicode'], color: 'Color', href: ['Extension','HTML'], 'class': ['Extension','HTML'], style: ['Extension','HTML'], cssId: ['Extension','HTML'], bbox: ['Extension','bbox'], mmlToken: 'MmlToken', require: 'Require' }, environment: { array: ['AlignedArray'], matrix: ['Array',null,null,null,'c'], pmatrix: ['Array',null,'(',')','c'], bmatrix: ['Array',null,'[',']','c'], Bmatrix: ['Array',null,'\\{','\\}','c'], vmatrix: ['Array',null,'\\vert','\\vert','c'], Vmatrix: ['Array',null,'\\Vert','\\Vert','c'], cases: ['Array',null,'\\{','.','ll',null,".2em",'T'], equation: [null,'Equation'], 'equation*': [null,'Equation'], eqnarray: ['ExtensionEnv',null,'AMSmath'], 'eqnarray*': ['ExtensionEnv',null,'AMSmath'], align: ['ExtensionEnv',null,'AMSmath'], 'align*': ['ExtensionEnv',null,'AMSmath'], aligned: ['ExtensionEnv',null,'AMSmath'], multline: ['ExtensionEnv',null,'AMSmath'], 'multline*': ['ExtensionEnv',null,'AMSmath'], split: ['ExtensionEnv',null,'AMSmath'], gather: ['ExtensionEnv',null,'AMSmath'], 'gather*': ['ExtensionEnv',null,'AMSmath'], gathered: ['ExtensionEnv',null,'AMSmath'], alignat: ['ExtensionEnv',null,'AMSmath'], 'alignat*': ['ExtensionEnv',null,'AMSmath'], alignedat: ['ExtensionEnv',null,'AMSmath'] }, p_height: 1.2 / .85 // cmex10 height plus depth over .85 }); // // Add macros defined in the configuration // if (this.config.Macros) { var MACROS = this.config.Macros; for (var id in MACROS) {if (MACROS.hasOwnProperty(id)) { if (typeof(MACROS[id]) === "string") {TEXDEF.macros[id] = ['Macro',MACROS[id]]} else {TEXDEF.macros[id] = ["Macro"].concat(MACROS[id])} TEXDEF.macros[id].isUser = true; }} } }; /************************************************************************/ /* * The TeX Parser */ var PARSE = MathJax.Object.Subclass({ Init: function (string,env) { this.string = string; this.i = 0; this.macroCount = 0; var ENV; if (env) {ENV = {}; for (var id in env) {if (env.hasOwnProperty(id)) {ENV[id] = env[id]}}} this.stack = TEX.Stack(ENV,!!env); this.Parse(); this.Push(STACKITEM.stop()); }, Parse: function () { var c, n; while (this.i < this.string.length) { c = this.string.charAt(this.i++); n = c.charCodeAt(0); if (n >= 0xD800 && n < 0xDC00) {c += this.string.charAt(this.i++)} if (TEXDEF.special.hasOwnProperty(c)) {this[TEXDEF.special[c]](c)} else if (TEXDEF.letter.test(c)) {this.Variable(c)} else if (TEXDEF.digit.test(c)) {this.Number(c)} else {this.Other(c)} } }, Push: function () {this.stack.Push.apply(this.stack,arguments)}, mml: function () { if (this.stack.Top().type !== "mml") {return null} return this.stack.Top().data[0]; }, mmlToken: function (token) {return token}, // used by boldsymbol extension /************************************************************************/ /* * Handle various token classes */ /* * Lookup a control-sequence and process it */ ControlSequence: function (c) { var name = this.GetCS(), macro = this.csFindMacro(name); if (macro) { if (!isArray(macro)) {macro = [macro]} var fn = macro[0]; if (!(fn instanceof Function)) {fn = this[fn]} fn.apply(this,[c+name].concat(macro.slice(1))); } else if (TEXDEF.mathchar0mi.hasOwnProperty(name)) {this.csMathchar0mi(name,TEXDEF.mathchar0mi[name])} else if (TEXDEF.mathchar0mo.hasOwnProperty(name)) {this.csMathchar0mo(name,TEXDEF.mathchar0mo[name])} else if (TEXDEF.mathchar7.hasOwnProperty(name)) {this.csMathchar7(name,TEXDEF.mathchar7[name])} else if (TEXDEF.delimiter.hasOwnProperty("\\"+name)) {this.csDelimiter(name,TEXDEF.delimiter["\\"+name])} else {this.csUndefined(c+name)} }, // // Look up a macro in the macros list // (overridden in begingroup extension) // csFindMacro: function (name) { return (TEXDEF.macros.hasOwnProperty(name) ? TEXDEF.macros[name] : null); }, // // Handle normal mathchar (as an mi) // csMathchar0mi: function (name,mchar) { var def = {mathvariant: MML.VARIANT.ITALIC}; if (isArray(mchar)) {def = mchar[1]; mchar = mchar[0]} this.Push(this.mmlToken(MML.mi(MML.entity("#x"+mchar)).With(def))); }, // // Handle normal mathchar (as an mo) // csMathchar0mo: function (name,mchar) { var def = {stretchy: false}; if (isArray(mchar)) {def = mchar[1]; def.stretchy = false; mchar = mchar[0]} this.Push(this.mmlToken(MML.mo(MML.entity("#x"+mchar)).With(def))); }, // // Handle mathchar in current family // csMathchar7: function (name,mchar) { var def = {mathvariant: MML.VARIANT.NORMAL}; if (isArray(mchar)) {def = mchar[1]; mchar = mchar[0]} if (this.stack.env.font) {def.mathvariant = this.stack.env.font} this.Push(this.mmlToken(MML.mi(MML.entity("#x"+mchar)).With(def))); }, // // Handle delimiter // csDelimiter: function (name,delim) { var def = {}; if (isArray(delim)) {def = delim[1]; delim = delim[0]} if (delim.length === 4) {delim = MML.entity('#x'+delim)} else {delim = MML.chars(delim)} this.Push(this.mmlToken(MML.mo(delim).With({fence: false, stretchy: false}).With(def))); }, // // Handle undefined control sequence // (overridden in noUndefined extension) // csUndefined: function (name) { TEX.Error(["UndefinedControlSequence","Undefined control sequence %1",name]); }, /* * Handle a variable (a single letter) */ Variable: function (c) { var def = {}; if (this.stack.env.font) {def.mathvariant = this.stack.env.font} this.Push(this.mmlToken(MML.mi(MML.chars(c)).With(def))); }, /* * Determine the extent of a number (pattern may need work) */ Number: function (c) { var mml, n = this.string.slice(this.i-1).match(TEXDEF.number); if (n) {mml = MML.mn(n[0].replace(/[{}]/g,"")); this.i += n[0].length - 1} else {mml = MML.mo(MML.chars(c))} if (this.stack.env.font) {mml.mathvariant = this.stack.env.font} this.Push(this.mmlToken(mml)); }, /* * Handle { and } */ Open: function (c) {this.Push(STACKITEM.open())}, Close: function (c) {this.Push(STACKITEM.close())}, /* * Handle tilde and spaces */ Tilde: function (c) {this.Push(MML.mtext(MML.chars(NBSP)))}, Space: function (c) {}, /* * Handle ^, _, and ' */ Superscript: function (c) { if (this.GetNext().match(/\d/)) // don't treat numbers as a unit {this.string = this.string.substr(0,this.i+1)+" "+this.string.substr(this.i+1)} var primes, base, top = this.stack.Top(); if (top.type === "prime") {base = top.data[0]; primes = top.data[1]; this.stack.Pop()} else {base = this.stack.Prev(); if (!base) {base = MML.mi("")}} if (base.isEmbellishedWrapper) {base = base.data[0].data[0]} var movesupsub = base.movesupsub, position = base.sup; if ((base.type === "msubsup" && base.data[base.sup]) || (base.type === "munderover" && base.data[base.over] && !base.subsupOK)) {TEX.Error(["DoubleExponent","Double exponent: use braces to clarify"])} if (base.type !== "msubsup") { if (movesupsub) { if (base.type !== "munderover" || base.data[base.over]) { if (base.movablelimits && base.isa(MML.mi)) {base = this.mi2mo(base)} base = MML.munderover(base,null,null).With({movesupsub:true}) } position = base.over; } else { base = MML.msubsup(base,null,null); position = base.sup; } } this.Push(STACKITEM.subsup(base).With({ position: position, primes: primes, movesupsub: movesupsub })); }, Subscript: function (c) { if (this.GetNext().match(/\d/)) // don't treat numbers as a unit {this.string = this.string.substr(0,this.i+1)+" "+this.string.substr(this.i+1)} var primes, base, top = this.stack.Top(); if (top.type === "prime") {base = top.data[0]; primes = top.data[1]; this.stack.Pop()} else {base = this.stack.Prev(); if (!base) {base = MML.mi("")}} if (base.isEmbellishedWrapper) {base = base.data[0].data[0]} var movesupsub = base.movesupsub, position = base.sub; if ((base.type === "msubsup" && base.data[base.sub]) || (base.type === "munderover" && base.data[base.under] && !base.subsupOK)) {TEX.Error(["DoubleSubscripts","Double subscripts: use braces to clarify"])} if (base.type !== "msubsup") { if (movesupsub) { if (base.type !== "munderover" || base.data[base.under]) { if (base.movablelimits && base.isa(MML.mi)) {base = this.mi2mo(base)} base = MML.munderover(base,null,null).With({movesupsub:true}) } position = base.under; } else { base = MML.msubsup(base,null,null); position = base.sub; } } this.Push(STACKITEM.subsup(base).With({ position: position, primes: primes, movesupsub: movesupsub })); }, PRIME: "\u2032", SMARTQUOTE: "\u2019", Prime: function (c) { var base = this.stack.Prev(); if (!base) {base = MML.mi()} if (base.type === "msubsup" && base.data[base.sup]) { TEX.Error(["DoubleExponentPrime", "Prime causes double exponent: use braces to clarify"]); } var sup = ""; this.i--; do {sup += this.PRIME; this.i++, c = this.GetNext()} while (c === "'" || c === this.SMARTQUOTE); sup = ["","\u2032","\u2033","\u2034","\u2057"][sup.length] || sup; this.Push(STACKITEM.prime(base,this.mmlToken(MML.mo(sup)))); }, mi2mo: function (mi) { var mo = MML.mo(); mo.Append.apply(mo,mi.data); var id; for (id in mo.defaults) {if (mo.defaults.hasOwnProperty(id) && mi[id] != null) {mo[id] = mi[id]}} for (id in MML.copyAttributes) {if (MML.copyAttributes.hasOwnProperty(id) && mi[id] != null) {mo[id] = mi[id]}} mo.lspace = mo.rspace = "0"; // prevent mo from having space in NativeMML mo.useMMLspacing &= ~(mo.SPACE_ATTR.lspace | mo.SPACE_ATTR.rspace); // don't count these explicit settings return mo; }, /* * Handle comments */ Comment: function (c) { while (this.i < this.string.length && this.string.charAt(this.i) != "\n") {this.i++} }, /* * Handle hash marks outside of definitions */ Hash: function (c) { TEX.Error(["CantUseHash1", "You can't use 'macro parameter character #' in math mode"]); }, /* * Handle other characters (as <mo> elements) */ Other: function (c) { var def, mo; if (this.stack.env.font) {def = {mathvariant: this.stack.env.font}} if (TEXDEF.remap.hasOwnProperty(c)) { c = TEXDEF.remap[c]; if (isArray(c)) {def = c[1]; c = c[0]} mo = MML.mo(MML.entity('#x'+c)).With(def); } else { mo = MML.mo(c).With(def); } if (mo.autoDefault("stretchy",true)) {mo.stretchy = false} if (mo.autoDefault("texClass",true) == "") {mo = MML.TeXAtom(mo)} this.Push(this.mmlToken(mo)); }, /************************************************************************/ /* * Macros */ SetFont: function (name,font) {this.stack.env.font = font}, SetStyle: function (name,texStyle,style,level) { this.stack.env.style = texStyle; this.stack.env.level = level; this.Push(STACKITEM.style().With({styles: {displaystyle: style, scriptlevel: level}})); }, SetSize: function (name,size) { this.stack.env.size = size; this.Push(STACKITEM.style().With({styles: {mathsize: size+"em"}})); // convert to absolute? }, Color: function (name) { var color = this.GetArgument(name); var old = this.stack.env.color; this.stack.env.color = color; var math = this.ParseArg(name); if (old) {this.stack.env.color} else {delete this.stack.env.color} this.Push(MML.mstyle(math).With({mathcolor: color})); }, Spacer: function (name,space) { this.Push(MML.mspace().With({width: space, mathsize: MML.SIZE.NORMAL, scriptlevel:0})); }, LeftRight: function (name) { this.Push(STACKITEM[name.substr(1)]().With({delim: this.GetDelimiter(name)})); }, Middle: function (name) { var delim = this.GetDelimiter(name); this.Push(MML.TeXAtom().With({texClass:MML.TEXCLASS.CLOSE})); if (this.stack.Top().type !== "left") {TEX.Error(["MisplacedMiddle","%1 must be within \\left and \\right",name])} this.Push(MML.mo(delim).With({stretchy:true})); this.Push(MML.TeXAtom().With({texClass:MML.TEXCLASS.OPEN})); }, NamedFn: function (name,id) { if (!id) {id = name.substr(1)}; var mml = MML.mi(id).With({texClass: MML.TEXCLASS.OP}); this.Push(STACKITEM.fn(this.mmlToken(mml))); }, NamedOp: function (name,id) { if (!id) {id = name.substr(1)}; id = id.replace(/ /,"\u2006"); var mml = MML.mo(id).With({ movablelimits: true, movesupsub: true, form: MML.FORM.PREFIX, texClass: MML.TEXCLASS.OP }); this.Push(this.mmlToken(mml)); }, Limits: function (name,limits) { var op = this.stack.Prev("nopop"); if (!op || (op.Get("texClass") !== MML.TEXCLASS.OP && op.movesupsub == null)) {TEX.Error(["MisplacedLimits","%1 is allowed only on operators",name])} var top = this.stack.Top(); if (op.type === "munderover" && !limits) { op = top.data[top.data.length-1] = MML.msubsup.apply(MML.subsup,op.data); } else if (op.type === "msubsup" && limits) { op = top.data[top.data.length-1] = MML.munderover.apply(MML.underover,op.data); } op.movesupsub = (limits ? true : false); op.Core().movablelimits = false; if (op.movablelimits) op.movablelimits = false; }, Over: function (name,open,close) { var mml = STACKITEM.over().With({name: name}); if (open || close) { mml.open = open; mml.close = close; } else if (name.match(/withdelims$/)) { mml.open = this.GetDelimiter(name); mml.close = this.GetDelimiter(name); } if (name.match(/^\\above/)) {mml.thickness = this.GetDimen(name)} else if (name.match(/^\\atop/) || open || close) {mml.thickness = 0} this.Push(mml); }, Frac: function (name) { var num = this.ParseArg(name); var den = this.ParseArg(name); this.Push(MML.mfrac(num,den)); }, Sqrt: function (name) { var n = this.GetBrackets(name), arg = this.GetArgument(name); if (arg === "\\frac") {arg += "{"+this.GetArgument(arg)+"}{"+this.GetArgument(arg)+"}"} var mml = TEX.Parse(arg,this.stack.env).mml(); if (!n) {mml = MML.msqrt.apply(MML,mml.array())} else {mml = MML.mroot(mml,this.parseRoot(n))} this.Push(mml); }, Root: function (name) { var n = this.GetUpTo(name,"\\of"); var arg = this.ParseArg(name); this.Push(MML.mroot(arg,this.parseRoot(n))); }, parseRoot: function (n) { var env = this.stack.env, inRoot = env.inRoot; env.inRoot = true; var parser = TEX.Parse(n,env); n = parser.mml(); var global = parser.stack.global; if (global.leftRoot || global.upRoot) { n = MML.mpadded(n); if (global.leftRoot) {n.width = global.leftRoot} if (global.upRoot) {n.voffset = global.upRoot; n.height = global.upRoot} } env.inRoot = inRoot; return n; }, MoveRoot: function (name,id) { if (!this.stack.env.inRoot) {TEX.Error(["MisplacedMoveRoot","%1 can appear only within a root",name])} if (this.stack.global[id]) {TEX.Error(["MultipleMoveRoot","Multiple use of %1",name])} var n = this.GetArgument(name); if (!n.match(/-?[0-9]+/)) {TEX.Error(["IntegerArg","The argument to %1 must be an integer",name])} n = (n/15)+"em"; if (n.substr(0,1) !== "-") {n = "+"+n} this.stack.global[id] = n; }, Accent: function (name,accent,stretchy) { var c = this.ParseArg(name); var def = {accent: true}; if (this.stack.env.font) {def.mathvariant = this.stack.env.font} var mml = this.mmlToken(MML.mo(MML.entity("#x"+accent)).With(def)); mml.stretchy = (stretchy ? true : false); var mo = (c.isEmbellished() ? c.CoreMO() : c); if (mo.isa(MML.mo)) mo.movablelimits = false; this.Push(MML.TeXAtom(MML.munderover(c,null,mml).With({accent: true}))); }, UnderOver: function (name,c,stack,noaccent) { var pos = {o: "over", u: "under"}[name.charAt(1)]; var base = this.ParseArg(name); if (base.Get("movablelimits")) {base.movablelimits = false} if (base.isa(MML.munderover) && base.isEmbellished()) { base.Core().With({lspace:0,rspace:0}); // get spacing right for NativeMML base = MML.mrow(MML.mo().With({rspace:0}),base); // add an empty <mi> so it's not embellished any more } var mml = MML.munderover(base,null,null); mml.SetData( mml[pos], this.mmlToken(MML.mo(MML.entity("#x"+c)).With({stretchy:true, accent:!noaccent})) ); if (stack) {mml = MML.TeXAtom(mml).With({texClass:MML.TEXCLASS.OP, movesupsub:true})} this.Push(mml.With({subsupOK:true})); }, Overset: function (name) { var top = this.ParseArg(name), base = this.ParseArg(name); base.movablelimits = false; this.Push(MML.mover(base,top)); }, Underset: function (name) { var bot = this.ParseArg(name), base = this.ParseArg(name); base.movablelimits = false; this.Push(MML.munder(base,bot)); }, TeXAtom: function (name,mclass) { var def = {texClass: mclass}, mml; if (mclass == MML.TEXCLASS.OP) { def.movesupsub = def.movablelimits = true; var arg = this.GetArgument(name); var match = arg.match(/^\s*\\rm\s+([a-zA-Z0-9 ]+)$/); if (match) { def.mathvariant = MML.VARIANT.NORMAL; mml = STACKITEM.fn(this.mmlToken(MML.mi(match[1]).With(def))); } else { mml = STACKITEM.fn(MML.TeXAtom(TEX.Parse(arg,this.stack.env).mml()).With(def)); } } else {mml = MML.TeXAtom(this.ParseArg(name)).With(def)} this.Push(mml); }, MmlToken: function (name) { var type = this.GetArgument(name), attr = this.GetBrackets(name,"").replace(/^\s+/,""), data = this.GetArgument(name), def = {attrNames:[]}, match; if (!MML[type] || !MML[type].prototype.isToken) {TEX.Error(["NotMathMLToken","%1 is not a token element",type])} while (attr !== "") { match = attr.match(/^([a-z]+)\s*=\s*('[^']*'|"[^"]*"|[^ ,]*)\s*,?\s*/i); if (!match) {TEX.Error(["InvalidMathMLAttr","Invalid MathML attribute: %1",attr])} if (MML[type].prototype.defaults[match[1]] == null && !this.MmlTokenAllow[match[1]]) { TEX.Error(["UnknownAttrForElement", "%1 is not a recognized attribute for %2", match[1],type]); } var value = this.MmlFilterAttribute(match[1],match[2].replace(/^(['"])(.*)\1$/,"$2")); if (value) { if (value.toLowerCase() === "true") {value = true} else if (value.toLowerCase() === "false") {value = false} def[match[1]] = value; def.attrNames.push(match[1]); } attr = attr.substr(match[0].length); } this.Push(this.mmlToken(MML[type](data).With(def))); }, MmlFilterAttribute: function (name,value) {return value}, MmlTokenAllow: { fontfamily:1, fontsize:1, fontweight:1, fontstyle:1, color:1, background:1, id:1, "class":1, href:1, style:1 }, Strut: function (name) { this.Push(MML.mpadded(MML.mrow()).With({height: "8.6pt", depth: "3pt", width: 0})); }, Phantom: function (name,v,h) { var box = MML.mphantom(this.ParseArg(name)); if (v || h) { box = MML.mpadded(box); if (h) {box.height = box.depth = 0} if (v) {box.width = 0} } this.Push(MML.TeXAtom(box)); }, Smash: function (name) { var bt = this.trimSpaces(this.GetBrackets(name,"")); var smash = MML.mpadded(this.ParseArg(name)); switch (bt) { case "b": smash.depth = 0; break; case "t": smash.height = 0; break; default: smash.height = smash.depth = 0; } this.Push(MML.TeXAtom(smash)); }, Lap: function (name) { var mml = MML.mpadded(this.ParseArg(name)).With({width: 0}); if (name === "\\llap") {mml.lspace = "-1width"} this.Push(MML.TeXAtom(mml)); }, RaiseLower: function (name) { var h = this.GetDimen(name); var item = STACKITEM.position().With({name: name, move: 'vertical'}); if (h.charAt(0) === '-') {h = h.slice(1); name = {raise: "\\lower", lower: "\\raise"}[name.substr(1)]} if (name === "\\lower") {item.dh = '-'+h; item.dd = '+'+h} else {item.dh = '+'+h; item.dd = '-'+h} this.Push(item); }, MoveLeftRight: function (name) { var h = this.GetDimen(name); var nh = (h.charAt(0) === '-' ? h.slice(1) : '-'+h); if (name === "\\moveleft") {var tmp = h; h = nh; nh = tmp} this.Push(STACKITEM.position().With({ name: name, move: 'horizontal', left: MML.mspace().With({width: h, mathsize: MML.SIZE.NORMAL}), right: MML.mspace().With({width: nh, mathsize: MML.SIZE.NORMAL}) })); }, Hskip: function (name) { this.Push(MML.mspace().With({width: this.GetDimen(name), mathsize: MML.SIZE.NORMAL})); }, Rule: function (name,style) { var w = this.GetDimen(name), h = this.GetDimen(name), d = this.GetDimen(name); var def = {width:w, height:h, depth:d}; if (style !== 'blank') { def.mathbackground = (this.stack.env.color || "black"); } this.Push(MML.mspace().With(def)); }, rule: function (name) { var v = this.GetBrackets(name), w = this.GetDimen(name), h = this.GetDimen(name); var mml = MML.mspace().With({ width: w, height:h, mathbackground: (this.stack.env.color || "black") }); if (v) { mml = MML.mpadded(mml).With({voffset: v}); if (v.match(/^\-/)) { mml.height = v; mml.depth = '+' + v.substr(1); } else { mml.height = '+' + v; } } this.Push(mml); }, MakeBig: function (name,mclass,size) { size *= TEXDEF.p_height; size = String(size).replace(/(\.\d\d\d).+/,'$1')+"em"; var delim = this.GetDelimiter(name,true); this.Push(MML.mstyle(MML.TeXAtom(MML.mo(delim).With({ minsize: size, maxsize: size, fence: true, stretchy: true, symmetric: true })).With({texClass: mclass})).With({scriptlevel: 0})); }, BuildRel: function (name) { var top = this.ParseUpTo(name,"\\over"); var bot = this.ParseArg(name); this.Push(MML.TeXAtom(MML.munderover(bot,null,top)).With({texClass: MML.TEXCLASS.REL})); }, HBox: function (name,style) { this.Push.apply(this,this.InternalMath(this.GetArgument(name),style)); }, FBox: function (name) { this.Push(MML.menclose.apply(MML,this.InternalMath(this.GetArgument(name))).With({notation:"box"})); }, Not: function (name) { this.Push(STACKITEM.not()); }, Dots: function (name) { this.Push(STACKITEM.dots().With({ ldots: this.mmlToken(MML.mo(MML.entity("#x2026")).With({stretchy:false})), cdots: this.mmlToken(MML.mo(MML.entity("#x22EF")).With({stretchy:false})) })); }, Require: function (name) { var file = this.GetArgument(name) .replace(/.*\//,"") // remove any leading path .replace(/[^a-z0-9_.-]/ig,""); // remove illegal characters this.Extension(null,file); }, Extension: function (name,file,array) { if (name && !typeof(name) === "string") {name = name.name} file = TEX.extensionDir+"/"+file; if (!file.match(/\.js$/)) {file += ".js"} if (!AJAX.loaded[AJAX.fileURL(file)]) { if (name != null) {delete TEXDEF[array || 'macros'][name.replace(/^\\/,"")]} HUB.RestartAfter(AJAX.Require(file)); } }, Macro: function (name,macro,argcount,def) { if (argcount) { var args = []; if (def != null) { var optional = this.GetBrackets(name); args.push(optional == null ? def : optional); } for (var i = args.length; i < argcount; i++) {args.push(this.GetArgument(name))} macro = this.SubstituteArgs(args,macro); } this.string = this.AddArgs(macro,this.string.slice(this.i)); this.i = 0; if (++this.macroCount > TEX.config.MAXMACROS) { TEX.Error(["MaxMacroSub1", "MathJax maximum macro substitution count exceeded; " + "is there a recursive macro call?"]); } }, Matrix: function (name,open,close,align,spacing,vspacing,style,cases,numbered) { var c = this.GetNext(); if (c === "") {TEX.Error(["MissingArgFor","Missing argument for %1",name])} if (c === "{") {this.i++} else {this.string = c+"}"+this.string.slice(this.i+1); this.i = 0} var array = STACKITEM.array().With({ requireClose: true, arraydef: { rowspacing: (vspacing||"4pt"), columnspacing: (spacing||"1em") } }); if (cases) {array.isCases = true} if (numbered) {array.isNumbered = true; array.arraydef.side = numbered} if (open || close) {array.open = open; array.close = close} if (style === "D") {array.arraydef.displaystyle = true} if (align != null) {array.arraydef.columnalign = align} this.Push(array); }, Entry: function (name) { this.Push(STACKITEM.cell().With({isEntry: true, name: name})); if (this.stack.Top().isCases) { // // Make second column be in \text{...} (unless it is already // in a \text{...}, for backward compatibility). // var string = this.string; var braces = 0, close = -1, i = this.i, m = string.length; // // Look through the string character by character... // while (i < m) { var c = string.charAt(i); if (c === "{") { // // Increase the nested brace count and go on // braces++; i++; } else if (c === "}") { // // If there are too many close braces, just end (we will get an // error message later when the rest of the string is parsed) // Otherwise // decrease the nested brace count, // if it is now zero and we haven't already marked the end of the // first brace group, record the position (use to check for \text{} later) // go on to the next character. // if (braces === 0) { m = 0; } else { braces--; if (braces === 0 && close < 0) { close = i - this.i; } i++; } } else if (c === "&" && braces === 0) { // // Extra alignment tabs are not allowed in cases // TEX.Error(["ExtraAlignTab","Extra alignment tab in \\cases text"]); } else if (c === "\\") { // // If the macro is \cr or \\, end the search, otherwise skip the macro // (multi-letter names don't matter, as we will skip the rest of the // characters in the main loop) // if (string.substr(i).match(/^((\\cr)[^a-zA-Z]|\\\\)/)) {m = 0} else {i += 2} } else { // // Go on to the next character // i++; } } // // Check if the second column text is already in \text{}, // If not, process the second column as text and continue parsing from there, // (otherwise process the second column as normal, since it is in \text{} // var text = string.substr(this.i,i-this.i); if (!text.match(/^\s*\\text[^a-zA-Z]/) || close !== text.replace(/\s+$/,'').length - 1) { this.Push.apply(this,this.InternalMath(text,0)); this.i = i; } } }, Cr: function (name) { this.Push(STACKITEM.cell().With({isCR: true, name: name})); }, CrLaTeX: function (name) { var n; if (this.string.charAt(this.i) === "[") { n = this.GetBrackets(name,"").replace(/ /g,"").replace(/,/,"."); if (n && !this.matchDimen(n)) { TEX.Error(["BracketMustBeDimension", "Bracket argument to %1 must be a dimension",name]); } } this.Push(STACKITEM.cell().With({isCR: true, name: name, linebreak: true})); var top = this.stack.Top(); if (top.isa(STACKITEM.array)) { if (n && top.arraydef.rowspacing) { var rows = top.arraydef.rowspacing.split(/ /); if (!top.rowspacing) {top.rowspacing = this.dimen2em(rows[0])} while (rows.length < top.table.length) {rows.push(this.Em(top.rowspacing))} rows[top.table.length-1] = this.Em(Math.max(0,top.rowspacing+this.dimen2em(n))); top.arraydef.rowspacing = rows.join(' '); } } else { if (n) {this.Push(MML.mspace().With({depth:n}))} this.Push(MML.mspace().With({linebreak:MML.LINEBREAK.NEWLINE})); } }, emPerInch: 7.2, pxPerInch: 72, matchDimen: function (dim) { return dim.match(/^(-?(?:\.\d+|\d+(?:\.\d*)?))(px|pt|em|ex|mu|pc|in|mm|cm)$/); }, dimen2em: function (dim) { var match = this.matchDimen(dim); var m = parseFloat(match[1]||"1"), unit = match[2]; if (unit === "em") {return m} if (unit === "ex") {return m * .43} if (unit === "pt") {return m / 10} // 10 pt to an em if (unit === "pc") {return m * 1.2} // 12 pt to a pc if (unit === "px") {return m * this.emPerInch / this.pxPerInch} if (unit === "in") {return m * this.emPerInch} if (unit === "cm") {return m * this.emPerInch / 2.54} // 2.54 cm to an inch if (unit === "mm") {return m * this.emPerInch / 25.4} // 10 mm to a cm if (unit === "mu") {return m / 18} return 0; }, Em: function (m) { if (Math.abs(m) < .0006) {return "0em"} return m.toFixed(3).replace(/\.?0+$/,"") + "em"; }, HLine: function (name,style) { if (style == null) {style = "solid"} var top = this.stack.Top(); if (!top.isa(STACKITEM.array) || top.data.length) {TEX.Error(["Misplaced","Misplaced %1",name])} if (top.table.length == 0) { top.frame.push("top"); } else { var lines = (top.arraydef.rowlines ? top.arraydef.rowlines.split(/ /) : []); while (lines.length < top.table.length) {lines.push("none")} lines[top.table.length-1] = style; top.arraydef.rowlines = lines.join(' '); } }, HFill: function (name) { var top = this.stack.Top(); if (top.isa(STACKITEM.array)) top.hfill.push(top.data.length); else TEX.Error(["UnsupportedHFill","Unsupported use of %1",name]); }, /************************************************************************/ /* * LaTeX environments */ BeginEnd: function (name) { var env = this.GetArgument(name), isEnd = false; if (env.match(/^\\end\\/)) {isEnd = true; env = env.substr(5)} // special \end{} for \newenvironment environments if (env.match(/\\/i)) {TEX.Error(["InvalidEnv","Invalid environment name '%1'",env])} var cmd = this.envFindName(env); if (!cmd) {TEX.Error(["UnknownEnv","Unknown environment '%1'",env])} if (!isArray(cmd)) {cmd = [cmd]} var end = (isArray(cmd[1]) ? cmd[1][0] : cmd[1]); var mml = STACKITEM.begin().With({name: env, end: end, parse:this}); if (name === "\\end") { if (!isEnd && isArray(cmd[1]) && this[cmd[1][1]]) { mml = this[cmd[1][1]].apply(this,[mml].concat(cmd.slice(2))); } else { mml = STACKITEM.end().With({name: env}); } } else { if (++this.macroCount > TEX.config.MAXMACROS) { TEX.Error(["MaxMacroSub2", "MathJax maximum substitution count exceeded; " + "is there a recursive latex environment?"]); } if (cmd[0] && this[cmd[0]]) {mml = this[cmd[0]].apply(this,[mml].concat(cmd.slice(2)))} } this.Push(mml); }, envFindName: function (name) { return (TEXDEF.environment.hasOwnProperty(name) ? TEXDEF.environment[name] : null); }, Equation: function (begin,row) {return row}, ExtensionEnv: function (begin,file) {this.Extension(begin.name,file,"environment")}, Array: function (begin,open,close,align,spacing,vspacing,style,raggedHeight) { if (!align) {align = this.GetArgument("\\begin{"+begin.name+"}")} var lines = ("c"+align).replace(/[^clr|:]/g,'').replace(/[^|:]([|:])+/g,'$1'); align = align.replace(/[^clr]/g,'').split('').join(' '); align = align.replace(/l/g,'left').replace(/r/g,'right').replace(/c/g,'center'); var array = STACKITEM.array().With({ arraydef: { columnalign: align, columnspacing: (spacing||"1em"), rowspacing: (vspacing||"4pt") } }); if (lines.match(/[|:]/)) { if (lines.charAt(0).match(/[|:]/)) {array.frame.push("left"); array.frame.dashed = lines.charAt(0) === ":"} if (lines.charAt(lines.length-1).match(/[|:]/)) {array.frame.push("right")} lines = lines.substr(1,lines.length-2); array.arraydef.columnlines = lines.split('').join(' ').replace(/[^|: ]/g,'none').replace(/\|/g,'solid').replace(/:/g,'dashed'); } if (open) {array.open = this.convertDelimiter(open)} if (close) {array.close = this.convertDelimiter(close)} if (style === "D") {array.arraydef.displaystyle = true} else if (style) {array.arraydef.displaystyle = false} if (style === "S") {array.arraydef.scriptlevel = 1} // FIXME: should use mstyle? if (raggedHeight) {array.arraydef.useHeight = false} this.Push(begin); return array; }, AlignedArray: function (begin) { var align = this.GetBrackets("\\begin{"+begin.name+"}"); return this.setArrayAlign(this.Array.apply(this,arguments),align); }, setArrayAlign: function (array,align) { align = this.trimSpaces(align||""); if (align === "t") {array.arraydef.align = "baseline 1"} else if (align === "b") {array.arraydef.align = "baseline -1"} else if (align === "c") {array.arraydef.align = "center"} else if (align) {array.arraydef.align = align} // FIXME: should be an error? return array; }, /************************************************************************/ /* * String handling routines */ /* * Convert delimiter to character */ convertDelimiter: function (c) { if (c) {c = (TEXDEF.delimiter.hasOwnProperty(c) ? TEXDEF.delimiter[c] : null)} if (c == null) {return null} if (isArray(c)) {c = c[0]} if (c.length === 4) {c = String.fromCharCode(parseInt(c,16))} return c; }, /* * Trim spaces from a string */ trimSpaces: function (text) { if (typeof(text) != 'string') {return text} var TEXT = text.replace(/^\s+|\s+$/g,''); if (TEXT.match(/\\$/) && text.match(/ $/)) TEXT += " "; return TEXT; }, /* * Check if the next character is a space */ nextIsSpace: function () { return this.string.charAt(this.i).match(/\s/); }, /* * Get the next non-space character */ GetNext: function () { while (this.nextIsSpace()) {this.i++} return this.string.charAt(this.i); }, /* * Get and return a control-sequence name */ GetCS: function () { var CS = this.string.slice(this.i).match(/^([a-z]+|.) ?/i); if (CS) {this.i += CS[1].length; return CS[1]} else {this.i++; return " "} }, /* * Get and return a TeX argument (either a single character or control sequence, * or the contents of the next set of braces). */ GetArgument: function (name,noneOK) { switch (this.GetNext()) { case "": if (!noneOK) {TEX.Error(["MissingArgFor","Missing argument for %1",name])} return null; case '}': if (!noneOK) { TEX.Error(["ExtraCloseMissingOpen", "Extra close brace or missing open brace"]); } return null; case '\\': this.i++; return "\\"+this.GetCS(); case '{': var j = ++this.i, parens = 1; while (this.i < this.string.length) { switch (this.string.charAt(this.i++)) { case '\\': this.i++; break; case '{': parens++; break; case '}': if (--parens == 0) {return this.string.slice(j,this.i-1)} break; } } TEX.Error(["MissingCloseBrace","Missing close brace"]); break; } return this.string.charAt(this.i++); }, /* * Get an optional LaTeX argument in brackets */ GetBrackets: function (name,def) { if (this.GetNext() != '[') {return def}; var j = ++this.i, parens = 0; while (this.i < this.string.length) { switch (this.string.charAt(this.i++)) { case '{': parens++; break; case '\\': this.i++; break; case '}': if (parens-- <= 0) { TEX.Error(["ExtraCloseLooking", "Extra close brace while looking for %1","']'"]); } break; case ']': if (parens == 0) {return this.string.slice(j,this.i-1)} break; } } TEX.Error(["MissingCloseBracket", "Couldn't find closing ']' for argument to %1",name]); }, /* * Get the name of a delimiter (check it in the delimiter list). */ GetDelimiter: function (name,braceOK) { while (this.nextIsSpace()) {this.i++} var c = this.string.charAt(this.i); this.i++; if (this.i <= this.string.length) { if (c == "\\") { c += this.GetCS(name); } else if (c === "{" && braceOK) { this.i--; c = this.GetArgument(name).replace(/^\s+/,'').replace(/\s+$/,''); } if (TEXDEF.delimiter.hasOwnProperty(c)) {return this.convertDelimiter(c)} } TEX.Error(["MissingOrUnrecognizedDelim", "Missing or unrecognized delimiter for %1",name]); }, /* * Get a dimension (including its units). */ GetDimen: function (name) { var dimen; if (this.nextIsSpace()) {this.i++} if (this.string.charAt(this.i) == '{') { dimen = this.GetArgument(name); if (dimen.match(/^\s*([-+]?([.,]\d+|\d+([.,]\d*)?))\s*(pt|em|ex|mu|px|mm|cm|in|pc)\s*$/)) {return dimen.replace(/ /g,"").replace(/,/,".")} } else { dimen = this.string.slice(this.i); var match = dimen.match(/^\s*(([-+]?([.,]\d+|\d+([.,]\d*)?))\s*(pt|em|ex|mu|px|mm|cm|in|pc)) ?/); if (match) { this.i += match[0].length; return match[1].replace(/ /g,"").replace(/,/,"."); } } TEX.Error(["MissingDimOrUnits", "Missing dimension or its units for %1",name]); }, /* * Get everything up to the given control sequence (token) */ GetUpTo: function (name,token) { while (this.nextIsSpace()) {this.i++} var j = this.i, k, c, parens = 0; while (this.i < this.string.length) { k = this.i; c = this.string.charAt(this.i++); switch (c) { case '\\': c += this.GetCS(); break; case '{': parens++; break; case '}': if (parens == 0) { TEX.Error(["ExtraCloseLooking", "Extra close brace while looking for %1",token]) } parens--; break; } if (parens == 0 && c == token) {return this.string.slice(j,k)} } TEX.Error(["TokenNotFoundForCommand", "Couldn't find %1 for %2",token,name]); }, /* * Parse various substrings */ ParseArg: function (name) {return TEX.Parse(this.GetArgument(name),this.stack.env).mml()}, ParseUpTo: function (name,token) {return TEX.Parse(this.GetUpTo(name,token),this.stack.env).mml()}, /* * Break up a string into text and math blocks */ InternalMath: function (text,level) { var def = (this.stack.env.font ? {mathvariant: this.stack.env.font} : {}); var mml = [], i = 0, k = 0, c, match = '', braces = 0; if (text.match(/\\?[${}\\]|\\\(|\\(eq)?ref\s*\{/)) { while (i < text.length) { c = text.charAt(i++); if (c === '$') { if (match === '$' && braces === 0) { mml.push(MML.TeXAtom(TEX.Parse(text.slice(k,i-1),{}).mml())); match = ''; k = i; } else if (match === '') { if (k < i-1) mml.push(this.InternalText(text.slice(k,i-1),def)); match = '$'; k = i; } } else if (c === '{' && match !== '') { braces++; } else if (c === '}') { if (match === '}' && braces === 0) { mml.push(MML.TeXAtom(TEX.Parse(text.slice(k,i),{}).mml().With(def))); match = ''; k = i; } else if (match !== '') { if (braces) braces--; } } else if (c === '\\') { if (match === '' && text.substr(i).match(/^(eq)?ref\s*\{/)) { var len = RegExp["$&"].length; if (k < i-1) mml.push(this.InternalText(text.slice(k,i-1),def)); match = '}'; k = i-1; i += len; } else { c = text.charAt(i++); if (c === '(' && match === '') { if (k < i-2) mml.push(this.InternalText(text.slice(k,i-2),def)); match = ')'; k = i; } else if (c === ')' && match === ')' && braces === 0) { mml.push(MML.TeXAtom(TEX.Parse(text.slice(k,i-2),{}).mml())); match = ''; k = i; } else if (c.match(/[${}\\]/) && match === '') { i--; text = text.substr(0,i-1) + text.substr(i); // remove \ from \$, \{, \}, or \\ } } } } if (match !== '') TEX.Error(["MathNotTerminated","Math not terminated in text box"]); } if (k < text.length) mml.push(this.InternalText(text.slice(k),def)); if (level != null) { mml = [MML.mstyle.apply(MML,mml).With({displaystyle:false,scriptlevel:level})]; } else if (mml.length > 1) { mml = [MML.mrow.apply(MML,mml)]; } return mml; }, InternalText: function (text,def) { text = text.replace(/^\s+/,NBSP).replace(/\s+$/,NBSP); return MML.mtext(MML.chars(text)).With(def); }, /* * Routines to set the macro and environment definitions * (overridden by begingroup to make localized versions) */ setDef: function (name,value) {value.isUser = true; TEXDEF.macros[name] = value}, setEnv: function (name,value) {value.isUser = true; TEXDEF.environment[name] = value}, /* * Replace macro parameters with their values */ SubstituteArgs: function (args,string) { var text = ''; var newstring = ''; var c; var i = 0; while (i < string.length) { c = string.charAt(i++); if (c === "\\") {text += c + string.charAt(i++)} else if (c === '#') { c = string.charAt(i++); if (c === '#') {text += c} else { if (!c.match(/[1-9]/) || c > args.length) { TEX.Error(["IllegalMacroParam", "Illegal macro parameter reference"]); } newstring = this.AddArgs(this.AddArgs(newstring,text),args[c-1]); text = ''; } } else {text += c} } return this.AddArgs(newstring,text); }, /* * Make sure that macros are followed by a space if their names * could accidentally be continued into the following text. */ AddArgs: function (s1,s2) { if (s2.match(/^[a-z]/i) && s1.match(/(^|[^\\])(\\\\)*\\[a-z]+$/i)) {s1 += ' '} if (s1.length + s2.length > TEX.config.MAXBUFFER) { TEX.Error(["MaxBufferSize", "MathJax internal buffer size exceeded; is there a recursive macro call?"]); } return s1+s2; } }); /************************************************************************/ TEX.Augment({ Stack: STACK, Parse: PARSE, Definitions: TEXDEF, Startup: STARTUP, config: { MAXMACROS: 10000, // maximum number of macro substitutions per equation MAXBUFFER: 5*1024 // maximum size of TeX string to process }, sourceMenuTitle: /*_(MathMenu)*/ ["TeXCommands","TeX Commands"], annotationEncoding: "application/x-tex", prefilterHooks: MathJax.Callback.Hooks(true), // hooks to run before processing TeX postfilterHooks: MathJax.Callback.Hooks(true), // hooks to run after processing TeX // // Check if AMSmath extension must be loaded and push // it on the extensions array, if needed // Config: function () { this.SUPER(arguments).Config.apply(this,arguments); if (this.config.equationNumbers.autoNumber !== "none") { if (!this.config.extensions) {this.config.extensions = []} this.config.extensions.push("AMSmath.js"); } }, // // Convert TeX to ElementJax // Translate: function (script) { var mml, isError = false, math = MathJax.HTML.getScript(script); var display = (script.type.replace(/\n/g," ").match(/(;|\s|\n)mode\s*=\s*display(;|\s|\n|$)/) != null); var data = {math:math, display:display, script:script}; var callback = this.prefilterHooks.Execute(data); if (callback) return callback; math = data.math; try { mml = TEX.Parse(math).mml(); } catch(err) { if (!err.texError) {throw err} mml = this.formatError(err,math,display,script); isError = true; } if (mml.isa(MML.mtable) && mml.displaystyle === "inherit") mml.displaystyle = display; // for tagged equations if (mml.inferred) {mml = MML.apply(MathJax.ElementJax,mml.data)} else {mml = MML(mml)} if (display) {mml.root.display = "block"} if (isError) {mml.texError = true} data.math = mml; return this.postfilterHooks.Execute(data) || data.math; }, prefilterMath: function (math,displaystyle,script) { return math; }, postfilterMath: function (math,displaystyle,script) { this.combineRelations(math.root); return math; }, formatError: function (err,math,display,script) { var message = err.message.replace(/\n.*/,""); HUB.signal.Post(["TeX Jax - parse error",message,math,display,script]); return MML.Error(message); }, // // Produce an error and stop processing this equation // Error: function (message) { // // Translate message if it is ["id","message",args] // if (isArray(message)) {message = _.apply(_,message)} throw HUB.Insert(Error(message),{texError: true}); }, // // Add a user-defined macro to the macro list // Macro: function (name,def,argn) { TEXDEF.macros[name] = ['Macro'].concat([].slice.call(arguments,1)); TEXDEF.macros[name].isUser = true; }, /* * Create an mrow that has stretchy delimiters at either end, as needed */ fenced: function (open,mml,close) { var mrow = MML.mrow().With({open:open, close:close, texClass:MML.TEXCLASS.INNER}); mrow.Append( MML.mo(open).With({fence:true, stretchy:true, symmetric:true, texClass:MML.TEXCLASS.OPEN}) ); if (mml.type === "mrow" && mml.inferred) { mrow.Append.apply(mrow, mml.data); } else { mrow.Append(mml); } mrow.Append( MML.mo(close).With({fence:true, stretchy:true, symmetric:true, texClass:MML.TEXCLASS.CLOSE}) ); return mrow; }, /* * Create an mrow that has \mathchoice using \bigg and \big for the delimiters */ fixedFence: function (open,mml,close) { var mrow = MML.mrow().With({open:open, close:close, texClass:MML.TEXCLASS.ORD}); if (open) {mrow.Append(this.mathPalette(open,"l"))} if (mml.type === "mrow") {mrow.Append.apply(mrow,mml.data)} else {mrow.Append(mml)} if (close) {mrow.Append(this.mathPalette(close,"r"))} return mrow; }, mathPalette: function (fence,side) { if (fence === '{' || fence === '}') {fence = "\\"+fence} var D = '{\\bigg'+side+' '+fence+'}', T = '{\\big'+side+' '+fence+'}'; return TEX.Parse('\\mathchoice'+D+T+T+T,{}).mml(); }, // // Combine adjacent <mo> elements that are relations // (since MathML treats the spacing very differently) // combineRelations: function (mml) { var i, m, m1, m2; for (i = 0, m = mml.data.length; i < m; i++) { if (mml.data[i]) { if (mml.isa(MML.mrow)) { while (i+1 < m && (m1 = mml.data[i]) && (m2 = mml.data[i+1]) && m1.isa(MML.mo) && m2.isa(MML.mo) && m1.Get("texClass") === MML.TEXCLASS.REL && m2.Get("texClass") === MML.TEXCLASS.REL) { if (m1.variantForm == m2.variantForm && m1.Get("mathvariant") == m2.Get("mathvariant") && m1.style == m2.style && m1["class"] == m2["class"] && !m1.id && !m2.id) { m1.Append.apply(m1,m2.data); mml.data.splice(i+1,1); m--; } else { m1.rspace = m2.lspace = "0pt"; i++; } } } if (!mml.data[i].isToken) {this.combineRelations(mml.data[i])} } } } }); // // Add the default filters // TEX.prefilterHooks.Add(function (data) { data.math = TEX.prefilterMath(data.math,data.display,data.script); }); TEX.postfilterHooks.Add(function (data) { data.math = TEX.postfilterMath(data.math,data.display,data.script); }); TEX.loadComplete("jax.js"); })(MathJax.InputJax.TeX,MathJax.Hub,MathJax.Ajax); /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/jax/output/SVG/config.js * * Initializes the SVG OutputJax (the main definition is in * MathJax/jax/input/SVG/jax.js, which is loaded when needed). * * --------------------------------------------------------------------- * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ MathJax.OutputJax.SVG = MathJax.OutputJax({ id: "SVG", version: "2.7.5", directory: MathJax.OutputJax.directory + "/SVG", extensionDir: MathJax.OutputJax.extensionDir + "/SVG", autoloadDir: MathJax.OutputJax.directory + "/SVG/autoload", fontDir: MathJax.OutputJax.directory + "/SVG/fonts", // font name added later config: { scale: 100, minScaleAdjust: 50, // global math scaling factor, and minimum adjusted scale factor font: "TeX", // currently the only font available blacker: 1, // stroke-width to make fonts blacker mtextFontInherit: false, // to make <mtext> be in page font rather than MathJax font undefinedFamily: "STIXGeneral,'Arial Unicode MS',serif", // fonts to use for missing characters addMMLclasses: false, // keep MathML structure and use CSS classes to mark elements useFontCache: true, // use <use> elements to re-use font paths rather than repeat paths every time useGlobalCache: true, // store fonts in a global <defs> for use in all equations, or one in each equation EqnChunk: (MathJax.Hub.Browser.isMobile ? 10: 50), // number of equations to process before showing them EqnChunkFactor: 1.5, // chunk size is multiplied by this after each chunk EqnChunkDelay: 100, // milliseconds to delay between chunks (to let browser // respond to other events) linebreaks: { automatic: false, // when false, only process linebreak="newline", // when true, insert line breaks automatically in long expressions. width: "container" // maximum width of a line for automatic line breaks (e.g. "30em"). // use "container" to compute size from containing element, // use "nn% container" for a portion of the container, // use "nn%" for a portion of the window size }, merrorStyle: { fontSize:"90%", color:"#C00", background:"#FF8", border: "1px solid #C00", padding:"3px" }, styles: { ".MathJax_SVG_Display": { "text-align": "center", margin: "1em 0em" }, // // For mtextFontInherit version of \texttt{} // ".MathJax_SVG .MJX-monospace": { "font-family": "monospace" }, // // For mtextFontInherit version of \textsf{} // ".MathJax_SVG .MJX-sans-serif": { "font-family": "sans-serif" }, // // For tooltips // "#MathJax_SVG_Tooltip": { "background-color": "InfoBackground", color: "InfoText", border: "1px solid black", "box-shadow": "2px 2px 5px #AAAAAA", // Opera 10.5 "-webkit-box-shadow": "2px 2px 5px #AAAAAA", // Safari 3 and Chrome "-moz-box-shadow": "2px 2px 5px #AAAAAA", // Forefox 3.5 "-khtml-box-shadow": "2px 2px 5px #AAAAAA", // Konqueror padding: "3px 4px", "z-index": 401 } } } }); if (!MathJax.Hub.config.delayJaxRegistration) {MathJax.OutputJax.SVG.Register("jax/mml")} MathJax.OutputJax.SVG.loadComplete("config.js"); /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/jax/output/SVG/jax.js * * Implements the SVG OutputJax that displays mathematics using * SVG (or VML in IE) to position the characters from math fonts * in their proper locations. * * --------------------------------------------------------------------- * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ (function (AJAX,HUB,HTML,SVG) { var MML; var isArray = MathJax.Object.isArray; var SVGNS = "http://www.w3.org/2000/svg"; var XLINKNS = "http://www.w3.org/1999/xlink"; var EVENT, TOUCH, HOVER; // filled in later // // Get the URL of the page (for use with xlink:href) when there // is a <base> element on the page. // var SVGURL = (document.getElementsByTagName("base").length === 0) ? "" : String(document.location).replace(/#.*$/,""); SVG.Augment({ HFUZZ: 2, // adjustments for height and depth of final svg element DFUZZ: 2, // to get baselines right (fragile). config: { styles: { ".MathJax_SVG": { "display": "inline", "font-style": "normal", "font-weight": "normal", "line-height": "normal", "font-size": "100%", "font-size-adjust":"none", "text-indent": 0, "text-align": "left", "text-transform": "none", "letter-spacing": "normal", "word-spacing": "normal", "word-wrap": "normal", "white-space": "nowrap", "float": "none", "direction": "ltr", "max-width": "none", "max-height": "none", "min-width": 0, "min-height": 0, border: 0, padding: 0, margin: 0 }, ".MathJax_SVG_Display": { position: "relative", display: "block!important", "text-indent": 0, "max-width": "none", "max-height": "none", "min-width": 0, "min-height": 0, width: "100%" }, ".MathJax_SVG *": { transition: "none", "-webkit-transition": "none", "-moz-transition": "none", "-ms-transition": "none", "-o-transition": "none" }, ".MathJax_SVG > div": { display: "inline-block" }, ".mjx-svg-href": { fill: "blue", stroke: "blue" }, ".MathJax_SVG_Processing": { visibility: "hidden", position:"absolute", top:0, left:0, width:0, height: 0, overflow:"hidden", display:"block!important" }, ".MathJax_SVG_Processed": {display:"none!important"}, ".MathJax_SVG_test": { "font-style": "normal", "font-weight": "normal", "font-size": "100%", "font-size-adjust":"none", "text-indent": 0, "text-transform": "none", "letter-spacing": "normal", "word-spacing": "normal", overflow: "hidden", height: "1px" }, ".MathJax_SVG_test.mjx-test-display": { display: "table!important" }, ".MathJax_SVG_test.mjx-test-inline": { display: "inline!important", "margin-right": "-1px" }, ".MathJax_SVG_test.mjx-test-default": { display: "block!important", clear: "both" }, ".MathJax_SVG_ex_box": { display: "inline-block!important", position: "absolute", overflow: "hidden", "min-height": 0, "max-height":"none", padding:0, border: 0, margin: 0, width:"1px", height:"60ex" }, ".mjx-test-inline .MathJax_SVG_left_box": { display: "inline-block", width: 0, "float":"left" }, ".mjx-test-inline .MathJax_SVG_right_box": { display: "inline-block", width: 0, "float":"right" }, ".mjx-test-display .MathJax_SVG_right_box": { display: "table-cell!important", width: "10000em!important", "min-width":0, "max-width":"none", padding:0, border:0, margin:0 }, "#MathJax_SVG_Tooltip": { position: "absolute", left: 0, top: 0, width: "auto", height: "auto", display: "none" } } }, hideProcessedMath: true, // use display:none until all math is processed fontNames: ["TeX","STIX","STIX-Web","Asana-Math", "Gyre-Termes","Gyre-Pagella","Latin-Modern","Neo-Euler"], Config: function () { this.SUPER(arguments).Config.apply(this,arguments); var settings = HUB.config.menuSettings, config = this.config, font = settings.font; if (settings.scale) {config.scale = settings.scale} if (font && font !== "Auto") { font = font.replace(/(Local|Web|Image)$/i,""); font = font.replace(/([a-z])([A-Z])/,"$1-$2"); this.fontInUse = font; } else { this.fontInUse = config.font || "TeX"; } if (this.fontNames.indexOf(this.fontInUse) < 0) {this.fontInUse = "TeX"} this.fontDir += "/" + this.fontInUse; if (!this.require) {this.require = []} this.require.push(this.fontDir+"/fontdata.js"); this.require.push(MathJax.OutputJax.extensionDir+"/MathEvents.js"); }, Startup: function () { // Set up event handling EVENT = MathJax.Extension.MathEvents.Event; TOUCH = MathJax.Extension.MathEvents.Touch; HOVER = MathJax.Extension.MathEvents.Hover; this.ContextMenu = EVENT.ContextMenu; this.Mousedown = EVENT.AltContextMenu; this.Mouseover = HOVER.Mouseover; this.Mouseout = HOVER.Mouseout; this.Mousemove = HOVER.Mousemove; // Make hidden div for doing tests and storing global SVG <defs> this.hiddenDiv = HTML.Element("div",{ style:{visibility:"hidden", overflow:"hidden", position:"absolute", top:0, height:"1px", width: "auto", padding:0, border:0, margin:0, textAlign:"left", textIndent:0, textTransform:"none", lineHeight:"normal", letterSpacing:"normal", wordSpacing:"normal"} }); if (!document.body.firstChild) {document.body.appendChild(this.hiddenDiv)} else {document.body.insertBefore(this.hiddenDiv,document.body.firstChild)} this.hiddenDiv = HTML.addElement(this.hiddenDiv,"div",{id:"MathJax_SVG_Hidden"}); // Determine pixels-per-inch and em-size var div = HTML.addElement(this.hiddenDiv,"div",{style:{width:"5in"}}); this.pxPerInch = div.offsetWidth/5; this.hiddenDiv.removeChild(div); // Used for measuring text sizes this.textSVG = this.Element("svg"); // Global defs for font glyphs BBOX.GLYPH.defs = this.addElement(this.addElement(this.hiddenDiv.parentNode,"svg"), "defs",{id:"MathJax_SVG_glyphs"}); // Used in preTranslate to get scaling factors this.TestSpan = HTML.Element("span",{className:"MathJax_SVG_test"},[ ["span",{className:"MathJax_SVG_left_box"}], ["span",{className:"MathJax_SVG_ex_box"}], ["span",{className:"MathJax_SVG_right_box"}] ]); // Set up styles return AJAX.Styles(this.config.styles,["InitializeSVG",this]); }, // // Handle initialization that requires styles to be set up // InitializeSVG: function () { // // Get the default sizes (need styles in place to do this) // var test = document.body.appendChild(this.TestSpan.cloneNode(true)); test.className += " mjx-test-inline mjx-test-default"; this.defaultEx = test.childNodes[1].offsetHeight/60; this.defaultWidth = Math.max(0,test.lastChild.offsetLeft - test.firstChild.offsetLeft - 2); document.body.removeChild(test); }, preTranslate: function (state) { var scripts = state.jax[this.id], i, m = scripts.length, n, script, prev, span, div, test, jax, ex, em, maxwidth, relwidth = false, cwidth, linebreak = this.config.linebreaks.automatic, width = this.config.linebreaks.width; if (linebreak) { relwidth = (width.match(/^\s*(\d+(\.\d*)?%\s*)?container\s*$/) != null); if (relwidth) {width = width.replace(/\s*container\s*/,"")} else {maxwidth = this.defaultWidth} if (width === "") {width = "100%"} } else {maxwidth = 100000} // a big width, so no implicit line breaks // // Loop through the scripts // for (i = 0; i < m; i++) { script = scripts[i]; if (!script.parentNode) continue; // // Remove any existing output // prev = script.previousSibling; if (prev && String(prev.className).match(/^MathJax(_SVG)?(_Display)?( MathJax(_SVG)?_Process(ing|ed))?$/)) {prev.parentNode.removeChild(prev)} if (script.MathJax.preview) script.MathJax.preview.style.display = "none"; // // Add the span, and a div if in display mode, // then set the role and mark it as being processed // jax = script.MathJax.elementJax; if (!jax) continue; jax.SVG = { display: (jax.root.Get("display") === "block"), preview: (jax.SVG||{}).preview // in case typeset calls are interleaved }; span = div = HTML.Element("span",{ style: {"font-size": this.config.scale+"%", display:"inline-block"}, className:"MathJax_SVG", id:jax.inputID+"-Frame", isMathJax:true, jaxID:this.id, oncontextmenu:EVENT.Menu, onmousedown: EVENT.Mousedown, onmouseover:EVENT.Mouseover, onmouseout:EVENT.Mouseout, onmousemove:EVENT.Mousemove, onclick:EVENT.Click, ondblclick:EVENT.DblClick, // Added for keyboard accessible menu. onkeydown: EVENT.Keydown, tabIndex: HUB.getTabOrder(jax) }); if (HUB.Browser.noContextMenu) { span.ontouchstart = TOUCH.start; span.ontouchend = TOUCH.end; } if (jax.SVG.display) { div = HTML.Element("div",{className:"MathJax_SVG_Display"}); div.appendChild(span); } div.className += " MathJax_SVG_Processing"; script.parentNode.insertBefore(div,script); // // Add the test span for determining scales and linebreak widths // test = this.TestSpan.cloneNode(true); test.className += " mjx-test-" + (jax.SVG.display ? "display" : "inline"); script.parentNode.insertBefore(test,script); } // // Determine the scaling factors for each script // (this only requires one reflow rather than a reflow for each equation) // var hidden = []; for (i = 0; i < m; i++) { script = scripts[i]; if (!script.parentNode) continue; test = script.previousSibling; div = test.previousSibling; jax = script.MathJax.elementJax; if (!jax) continue; ex = test.childNodes[1].offsetHeight/60; cwidth = Math.max(0, jax.SVG.display ? test.lastChild.offsetWidth - 1: test.lastChild.offsetLeft - test.firstChild.offsetLeft - 2) / this.config.scale * 100; if (ex === 0 || ex === "NaN") { // can't read width, so move to hidden div for processing hidden.push(div); jax.SVG.isHidden = true; ex = this.defaultEx; cwidth = this.defaultWidth; } if (cwidth === 0 && !jax.SVG.display) cwidth = this.defaultWidth; if (relwidth) {maxwidth = cwidth} jax.SVG.ex = ex; jax.SVG.em = em = ex / SVG.TeX.x_height * 1000; // scale ex to x_height jax.SVG.cwidth = cwidth/em * 1000; jax.SVG.lineWidth = (linebreak ? this.length2em(width,1,maxwidth/em*1000) : SVG.BIGDIMEN); } for (i = 0, n = hidden.length; i < n; i++) { this.hiddenDiv.appendChild(hidden[i]); this.addElement(this.hiddenDiv,"br"); } // // Remove the test spans used for determining scales and linebreak widths // for (i = 0; i < m; i++) { script = scripts[i]; if (!script.parentNode) continue; jax = script.MathJax.elementJax; if (!jax) continue; script.parentNode.removeChild(script.previousSibling); if (script.MathJax.preview) script.MathJax.preview.style.display = ""; } // // Set state variables used for displaying equations in chunks // state.SVGeqn = state.SVGlast = 0; state.SVGi = -1; state.SVGchunk = this.config.EqnChunk; state.SVGdelay = false; }, Translate: function (script,state) { if (!script.parentNode) return; // // If we are supposed to do a chunk delay, do it // if (state.SVGdelay) { state.SVGdelay = false; HUB.RestartAfter(MathJax.Callback.Delay(this.config.EqnChunkDelay)); } // // Get the data about the math // var jax = script.MathJax.elementJax, math = jax.root, div, span, localCache = (SVG.config.useFontCache && !SVG.config.useGlobalCache); if (jax.SVG.isHidden) { span = document.getElementById(jax.inputID+"-Frame"); div = (jax.SVG.display ? span.parentElement : span); } else { div = script.previousSibling; span = (jax.SVG.display ? (div||{}).firstChild||div : div); } if (!div) return; // // Set the font metrics // this.em = MML.mbase.prototype.em = jax.SVG.em; this.ex = jax.SVG.ex; this.linebreakWidth = jax.SVG.lineWidth; this.cwidth = jax.SVG.cwidth; // // Typeset the math // this.mathDiv = div; span.appendChild(this.textSVG); if (localCache) {SVG.resetGlyphs()} this.initSVG(math,span); math.setTeXclass(); try {math.toSVG(span,div)} catch (err) { if (err.restart) {while (span.firstChild) {span.removeChild(span.firstChild)}} if (localCache) {BBOX.GLYPH.n--} throw err; } span.removeChild(this.textSVG); // // Put it in place, and remove the processing marker // if (jax.SVG.isHidden) {script.parentNode.insertBefore(div,script)} div.className = div.className.split(/ /)[0]; // // Check if we are hiding the math until more is processed // if (this.hideProcessedMath) { // // Hide the math and don't let its preview be removed // div.className += " MathJax_SVG_Processed"; if (script.MathJax.preview) { jax.SVG.preview = script.MathJax.preview; delete script.MathJax.preview; } // // Check if we should show this chunk of equations // state.SVGeqn += (state.i - state.SVGi); state.SVGi = state.i; if (state.SVGeqn >= state.SVGlast + state.SVGchunk) { this.postTranslate(state,true); state.SVGchunk = Math.floor(state.SVGchunk*this.config.EqnChunkFactor); state.SVGdelay = true; // delay if there are more scripts } } }, postTranslate: function (state,partial) { var scripts = state.jax[this.id]; if (!this.hideProcessedMath) return; // // Reveal this chunk of math // for (var i = state.SVGlast, m = state.SVGeqn; i < m; i++) { var script = scripts[i]; if (script && script.MathJax.elementJax) { // // Remove the processed marker // script.previousSibling.className = script.previousSibling.className.split(/ /)[0]; var data = script.MathJax.elementJax.SVG; // // Remove the preview, if any // if (data.preview) { data.preview.innerHTML = ""; script.MathJax.preview = data.preview; delete data.preview; } } } // // Save our place so we know what is revealed // state.SVGlast = state.SVGeqn; }, resetGlyphs: function (reset) { if (this.config.useFontCache) { var GLYPH = BBOX.GLYPH; if (this.config.useGlobalCache) { GLYPH.defs = document.getElementById("MathJax_SVG_glyphs"); GLYPH.defs.innerHTML = ""; } else { GLYPH.defs = this.Element("defs"); GLYPH.n++; } GLYPH.glyphs = {}; if (reset) {GLYPH.n = 0} } }, // // Return the containing HTML element rather than the SVG element, since // most browsers can't position to an SVG element properly. // hashCheck: function (target) { if (target && target.nodeName.toLowerCase() === "g") {do {target = target.parentNode} while (target && target.firstChild.nodeName !== "svg")} return target; }, getJaxFromMath: function (math) { if (math.parentNode.className.match(/MathJax_SVG_Display/)) {math = math.parentNode} do {math = math.nextSibling} while (math && math.nodeName.toLowerCase() !== "script"); return HUB.getJaxFor(math); }, getHoverSpan: function (jax,math) { math.style.position = "relative"; // make sure inline containers have position set return math.firstChild; }, getHoverBBox: function (jax,span,math) { var bbox = EVENT.getBBox(span.parentNode); bbox.h += 2; bbox.d -= 2; // bbox seems to be a bit off, so compensate (FIXME) return bbox; }, Zoom: function (jax,span,math,Mw,Mh) { // // Re-render at larger size // span.className = "MathJax_SVG"; // // get em size (taken from this.preTranslate) // var emex = span.appendChild(this.TestSpan.cloneNode(true)); var ex = emex.childNodes[1].offsetHeight/60; this.em = MML.mbase.prototype.em = ex / SVG.TeX.x_height * 1000; this.ex = ex; this.linebreakWidth = jax.SVG.lineWidth; this.cwidth = jax.SVG.cwidth; emex.parentNode.removeChild(emex); span.appendChild(this.textSVG); this.mathDIV = span; this.zoomScale = parseInt(HUB.config.menuSettings.zscale) / 100; var tw = jax.root.data[0].SVGdata.tw; if (tw && tw < this.cwidth) this.cwidth = tw; this.idPostfix = "-zoom"; jax.root.toSVG(span,span); this.idPostfix = ""; this.zoomScale = 1; span.removeChild(this.textSVG); // // Don't allow overlaps on any edge // var svg = span.getElementsByTagName("svg")[0].style; svg.marginTop = svg.marginRight = svg.marginLeft = 0; if (svg.marginBottom.charAt(0) === "-") span.style.marginBottom = svg.marginBottom.substr(1); if (this.operaZoomRefresh) {setTimeout(function () {span.firstChild.style.border="1px solid transparent"},1)} // // WebKit bug (issue #749) // if (span.offsetWidth < span.firstChild.offsetWidth) { span.style.minWidth = span.firstChild.offsetWidth + "px"; math.style.minWidth = math.firstChild.offsetWidth + "px"; } // // Get height and width of zoomed math and original math // span.style.position = math.style.position = "absolute"; var zW = span.offsetWidth, zH = span.offsetHeight, mH = math.offsetHeight, mW = math.offsetWidth; span.style.position = math.style.position = ""; // return {Y:-EVENT.getBBox(span).h, mW:mW, mH:mH, zW:zW, zH:zH}; }, initSVG: function (math,span) {}, Remove: function (jax) { var span = document.getElementById(jax.inputID+"-Frame"); if (span) { if (jax.SVG.display) {span = span.parentNode} span.parentNode.removeChild(span); } delete jax.SVG; }, Em: function (m) { if (Math.abs(m) < .0006) return "0"; return m.toFixed(3).replace(/\.?0+$/,"") + "em"; }, Ex: function (m) { m = m / this.TeX.x_height; if (Math.abs(m) < .0006) return "0"; return m.toFixed(3).replace(/\.?0+$/,"") + "ex"; }, Percent: function (m) { return (100*m).toFixed(1).replace(/\.?0+$/,"") + "%"; }, Fixed: function (m,n) { if (Math.abs(m) < .0006) return "0"; return m.toFixed(n||3).replace(/\.?0+$/,""); }, length2em: function (length,mu,size) { if (typeof(length) !== "string") {length = length.toString()} if (length === "") {return ""} if (length === MML.SIZE.NORMAL) {return 1000} if (length === MML.SIZE.BIG) {return 2000} if (length === MML.SIZE.SMALL) {return 710} if (length === "infinity") {return SVG.BIGDIMEN} if (length.match(/mathspace$/)) {return 1000*SVG.MATHSPACE[length]} var emFactor = (this.zoomScale || 1) / SVG.em; var match = length.match(/^\s*([-+]?(?:\.\d+|\d+(?:\.\d*)?))?(pt|em|ex|mu|px|pc|in|mm|cm|%)?/); var m = parseFloat(match[1]||"1") * 1000, unit = match[2]; if (size == null) {size = 1000}; if (mu == null) {mu = 1} if (unit === "em") {return m} if (unit === "ex") {return m * SVG.TeX.x_height/1000} if (unit === "%") {return m / 100 * size / 1000} if (unit === "px") {return m * emFactor} if (unit === "pt") {return m / 10} // 10 pt to an em if (unit === "pc") {return m * 1.2} // 12 pt to a pc if (unit === "in") {return m * this.pxPerInch * emFactor} if (unit === "cm") {return m * this.pxPerInch * emFactor / 2.54} // 2.54 cm to an inch if (unit === "mm") {return m * this.pxPerInch * emFactor / 25.4} // 10 mm to a cm if (unit === "mu") {return m / 18 * mu} return m*size / 1000; // relative to given size (or 1em as default) }, thickness2em: function (length,mu) { var thick = SVG.TeX.rule_thickness; if (length === MML.LINETHICKNESS.MEDIUM) {return thick} if (length === MML.LINETHICKNESS.THIN) {return .67*thick} if (length === MML.LINETHICKNESS.THICK) {return 1.67*thick} return this.length2em(length,mu,thick); }, border2em: function (length,mu) { if (length === MML.LINETHICKNESS.THIN) {length = "1px"} if (length === MML.LINETHICKNESS.MEDIUM) {length = "3px"} if (length === MML.LINETHICKNESS.THICK) {length = "5px"} return this.length2em(length,mu); }, getPadding: function (styles) { var padding = {top:0, right:0, bottom:0, left:0}, has = false; for (var id in padding) {if (padding.hasOwnProperty(id)) { var pad = styles["padding"+id.charAt(0).toUpperCase()+id.substr(1)]; if (pad) {padding[id] = this.length2em(pad); has = true;} }} return (has ? padding : false); }, getBorders: function (styles) { var border = {top:0, right:0, bottom:0, left:0}, has = false; for (var id in border) {if (border.hasOwnProperty(id)) { var ID = "border"+id.charAt(0).toUpperCase()+id.substr(1); var style = styles[ID+"Style"]; if (style && style !== "none") { has = true; border[id] = this.border2em(styles[ID+"Width"] || MML.LINETHICKNESS.MEDIUM); border[id+"Style"] = styles[ID+"Style"]; border[id+"Color"] = styles[ID+"Color"]; if (border[id+"Color"] === "initial") {border[id+"Color"] = ""} } else {delete border[id]} }} return (has ? border : false); }, Element: function (type,def) { var obj = (typeof(type) === "string" ? document.createElementNS(SVGNS,type) : type); obj.isMathJax = true; if (def) {for (var id in def) {if (def.hasOwnProperty(id)) {obj.setAttribute(id,def[id].toString())}}} return obj; }, addElement: function (parent,type,def) {return parent.appendChild(this.Element(type,def))}, TextNode: HTML.TextNode, addText: HTML.addText, ucMatch: HTML.ucMatch, HandleVariant: function (variant,scale,text) { var svg = BBOX.G(); var n, N, c, font, VARIANT, i, m, id, M, RANGES; if (!variant) {variant = this.FONTDATA.VARIANT[MML.VARIANT.NORMAL]} if (variant.forceFamily) { text = BBOX.TEXT(scale,text,variant.font); if (variant.h != null) {text.h = variant.h}; if (variant.d != null) {text.d = variant.d} svg.Add(text); text = ""; } VARIANT = variant; for (i = 0, m = text.length; i < m; i++) { variant = VARIANT; n = text.charCodeAt(i); c = text.charAt(i); if (n >= 0xD800 && n < 0xDBFF) { i++; n = (((n-0xD800)<<10)+(text.charCodeAt(i)-0xDC00))+0x10000; if (this.FONTDATA.RemapPlane1) { var nv = this.FONTDATA.RemapPlane1(n,variant); n = nv.n; variant = nv.variant; } } else { RANGES = this.FONTDATA.RANGES; for (id = 0, M = RANGES.length; id < M; id++) { if (RANGES[id].name === "alpha" && variant.noLowerCase) continue; N = variant["offset"+RANGES[id].offset]; if (N && n >= RANGES[id].low && n <= RANGES[id].high) { if (RANGES[id].remap && RANGES[id].remap[n]) { n = N + RANGES[id].remap[n]; } else { if (RANGES[id].remapOnly) break; n = n - RANGES[id].low + N; if (RANGES[id].add) {n += RANGES[id].add} } if (variant["variant"+RANGES[id].offset]) {variant = this.FONTDATA.VARIANT[variant["variant"+RANGES[id].offset]]} break; } } } if (variant.remap && variant.remap[n]) { n = variant.remap[n]; if (variant.remap.variant) {variant = this.FONTDATA.VARIANT[variant.remap.variant]} } else if (this.FONTDATA.REMAP[n] && !variant.noRemap) { n = this.FONTDATA.REMAP[n]; } if (isArray(n)) {variant = this.FONTDATA.VARIANT[n[1]]; n = n[0]} if (typeof(n) === "string") { text = n+text.substr(i+1); m = text.length; i = -1; continue; } font = this.lookupChar(variant,n); c = font[n]; if (c) { if ((c[5] && c[5].space) || (c[5] === "" && c[0]+c[1] === 0)) {svg.w += c[2]} else { c = [scale,font.id+"-"+n.toString(16).toUpperCase()].concat(c); svg.Add(BBOX.GLYPH.apply(BBOX,c),svg.w,0); } } else if (this.FONTDATA.DELIMITERS[n]) { c = this.createDelimiter(n,0,1,font); svg.Add(c,svg.w,(this.FONTDATA.DELIMITERS[n].dir === "V" ? c.d: 0)); } else { if (n <= 0xFFFF) {c = String.fromCharCode(n)} else { N = n - 0x10000; c = String.fromCharCode((N>>10)+0xD800) + String.fromCharCode((N&0x3FF)+0xDC00); } var box = BBOX.TEXT(scale*100/SVG.config.scale,c,{ "font-family":variant.defaultFamily||SVG.config.undefinedFamily, "font-style":(variant.italic?"italic":""), "font-weight":(variant.bold?"bold":"") }) if (variant.h != null) {box.h = variant.h}; if (variant.d != null) {box.d = variant.d} c = BBOX.G(); c.Add(box); svg.Add(c,svg.w,0); HUB.signal.Post(["SVG Jax - unknown char",n,variant]); } } if (SVG.isChar(text) && font.skew && font.skew[n]) {svg.skew = font.skew[n]*1000} if (svg.element.childNodes.length === 1 && !svg.element.firstChild.getAttribute("x")) { svg.element = svg.element.firstChild; svg.removeable = false; svg.scale = scale; } return svg; }, lookupChar: function (variant,n) { var i, m; if (!variant.FONTS) { var FONTS = this.FONTDATA.FONTS; var fonts = (variant.fonts || this.FONTDATA.VARIANT.normal.fonts); if (!(fonts instanceof Array)) {fonts = [fonts]} if (variant.fonts != fonts) {variant.fonts = fonts} variant.FONTS = []; for (i = 0, m = fonts.length; i < m; i++) { if (FONTS[fonts[i]]) {variant.FONTS.push(FONTS[fonts[i]])} } } for (i = 0, m = variant.FONTS.length; i < m; i++) { var font = variant.FONTS[i]; if (typeof(font) === "string") {delete variant.FONTS; this.loadFont(font)} if (font[n]) {return font} else {this.findBlock(font,n)} } return {id:"unknown"}; }, isChar: function (text) { if (text.length === 1) return true; if (text.length !== 2) return false; var n = text.charCodeAt(0); return (n >= 0xD800 && n < 0xDBFF); }, findBlock: function (font,c) { if (font.Ranges) { // FIXME: do binary search? for (var i = 0, m = font.Ranges.length; i < m; i++) { if (c < font.Ranges[i][0]) return; if (c <= font.Ranges[i][1]) { var file = font.Ranges[i][2]; for (var j = font.Ranges.length-1; j >= 0; j--) {if (font.Ranges[j][2] == file) {font.Ranges.splice(j,1)}} this.loadFont(font.directory+"/"+file+".js"); } } } }, loadFont: function (file) { HUB.RestartAfter(AJAX.Require(this.fontDir+"/"+file)); }, createDelimiter: function (code,HW,scale,font) { if (!scale) {scale = 1}; var svg = BBOX.G(); if (!code) { svg.Clean(); delete svg.element; svg.w = svg.r = this.TeX.nulldelimiterspace * scale; return svg; } if (!(HW instanceof Array)) {HW = [HW,HW]} var hw = HW[1]; HW = HW[0]; var delim = {alias: code}; while (delim.alias) { code = delim.alias; delim = this.FONTDATA.DELIMITERS[code]; if (!delim) {delim = {HW: [0,this.FONTDATA.VARIANT[MML.VARIANT.NORMAL]]}} } if (delim.load) {HUB.RestartAfter(AJAX.Require(this.fontDir+"/fontdata-"+delim.load+".js"))} for (var i = 0, m = delim.HW.length; i < m; i++) { if (delim.HW[i][0]*scale >= HW-10-SVG.config.blacker || (i == m-1 && !delim.stretch)) { if (delim.HW[i][2]) {scale *= delim.HW[i][2]} if (delim.HW[i][3]) {code = delim.HW[i][3]} return this.createChar(scale,[code,delim.HW[i][1]],font).With({stretched: true}); } } if (delim.stretch) {this["extendDelimiter"+delim.dir](svg,hw,delim.stretch,scale,font)} return svg; }, createChar: function (scale,data,font) { var text = "", variant = {fonts: [data[1]], noRemap:true}; if (font && font === MML.VARIANT.BOLD) {variant.fonts = [data[1]+"-bold",data[1]]} if (typeof(data[1]) !== "string") {variant = data[1]} if (data[0] instanceof Array) { for (var i = 0, m = data[0].length; i < m; i++) {text += String.fromCharCode(data[0][i])} } else {text = String.fromCharCode(data[0])} if (data[4]) {scale = scale*data[4]} var svg = this.HandleVariant(variant,scale,text); if (data[2]) {svg.x = data[2]*1000} if (data[3]) {svg.y = data[3]*1000} if (data[5]) {svg.h += data[5]*1000} if (data[6]) {svg.d += data[6]*1000} return svg; }, extendDelimiterV: function (svg,H,delim,scale,font) { var top = this.createChar(scale,(delim.top||delim.ext),font); var bot = this.createChar(scale,(delim.bot||delim.ext),font); var h = top.h + top.d + bot.h + bot.d; var y = -top.h; svg.Add(top,0,y); y -= top.d; if (delim.mid) {var mid = this.createChar(scale,delim.mid,font); h += mid.h + mid.d} if (delim.min && H < h*delim.min) {H = h*delim.min} if (H > h) { var ext = this.createChar(scale,delim.ext,font); var k = (delim.mid ? 2 : 1), eH = (H-h) / k, s = (eH+100) / (ext.h+ext.d); while (k-- > 0) { var g = SVG.Element("g",{transform:"translate("+ext.y+","+(y-s*ext.h+50+ext.y)+") scale(1,"+s+")"}); g.appendChild(ext.element.cloneNode(false)); svg.element.appendChild(g); y -= eH; if (delim.mid && k) {svg.Add(mid,0,y-mid.h); y -= (mid.h+mid.d)} } } else if (delim.mid) { y += (h - H)/2; svg.Add(mid,0,y-mid.h); y += -(mid.h + mid.d) + (h - H)/2; } else { y += (h - H); } svg.Add(bot,0,y-bot.h); svg.Clean(); svg.scale = scale; svg.isMultiChar = true; }, extendDelimiterH: function (svg,W,delim,scale,font) { var left = this.createChar(scale,(delim.left||delim.rep),font); var right = this.createChar(scale,(delim.right||delim.rep),font); svg.Add(left,-left.l,0); var w = (left.r - left.l) + (right.r - right.l), x = left.r - left.l; if (delim.mid) {var mid = this.createChar(scale,delim.mid,font); w += mid.w} if (delim.min && W < w*delim.min) {W = w*delim.min} if (W > w) { var rep = this.createChar(scale,delim.rep,font), fuzz = delim.fuzz || 0; var k = (delim.mid ? 2 : 1), rW = (W-w) / k, s = (rW+fuzz) / (rep.r-rep.l); while (k-- > 0) { var g = SVG.Element("g",{transform:"translate("+(x-fuzz/2-s*rep.l+rep.x)+","+rep.y+") scale("+s+",1)"}); g.appendChild(rep.element.cloneNode(false)); svg.element.appendChild(g); x += rW; if (delim.mid && k) {svg.Add(mid,x,0); x += mid.w} } } else if (delim.mid) { x -= (w - W)/2; svg.Add(mid,x,0); x += mid.w - (w - W)/2; } else { x -= (w - W); } svg.Add(right,x-right.l,0); svg.Clean(); svg.scale = scale; svg.isMultiChar = true; }, MATHSPACE: { veryverythinmathspace: 1/18, verythinmathspace: 2/18, thinmathspace: 3/18, mediummathspace: 4/18, thickmathspace: 5/18, verythickmathspace: 6/18, veryverythickmathspace: 7/18, negativeveryverythinmathspace: -1/18, negativeverythinmathspace: -2/18, negativethinmathspace: -3/18, negativemediummathspace: -4/18, negativethickmathspace: -5/18, negativeverythickmathspace: -6/18, negativeveryverythickmathspace: -7/18 }, // // Units are em/1000 so quad is 1em // TeX: { x_height: 430.554, quad: 1000, num1: 676.508, num2: 393.732, num3: 443.73, denom1: 685.951, denom2: 344.841, sup1: 412.892, sup2: 362.892, sup3: 288.888, sub1: 150, sub2: 247.217, sup_drop: 386.108, sub_drop: 50, delim1: 2390, delim2: 1000, axis_height: 250, rule_thickness: 60, big_op_spacing1: 111.111, big_op_spacing2: 166.666, big_op_spacing3: 200, big_op_spacing4: 600, big_op_spacing5: 100, scriptspace: 100, nulldelimiterspace: 120, delimiterfactor: 901, delimitershortfall: 300, min_rule_thickness: 1.25, // in pixels min_root_space: 1.5 // in pixels }, BIGDIMEN: 10000000, NBSP: "\u00A0" }); var BBOX = SVG.BBOX = MathJax.Object.Subclass({ type: "g", removeable: true, Init: function (def) { this.h = this.d = -SVG.BIGDIMEN; this.H = this.D = 0; this.w = this.r = 0; this.l = SVG.BIGDIMEN; this.x = this.y = 0; this.scale = 1; this.n = 0; if (this.type) {this.element = SVG.Element(this.type,def)} }, With: function (def) {return HUB.Insert(this,def)}, Add: function (svg,dx,dy,forcew,infront) { if (dx) {svg.x += dx}; if (dy) {svg.y += dy}; if (svg.element) { if (svg.removeable && svg.element.childNodes.length === 1 && svg.n === 1) { var child = svg.element.firstChild, nodeName = child.nodeName.toLowerCase(); if (nodeName === "use" || nodeName === "rect") { svg.element = child; svg.scale = svg.childScale; var x = svg.childX, y = svg.childY; svg.x += x; svg.y += y; svg.h -= y; svg.d += y; svg.H -= y; svg.D +=y; svg.w -= x; svg.r -= x; svg.l += x; svg.removeable = false; child.setAttribute("x",Math.floor(svg.x/svg.scale)); child.setAttribute("y",Math.floor(svg.y/svg.scale)); } } if (Math.abs(svg.x) < 1 && Math.abs(svg.y) < 1) { svg.remove = svg.removeable; } else { nodeName = svg.element.nodeName.toLowerCase(); if (nodeName === "g") { if (!svg.element.firstChild) {svg.remove = svg.removeable} else {svg.element.setAttribute("transform","translate("+Math.floor(svg.x)+","+Math.floor(svg.y)+")")} } else if (nodeName === "line" || nodeName === "polygon" || nodeName === "path" || nodeName === "a") { var transform = svg.element.getAttribute("transform") || ""; if (transform) transform = " "+transform; transform = "translate("+Math.floor(svg.x)+","+Math.floor(svg.y)+")"+transform; svg.element.setAttribute("transform",transform); } else { svg.element.setAttribute("x",Math.floor(svg.x/svg.scale)); svg.element.setAttribute("y",Math.floor(svg.y/svg.scale)); } } if (svg.remove) { this.n += svg.n; while (svg.element.firstChild) { if (infront && this.element.firstChild) { this.element.insertBefore(svg.element.firstChild,this.element.firstChild); } else { this.element.appendChild(svg.element.firstChild); } } } else { if (infront) {this.element.insertBefore(svg.element,this.element.firstChild)} else {this.element.appendChild(svg.element)} } delete svg.element; } if (svg.hasIndent) {this.hasIndent = svg.hasIndent} if (svg.tw != null) {this.tw = svg.tw} if (svg.d - svg.y > this.d) {this.d = svg.d - svg.y; if (this.d > this.D) {this.D = this.d}} if (svg.y + svg.h > this.h) {this.h = svg.y + svg.h; if (this.h > this.H) {this.H = this.h}} if (svg.D - svg.y > this.D) {this.D = svg.D - svg.y} if (svg.y + svg.H > this.H) {this.H = svg.y + svg.H} if (svg.x + svg.l < this.l) {this.l = svg.x + svg.l} if (svg.x + svg.r > this.r) {this.r = svg.x + svg.r} if (forcew || svg.x + svg.w + (svg.X||0) > this.w) {this.w = svg.x + svg.w + (svg.X||0)} this.childScale = svg.scale; this.childX = svg.x; this.childY = svg.y; this.n++; return svg; }, Align: function (svg,align,dx,dy,shift) { dx = ({left: dx, center: (this.w - svg.w)/2, right: this.w - svg.w - dx})[align] || 0; var w = this.w; this.Add(svg,dx+(shift||0),dy); this.w = w; }, Clean: function () { if (this.h === -SVG.BIGDIMEN) {this.h = this.d = this.l = 0} return this; } }); BBOX.ROW = BBOX.Subclass({ Init: function () { this.SUPER(arguments).Init.call(this); this.svg = []; this.sh = this.sd = 0; }, Check: function (data) { var svg = data.toSVG(); this.svg.push(svg); if (data.SVGcanStretch("Vertical")) {svg.mml = data} if (svg.h + svg.y > this.sh) {this.sh = svg.h + svg.y} if (svg.d - svg.y > this.sd) {this.sd = svg.d - svg.y} }, Stretch: function () { for (var i = 0, m = this.svg.length; i < m; i++) { var svg = this.svg[i], mml = svg.mml; if (mml) { if (mml.forceStretch || mml.SVGdata.h !== this.sh || mml.SVGdata.d !== this.sd) { svg = mml.SVGstretchV(this.sh,this.sd); } mml.SVGdata.HW = this.sh; mml.SVGdata.D = this.sd; } if (svg.ic) {this.ic = svg.ic} else {delete this.ic} this.Add(svg,this.w,0,true); } delete this.svg; } }); BBOX.RECT = BBOX.Subclass({ type: "rect", removeable: false, Init: function (h,d,w,def) { if (def == null) {def = {stroke:"none"}} def.width = Math.floor(w); def.height = Math.floor(h+d); this.SUPER(arguments).Init.call(this,def); this.w = this.r = w; this.h = this.H = h+d; this.d = this.D = this.l = 0; this.y = -d; } }); BBOX.FRAME = BBOX.Subclass({ type: "rect", removeable: false, Init: function (h,d,w,t,dash,color,def) { if (def == null) {def = {}}; def.fill = "none"; def["stroke-width"] = SVG.Fixed(t,2); def.width = Math.floor(w-t); def.height = Math.floor(h+d-t); def.transform = "translate("+Math.floor(t/2)+","+Math.floor(-d+t/2)+")"; if (dash === "dashed") {def["stroke-dasharray"] = [Math.floor(6*SVG.em),Math.floor(6*SVG.em)].join(" ")} this.SUPER(arguments).Init.call(this,def); this.w = this.r = w; this.h = this.H = h; this.d = this.D = d; this.l = 0; } }); BBOX.HLINE = BBOX.Subclass({ type: "line", removeable: false, Init: function (w,t,dash,color,def) { if (def == null) {def = {"stroke-linecap":"square"}} if (color && color !== "") {def.stroke = color} def["stroke-width"] = SVG.Fixed(t,2); def.x1 = def.y1 = def.y2 = Math.floor(t/2); def.x2 = Math.floor(w-t/2); if (dash === "dashed") { var n = Math.floor(Math.max(0,w-t)/(6*t)), m = Math.floor(Math.max(0,w-t)/(2*n+1)); def["stroke-dasharray"] = m+" "+m; } if (dash === "dotted") { def["stroke-dasharray"] = [1,Math.max(150,Math.floor(2*t))].join(" "); def["stroke-linecap"] = "round"; } this.SUPER(arguments).Init.call(this,def); this.w = this.r = w; this.l = 0; this.h = this.H = t; this.d = this.D = 0; } }); BBOX.VLINE = BBOX.Subclass({ type: "line", removeable: false, Init: function (h,t,dash,color,def) { if (def == null) {def = {"stroke-linecap":"square"}} if (color && color !== "") {def.stroke = color} def["stroke-width"] = SVG.Fixed(t,2); def.x1 = def.x2 = def.y1 = Math.floor(t/2); def.y2 = Math.floor(h-t/2); if (dash === "dashed") { var n = Math.floor(Math.max(0,h-t)/(6*t)), m = Math.floor(Math.max(0,h-t)/(2*n+1)); def["stroke-dasharray"] = m+" "+m; } if (dash === "dotted") { def["stroke-dasharray"] = [1,Math.max(150,Math.floor(2*t))].join(" "); def["stroke-linecap"] = "round"; } this.SUPER(arguments).Init.call(this,def); this.w = this.r = t; this.l = 0; this.h = this.H = h; this.d = this.D = 0; } }); BBOX.TEXT = BBOX.Subclass({ type: "text", removeable: false, Init: function (scale,text,def) { if (!def) {def = {}}; def.stroke = "none"; if (def["font-style"] === "") delete def["font-style"]; if (def["font-weight"] === "") delete def["font-weight"]; this.SUPER(arguments).Init.call(this,def); SVG.addText(this.element,text); SVG.textSVG.appendChild(this.element); var bbox = this.element.getBBox(); SVG.textSVG.removeChild(this.element); scale *= 1000/SVG.em; this.element.setAttribute("transform","scale("+SVG.Fixed(scale)+") matrix(1 0 0 -1 0 0)"); this.w = this.r = bbox.width*scale; this.l = 0; this.h = this.H = -bbox.y*scale; this.d = this.D = (bbox.height + bbox.y)*scale; } }); BBOX.G = BBOX; BBOX.NULL = BBOX.Subclass({ Init: function () { this.SUPER(arguments).Init.apply(this,arguments); this.Clean(); } }); BBOX.GLYPH = BBOX.Subclass({ type: "path", removeable: false, Init: function (scale,id,h,d,w,l,r,p) { var def, t = SVG.config.blacker, GLYPH = BBOX.GLYPH; var cache = SVG.config.useFontCache; var transform = (scale === 1 ? null : "scale("+SVG.Fixed(scale)+")"); if (cache && !SVG.config.useGlobalCache) {id = "E"+GLYPH.n+"-"+id} if (!cache || !GLYPH.glyphs[id]) { def = {"stroke-width":t}; if (cache) {def.id = id} else if (transform) {def.transform = transform} def.d = (p ? "M"+p+"Z" : ""); this.SUPER(arguments).Init.call(this,def); if (cache) {GLYPH.defs.appendChild(this.element); GLYPH.glyphs[id] = true;} } if (cache) { def = {}; if (transform) {def.transform = transform} this.element = SVG.Element("use",def); this.element.setAttributeNS(XLINKNS,"href",SVGURL+"#"+id); } this.h = (h+t) * scale; this.d = (d+t) * scale; this.w = (w+t/2) *scale; this.l = (l+t/2) * scale; this.r = (r+t/2) * scale; this.H = Math.max(0,this.h); this.D = Math.max(0,this.d); this.x = this.y = 0; this.scale = scale; } },{ glyphs: {}, // which glpyhs have been used defs: null, // the SVG <defs> element where glyphs are stored n: 0 // the ID for local <defs> for self-contained SVG elements }); HUB.Register.StartupHook("mml Jax Ready",function () { MML = MathJax.ElementJax.mml; MML.mbase.Augment({ SVG: BBOX, toSVG: function () { this.SVGgetStyles(); var variant = this.SVGgetVariant(); var svg = this.SVG(); this.SVGgetScale(svg); this.SVGhandleSpace(svg); for (var i = 0, m = this.data.length; i < m; i++) { if (this.data[i]) { var child = svg.Add(this.data[i].toSVG(variant,svg.scale),svg.w,0,true); if (child.skew) {svg.skew = child.skew} } } svg.Clean(); var text = this.data.join(""); if (svg.skew && !SVG.isChar(text)) {delete svg.skew} if (svg.r > svg.w && SVG.isChar(text) && !variant.noIC) {svg.ic = svg.r - svg.w; svg.w = svg.r} this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; }, SVGchildSVG: function (i) { return (this.data[i] ? this.data[i].toSVG() : BBOX()); }, SVGdataStretched: function (i,HW,D) { this.SVGdata = {HW:HW, D:D}; if (!this.data[i]) {return BBOX()} if (D != null) {return this.data[i].SVGstretchV(HW,D)} if (HW != null) {return this.data[i].SVGstretchH(HW)} return this.data[i].toSVG(); }, SVGsaveData: function (svg) { if (!this.SVGdata) {this.SVGdata = {}} this.SVGdata.w = svg.w, this.SVGdata.x = svg.x; this.SVGdata.h = svg.h, this.SVGdata.d = svg.d; if (svg.y) {this.SVGdata.h += svg.y; this.SVGdata.d -= svg.y} if (svg.X != null) {this.SVGdata.X = svg.X} if (svg.tw != null) {this.SVGdata.tw = svg.tw} if (svg.skew) {this.SVGdata.skew = svg.skew} if (svg.ic) {this.SVGdata.ic = svg.ic} if (this["class"]) {svg.removeable = false; SVG.Element(svg.element,{"class":this["class"]})} // FIXME: if an element is split by linebreaking, the ID will be the same on both parts // FIXME: if an element has an id, its zoomed copy will have the same ID if (this.id) {svg.removeable = false; SVG.Element(svg.element,{"id":this.id})} if (this.href) {this.SVGaddHref(svg)} if (SVG.config.addMMLclasses) { this.SVGaddClass(svg.element,"mjx-svg-"+this.type); svg.removeable = false; } var style = this.style; if (style && svg.element) { svg.element.style.cssText = style; if (svg.element.style.fontSize) {svg.element.style.fontSize = ""} // handled by scale svg.element.style.border = svg.element.style.padding = ""; if (svg.removeable) {svg.removeable = (svg.element.style.cssText === "")} } this.SVGaddAttributes(svg); }, SVGaddClass: function (node,name) { var classes = node.getAttribute("class"); node.setAttribute("class",(classes ? classes+" " : "")+name); }, SVGaddAttributes: function (svg) { // // Copy RDFa, aria, and other tags from the MathML to the HTML-CSS // output spans Don't copy those in the MML.nocopyAttributes list, // the ignoreMMLattributes configuration list, or anything tha // already exists as a property of the span (e.g., no "onlick", etc.) // If a name in the ignoreMMLattributes object is set to false, then // the attribute WILL be copied. // if (this.attrNames) { var copy = this.attrNames, skip = MML.nocopyAttributes, ignore = HUB.config.ignoreMMLattributes; var defaults = (this.type === "mstyle" ? MML.math.prototype.defaults : this.defaults); for (var i = 0, m = copy.length; i < m; i++) { var id = copy[i]; if (ignore[id] == false || (!skip[id] && !ignore[id] && defaults[id] == null && typeof(svg.element[id]) === "undefined")) { svg.element.setAttribute(id,this.attr[id]); svg.removeable = false; } } } }, SVGaddHref: function (svg) { var a = SVG.Element("a",{"class":"mjx-svg-href"}); a.setAttributeNS(XLINKNS,"href",this.href); a.onclick = this.SVGlink; SVG.addElement(a,"rect",{width:svg.w, height:svg.h+svg.d, y:-svg.d, fill:"none", stroke:"none", "pointer-events":"all"}); if (svg.type === "svg") { // for svg element, put <a> inside the main <g> element var g = svg.element.firstChild; while (g.firstChild) {a.appendChild(g.firstChild)} g.appendChild(a); } else { a.appendChild(svg.element); svg.element = a; } svg.removeable = false; }, // // WebKit currently scrolls to the BOTTOM of an svg element if it contains the // target of the link, so implement link by hand, to the containing span element. // SVGlink: function () { var href = this.href.animVal; if (href.charAt(0) === "#") { var target = SVG.hashCheck(document.getElementById(href.substr(1))); if (target && target.scrollIntoView) {setTimeout(function () {target.parentNode.scrollIntoView(true)},1)} } document.location = href; }, SVGgetStyles: function () { if (this.style) { var span = HTML.Element("span"); span.style.cssText = this.style; this.styles = this.SVGprocessStyles(span.style); } }, SVGprocessStyles: function (style) { var styles = {border:SVG.getBorders(style), padding:SVG.getPadding(style)}; if (!styles.border) {delete styles.border} if (!styles.padding) {delete styles.padding} if (style.fontSize) {styles.fontSize = style.fontSize} if (style.color) {styles.color = style.color} if (style.backgroundColor) {styles.background = style.backgroundColor} if (style.fontStyle) {styles.fontStyle = style.fontStyle} if (style.fontWeight) {styles.fontWeight = style.fontWeight} if (style.fontFamily) {styles.fontFamily = style.fontFamily} if (styles.fontWeight && styles.fontWeight.match(/^\d+$/)) {styles.fontWeight = (parseInt(styles.fontWeight) > 600 ? "bold" : "normal")} return styles; }, SVGhandleSpace: function (svg) { if (this.hasMMLspacing()) { if (this.type !== "mo") return; var values = this.getValues("scriptlevel","lspace","rspace"); if (values.scriptlevel <= 0 || this.hasValue("lspace") || this.hasValue("rspace")) { var mu = this.SVGgetMu(svg); values.lspace = Math.max(0,SVG.length2em(values.lspace,mu)); values.rspace = Math.max(0,SVG.length2em(values.rspace,mu)); var core = this, parent = this.Parent(); while (parent && parent.isEmbellished() && parent.Core() === core) {core = parent; parent = parent.Parent()} if (values.lspace) {svg.x += values.lspace} if (values.rspace) {svg.X = values.rspace} } } else { var space = this.texSpacing(); this.SVGgetScale(); if (space !== "") {svg.x += SVG.length2em(space,this.scale)*this.mscale} } }, SVGhandleColor: function (svg) { var values = this.getValues("mathcolor","color"); if (this.styles && this.styles.color && !values.color) {values.color = this.styles.color} if (values.color && !this.mathcolor) {values.mathcolor = values.color} if (values.mathcolor) { SVG.Element(svg.element,{fill:values.mathcolor,stroke:values.mathcolor}) svg.removeable = false; } var borders = (this.styles||{}).border, padding = (this.styles||{}).padding, bleft = ((borders||{}).left||0), pleft = ((padding||{}).left||0), id; values.background = (this.mathbackground || this.background || (this.styles||{}).background || MML.COLOR.TRANSPARENT); if (bleft + pleft) { // // Make a box and move the contents of svg to it, // then add it back into svg, but offset by the left amount // var dup = BBOX(); for (id in svg) {if (svg.hasOwnProperty(id)) {dup[id] = svg[id]}} dup.x = 0; dup.y = 0; svg.element = SVG.Element("g"); svg.removeable = true; svg.Add(dup,bleft+pleft,0); } // // Adjust size by padding and dashed borders (left is taken care of above) // if (padding) {svg.w += padding.right||0; svg.h += padding.top||0; svg.d += padding.bottom||0} if (borders) {svg.w += borders.right||0; svg.h += borders.top||0; svg.d += borders.bottom||0} // // Add background color // if (values.background !== MML.COLOR.TRANSPARENT) { var nodeName = svg.element.nodeName.toLowerCase(); if (nodeName !== "g" && nodeName !== "svg") { var g = SVG.Element("g"); g.appendChild(svg.element); svg.element = g; svg.removeable = true; } svg.Add(BBOX.RECT(svg.h,svg.d,svg.w,{fill:values.background,stroke:"none"}),0,0,false,true) } // // Add borders // if (borders) { var dd = 5; // fuzz factor to avoid anti-alias problems at edges var sides = { left: ["V",svg.h+svg.d,-dd,-svg.d], right: ["V",svg.h+svg.d,svg.w-borders.right+dd,-svg.d], top: ["H",svg.w,0,svg.h-borders.top+dd], bottom:["H",svg.w,0,-svg.d-dd] } for (id in sides) {if (sides.hasOwnProperty(id)) { if (borders[id]) { var side = sides[id], box = BBOX[side[0]+"LINE"]; svg.Add(box(side[1],borders[id],borders[id+"Style"],borders[id+"Color"]),side[2],side[3]); } }} } }, SVGhandleVariant: function (variant,scale,text) { return SVG.HandleVariant(variant,scale,text); }, SVGgetVariant: function () { var values = this.getValues("mathvariant","fontfamily","fontweight","fontstyle"); var variant = values.mathvariant; if (this.variantForm) variant = "-"+SVG.fontInUse+"-variant"; values.hasVariant = this.Get("mathvariant",true); // null if not explicitly specified if (!values.hasVariant) { values.family = values.fontfamily; values.weight = values.fontweight; values.style = values.fontstyle; } if (this.styles) { if (!values.style && this.styles.fontStyle) {values.style = this.styles.fontStyle} if (!values.weight && this.styles.fontWeight) {values.weight = this.styles.fontWeight} if (!values.family && this.styles.fontFamily) {values.family = this.styles.fontFamily} } if (values.family && !values.hasVariant) { if (!values.weight && values.mathvariant.match(/bold/)) {values.weight = "bold"} if (!values.style && values.mathvariant.match(/italic/)) {values.style = "italic"} variant = {forceFamily: true, font: {"font-family":values.family}}; if (values.style) {variant.font["font-style"] = values.style} if (values.weight) {variant.font["font-weight"] = values.weight} return variant; } if (values.weight === "bold") { variant = { normal:MML.VARIANT.BOLD, italic:MML.VARIANT.BOLDITALIC, fraktur:MML.VARIANT.BOLDFRAKTUR, script:MML.VARIANT.BOLDSCRIPT, "sans-serif":MML.VARIANT.BOLDSANSSERIF, "sans-serif-italic":MML.VARIANT.SANSSERIFBOLDITALIC }[variant]||variant; } else if (values.weight === "normal") { variant = { bold:MML.VARIANT.normal, "bold-italic":MML.VARIANT.ITALIC, "bold-fraktur":MML.VARIANT.FRAKTUR, "bold-script":MML.VARIANT.SCRIPT, "bold-sans-serif":MML.VARIANT.SANSSERIF, "sans-serif-bold-italic":MML.VARIANT.SANSSERIFITALIC }[variant]||variant; } if (values.style === "italic") { variant = { normal:MML.VARIANT.ITALIC, bold:MML.VARIANT.BOLDITALIC, "sans-serif":MML.VARIANT.SANSSERIFITALIC, "bold-sans-serif":MML.VARIANT.SANSSERIFBOLDITALIC }[variant]||variant; } else if (values.style === "normal") { variant = { italic:MML.VARIANT.NORMAL, "bold-italic":MML.VARIANT.BOLD, "sans-serif-italic":MML.VARIANT.SANSSERIF, "sans-serif-bold-italic":MML.VARIANT.BOLDSANSSERIF }[variant]||variant; } if (!(variant in SVG.FONTDATA.VARIANT)) { // If the mathvariant value is invalid or not supported by this // font, fallback to normal. See issue 363. variant = "normal"; } return SVG.FONTDATA.VARIANT[variant]; }, SVGgetScale: function (svg) { var scale = 1; if (this.mscale) { scale = this.scale; } else { var values = this.getValues("scriptlevel","fontsize"); values.mathsize = (this.isToken ? this : this.Parent()).Get("mathsize"); if ((this.styles||{}).fontSize && !values.fontsize) {values.fontsize = this.styles.fontSize} if (values.fontsize && !this.mathsize) {values.mathsize = values.fontsize} if (values.scriptlevel !== 0) { if (values.scriptlevel > 2) {values.scriptlevel = 2} scale = Math.pow(this.Get("scriptsizemultiplier"),values.scriptlevel); values.scriptminsize = SVG.length2em(this.Get("scriptminsize"))/1000; if (scale < values.scriptminsize) {scale = values.scriptminsize} } this.scale = scale; this.mscale = SVG.length2em(values.mathsize)/1000; } if (svg) {svg.scale = scale; if (this.isToken) {svg.scale *= this.mscale}} return scale * this.mscale; }, SVGgetMu: function (svg) { var mu = 1, values = this.getValues("scriptlevel","scriptsizemultiplier"); if (svg.scale && svg.scale !== 1) {mu = 1/svg.scale} if (values.scriptlevel !== 0) { if (values.scriptlevel > 2) {values.scriptlevel = 2} mu = Math.sqrt(Math.pow(values.scriptsizemultiplier,values.scriptlevel)); } return mu; }, SVGnotEmpty: function (data) { while (data) { if ((data.type !== "mrow" && data.type !== "texatom") || data.data.length > 1) {return true} data = data.data[0]; } return false; }, SVGcanStretch: function (direction) { var can = false; if (this.isEmbellished()) { var core = this.Core(); if (core && core !== this) { can = core.SVGcanStretch(direction); if (can && core.forceStretch) {this.forceStretch = true} } } return can; }, SVGstretchV: function (h,d) {return this.toSVG(h,d)}, SVGstretchH: function (w) {return this.toSVG(w)}, SVGlineBreaks: function () {return false} },{ SVGemptySVG: function () { var svg = this.SVG(); svg.Clean(); this.SVGsaveData(svg); return svg; }, SVGautoload: function () { this.constructor.Augment({toSVG: MML.mbase.SVGautoloadFail}); var file = SVG.autoloadDir+"/"+this.type+".js"; HUB.RestartAfter(AJAX.Require(file)); }, SVGautoloadFail: function () { throw Error("SVG can't autoload '"+ this.type + "'"); }, SVGautoloadList: {}, SVGautoloadFile: function (name) { if (MML.mbase.SVGautoloadList.hasOwnProperty(name)) { throw Error("SVG can't autoload file '"+name+"'"); } MML.mbase.SVGautoloadList[name] = true; var file = SVG.autoloadDir+"/"+name+".js"; HUB.RestartAfter(AJAX.Require(file)); } }); MML.chars.Augment({ toSVG: function (variant,scale,remap,chars) { var text = this.data.join("").replace(/[\u2061-\u2064]/g,""); // remove invisibles if (remap) {text = remap(text,chars)} return this.SVGhandleVariant(variant,scale,text); } }); MML.entity.Augment({ toSVG: function (variant,scale,remap,chars) { var text = this.toString().replace(/[\u2061-\u2064]/g,""); // remove invisibles if (remap) {text = remap(text,chars)} return this.SVGhandleVariant(variant,scale,text); } }); MML.mo.Augment({ toSVG: function (HW,D) { this.SVGgetStyles(); var svg = this.svg = this.SVG(); var scale = this.SVGgetScale(svg); this.SVGhandleSpace(svg); if (this.data.length == 0) {svg.Clean(); this.SVGsaveData(svg); return svg} // // Stretch the operator, if that is requested // if (D != null) {return this.SVGstretchV(HW,D)} else if (HW != null) {return this.SVG.strechH(HW)} // // Get the variant, and check for operator size // var variant = this.SVGgetVariant(); var values = this.getValues("largeop","displaystyle"); if (values.largeop) {variant = SVG.FONTDATA.VARIANT[values.displaystyle ? "-largeOp" : "-smallOp"]} // // Get character translation for superscript and accents // var parent = this.CoreParent(), isScript = (parent && parent.isa(MML.msubsup) && this !== parent.data[0]), mapchars = (isScript?this.remapChars:null); if (SVG.isChar(this.data.join("")) && parent && parent.isa(MML.munderover) && SVG.isChar(this.CoreText(parent.data[parent.base]))) { var over = parent.data[parent.over], under = parent.data[parent.under]; if (over && this === over.CoreMO() && parent.Get("accent")) {mapchars = SVG.FONTDATA.REMAPACCENT} else if (under && this === under.CoreMO() && parent.Get("accentunder")) {mapchars = SVG.FONTDATA.REMAPACCENTUNDER} } // // Primes must come from another font // if (isScript && this.data.join("").match(/['`"\u00B4\u2032-\u2037\u2057]/)) {variant = SVG.FONTDATA.VARIANT["-"+SVG.fontInUse+"-variant"]} // // Typeset contents // for (var i = 0, m = this.data.length; i < m; i++) { if (this.data[i]) { var text = this.data[i].toSVG(variant,scale,this.remap,mapchars), x = svg.w; if (x === 0 && -text.l > 10*text.w) {x += -text.l} // initial combining character doesn't combine svg.Add(text,x,0,true); if (text.skew) {svg.skew = text.skew} } } svg.Clean(); if (!SVG.isChar(this.data.join(""))) {delete svg.skew} // // Handle large operator centering // if (values.largeop) { svg.y = SVG.TeX.axis_height - (svg.h - svg.d)/2/scale; if (svg.r > svg.w) {svg.ic = svg.r - svg.w; svg.w = svg.r} } // // Finish up // this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; }, SVGcanStretch: function (direction) { if (!this.Get("stretchy")) {return false} var c = this.data.join(""); if (c.length > 1) {return false} var parent = this.CoreParent(); if (parent && parent.isa(MML.munderover) && SVG.isChar(this.CoreText(parent.data[parent.base]))) { var over = parent.data[parent.over], under = parent.data[parent.under]; if (over && this === over.CoreMO() && parent.Get("accent")) {c = SVG.FONTDATA.REMAPACCENT[c]||c} else if (under && this === under.CoreMO() && parent.Get("accentunder")) {c = SVG.FONTDATA.REMAPACCENTUNDER[c]||c} } c = SVG.FONTDATA.DELIMITERS[c.charCodeAt(0)]; var can = (c && c.dir == direction.substr(0,1)); if (!can) {delete this.svg} this.forceStretch = can && (this.Get("minsize",true) || this.Get("maxsize",true)); return can; }, SVGstretchV: function (h,d) { var svg = this.svg || this.toSVG(); var values = this.getValues("symmetric","maxsize","minsize"); var axis = SVG.TeX.axis_height*svg.scale, mu = this.SVGgetMu(svg), H; if (values.symmetric) {H = 2*Math.max(h-axis,d+axis)} else {H = h + d} values.maxsize = SVG.length2em(values.maxsize,mu,svg.h+svg.d); values.minsize = SVG.length2em(values.minsize,mu,svg.h+svg.d); H = Math.max(values.minsize,Math.min(values.maxsize,H)); if (H != values.minsize) {H = [Math.max(H*SVG.TeX.delimiterfactor/1000,H-SVG.TeX.delimitershortfall),H]} svg = SVG.createDelimiter(this.data.join("").charCodeAt(0),H,svg.scale); if (values.symmetric) {H = (svg.h + svg.d)/2 + axis} else {H = (svg.h + svg.d) * h/(h + d)} svg.y = H - svg.h; this.SVGhandleSpace(svg); this.SVGhandleColor(svg); delete this.svg.element; this.SVGsaveData(svg); svg.stretched = true; return svg; }, SVGstretchH: function (w) { var svg = this.svg || this.toSVG(), mu = this.SVGgetMu(svg); var values = this.getValues("maxsize","minsize","mathvariant","fontweight"); // FIXME: should take style="font-weight:bold" into account as well if ((values.fontweight === "bold" || parseInt(values.fontweight) >= 600) && !this.Get("mathvariant",true)) {values.mathvariant = MML.VARIANT.BOLD} values.maxsize = SVG.length2em(values.maxsize,mu,svg.w); values.minsize = SVG.length2em(values.minsize,mu,svg.w); w = Math.max(values.minsize,Math.min(values.maxsize,w)); svg = SVG.createDelimiter(this.data.join("").charCodeAt(0),w,svg.scale,values.mathvariant); this.SVGhandleSpace(svg); this.SVGhandleColor(svg); delete this.svg.element; this.SVGsaveData(svg); svg.stretched = true; return svg; } }); MML.mn.Augment({ SVGremapMinus: function (text) {return text.replace(/^-/,"\u2212")}, toSVG: function () { this.SVGgetStyles(); var variant = this.SVGgetVariant(); var svg = this.SVG(); this.SVGgetScale(svg); this.SVGhandleSpace(svg); var remap = this.SVGremapMinus; for (var i = 0, m = this.data.length; i < m; i++) { if (this.data[i]) { var child = svg.Add(this.data[i].toSVG(variant,svg.scale,remap),svg.w,0,true); if (child.skew) {svg.skew = child.skew} remap = null; } } svg.Clean(); var text = this.data.join(""); if (svg.skew && !SVG.isChar(text)) {delete svg.skew} if (svg.r > svg.w && SVG.isChar(text) && !variant.noIC) {svg.ic = svg.r - svg.w; svg.w = svg.r} this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; }, }), MML.mtext.Augment({ toSVG: function () { if (SVG.config.mtextFontInherit || this.Parent().type === "merror") { this.SVGgetStyles(); var svg = this.SVG(), scale = this.SVGgetScale(svg); this.SVGhandleSpace(svg); var variant = this.SVGgetVariant(), def = {direction:this.Get("dir")}; if (variant.bold) {def["font-weight"] = "bold"} if (variant.italic) {def["font-style"] = "italic"} variant = this.Get("mathvariant"); if (variant === "monospace") {def["class"] = "MJX-monospace"} else if (variant.match(/sans-serif/)) {def["class"] = "MJX-sans-serif"} svg.Add(BBOX.TEXT(scale*100/SVG.config.scale,this.data.join(""),def)); svg.Clean(); this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; } else { return this.SUPER(arguments).toSVG.call(this); } } }); MML.merror.Augment({ toSVG: function (HW,D) { this.SVGgetStyles(); var svg = this.SVG(), scale = SVG.length2em(this.styles.fontSize||1)/1000; this.SVGhandleSpace(svg); var def = (scale !== 1 ? {transform:"scale("+SVG.Fixed(scale)+")"} : {}); var bbox = BBOX(def); bbox.Add(this.SVGchildSVG(0)); bbox.Clean(); if (scale !== 1) { bbox.removeable = false; var adjust = ["w","h","d","l","r","D","H"]; for (var i = 0, m = adjust.length; i < m; i++) {bbox[adjust[i]] *= scale} } svg.Add(bbox); svg.Clean(); this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; }, SVGgetStyles: function () { var span = HTML.Element("span",{style: SVG.config.merrorStyle}); this.styles = this.SVGprocessStyles(span.style); if (this.style) { span.style.cssText = this.style; HUB.Insert(this.styles,this.SVGprocessStyles(span.style)); } } }); MML.ms.Augment({toSVG: MML.mbase.SVGautoload}); MML.mglyph.Augment({toSVG: MML.mbase.SVGautoload}); MML.mspace.Augment({ toSVG: function () { this.SVGgetStyles(); var values = this.getValues("height","depth","width"); values.mathbackground = this.mathbackground; if (this.background && !this.mathbackground) {values.mathbackground = this.background} var svg = this.SVG(); this.SVGgetScale(svg); var scale = this.mscale, mu = this.SVGgetMu(svg); svg.h = SVG.length2em(values.height,mu) * scale; svg.d = SVG.length2em(values.depth,mu) * scale; svg.w = svg.r = SVG.length2em(values.width,mu) * scale; if (svg.w < 0) {svg.x = svg.w; svg.w = svg.r = 0} if (svg.h < -svg.d) {svg.d = -svg.h} svg.l = 0; svg.Clean(); this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; } }); MML.mphantom.Augment({ toSVG: function (HW,D) { this.SVGgetStyles(); var svg = this.SVG(); this.SVGgetScale(svg); if (this.data[0] != null) { this.SVGhandleSpace(svg); svg.Add(this.SVGdataStretched(0,HW,D)); svg.Clean(); while (svg.element.firstChild) {svg.element.removeChild(svg.element.firstChild)} } this.SVGhandleColor(svg); this.SVGsaveData(svg); if (svg.removeable && !svg.element.firstChild) {delete svg.element} return svg; } }); MML.mpadded.Augment({ toSVG: function (HW,D) { this.SVGgetStyles(); var svg = this.SVG(); if (this.data[0] != null) { this.SVGgetScale(svg); this.SVGhandleSpace(svg); var pad = this.SVGdataStretched(0,HW,D), mu = this.SVGgetMu(svg); var values = this.getValues("height","depth","width","lspace","voffset"), X = 0, Y = 0; if (values.lspace) {X = this.SVGlength2em(pad,values.lspace,mu)} if (values.voffset) {Y = this.SVGlength2em(pad,values.voffset,mu)} var h = pad.h, d = pad.d, w = pad.w, y = pad.y; // these can change during the Add() svg.Add(pad,X,Y); svg.Clean(); svg.h = h+y; svg.d = d-y; svg.w = w; svg.removeable = false; if (values.height !== "") {svg.h = this.SVGlength2em(svg,values.height,mu,"h",0)} if (values.depth !== "") {svg.d = this.SVGlength2em(svg,values.depth,mu,"d",0)} if (values.width !== "") {svg.w = this.SVGlength2em(svg,values.width,mu,"w",0)} if (svg.h > svg.H) {svg.H = svg.h}; if (svg.d > svg.D) {svg.D = svg.d} } this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; }, SVGlength2em: function (svg,length,mu,d,m) { if (m == null) {m = -SVG.BIGDIMEN} var match = String(length).match(/width|height|depth/); var size = (match ? svg[match[0].charAt(0)] : (d ? svg[d] : 0)); var v = SVG.length2em(length,mu,size/this.mscale)*this.mscale; if (d && String(length).match(/^\s*[-+]/)) {return Math.max(m,svg[d]+v)} else {return v} } }); MML.mrow.Augment({ SVG: BBOX.ROW, toSVG: function (h,d) { this.SVGgetStyles(); var svg = this.SVG(); this.SVGhandleSpace(svg); if (d != null) {svg.sh = h; svg.sd = d} for (var i = 0, m = this.data.length; i < m; i++) {if (this.data[i]) {svg.Check(this.data[i])}} svg.Stretch(); svg.Clean(); if (this.data.length === 1 && this.data[0]) { var data = this.data[0].SVGdata; if (data.skew) {svg.skew = data.skew} } if (this.SVGlineBreaks(svg)) {svg = this.SVGmultiline(svg)} this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; }, SVGlineBreaks: function (svg) { if (!this.parent.linebreakContainer) {return false} return (SVG.config.linebreaks.automatic && svg.w > SVG.linebreakWidth) || this.hasNewline(); }, SVGmultiline: function (span) {MML.mbase.SVGautoloadFile("multiline")}, SVGstretchH: function (w) { var svg = this.SVG(); this.SVGhandleSpace(svg); for (var i = 0, m = this.data.length; i < m; i++) {svg.Add(this.SVGdataStretched(i,w),svg.w,0)} svg.Clean(); this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; } }); MML.mstyle.Augment({ toSVG: function () { this.SVGgetStyles(); var svg = this.SVG(); if (this.data[0] != null) { this.SVGhandleSpace(svg); var math = svg.Add(this.data[0].toSVG()); svg.Clean(); if (math.ic) {svg.ic = math.ic} this.SVGhandleColor(svg); } this.SVGsaveData(svg); return svg; }, SVGstretchH: function (w) { return (this.data[0] != null ? this.data[0].SVGstretchH(w) : BBOX.NULL()); }, SVGstretchV: function (h,d) { return (this.data[0] != null ? this.data[0].SVGstretchV(h,d) : BBOX.NULL()); } }); MML.mfrac.Augment({ toSVG: function () { this.SVGgetStyles(); var svg = this.SVG(), scale = this.SVGgetScale(svg); var frac = BBOX(); frac.scale = svg.scale; this.SVGhandleSpace(frac); var num = this.SVGchildSVG(0), den = this.SVGchildSVG(1); var values = this.getValues("displaystyle","linethickness","numalign","denomalign","bevelled"); var isDisplay = values.displaystyle; var a = SVG.TeX.axis_height * scale; if (values.bevelled) { var delta = (isDisplay ? 400 : 150); var H = Math.max(num.h+num.d,den.h+den.d)+2*delta; var bevel = SVG.createDelimiter(0x2F,H); frac.Add(num,0,(num.d-num.h)/2+a+delta); frac.Add(bevel,num.w-delta/2,(bevel.d-bevel.h)/2+a); frac.Add(den,num.w+bevel.w-delta,(den.d-den.h)/2+a-delta); } else { var W = Math.max(num.w,den.w); var t = SVG.thickness2em(values.linethickness,this.scale)*this.mscale, p,q, u,v; var mt = SVG.TeX.min_rule_thickness/SVG.em * 1000; if (isDisplay) {u = SVG.TeX.num1; v = SVG.TeX.denom1} else {u = (t === 0 ? SVG.TeX.num3 : SVG.TeX.num2); v = SVG.TeX.denom2} u *= scale; v *= scale; if (t === 0) {// \atop p = Math.max((isDisplay ? 7 : 3) * SVG.TeX.rule_thickness, 2*mt); // force to at least 2 px q = (u - num.d) - (den.h - v); if (q < p) {u += (p - q)/2; v += (p - q)/2} frac.w = W; t = 0; } else {// \over p = Math.max((isDisplay ? 2 : 0) * mt + t, t/2 + 1.5*mt); // force to be at least 1.5px q = (u - num.d) - (a + t/2); if (q < p) {u += p - q} q = (a - t/2) - (den.h - v); if (q < p) {v += p - q} frac.Add(BBOX.RECT(t/2,t/2,W+2*t),0,a); } frac.Align(num,values.numalign,t,u); frac.Align(den,values.denomalign,t,-v); } frac.Clean(); svg.Add(frac,0,0); svg.Clean(); this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; }, SVGcanStretch: function (direction) {return false}, SVGhandleSpace: function (svg) { if (!this.texWithDelims) { // // Add nulldelimiterspace around the fraction // (TeXBook pg 150 and Appendix G rule 15e) // svg.x = svg.X = SVG.TeX.nulldelimiterspace * this.mscale; } this.SUPER(arguments).SVGhandleSpace.call(this,svg); } }); MML.msqrt.Augment({ toSVG: function () { this.SVGgetStyles(); var svg = this.SVG(), scale = this.SVGgetScale(svg); this.SVGhandleSpace(svg); var base = this.SVGchildSVG(0), rule, surd; var t = SVG.TeX.rule_thickness * scale, p,q, H, x = 0; if (this.Get("displaystyle")) {p = SVG.TeX.x_height * scale} else {p = t} q = Math.max(t + p/4,1000*SVG.TeX.min_root_space/SVG.em); H = base.h + base.d + q + t; surd = SVG.createDelimiter(0x221A,H,scale); if (surd.h + surd.d > H) {q = ((surd.h+surd.d) - (H-t)) / 2} rule = BBOX.RECT(t,0,base.w); H = base.h + q + t; x = this.SVGaddRoot(svg,surd,x,surd.h+surd.d-H,scale); svg.Add(surd,x,H-surd.h); svg.Add(rule,x+surd.w,H-rule.h); svg.Add(base,x+surd.w,0); svg.Clean(); svg.h += t; svg.H += t; this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; }, SVGaddRoot: function (svg,surd,x,d,scale) {return x} }); MML.mroot.Augment({ toSVG: MML.msqrt.prototype.toSVG, SVGaddRoot: function (svg,surd,x,d,scale) { var dx = (surd.isMultiChar ? .55 : .65) * surd.w; if (this.data[1]) { var root = this.data[1].toSVG(); root.x = 0; var h = this.SVGrootHeight(surd.h+surd.d,scale,root)-d; var w = Math.min(root.w,root.r); // remove extra right-hand padding, if any x = Math.max(w,dx); svg.Add(root,x-w,h); } else {dx = x} return x - dx; }, SVGrootHeight: function (d,scale,root) { return .45*(d-900*scale) + 600*scale + Math.max(0,root.d-75); } }); MML.mfenced.Augment({ SVG: BBOX.ROW, toSVG: function () { this.SVGgetStyles(); var svg = this.SVG(); this.SVGhandleSpace(svg); if (this.data.open) {svg.Check(this.data.open)} if (this.data[0] != null) {svg.Check(this.data[0])} for (var i = 1, m = this.data.length; i < m; i++) { if (this.data[i]) { if (this.data["sep"+i]) {svg.Check(this.data["sep"+i])} svg.Check(this.data[i]); } } if (this.data.close) {svg.Check(this.data.close)} svg.Stretch(); svg.Clean(); this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; } }); MML.menclose.Augment({toSVG: MML.mbase.SVGautoload}); MML.maction.Augment({toSVG: MML.mbase.SVGautoload}); MML.semantics.Augment({ toSVG: function () { this.SVGgetStyles(); var svg = this.SVG(); if (this.data[0] != null) { this.SVGhandleSpace(svg); svg.Add(this.data[0].toSVG()); svg.Clean(); } else {svg.Clean()} this.SVGsaveData(svg); return svg; }, SVGstretchH: function (w) { return (this.data[0] != null ? this.data[0].SVGstretchH(w) : BBOX.NULL()); }, SVGstretchV: function (h,d) { return (this.data[0] != null ? this.data[0].SVGstretchV(h,d) : BBOX.NULL()); } }); MML.munderover.Augment({ toSVG: function (HW,D) { this.SVGgetStyles(); var values = this.getValues("displaystyle","accent","accentunder","align"); var base = this.data[this.base]; if (!values.displaystyle && base != null && (base.movablelimits || base.CoreMO().Get("movablelimits"))) {return MML.msubsup.prototype.toSVG.call(this)} var svg = this.SVG(), scale = this.SVGgetScale(svg); this.SVGhandleSpace(svg); var boxes = [], stretch = [], box, i, m, W = -SVG.BIGDIMEN, WW = W, ww; for (i = 0, m = this.data.length; i < m; i++) { if (this.data[i] != null) { if (i == this.base) { box = boxes[i] = this.SVGdataStretched(i,HW,D); stretch[i] = (D != null || HW == null) && this.data[i].SVGcanStretch("Horizontal"); if (this.data[this.over] && values.accent) { box.h = Math.max(box.h,scale*SVG.TeX.x_height); // min height of 1ex (#1706) } } else { box = boxes[i] = this.data[i].toSVG(); box.x = 0; delete box.X; stretch[i] = this.data[i].SVGcanStretch("Horizontal"); } ww = box.w + box.x + (box.X || 0); if (ww > WW) {WW = ww} if (!stretch[i] && WW > W) {W = WW} } } if (D == null && HW != null) {W = HW} else if (W == -SVG.BIGDIMEN) {W = WW} for (i = WW = 0, m = this.data.length; i < m; i++) {if (this.data[i]) { box = boxes[i]; if (stretch[i]) { box = boxes[i] = this.data[i].SVGstretchH(W); if (i !== this.base) {box.x = 0; delete box.X} } ww = box.w + box.x + (box.X || 0); if (ww > WW) {WW = ww} }} var t = SVG.TeX.rule_thickness * this.mscale; var x, y, z1, z2, z3, dw, k, delta = 0; base = boxes[this.base] || {w:0, h:0, d:0, H:0, D:0, l:0, r:0, y:0, scale:scale}; if (base.ic) {delta = 1.3*base.ic + .05} // adjust faked IC to be more in line with expeted results for (i = 0, m = this.data.length; i < m; i++) { if (this.data[i] != null) { box = boxes[i]; z3 = SVG.TeX.big_op_spacing5 * scale; var accent = (i != this.base && values[this.ACCENTS[i]]); if (accent && box.w <= 1) { box.x = -box.l; boxes[i] = BBOX.G().With({removeable: false}); boxes[i].Add(box); boxes[i].Clean(); boxes[i].w = -box.l; box = boxes[i]; } ww = box.w + box.x + (box.X || 0); dw = {left:0, center:(WW-ww)/2, right:WW-ww}[values.align]; x = dw; y = 0; if (i == this.over) { if (accent) { k = t * scale; z3 = 0; if (base.skew) { x += base.skew; svg.skew = base.skew; if (x+ww > WW) {svg.skew += (WW-ww-x)/2} } } else { z1 = SVG.TeX.big_op_spacing1 * scale; z2 = SVG.TeX.big_op_spacing3 * scale; k = Math.max(z1,z2-Math.max(0,box.d)); } k = Math.max(k,1500/SVG.em); x += delta/2; y = base.y + base.h + box.d + k; box.h += z3; if (box.h > box.H) {box.H = box.h} } else if (i == this.under) { if (accent) { k = 3*t * scale; z3 = 0; } else { z1 = SVG.TeX.big_op_spacing2 * scale; z2 = SVG.TeX.big_op_spacing4 * scale; k = Math.max(z1,z2-box.h); } k = Math.max(k,1500/SVG.em); x -= delta/2; y = base.y -(base.d + box.h + k); box.d += z3; if (box.d > box.D) {box.D = box.d} } svg.Add(box,x,y); } } svg.Clean(); this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; } }); MML.msubsup.Augment({ toSVG: function (HW,D) { this.SVGgetStyles(); var svg = this.SVG(), scale = this.SVGgetScale(svg); this.SVGhandleSpace(svg); var mu = this.SVGgetMu(svg); var base = svg.Add(this.SVGdataStretched(this.base,HW,D)); var sscale = (this.data[this.sup] || this.data[this.sub] || this).SVGgetScale(); var x_height = SVG.TeX.x_height * scale, s = SVG.TeX.scriptspace * scale; var sup, sub; if (this.SVGnotEmpty(this.data[this.sup])) { sup = this.data[this.sup].toSVG(); sup.w += s; sup.r = Math.max(sup.w,sup.r); } if (this.SVGnotEmpty(this.data[this.sub])) { sub = this.data[this.sub].toSVG(); sub.w += s; sub.r = Math.max(sub.w,sub.r); } var q = SVG.TeX.sup_drop * sscale, r = SVG.TeX.sub_drop * sscale; var u = base.h+(base.y||0) - q, v = base.d-(base.y||0) + r, delta = 0, p; if (base.ic) { base.w -= base.ic; // remove IC (added by mo and mi) delta = 1.3*base.ic+.05; // adjust faked IC to be more in line with expeted results } if (this.data[this.base] && (this.data[this.base].type === "mi" || this.data[this.base].type === "mo")) { if (SVG.isChar(this.data[this.base].data.join("")) && base.scale === 1 && !base.stretched && !this.data[this.base].Get("largeop")) {u = v = 0} } var min = this.getValues("subscriptshift","superscriptshift"); min.subscriptshift = (min.subscriptshift === "" ? 0 : SVG.length2em(min.subscriptshift,mu)); min.superscriptshift = (min.superscriptshift === "" ? 0 : SVG.length2em(min.superscriptshift,mu)); var x = base.w + base.x; if (!sup) { if (sub) { v = Math.max(v,SVG.TeX.sub1*scale,sub.h-(4/5)*x_height,min.subscriptshift); svg.Add(sub,x,-v); this.data[this.sub].SVGdata.dy = -v; } } else { if (!sub) { var values = this.getValues("displaystyle","texprimestyle"); p = SVG.TeX[(values.displaystyle ? "sup1" : (values.texprimestyle ? "sup3" : "sup2"))]; u = Math.max(u,p*scale,sup.d+(1/4)*x_height,min.superscriptshift); svg.Add(sup,x+delta,u); this.data[this.sup].SVGdata.dx = delta; this.data[this.sup].SVGdata.dy = u; } else { v = Math.max(v,SVG.TeX.sub2*scale); var t = SVG.TeX.rule_thickness * scale; if ((u - sup.d) - (sub.h - v) < 3*t) { v = 3*t - u + sup.d + sub.h; q = (4/5)*x_height - (u - sup.d); if (q > 0) {u += q; v -= q} } svg.Add(sup,x+delta,Math.max(u,min.superscriptshift)); svg.Add(sub,x,-Math.max(v,min.subscriptshift)); this.data[this.sup].SVGdata.dx = delta; this.data[this.sup].SVGdata.dy = Math.max(u,min.superscriptshift); this.data[this.sub].SVGdata.dy = -Math.max(v,min.subscriptshift); } } svg.Clean(); this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; } }); MML.mmultiscripts.Augment({toSVG: MML.mbase.SVGautoload}); MML.mtable.Augment({toSVG: MML.mbase.SVGautoload}); MML["annotation-xml"].Augment({toSVG: MML.mbase.SVGautoload}); MML.math.Augment({ SVG: BBOX.Subclass({type:"svg", removeable: false}), toSVG: function (span,div) { var CONFIG = SVG.config; // // All the data should be in an inferrerd row // if (this.data[0]) { this.SVGgetStyles(); MML.mbase.prototype.displayAlign = HUB.config.displayAlign; MML.mbase.prototype.displayIndent = HUB.config.displayIndent; if (String(HUB.config.displayIndent).match(/^0($|[a-z%])/i)) MML.mbase.prototype.displayIndent = "0"; // // Put content in a <g> with defaults and matrix that flips y axis. // Put that in an <svg> with xlink defined. // var box = BBOX.G(); box.Add(this.data[0].toSVG(),0,0,true); box.Clean(); this.SVGhandleColor(box); SVG.Element(box.element,{ stroke:"currentColor", fill:"currentColor", "stroke-width":0, transform: "matrix(1 0 0 -1 0 0)" }); box.removeable = false; var svg = this.SVG(); svg.element.setAttribute("xmlns:xlink",XLINKNS); if (CONFIG.useFontCache && !CONFIG.useGlobalCache) {svg.element.appendChild(BBOX.GLYPH.defs)} svg.Add(box); svg.Clean(); this.SVGsaveData(svg); // // If this element is not the top-level math element // remove the transform and return the svg object // (issue #614). // if (!span) { svg.element = svg.element.firstChild; // remove <svg> element svg.element.removeAttribute("transform"); svg.removable = true; return svg; } // // Style the <svg> to get the right size and placement // var l = Math.max(-svg.l,0), r = Math.max(svg.r-svg.w,0); var style = svg.element.style, px = SVG.TeX.x_height/SVG.ex; var H = (Math.ceil(svg.H/px)+1)*px+SVG.HFUZZ, // round to pixels and add padding D = (Math.ceil(svg.D/px)+1)*px+SVG.DFUZZ; var w = l + svg.w + r; svg.element.setAttribute("width",SVG.Ex(w)); svg.element.setAttribute("height",SVG.Ex(H+D)); style.verticalAlign = SVG.Ex(-D); if (l) style.marginLeft = SVG.Ex(-l); if (r) style.marginRight = SVG.Ex(-r); svg.element.setAttribute("viewBox",SVG.Fixed(-l,1)+" "+SVG.Fixed(-H,1)+" "+ SVG.Fixed(w,1)+" "+SVG.Fixed(H+D,1)); // // If there is extra height or depth, hide that // if (svg.H > svg.h) style.marginTop = SVG.Ex(svg.h-H); if (svg.D > svg.d) { style.marginBottom = SVG.Ex(svg.d-D); style.verticalAlign = SVG.Ex(-svg.d); } // // The approximate ex can cause full-width equations to be too wide, // so if they are close to full width, make sure they aren't too big. // if (Math.abs(w-SVG.cwidth) < 10) style.maxWidth = SVG.Fixed(SVG.cwidth*SVG.em/1000*SVG.config.scale) + "px"; // // Add it to the MathJax span // var alttext = this.Get("alttext"); if (alttext && !svg.element.getAttribute("aria-label")) svg.element.setAttribute("aria-label",alttext); if (!svg.element.getAttribute("role")) svg.element.setAttribute("role","img"); svg.element.setAttribute("focusable","false"); span.appendChild(svg.element); svg.element = null; // // Handle indentalign and indentshift for single-line displays // if (!this.isMultiline && this.Get("display") === "block" && !svg.hasIndent) { var values = this.getValues("indentalignfirst","indentshiftfirst","indentalign","indentshift"); if (values.indentalignfirst !== MML.INDENTALIGN.INDENTALIGN) {values.indentalign = values.indentalignfirst} if (values.indentalign === MML.INDENTALIGN.AUTO) {values.indentalign = this.displayAlign} if (values.indentshiftfirst !== MML.INDENTSHIFT.INDENTSHIFT) {values.indentshift = values.indentshiftfirst} if (values.indentshift === "auto") {values.indentshift = "0"} var shift = SVG.length2em(values.indentshift,1,SVG.cwidth); if (this.displayIndent !== "0") { var indent = SVG.length2em(this.displayIndent,1,SVG.cwidth); shift += (values.indentalign === MML.INDENTALIGN.RIGHT ? -indent : indent); } div.style.textAlign = values.indentalign; if (shift) { HUB.Insert(style,({ left: {marginLeft: SVG.Ex(shift)}, right: {marginRight: SVG.Ex(-shift), marginLeft: SVG.Ex(Math.max(0,shift-w))}, center: {marginLeft: SVG.Ex(shift), marginRight: SVG.Ex(-shift)} })[values.indentalign]); } } } return span; } }); MML.TeXAtom.Augment({ toSVG: function (HW,D) { this.SVGgetStyles(); var svg = this.SVG(); this.SVGhandleSpace(svg); if (this.data[0] != null) { var box = this.SVGdataStretched(0,HW,D), y = 0; if (this.texClass === MML.TEXCLASS.VCENTER) {y = SVG.TeX.axis_height - (box.h+box.d)/2 + box.d} svg.Add(box,0,y); svg.ic = box.ic; svg.skew = box.skew; } this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; } }); // // Make sure these don't generate output // MML.maligngroup.Augment({toSVG: MML.mbase.SVGemptySVG}); MML.malignmark.Augment({toSVG: MML.mbase.SVGemptySVG}); MML.mprescripts.Augment({toSVG: MML.mbase.SVGemptySVG}); MML.none.Augment({toSVG: MML.mbase.SVGemptySVG}); // // Loading isn't complete until the element jax is modified, // but can't call loadComplete within the callback for "mml Jax Ready" // (it would call SVG's Require routine, asking for the mml jax again) // so wait until after the mml jax has finished processing. // // We also need to wait for the onload handler to run, since the loadComplete // will call Config and Startup, which need to modify the body. // HUB.Register.StartupHook("onLoad",function () { setTimeout(MathJax.Callback(["loadComplete",SVG,"jax.js"]),0); }); }); HUB.Browser.Select({ Opera: function (browser) { SVG.Augment({ operaZoomRefresh: true // Opera needs a kick to redraw zoomed equations }); } }); HUB.Register.StartupHook("End Cookie", function () { if (HUB.config.menuSettings.zoom !== "None") {AJAX.Require("[MathJax]/extensions/MathZoom.js")} }); if (!document.createElementNS) { // // Try to handle SVG in IE8 and below, but fail // (but don't crash on loading the file, so no delay for loadComplete) // if (!document.namespaces.svg) {document.namespaces.add("svg",SVGNS)} SVG.Augment({ Element: function (type,def) { var obj = (typeof(type) === "string" ? document.createElement("svg:"+type) : type); obj.isMathJax = true; if (def) {for (var id in def) {if (def.hasOwnProperty(id)) {obj.setAttribute(id,def[id].toString())}}} return obj; } }); } })(MathJax.Ajax, MathJax.Hub, MathJax.HTML, MathJax.OutputJax.SVG); /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/jax/output/SVG/autoload/mtable.js * * Implements the SVG output for <mtable> elements. * * --------------------------------------------------------------------- * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () { var VERSION = "2.7.5"; var MML = MathJax.ElementJax.mml, SVG = MathJax.OutputJax.SVG, BBOX = SVG.BBOX; MML.mtable.Augment({ toSVG: function (span) { this.SVGgetStyles(); var svg = this.SVG(), scale = this.SVGgetScale(svg); if (this.data.length === 0) {this.SVGsaveData(svg);return svg} var values = this.getValues("columnalign","rowalign","columnspacing","rowspacing", "columnwidth","equalcolumns","equalrows", "columnlines","rowlines","frame","framespacing", "align","useHeight","width","side","minlabelspacing"); // Handle relative width as fixed width in relation to container if (values.width.match(/%$/)) {svg.width = values.width = SVG.Em((SVG.cwidth/1000)*(parseFloat(values.width)/100))} var mu = this.SVGgetMu(svg); var LABEL = -1; var H = [], D = [], W = [], A = [], C = [], i, j, J = -1, m, M, s, row, cell, mo, HD; var LH = SVG.FONTDATA.lineH * scale * values.useHeight, LD = SVG.FONTDATA.lineD * scale * values.useHeight; // // Create cells and measure columns and rows // for (i = 0, m = this.data.length; i < m; i++) { row = this.data[i]; s = (row.type === "mlabeledtr" ? LABEL : 0); A[i] = []; H[i] = LH; D[i] = LD; for (j = s, M = row.data.length + s; j < M; j++) { if (W[j] == null) { if (j > J) {J = j} C[j] = BBOX.G(); W[j] = -SVG.BIGDIMEN; } cell = row.data[j-s]; A[i][j] = cell.toSVG(); // if (row.data[j-s].isMultiline) {A[i][j].style.width = "100%"} if (cell.isEmbellished()) { mo = cell.CoreMO(); var min = mo.Get("minsize",true); if (min) { if (mo.SVGcanStretch("Vertical")) { HD = mo.SVGdata.h + mo.SVGdata.d; if (HD) { min = SVG.length2em(min,mu,HD); if (min*mo.SVGdata.h/HD > H[i]) {H[i] = min*mo.SVGdata.h/HD} if (min*mo.SVGdata.d/HD > D[i]) {D[i] = min*mo.SVGdata.d/HD} } } else if (mo.SVGcanStretch("Horizontal")) { min = SVG.length2em(min,mu,mo.SVGdata.w); if (min > W[j]) {W[j] = min} } } } if (A[i][j].h > H[i]) {H[i] = A[i][j].h} if (A[i][j].d > D[i]) {D[i] = A[i][j].d} if (A[i][j].w > W[j]) {W[j] = A[i][j].w} } } // // Determine spacing and alignment // var SPLIT = MathJax.Hub.SplitList; var CSPACE = SPLIT(values.columnspacing), RSPACE = SPLIT(values.rowspacing), CALIGN = SPLIT(values.columnalign), RALIGN = SPLIT(values.rowalign), CLINES = SPLIT(values.columnlines), RLINES = SPLIT(values.rowlines), CWIDTH = SPLIT(values.columnwidth), RCALIGN = []; for (i = 0, m = CSPACE.length; i < m; i++) {CSPACE[i] = SVG.length2em(CSPACE[i],mu)} for (i = 0, m = RSPACE.length; i < m; i++) {RSPACE[i] = SVG.length2em(RSPACE[i],mu)} while (CSPACE.length < J) {CSPACE.push(CSPACE[CSPACE.length-1])} while (CALIGN.length <= J) {CALIGN.push(CALIGN[CALIGN.length-1])} while (CLINES.length < J) {CLINES.push(CLINES[CLINES.length-1])} while (CWIDTH.length <= J) {CWIDTH.push(CWIDTH[CWIDTH.length-1])} while (RSPACE.length < A.length) {RSPACE.push(RSPACE[RSPACE.length-1])} while (RALIGN.length <= A.length) {RALIGN.push(RALIGN[RALIGN.length-1])} while (RLINES.length < A.length) {RLINES.push(RLINES[RLINES.length-1])} if (C[LABEL]) { CALIGN[LABEL] = (values.side.substr(0,1) === "l" ? "left" : "right"); CSPACE[LABEL] = -W[LABEL]; } // // Override row data // for (i = 0, m = A.length; i < m; i++) { row = this.data[i]; RCALIGN[i] = []; if (row.rowalign) {RALIGN[i] = row.rowalign} if (row.columnalign) { RCALIGN[i] = SPLIT(row.columnalign); while (RCALIGN[i].length <= J) {RCALIGN[i].push(RCALIGN[i][RCALIGN[i].length-1])} } } // // Handle equal heights // if (values.equalrows) { // FIXME: should really be based on row align (below is for baseline) var Hm = Math.max.apply(Math,H), Dm = Math.max.apply(Math,D); for (i = 0, m = A.length; i < m; i++) {s = ((Hm + Dm) - (H[i] + D[i])) / 2; H[i] += s; D[i] += s} } // FIXME: do background colors for entire cell (include half the intercolumn space?) // // Determine array total height // HD = H[0] + D[A.length-1]; for (i = 0, m = A.length-1; i < m; i++) {HD += Math.max(0,D[i]+H[i+1]+RSPACE[i])} // // Determine frame and line sizes // var fx = 0, fy = 0, fW, fH = HD; if (values.frame !== "none" || (values.columnlines+values.rowlines).match(/solid|dashed/)) { var frameSpacing = SPLIT(values.framespacing); if (frameSpacing.length != 2) { // invalid attribute value: use the default. frameSpacing = SPLIT(this.defaults.framespacing); } fx = SVG.length2em(frameSpacing[0],mu); fy = SVG.length2em(frameSpacing[1],mu); fH = HD + 2*fy; // fW waits until svg.w is determined } // // Compute alignment // var Y, fY, n = ""; if (typeof(values.align) !== "string") {values.align = String(values.align)} if (values.align.match(/(top|bottom|center|baseline|axis)( +(-?\d+))?/)) {n = RegExp.$3||""; values.align = RegExp.$1} else {values.align = this.defaults.align} if (n !== "") { // // Find the height of the given row // n = parseInt(n); if (n < 0) {n = A.length + 1 + n} if (n < 1) {n = 1} else if (n > A.length) {n = A.length} Y = 0; fY = -(HD + fy) + H[0]; for (i = 0, m = n-1; i < m; i++) { // FIXME: Should handle values.align for final row var dY = Math.max(0,D[i]+H[i+1]+RSPACE[i]); Y += dY; fY += dY; } } else { Y = ({ top: -(H[0] + fy), bottom: HD + fy - H[0], center: HD/2 - H[0], baseline: HD/2 - H[0], axis: HD/2 + SVG.TeX.axis_height*scale - H[0] })[values.align]; fY = ({ top: -(HD + 2*fy), bottom: 0, center: -(HD/2 + fy), baseline: -(HD/2 + fy), axis: SVG.TeX.axis_height*scale - HD/2 - fy })[values.align]; } var WW, WP = 0, Wt = 0, Wp = 0, p = 0, f = 0, P = [], F = [], Wf = 1; // if (values.equalcolumns && values.width !== "auto") { // // Handle equalcolumns for percent-width and fixed-width tables // // Get total width minus column spacing WW = SVG.length2em(values.width,mu); for (i = 0, m = Math.min(J,CSPACE.length); i < m; i++) {WW -= CSPACE[i]} // Determine individual column widths WW /= J; for (i = 0, m = Math.min(J+1,CWIDTH.length); i < m; i++) {W[i] = WW} } else { // // Get column widths for fit and percentage columns // // Calculate the natural widths and percentage widths, // while keeping track of the fit and percentage columns for(i = 0, m = Math.min(J+1,CWIDTH.length); i < m; i++) { if (CWIDTH[i] === "auto") {Wt += W[i]} else if (CWIDTH[i] === "fit") {F[f] = i; f++; Wt += W[i]} else if (CWIDTH[i].match(/%$/)) {P[p] = i; p++; Wp += W[i]; WP += SVG.length2em(CWIDTH[i],mu,1)} else {W[i] = SVG.length2em(CWIDTH[i],mu); Wt += W[i]} } // Get the full width (excluding inter-column spacing) if (values.width === "auto") { if (WP > .98) {Wf = Wp/(Wt+Wp); WW = Wt + Wp} else {WW = Wt / (1-WP)} } else { WW = Math.max(Wt + Wp, SVG.length2em(values.width,mu)); for (i = 0, m = Math.min(J,CSPACE.length); i < m; i++) {WW -= CSPACE[i]} } // Determine the relative column widths for (i = 0, m = P.length; i < m; i++) { W[P[i]] = SVG.length2em(CWIDTH[P[i]],mu,WW*Wf); Wt += W[P[i]]; } // Stretch fit columns, if any, otherwise stretch (or shrink) everything if (Math.abs(WW - Wt) > .01) { if (f && WW > Wt) { WW = (WW - Wt) / f; for (i = 0, m = F.length; i < m; i++) {W[F[i]] += WW} } else {WW = WW/Wt; for (j = 0; j <= J; j++) {W[j] *= WW}} } // // Handle equal columns // if (values.equalcolumns) { var Wm = Math.max.apply(Math,W); for (j = 0; j <= J; j++) {W[j] = Wm} } } // // Lay out array columns // var y = Y, dy, align; s = (C[LABEL] ? LABEL : 0); for (j = s; j <= J; j++) { C[j].w = W[j]; for (i = 0, m = A.length; i < m; i++) { if (A[i][j]) { s = (this.data[i].type === "mlabeledtr" ? LABEL : 0); cell = this.data[i].data[j-s]; if (cell.SVGcanStretch("Horizontal")) { A[i][j] = cell.SVGstretchH(W[j]); } else if (cell.SVGcanStretch("Vertical")) { mo = cell.CoreMO(); var symmetric = mo.symmetric; mo.symmetric = false; A[i][j] = cell.SVGstretchV(H[i],D[i]); mo.symmetric = symmetric; } align = cell.rowalign||this.data[i].rowalign||RALIGN[i]; dy = ({top: H[i] - A[i][j].h, bottom: A[i][j].d - D[i], center: ((H[i]-D[i]) - (A[i][j].h-A[i][j].d))/2, baseline: 0, axis: 0})[align] || 0; // FIXME: handle axis better? align = (cell.columnalign||RCALIGN[i][j]||CALIGN[j]) C[j].Align(A[i][j],align,0,y+dy); } if (i < A.length-1) {y -= Math.max(0,D[i]+H[i+1]+RSPACE[i])} } y = Y; } // // Place the columns and add column lines // var lw = 1.5*SVG.em; var x = fx - lw/2; for (j = 0; j <= J; j++) { svg.Add(C[j],x,0); x += W[j] + CSPACE[j]; if (CLINES[j] !== "none" && j < J && j !== LABEL) {svg.Add(BBOX.VLINE(fH,lw,CLINES[j]),x-CSPACE[j]/2,fY)} } svg.w += fx; svg.d = -fY; svg.h = fH+fY; fW = svg.w; // // Add frame // if (values.frame !== "none") { svg.Add(BBOX.HLINE(fW,lw,values.frame),0,fY+fH-lw); svg.Add(BBOX.HLINE(fW,lw,values.frame),0,fY); svg.Add(BBOX.VLINE(fH,lw,values.frame),0,fY); svg.Add(BBOX.VLINE(fH,lw,values.frame),fW-lw,fY); } // // Add row lines // y = Y - lw/2; for (i = 0, m = A.length-1; i < m; i++) { dy = Math.max(0,D[i]+H[i+1]+RSPACE[i]); if (RLINES[i] !== MML.LINES.NONE && RLINES[i] !== "") {svg.Add(BBOX.HLINE(fW,lw,RLINES[i]),0,y-D[i]-(dy-D[i]-H[i+1])/2)} y -= dy; } // // Finish the table // svg.Clean(); this.SVGhandleSpace(svg); this.SVGhandleColor(svg); // // Place the labels, if any // if (C[LABEL]) { svg.tw = Math.max(svg.w,svg.r) - Math.min(0,svg.l); var indent = this.getValues("indentalignfirst","indentshiftfirst","indentalign","indentshift"); if (indent.indentalignfirst !== MML.INDENTALIGN.INDENTALIGN) {indent.indentalign = indent.indentalignfirst} if (indent.indentalign === MML.INDENTALIGN.AUTO) {indent.indentalign = this.displayAlign} if (indent.indentshiftfirst !== MML.INDENTSHIFT.INDENTSHIFT) {indent.indentshift = indent.indentshiftfirst} if (indent.indentshift === "auto" || indent.indentshift === "") {indent.indentshift = "0"} var shift = SVG.length2em(indent.indentshift,mu,SVG.cwidth); var labelspace = SVG.length2em(values.minlabelspacing,mu,SVG.cwidth); var labelW = labelspace + C[LABEL].w, labelshift = 0, tw = svg.w; var dIndent = SVG.length2em(this.displayIndent,mu,SVG.cwidth); s = (CALIGN[LABEL] === MML.INDENTALIGN.RIGHT ? -1 : 1); if (indent.indentalign === MML.INDENTALIGN.CENTER) { var dx = (SVG.cwidth-tw)/2; shift += dIndent; if (labelW + s*labelshift > dx + s*shift) { indent.indentalign = CALIGN[LABEL]; shift = s*(labelW + s*labelshift); tw += labelW + Math.max(0,shift); } } else if (CALIGN[LABEL] === indent.indentalign) { if (dIndent < 0) {labelshift = s*dIndent; dIndent = 0} shift += s*dIndent; if (labelW > s*shift) shift = s*labelW; shift += labelshift; tw += s*shift; } else { shift -= s*dIndent; if (tw - s*shift + labelW > SVG.cwidth) { shift = s*(tw + labelW - SVG.cwidth); if (s*shift > 0) {tw = SVG.cwidth + s*shift; shift = 0} } } var eqn = svg; svg = this.SVG(); svg.hasIndent = true; svg.w = svg.r = Math.max(tw,SVG.cwidth); svg.Align(C[LABEL],CALIGN[LABEL],0,0,labelshift); svg.Align(eqn,indent.indentalign,0,0,shift); svg.tw = tw; } this.SVGsaveData(svg); return svg; }, SVGhandleSpace: function (svg) { if (!this.hasFrame && !svg.width) {svg.x = svg.X = 167} this.SUPER(arguments).SVGhandleSpace.call(this,svg); } }); MML.mtd.Augment({ toSVG: function (HW,D) { var svg = this.svg = this.SVG(); if (this.data[0]) { svg.Add(this.SVGdataStretched(0,HW,D)); svg.Clean(); } this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; } }); MathJax.Hub.Startup.signal.Post("SVG mtable Ready"); MathJax.Ajax.loadComplete(SVG.autoloadDir+"/mtable.js"); }); /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/jax/output/SVG/autoload/mglyph.js * * Implements the SVG output for <mglyph> elements. * * --------------------------------------------------------------------- * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () { var VERSION = "2.7.5"; var MML = MathJax.ElementJax.mml, SVG = MathJax.OutputJax.SVG, BBOX = SVG.BBOX, LOCALE = MathJax.Localization; var XLINKNS = "http://www.w3.org/1999/xlink"; BBOX.MGLYPH = BBOX.Subclass({ type: "image", removeable: false, Init: function (img,w,h,align,mu,def) { if (def == null) {def = {}} var W = img.width*1000/SVG.em, H = img.height*1000/SVG.em; var WW = W, HH = H, y = 0; if (w !== "") {W = SVG.length2em(w,mu,WW); H = (WW ? W/WW * HH : 0)} if (h !== "") {H = SVG.length2em(h,mu,HH); if (w === "") {W = (HH ? H/HH * WW : 0)}} if (align !== "" && align.match(/\d/)) {y = SVG.length2em(align,mu); def.y = -y} def.height = Math.floor(H); def.width = Math.floor(W); def.transform = "translate(0,"+H+") matrix(1 0 0 -1 0 0)"; def.preserveAspectRatio = "none"; this.SUPER(arguments).Init.call(this,def); this.element.setAttributeNS(XLINKNS,"href",img.SRC); this.w = this.r = W; this.h = this.H = H + y; this.d = this.D = -y; this.l = 0; } }); MML.mglyph.Augment({ toSVG: function (variant,scale) { this.SVGgetStyles(); var svg = this.SVG(), img, err; this.SVGhandleSpace(svg); var values = this.getValues("src","width","height","valign","alt"); if (values.src === "") { values = this.getValues("index","fontfamily"); if (values.index) { if (!scale) {scale = this.SVGgetScale()} var def = {}; if (values.fontfamily) {def["font-family"] = values.fontfamily} svg.Add(BBOX.TEXT(scale,String.fromCharCode(values.index),def)); } } else { if (!this.img) {this.img = MML.mglyph.GLYPH[values.src]} if (!this.img) { this.img = MML.mglyph.GLYPH[values.src] = {img: new Image(), status: "pending"}; img = this.img.img; img.onload = MathJax.Callback(["SVGimgLoaded",this]); img.onerror = MathJax.Callback(["SVGimgError",this]); img.src = img.SRC = values.src; MathJax.Hub.RestartAfter(img.onload); } if (this.img.status !== "OK") { err = MML.Error( LOCALE._(["MathML","BadMglyph"],"Bad mglyph: %1",values.src), {mathsize:"75%"}); this.Append(err); svg = err.toSVG(); this.data.pop(); } else { var mu = this.SVGgetMu(svg); svg.Add(BBOX.MGLYPH(this.img.img,values.width,values.height,values.valign,mu, {alt:values.alt, title:values.alt})); } } svg.Clean(); this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; }, SVGimgLoaded: function (event,status) { if (typeof(event) === "string") {status = event} this.img.status = (status || "OK") }, SVGimgError: function () {this.img.img.onload("error")} },{ GLYPH: {} // global list of all loaded glyphs }); MathJax.Hub.Startup.signal.Post("SVG mglyph Ready"); MathJax.Ajax.loadComplete(SVG.autoloadDir+"/mglyph.js"); }); /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/jax/output/SVG/autoload/mmultiscripts.js * * Implements the SVG output for <mmultiscripts> elements. * * --------------------------------------------------------------------- * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () { var VERSION = "2.7.5"; var MML = MathJax.ElementJax.mml, SVG = MathJax.OutputJax.SVG; MML.mmultiscripts.Augment({ toSVG: function (HW,D) { this.SVGgetStyles(); var svg = this.SVG(), scale = this.SVGgetScale(svg); this.SVGhandleSpace(svg); var base = (this.data[this.base] ? this.SVGdataStretched(this.base,HW,D) : SVG.BBOX.G().Clean()); var x_height = SVG.TeX.x_height * scale, s = SVG.TeX.scriptspace * scale * .75; // FIXME: .75 can be removed when IC is right? var BOX = this.SVGgetScripts(s); var sub = BOX[0], sup = BOX[1], presub = BOX[2], presup = BOX[3]; var sscale = (this.data[1]||this).SVGgetScale(); var q = SVG.TeX.sup_drop * sscale, r = SVG.TeX.sub_drop * sscale; var u = base.h - q, v = base.d + r, delta = 0, p; if (base.ic) {delta = base.ic} if (this.data[this.base] && (this.data[this.base].type === "mi" || this.data[this.base].type === "mo")) { if (SVG.isChar(this.data[this.base].data.join("")) && base.scale === 1 && !base.stretched && !this.data[this.base].Get("largeop")) {u = v = 0} } var min = this.getValues("subscriptshift","superscriptshift"), mu = this.SVGgetMu(svg); min.subscriptshift = (min.subscriptshift === "" ? 0 : SVG.length2em(min.subscriptshift,mu)); min.superscriptshift = (min.superscriptshift === "" ? 0 : SVG.length2em(min.superscriptshift,mu)); var dx = 0; if (presub) {dx = presub.w+delta} else if (presup) {dx = presup.w-delta} svg.Add(base,Math.max(0,dx),0); if (!sup && !presup) { v = Math.max(v,SVG.TeX.sub1*scale,min.subscriptshift); if (sub) {v = Math.max(v,sub.h-(4/5)*x_height)} if (presub) {v = Math.max(v,presub.h-(4/5)*x_height)} if (sub) {svg.Add(sub,dx+base.w+s-delta,-v)} if (presub) {svg.Add(presub,0,-v)} } else { if (!sub && !presub) { var values = this.getValues("displaystyle","texprimestyle"); p = SVG.TeX[(values.displaystyle ? "sup1" : (values.texprimestyle ? "sup3" : "sup2"))]; u = Math.max(u,p*scale,min.superscriptshift); if (sup) {u = Math.max(u,sup.d+(1/4)*x_height)} if (presup) {u = Math.max(u,presup.d+(1/4)*x_height)} if (sup) {svg.Add(sup,dx+base.w+s,u)} if (presup) {svg.Add(presup,0,u)} } else { v = Math.max(v,SVG.TeX.sub2*scale); var t = SVG.TeX.rule_thickness * scale; var h = (sub||presub).h, d = (sup||presup).d; if (presub) {h = Math.max(h,presub.h)} if (presup) {d = Math.max(d,presup.d)} if ((u - d) - (h - v) < 3*t) { v = 3*t - u + d + h; q = (4/5)*x_height - (u - d); if (q > 0) {u += q; v -= q} } u = Math.max(u,min.superscriptshift); v = Math.max(v,min.subscriptshift); if (sup) {svg.Add(sup,dx+base.w+s,u)} if (presup) {svg.Add(presup,dx+delta-presup.w,u)} if (sub) {svg.Add(sub,dx+base.w+s-delta,-v)} if (presub) {svg.Add(presub,dx-presub.w,-v)} } } svg.Clean(); this.SVGhandleColor(svg); this.SVGsaveData(svg); var data = this.SVGdata; data.dx = dx; data.s = s; data.u = u, data.v = v; data.delta = delta; return svg; }, SVGgetScripts: function (s) { var sup, sub, BOX = []; var i = 1, m = this.data.length, W = 0; for (var k = 0; k < 4; k += 2) { while (i < m && (this.data[i]||{}).type !== "mprescripts") { var box = [null,null,null,null]; for (var j = k; j < k+2; j++) { if (this.data[i] && this.data[i].type !== "none" && this.data[i].type !== "mprescripts") { if (!BOX[j]) {BOX[j] = SVG.BBOX.G()} box[j] = this.data[i].toSVG(); } if ((this.data[i]||{}).type !== "mprescripts") i++; } var isPre = (k === 2); if (isPre) W += Math.max((box[k]||{w:0}).w,(box[k+1]||{w:0}).w); if (box[k]) BOX[k].Add(box[k].With({x:W-(isPre?box[k].w:0)})); if (box[k+1]) BOX[k+1].Add(box[k+1].With({x:W-(isPre?box[k+1].w:0)})); sub = BOX[k]||{w:0}; sup = BOX[k+1]||{w:0}; sub.w = sup.w = W = Math.max(sub.w,sup.w); } i++; W = 0; } for (j = 0; j < 4; j++) {if (BOX[j]) {BOX[j].w += s; BOX[j].Clean()}} return BOX; } }); MathJax.Hub.Startup.signal.Post("SVG mmultiscripts Ready"); MathJax.Ajax.loadComplete(SVG.autoloadDir+"/mmultiscripts.js"); }); /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/jax/output/SVG/autoload/annotation-xml.js * * Implements the SVG output for <annotation-xml> elements. * * --------------------------------------------------------------------- * * Copyright (c) 2013-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () { var VERSION = "2.7.5"; var MML = MathJax.ElementJax.mml, SVG = MathJax.OutputJax.SVG; var BBOX = SVG.BBOX; BBOX.FOREIGN = BBOX.Subclass({type: "foreignObject", removeable: false}); MML["annotation-xml"].Augment({ toSVG: function () { var svg = this.SVG(); this.SVGhandleSpace(svg); var encoding = this.Get("encoding"); for (var i = 0, m = this.data.length; i < m; i++) {svg.Add(this.data[i].toSVG(encoding),svg.w,0)} svg.Clean(); this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; } }); MML.xml.Augment({ toSVG: function (encoding) { // // Get size of xml content // var span = SVG.textSVG.parentNode; SVG.mathDiv.style.width = "auto"; // Firefox returns offsetWidth = 0 without this span.insertBefore(this.div,SVG.textSVG); var w = this.div.offsetWidth, h = this.div.offsetHeight; var strut = MathJax.HTML.addElement(this.div,"span",{ style:{display:"inline-block", overflow:"hidden", height:h+"px", width:"1px", marginRight:"-1px"} }); var d = this.div.offsetHeight - h; h -= d; this.div.removeChild(strut); span.removeChild(this.div); SVG.mathDiv.style.width = ""; // // Create foreignObject element for the content // var scale = 1000/SVG.em; var svg = BBOX.FOREIGN({ y:(-h)+"px", width:w+"px", height:(h+d)+"px", transform:"scale("+scale+") matrix(1 0 0 -1 0 0)" }); // // Add the children to the foreignObject // for (var i = 0, m = this.data.length; i < m; i++) {svg.element.appendChild(this.data[i].cloneNode(true))} // // Set the scale and finish up // svg.w = w*scale; svg.h = h*scale; svg.d = d*scale; svg.r = svg.w; svg.l = 0; svg.Clean(); this.SVGsaveData(svg); return svg; } }); MathJax.Hub.Startup.signal.Post("SVG annotation-xml Ready"); MathJax.Ajax.loadComplete(SVG.autoloadDir+"/annotation-xml.js"); }); /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/jax/output/SVG/autoload/maction.js * * Implements the SVG output for <maction> elements. * * --------------------------------------------------------------------- * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () { var VERSION = "2.7.5"; var MML = MathJax.ElementJax.mml, SVG = MathJax.OutputJax["SVG"]; var currentTip, hover, clear; // // Add configuration for tooltips // var CONFIG = SVG.config.tooltip = MathJax.Hub.Insert({ delayPost: 600, delayClear: 600, offsetX: 10, offsetY: 5 },SVG.config.tooltip||{}); MML.maction.Augment({ SVGtooltip: MathJax.HTML.addElement(document.body,"div",{id:"MathJax_SVG_Tooltip"}), toSVG: function (HW,D) { this.SVGgetStyles(); var svg = this.SVG(); var selected = this.selected(); if (selected.type == "null") {this.SVGsaveData(svg);return svg;} svg.Add(this.SVGdataStretched(this.Get("selection")-1,HW,D)); svg.removeable = false; this.SVGhandleHitBox(svg); this.SVGhandleSpace(svg); this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; }, SVGhandleHitBox: function (svg) { var frame = SVG.Element("rect", {width:svg.w, height:svg.h+svg.d, y:-svg.d, fill:"none", "pointer-events":"all"}); svg.element.insertBefore(frame,svg.element.firstChild); var type = this.Get("actiontype"); if (this.SVGaction[type]) {this.SVGaction[type].call(this,svg,svg.element,this.Get("selection"))} }, SVGstretchH: MML.mbase.prototype.SVGstretchH, SVGstretchV: MML.mbase.prototype.SVGstretchV, // // Implementations for the various actions // SVGaction: { toggle: function (svg,frame,selection) { this.selection = selection; SVG.Element(frame,{cursor:"pointer"}); frame.onclick = MathJax.Callback(["SVGclick",this]); }, statusline: function (svg,frame,selection) { frame.onmouseover = MathJax.Callback(["SVGsetStatus",this]), frame.onmouseout = MathJax.Callback(["SVGclearStatus",this]); frame.onmouseover.autoReset = frame.onmouseout.autoReset = true; }, tooltip: function(svg,frame,selection) { frame.onmouseover = MathJax.Callback(["SVGtooltipOver",this]), frame.onmouseout = MathJax.Callback(["SVGtooltipOut",this]); frame.onmouseover.autoReset = frame.onmouseout.autoReset = true; } }, // // Handle a click on the maction element // (remove the original rendering and rerender) // SVGclick: function (event) { this.selection++; if (this.selection > this.data.length) {this.selection = 1} var math = this; while (math.type !== "math") {math = math.inherit} var jax = MathJax.Hub.getJaxFor(math.inputID); //, hover = !!jax.hover; jax.Update(); /* * if (hover) { * var span = document.getElementById(jax.inputID+"-Span"); * MathJax.Extension.MathEvents.Hover.Hover(jax,span); * } */ return MathJax.Extension.MathEvents.Event.False(event); }, // // Set/Clear the window status message // SVGsetStatus: function (event) { // FIXME: Do something better with non-token elements this.messageID = MathJax.Message.Set ((this.data[1] && this.data[1].isToken) ? this.data[1].data.join("") : this.data[1].toString()); }, SVGclearStatus: function (event) { if (this.messageID) {MathJax.Message.Clear(this.messageID,0)} delete this.messageID; }, // // Handle tooltips // SVGtooltipOver: function (event) { if (!event) {event = window.event} if (clear) {clearTimeout(clear); clear = null} if (hover) {clearTimeout(hover)} var x = event.pageX; var y = event.pageY; if (x == null) { x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; } var callback = MathJax.Callback(["SVGtooltipPost",this,x+CONFIG.offsetX,y+CONFIG.offsetY]) hover = setTimeout(callback,CONFIG.delayPost); }, SVGtooltipOut: function (event) { if (hover) {clearTimeout(hover); hover = null} if (clear) {clearTimeout(clear)} var callback = MathJax.Callback(["SVGtooltipClear",this,80]); clear = setTimeout(callback,CONFIG.delayClear); }, SVGtooltipPost: function (x,y) { hover = null; if (clear) {clearTimeout(clear); clear = null} // // Get the tip div and show it at the right location, then clear its contents // var tip = this.SVGtooltip; tip.style.display = "block"; tip.style.opacity = ""; if (this === currentTip) return; tip.style.left = x+"px"; tip.style.top = y+"px"; tip.innerHTML = ''; var span = MathJax.HTML.addElement(tip,"span"); // // Get the sizes from the jax (FIXME: should calculate again?) // var math = this; while (math.type !== "math") {math = math.inherit} var jax = MathJax.Hub.getJaxFor(math.inputID); this.em = MML.mbase.prototype.em = jax.SVG.em; this.ex = jax.SVG.ex; this.linebreakWidth = jax.SVG.lineWidth; this.cwidth = jax.SVG.cwidth; // // Make a new math element and temporarily move the tooltip to it // Display the math containing the tip, but check for errors // Then put the tip back into the maction element // var mml = this.data[1]; math = MML.math(mml); try {math.toSVG(span,tip)} catch(err) { this.SetData(1,mml); tip.style.display = "none"; if (!err.restart) {throw err} MathJax.Callback.After(["SVGtooltipPost",this,x,y],err.restart); return; } this.SetData(1,mml); currentTip = this; }, SVGtooltipClear: function (n) { var tip = this.SVGtooltip; if (n <= 0) { tip.style.display = "none"; tip.style.opacity = ""; clear = null; } else { tip.style.opacity = n/100; clear = setTimeout(MathJax.Callback(["SVGtooltipClear",this,n-20]),50); } } }); MathJax.Hub.Startup.signal.Post("SVG maction Ready"); MathJax.Ajax.loadComplete(SVG.autoloadDir+"/maction.js"); }); /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/jax/output/SVG/autoload/multiline.js * * Implements the SVG output for <mrow>'s that contain line breaks. * * --------------------------------------------------------------------- * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () { var VERSION = "2.7.5"; var MML = MathJax.ElementJax.mml, SVG = MathJax.OutputJax.SVG, BBOX = SVG.BBOX; // // Fake node used for testing end-of-line potential breakpoint // var MO = MML.mo().With({SVGdata: {w: 0, x:0}}); // // Penalties for the various line breaks // var PENALTY = { newline: 0, nobreak: 1000000, goodbreak: [-200], badbreak: [+200], auto: [0], maxwidth: 1.33, // stop looking for breaks after this time the line-break width toobig: 800, nestfactor: 400, spacefactor: -100, spaceoffset: 2, spacelimit: 1, // spaces larger than this get a penalty boost fence: 500, close: 500 }; var ENDVALUES = {linebreakstyle: "after"}; /**************************************************************************/ MML.mrow.Augment({ // // Handle breaking an mrow into separate lines // SVGmultiline: function (svg) { // // Find the parent element and mark it as multiline // var parent = this; while (parent.inferred || (parent.parent && parent.parent.type === "mrow" && parent.isEmbellished())) {parent = parent.parent} var isTop = ((parent.type === "math" && parent.Get("display") === "block") || parent.type === "mtd"); parent.isMultiline = true; // // Default values for the line-breaking parameters // var VALUES = this.getValues( "linebreak","linebreakstyle","lineleading","linebreakmultchar", "indentalign","indentshift", "indentalignfirst","indentshiftfirst", "indentalignlast","indentshiftlast" ); if (VALUES.linebreakstyle === MML.LINEBREAKSTYLE.INFIXLINEBREAKSTYLE) {VALUES.linebreakstyle = this.Get("infixlinebreakstyle")} VALUES.lineleading = SVG.length2em(VALUES.lineleading,1,0.5); // // Start with a fresh SVG element // and make it full width if we are breaking to a specific width // in the top-level math element // svg = this.SVG(); if (isTop && parent.type !== "mtd") { if (SVG.linebreakWidth < SVG.BIGDIMEN) {svg.w = SVG.linebreakWidth} else {svg.w = SVG.cwidth} } var state = { n: 0, Y: 0, scale: this.scale || 1, isTop: isTop, values: {}, VALUES: VALUES }, align = this.SVGgetAlign(state,{}), shift = this.SVGgetShift(state,{},align), start = [], end = { index:[], penalty:PENALTY.nobreak, w:0, W:shift, shift:shift, scanW:shift, nest: 0 }, broken = false; // // Break the expression at its best line breaks // while (this.SVGbetterBreak(end,state,true) && (end.scanW >= SVG.linebreakWidth || end.penalty === PENALTY.newline)) { this.SVGaddLine(svg,start,end.index,state,end.values,broken); start = end.index.slice(0); broken = true; align = this.SVGgetAlign(state,end.values); shift = this.SVGgetShift(state,end.values,align); if (align === MML.INDENTALIGN.CENTER) {shift = 0} end.W = end.shift = end.scanW = shift; end.penalty = PENALTY.nobreak; } state.isLast = true; this.SVGaddLine(svg,start,[],state,ENDVALUES,broken); this.SVGhandleSpace(svg); this.SVGhandleColor(svg); svg.isMultiline = true; this.SVGsaveData(svg); return svg; } }); /**************************************************************************/ MML.mbase.Augment({ SVGlinebreakPenalty: PENALTY, /****************************************************************/ // // Locate the next linebreak that is better than the current one // SVGbetterBreak: function (info,state,toplevel) { if (this.isToken) {return false} // FIXME: handle breaking of token elements if (this.isEmbellished()) { info.embellished = this; return this.CoreMO().SVGbetterBreak(info,state); } if (this.linebreakContainer) {return false} // // Get the current breakpoint position and other data // var index = info.index.slice(0), i = info.index.shift(), m = this.data.length, W, w, scanW, broken = (info.index.length > 0), better = false; if (i == null) {i = -1}; if (!broken) {i++; info.W += info.w; info.w = 0} scanW = info.scanW = info.W; info.nest++; // // Look through the line for breakpoints, // (as long as we are not too far past the breaking width) // while (i < m && (info.scanW < PENALTY.maxwidth*SVG.linebreakWidth || info.w === 0)) { if (this.data[i]) { if (this.data[i].SVGbetterBreak(info,state)) { better = true; index = [i].concat(info.index); W = info.W; w = info.w; if (info.penalty === PENALTY.newline) { info.index = index; if (info.nest) {info.nest--} return true; } } scanW = (broken ? info.scanW : this.SVGaddWidth(i,info,scanW)); } info.index = []; i++; broken = false; } // // Check if end-of-line is a better breakpoint // if (toplevel && better) { MO.parent = this.parent; MO.inherit = this.inherit; if (MO.SVGbetterBreak(info,state)) {better = false; index = info.index} } if (info.nest) {info.nest--} info.index = index; if (better) {info.W = W} return better; }, SVGaddWidth: function (i,info,scanW) { if (this.data[i]) { var svg = this.data[i].SVGdata; scanW += svg.w + svg.x; if (svg.X) {scanW += svg.X} info.W = info.scanW = scanW; info.w = 0; } return scanW; }, /****************************************************************/ // // Create a new line and move the required elements into it // Position it using proper alignment and indenting // SVGaddLine: function (svg,start,end,state,values,broken) { // // Create a box for the line, with empty BBox // fill it with the proper elements, // and clean up the bbox // var line = BBOX(); state.first = broken; state.last = true; this.SVGmoveLine(start,end,line,state,values); line.Clean(); // // Get the alignment and shift values // var align = this.SVGgetAlign(state,values), shift = this.SVGgetShift(state,values,align); // // Set the Y offset based on previous depth, leading, and current height // if (state.n > 0) { var LHD = SVG.FONTDATA.baselineskip * state.scale; var leading = (state.values.lineleading == null ? state.VALUES : state.values).lineleading * state.scale; state.Y -= Math.max(LHD,state.d + line.h + leading); } // // Place the new line // if (line.w + shift > svg.w) svg.w = line.w + shift; svg.Align(line,align,0,state.Y,shift); // // Save the values needed for the future // state.d = line.d; state.values = values; state.n++; }, /****************************************************************/ // // Get alignment and shift values from the given data // SVGgetAlign: function (state,values) { var cur = values, prev = state.values, def = state.VALUES, align; if (state.n === 0) {align = cur.indentalignfirst || prev.indentalignfirst || def.indentalignfirst} else if (state.isLast) {align = prev.indentalignlast || def.indentalignlast} else {align = prev.indentalign || def.indentalign} if (align === MML.INDENTALIGN.INDENTALIGN) {align = prev.indentalign || def.indentalign} if (align === MML.INDENTALIGN.AUTO) {align = (state.isTop ? this.displayAlign : MML.INDENTALIGN.LEFT)} return align; }, SVGgetShift: function (state,values,align) { var cur = values, prev = state.values, def = state.VALUES, shift; if (state.n === 0) {shift = cur.indentshiftfirst || prev.indentshiftfirst || def.indentshiftfirst} else if (state.isLast) {shift = prev.indentshiftlast || def.indentshiftlast} else {shift = prev.indentshift || def.indentshift} if (shift === MML.INDENTSHIFT.INDENTSHIFT) {shift = prev.indentshift || def.indentshift} if (shift === "auto" || shift === "") {shift = "0"} shift = SVG.length2em(shift,1,SVG.cwidth); if (state.isTop && this.displayIndent !== "0") { var indent = SVG.length2em(this.displayIndent,1,SVG.cwidth); shift += (align === MML.INDENTALIGN.RIGHT ? -indent: indent); } return shift; }, /****************************************************************/ // // Move the selected elements into the new line, // moving whole items when possible, and parts of ones // that are split by a line break. // SVGmoveLine: function (start,end,svg,state,values) { var i = start[0], j = end[0]; if (i == null) {i = -1}; if (j == null) {j = this.data.length-1} if (i === j && start.length > 1) { // // If starting and ending in the same element move the subpiece to the new line // this.data[i].SVGmoveSlice(start.slice(1),end.slice(1),svg,state,values,"paddingLeft"); } else { // // Otherwise, move the remainder of the initial item // and any others up to the last one // var last = state.last; state.last = false; while (i < j) { if (this.data[i]) { if (start.length <= 1) {this.data[i].SVGmove(svg,state,values)} else {this.data[i].SVGmoveSlice(start.slice(1),[],svg,state,values,"paddingLeft")} } i++; state.first = false; start = []; } // // If the last item is complete, move it, // otherwise move the first part of it up to the split // state.last = last; if (this.data[i]) { if (end.length <= 1) {this.data[i].SVGmove(svg,state,values)} else {this.data[i].SVGmoveSlice([],end.slice(1),svg,state,values,"paddingRight")} } } }, /****************************************************************/ // // Split an element and copy the selected items into the new part // SVGmoveSlice: function (start,end,svg,state,values,padding) { // // Create a new container for the slice of the element // Move the selected portion into the slice // var slice = BBOX(); this.SVGmoveLine(start,end,slice,state,values); slice.Clean(); if (this.href) {this.SVGaddHref(slice)} this.SVGhandleColor(slice); if (start.length == 0) this.SVGhandleSpace(slice); svg.Add(slice,svg.w,0,true); return slice; }, /****************************************************************/ // // Move an element from its original position to its new location in // a split element or the new line's position // SVGmove: function (line,state,values) { // FIXME: handle linebreakstyle === "duplicate" // FIXME: handle linebreakmultchar if (!(state.first || state.last) || (state.first && state.values.linebreakstyle === MML.LINEBREAKSTYLE.BEFORE) || (state.last && values.linebreakstyle === MML.LINEBREAKSTYLE.AFTER)) { // // Recreate output // Remove padding (if first, remove at leftt, if last remove at right) // Add to line // var svg = this.toSVG(this.SVGdata.HW,this.SVGdata.D); if (state.first || state.nextIsFirst) {svg.x = 0} if (state.last && svg.X) {svg.X = 0} line.Add(svg,line.w,0,true); } if (state.first && svg && svg.w === 0) {state.nextIsFirst = true} else {delete state.nextIsFirst} } }); /**************************************************************************/ MML.mfenced.Augment({ SVGbetterBreak: function (info,state) { // // Get the current breakpoint position and other data // var index = info.index.slice(0), i = info.index.shift(), m = this.data.length, W, w, scanW, broken = (info.index.length > 0), better = false; if (i == null) {i = -1}; if (!broken) {i++; info.W += info.w; info.w = 0} scanW = info.scanW = info.W; info.nest++; // // Create indices that include the delimiters and separators // if (!this.dataI) { this.dataI = []; if (this.data.open) {this.dataI.push("open")} if (m) {this.dataI.push(0)} for (var j = 1; j < m; j++) { if (this.data["sep"+j]) {this.dataI.push("sep"+j)} this.dataI.push(j); } if (this.data.close) {this.dataI.push("close")} } m = this.dataI.length; // // Look through the line for breakpoints, including the open, close, and separators // (as long as we are not too far past the breaking width) // while (i < m && (info.scanW < PENALTY.maxwidth*SVG.linebreakWidth || info.w === 0)) { var k = this.dataI[i]; if (this.data[k]) { if (this.data[k].SVGbetterBreak(info,state)) { better = true; index = [i].concat(info.index); W = info.W; w = info.w; if (info.penalty === PENALTY.newline) { info.index = index; if (info.nest) {info.nest--} return true; } } scanW = (broken ? info.scanW : this.SVGaddWidth(i,info,scanW)); } info.index = []; i++; broken = false; } if (info.nest) {info.nest--} info.index = index; if (better) {info.W = W; info.w = w} return better; }, SVGmoveLine: function (start,end,svg,state,values) { var i = start[0], j = end[0]; if (i == null) {i = -1}; if (j == null) {j = this.dataI.length-1} if (i === j && start.length > 1) { // // If starting and ending in the same element move the subpiece to the new line // this.data[this.dataI[i]].SVGmoveSlice(start.slice(1),end.slice(1),svg,state,values,"paddingLeft"); } else { // // Otherwise, move the remainder of the initial item // and any others (including open and separators) up to the last one // var last = state.last; state.last = false; var k = this.dataI[i]; while (i < j) { if (this.data[k]) { if (start.length <= 1) {this.data[k].SVGmove(svg,state,values)} else {this.data[k].SVGmoveSlice(start.slice(1),[],svg,state,values,"paddingLeft")} } i++; k = this.dataI[i]; state.first = false; start = []; } // // If the last item is complete, move it // state.last = last; if (this.data[k]) { if (end.length <= 1) {this.data[k].SVGmove(svg,state,values)} else {this.data[k].SVGmoveSlice([],end.slice(1),svg,state,values,"paddingRight")} } } } }); /**************************************************************************/ MML.msubsup.Augment({ SVGbetterBreak: function (info,state) { if (!this.data[this.base]) {return false} // // Get the current breakpoint position and other data // var index = info.index.slice(0), i = info.index.shift(), W, w, scanW, broken = (info.index.length > 0), better = false; if (!broken) {info.W += info.w; info.w = 0} scanW = info.scanW = info.W; // // Record the width of the base and the super- and subscripts // if (i == null) {this.SVGdata.dw = this.SVGdata.w - this.data[this.base].SVGdata.w} // // Check if the base can be broken // if (this.data[this.base].SVGbetterBreak(info,state)) { better = true; index = [this.base].concat(info.index); W = info.W; w = info.w; if (info.penalty === PENALTY.newline) {better = broken = true} } // // Add in the base if it is unbroken, and add the scripts // if (!broken) {this.SVGaddWidth(this.base,info,scanW)} info.scanW += this.SVGdata.dw; info.W = info.scanW; info.index = []; if (better) {info.W = W; info.w = w; info.index = index} return better; }, SVGmoveLine: function (start,end,svg,state,values) { // // Move the proper part of the base // if (this.data[this.base]) { if (start.length > 1) { this.data[this.base].SVGmoveSlice(start.slice(1),end.slice(1),svg,state,values,"paddingLeft"); } else { if (end.length <= 1) {this.data[this.base].SVGmove(svg,state,values)} else {this.data[this.base].SVGmoveSlice([],end.slice(1),svg,state,values,"paddingRight")} } } // // If this is the end, check for super and subscripts, and move those // by moving the stack that contains them, and shifting by the amount of the // base that has been removed. Remove the empty base box from the stack. // if (end.length === 0) { var sup = this.data[this.sup], sub = this.data[this.sub], w = svg.w, data; if (sup) {data = sup.SVGdata||{}; svg.Add(sup.toSVG(),w+(data.dx||0),data.dy)} if (sub) {data = sub.SVGdata||{}; svg.Add(sub.toSVG(),w+(data.dx||0),data.dy)} } } }); /**************************************************************************/ MML.mmultiscripts.Augment({ SVGbetterBreak: function (info,state) { if (!this.data[this.base]) {return false} // // Get the current breakpoint position and other data // var index = info.index.slice(0); info.index.shift(); var W, w, scanW, broken = (info.index.length > 0), better = false; if (!broken) {info.W += info.w; info.w = 0} info.scanW = info.W; // // The width of the postscripts // var dw = this.SVGdata.w - this.data[this.base].SVGdata.w - this.SVGdata.dx; // // Add in the prescripts // info.scanW += this.SVGdata.dx; scanW = info.scanW; // // Check if the base can be broken (but don't break between prescripts and base) // if (this.data[this.base].SVGbetterBreak(info,state)) { better = true; index = [this.base].concat(info.index); W = info.W; w = info.w; if (info.penalty === PENALTY.newline) {better = broken = true} } // // Add in the base if it is unbroken, and add the postscripts // if (!broken) {this.SVGaddWidth(this.base,info,scanW)} info.scanW += dw; info.W = info.scanW; info.index = []; if (better) {info.W = W; info.w = w; info.index = index} return better; }, SVGmoveLine: function (start,end,svg,state,values) { var dx, data = this.SVGdata; // // If this is the start, move the prescripts, if any. // if (start.length < 1) { this.scriptBox = this.SVGgetScripts(this.SVGdata.s); var presub = this.scriptBox[2], presup = this.scriptBox[3]; dx = svg.w + data.dx; if (presup) {svg.Add(presup,dx+data.delta-presup.w,data.u)} if (presub) {svg.Add(presub,dx-presub.w,-data.v)} } // // Move the proper part of the base // if (this.data[this.base]) { if (start.length > 1) { this.data[this.base].SVGmoveSlice(start.slice(1),end.slice(1),svg,state,values,"paddingLeft"); } else { if (end.length <= 1) {this.data[this.base].SVGmove(svg,state,values)} else {this.data[this.base].SVGmoveSlice([],end.slice(1),svg,state,values,"paddingRight")} } } // // If this is the end, move the postscripts, if any. // if (end.length === 0) { var sub = this.scriptBox[0], sup = this.scriptBox[1]; dx = svg.w + data.s; if (sup) {svg.Add(sup,dx,data.u)} if (sub) {svg.Add(sub,dx-data.delta,-data.v)} delete this.scriptBox; } } }); /**************************************************************************/ MML.mo.Augment({ // // Override the method for checking line breaks to properly handle <mo> // SVGbetterBreak: function (info,state) { if (info.values && info.values.last === this) {return false} var values = this.getValues( "linebreak","linebreakstyle","lineleading","linebreakmultchar", "indentalign","indentshift", "indentalignfirst","indentshiftfirst", "indentalignlast","indentshiftlast", "texClass", "fence" ); if (values.linebreakstyle === MML.LINEBREAKSTYLE.INFIXLINEBREAKSTYLE) {values.linebreakstyle = this.Get("infixlinebreakstyle")} // // Adjust nesting by TeX class (helps output that does not include // mrows for nesting, but can leave these unbalanced. // if (values.texClass === MML.TEXCLASS.OPEN) {info.nest++} if (values.texClass === MML.TEXCLASS.CLOSE && info.nest) {info.nest--} // // Get the default penalty for this location // var W = info.scanW, mo = info.embellished; delete info.embellished; if (!mo || !mo.SVGdata) {mo = this} var svg = mo.SVGdata, w = svg.w + svg.x; if (values.linebreakstyle === MML.LINEBREAKSTYLE.AFTER) {W += w; w = 0} if (W - info.shift === 0 && values.linebreak !== MML.LINEBREAK.NEWLINE) {return false} // don't break at zero width (FIXME?) var offset = SVG.linebreakWidth - W; // adjust offest for explicit first-line indent and align if (state.n === 0 && (values.indentshiftfirst !== state.VALUES.indentshiftfirst || values.indentalignfirst !== state.VALUES.indentalignfirst)) { var align = this.SVGgetAlign(state,values), shift = this.SVGgetShift(state,values,align); offset += (info.shift - shift); } // var penalty = Math.floor(offset / SVG.linebreakWidth * 1000); if (penalty < 0) {penalty = PENALTY.toobig - 3*penalty} if (values.fence) {penalty += PENALTY.fence} if ((values.linebreakstyle === MML.LINEBREAKSTYLE.AFTER && values.texClass === MML.TEXCLASS.OPEN) || values.texClass === MML.TEXCLASS.CLOSE) {penalty += PENALTY.close} penalty += info.nest * PENALTY.nestfactor; // // Get the penalty for this type of break and // use it to modify the default penalty // var linebreak = PENALTY[values.linebreak||MML.LINEBREAK.AUTO]||0; if (!MathJax.Object.isArray(linebreak)) { // for breaks past the width, keep original penalty for newline if (linebreak || offset >= 0) {penalty = linebreak * info.nest} } else {penalty = Math.max(1,penalty + linebreak[0] * info.nest)} // // If the penalty is no better than the current one, return false // Otherwise save the data for this breakpoint and return true // if (penalty >= info.penalty) {return false} info.penalty = penalty; info.values = values; info.W = W; info.w = w; values.lineleading = SVG.length2em(values.lineleading,1,state.VALUES.lineleading); values.last = this; return true; } }); /**************************************************************************/ MML.mspace.Augment({ // // Override the method for checking line breaks to properly handle <mspace> // SVGbetterBreak: function (info,state) { if (info.values && info.values.last === this) {return false} var values = this.getValues("linebreak"); var linebreakValue = values.linebreak; if (!linebreakValue || this.hasDimAttr()) { // The MathML spec says that the linebreak attribute should be ignored // if any dimensional attribute is set. linebreakValue = MML.LINEBREAK.AUTO; } // // Get the default penalty for this location // var W = info.scanW, svg = this.SVGdata, w = svg.w + svg.x; if (W - info.shift === 0) {return false} // don't break at zero width (FIXME?) var offset = SVG.linebreakWidth - W; // var penalty = Math.floor(offset / SVG.linebreakWidth * 1000); if (penalty < 0) {penalty = PENALTY.toobig - 3*penalty} penalty += info.nest * PENALTY.nestfactor; // // Get the penalty for this type of break and // use it to modify the default penalty // var linebreak = PENALTY[linebreakValue]||0; if (linebreakValue === MML.LINEBREAK.AUTO && w >= PENALTY.spacelimit*1000 && !this.mathbackground && !this.backrgound) {linebreak = [(w/1000+PENALTY.spaceoffset)*PENALTY.spacefactor]} if (!MathJax.Object.isArray(linebreak)) { // for breaks past the width, keep original penalty for newline if (linebreak || offset >= 0) {penalty = linebreak * info.nest} } else {penalty = Math.max(1,penalty + linebreak[0] * info.nest)} // // If the penalty is no better than the current one, return false // Otherwise save the data for this breakpoint and return true // if (penalty >= info.penalty) {return false} info.penalty = penalty; info.values = values; info.W = W; info.w = w; values.lineleading = state.VALUES.lineleading; values.linebreakstyle = "before"; values.last = this; return true; } }); // // Hook into the mathchoice extension // MathJax.Hub.Register.StartupHook("TeX mathchoice Ready",function () { MML.TeXmathchoice.Augment({ SVGbetterBreak: function (info,state) { return this.Core().SVGbetterBreak(info,state); }, SVGmoveLine: function (start,end,svg,state,values) { return this.Core().SVGmoveSlice(start,end,svg,state,values); } }); }); // // Have maction process only the selected item // MML.maction.Augment({ SVGbetterBreak: function (info,state) { return this.Core().SVGbetterBreak(info,state); }, SVGmoveLine: function (start,end,svg,state,values) { return this.Core().SVGmoveSlice(start,end,svg,state,values); }, }); // // Have semantics only do the first element // (FIXME: do we need to do anything special about annotation-xml?) // MML.semantics.Augment({ SVGbetterBreak: function (info,state) { return (this.data[0] ? this.data[0].SVGbetterBreak(info,state) : false); }, SVGmoveLine: function (start,end,svg,state,values) { return (this.data[0] ? this.data[0].SVGmoveSlice(start,end,svg,state,values) : null); } }); /**************************************************************************/ MathJax.Hub.Startup.signal.Post("SVG multiline Ready"); MathJax.Ajax.loadComplete(SVG.autoloadDir+"/multiline.js"); }); /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/jax/output/SVG/autoload/menclose.js * * Implements the SVG output for <menclose> elements. * * --------------------------------------------------------------------- * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () { var VERSION = "2.7.5"; var MML = MathJax.ElementJax.mml, SVG = MathJax.OutputJax.SVG, BBOX = SVG.BBOX; BBOX.ELLIPSE = BBOX.Subclass({ type: "ellipse", removeable: false, Init: function (h,d,w,t,color,def) { if (def == null) {def = {}}; def.fill = "none"; if (color) {def.stroke = color} def["stroke-width"] = t.toFixed(2).replace(/\.?0+$/,""); def.cx = Math.floor(w/2); def.cy = Math.floor((h+d)/2-d); def.rx = Math.floor((w-t)/2); def.ry = Math.floor((h+d-t)/2); this.SUPER(arguments).Init.call(this,def); this.w = this.r = w; this.h = this.H = h; this.d = this.D = d; this.l = 0; } }); BBOX.DLINE = BBOX.Subclass({ type: "line", removeable: false, Init: function (h,d,w,t,color,updown,def) { if (def == null) {def = {}}; def.fill = "none"; if (color) {def.stroke = color} def["stroke-width"] = t.toFixed(2).replace(/\.?0+$/,""); if (updown == "up") { def.x1 = Math.floor(t/2); def.y1 = Math.floor(t/2-d); def.x2 = Math.floor(w-t/2); def.y2 = Math.floor(h-t/2); } else { def.x1 = Math.floor(t/2); def.y1 = Math.floor(h-t/2); def.x2 = Math.floor(w-t/2); def.y2 = Math.floor(t/2-d); } this.SUPER(arguments).Init.call(this,def); this.w = this.r = w; this.h = this.H = h; this.d = this.D = d; this.l = 0; } }); BBOX.FPOLY = BBOX.Subclass({ type: "polygon", removeable: false, Init: function (points,color,def) { if (def == null) {def = {}} if (color) {def.fill = color} var P = [], mx = 100000000, my = mx, Mx = -mx, My = Mx; for (var i = 0, m = points.length; i < m; i++) { var x = points[i][0], y = points[i][1]; if (x > Mx) {Mx = x}; if (x < mx) {mx = x} if (y > My) {My = y}; if (y < my) {my = y} P.push(Math.floor(x)+","+Math.floor(y)); } def.points = P.join(" "); this.SUPER(arguments).Init.call(this,def); this.w = this.r = Mx; this.h = this.H = My; this.d = this.D = -my; this.l = -mx; } }); BBOX.PPATH = BBOX.Subclass({ type: "path", removeable: false, Init: function (h,d,w,p,t,color,def) { if (def == null) {def = {}}; def.fill = "none"; if (color) {def.stroke = color} def["stroke-width"] = t.toFixed(2).replace(/\.?0+$/,""); def.d = p; this.SUPER(arguments).Init.call(this,def); this.w = this.r = w; this.h = this.H = h+d; this.d = this.D = this.l = 0; this.y = -d; } }); MML.menclose.Augment({ toSVG: function (HW,DD) { this.SVGgetStyles(); var svg = this.SVG(), scale = this.SVGgetScale(svg); this.SVGhandleSpace(svg); var base = this.SVGdataStretched(0,HW,DD); var values = this.getValues("notation","thickness","padding","mathcolor","color"); if (values.color && !this.mathcolor) {values.mathcolor = values.color} if (values.thickness == null) {values.thickness = ".075em"} if (values.padding == null) {values.padding = ".2em"} var mu = this.SVGgetMu(svg); var p = SVG.length2em(values.padding,mu,1/SVG.em) * scale; // padding for enclosure var t = SVG.length2em(values.thickness,mu,1/SVG.em); // thickness of lines t = Math.max(1/SVG.em,t); // see issue #414 var H = base.h+p+t, D = base.d+p+t, W = base.w+2*(p+t); var dx = 0, w, h, i, m, borders = [false,false,false,false]; // perform some reduction e.g. eliminate duplicate notations. var nl = MathJax.Hub.SplitList(values.notation), notation = {}; for (i = 0, m = nl.length; i < m; i++) notation[nl[i]] = true; if (notation[MML.NOTATION.UPDIAGONALARROW]) notation[MML.NOTATION.UPDIAGONALSTRIKE] = false; for (var n in notation) { if (!notation.hasOwnProperty(n) || !notation[n]) continue; switch (n) { case MML.NOTATION.BOX: borders = [true,true,true,true]; break; case MML.NOTATION.ROUNDEDBOX: svg.Add(BBOX.FRAME(H,D,W,t,"solid",values.mathcolor, {rx:Math.floor(Math.min(H+D-t,W-t)/4)})); break; case MML.NOTATION.CIRCLE: svg.Add(BBOX.ELLIPSE(H,D,W,t,values.mathcolor)); break; case MML.NOTATION.ACTUARIAL: borders[0] = true; case MML.NOTATION.RIGHT: borders[1] = true; break; case MML.NOTATION.LEFT: borders[3] = true; break; case MML.NOTATION.TOP: borders[0] = true; break; case MML.NOTATION.BOTTOM: borders[2] = true; break; case MML.NOTATION.VERTICALSTRIKE: svg.Add(BBOX.VLINE(H+D,t,"solid",values.mathcolor),(W-t)/2,-D); break; case MML.NOTATION.HORIZONTALSTRIKE: svg.Add(BBOX.HLINE(W,t,"solid",values.mathcolor),0,(H+D-t)/2-D); break; case MML.NOTATION.UPDIAGONALSTRIKE: svg.Add(BBOX.DLINE(H,D,W,t,values.mathcolor,"up")); break; case MML.NOTATION.UPDIAGONALARROW: var l = Math.sqrt(W*W + (H+D)*(H+D)), f = 1/l * 10/SVG.em * t/.075; w = W * f; h = (H+D) * f; var x = .4*h; svg.Add(BBOX.DLINE(H-.5*h,D,W-.5*w,t,values.mathcolor,"up")); svg.Add(BBOX.FPOLY( [[x+w,h], [x-.4*h,.4*w], [x+.3*w,.3*h], [x+.4*h,-.4*w], [x+w,h]], values.mathcolor),W-w-x,H-h); break; case MML.NOTATION.DOWNDIAGONALSTRIKE: svg.Add(BBOX.DLINE(H,D,W,t,values.mathcolor,"down")); break; case MML.NOTATION.PHASORANGLE: borders[2] = true; W -= 2*p; p = (H+D)/2; W += p; svg.Add(BBOX.DLINE(H,D,p,t,values.mathcolor,"up")); break; case MML.NOTATION.MADRUWB: borders[1] = borders[2] = true; break; case MML.NOTATION.RADICAL: svg.Add(BBOX.PPATH(H,D,W, "M "+this.SVGxy(t/2,.4*(H+D)) + " L "+this.SVGxy(p,t/2) + " L "+this.SVGxy(2*p,H+D-t/2) + " L "+this.SVGxy(W,H+D-t/2), t,values.mathcolor),0,t); dx = p; break; case MML.NOTATION.LONGDIV: svg.Add(BBOX.PPATH(H,D,W, "M "+this.SVGxy(t/2,t/2) + " a "+this.SVGxy(p,(H+D)/2-2*t) + " 0 0,1 " + this.SVGxy(t/2,H+D-t) + " L "+this.SVGxy(W,H+D-t/2), t,values.mathcolor),0,t/2); dx = p; break; } } var sides = [["H",W,0,H-t],["V",H+D,W-t,-D],["H",W,0,-D],["V",H+D,0,-D]]; for (i = 0; i < 4; i++) { if (borders[i]) { var side = sides[i]; svg.Add(BBOX[side[0]+"LINE"](side[1],t,"solid",values.mathcolor),side[2],side[3]); } } svg.Add(base,dx+p+t,0,false,true); svg.Clean(); this.SVGhandleSpace(svg); this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; }, SVGxy: function (x,y) {return Math.floor(x)+","+Math.floor(y)} }); MathJax.Hub.Startup.signal.Post("SVG menclose Ready"); MathJax.Ajax.loadComplete(SVG.autoloadDir+"/menclose.js"); }); /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/jax/output/SVG/autoload/ms.js * * Implements the SVG output for <ms> elements. * * --------------------------------------------------------------------- * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () { var VERSION = "2.7.5"; var MML = MathJax.ElementJax.mml, SVG = MathJax.OutputJax.SVG; MML.ms.Augment({ toSVG: function () { this.SVGgetStyles(); var svg = this.SVG(); this.SVGhandleSpace(svg); var values = this.getValues("lquote","rquote","mathvariant"); if (!this.hasValue("lquote") || values.lquote === '"') values.lquote = "\u201C"; if (!this.hasValue("rquote") || values.rquote === '"') values.rquote = "\u201D"; if (values.lquote === "\u201C" && values.mathvariant === "monospace") values.lquote = '"'; if (values.rquote === "\u201D" && values.mathvariant === "monospace") values.rquote = '"'; var variant = this.SVGgetVariant(), scale = this.SVGgetScale(); var text = values.lquote+this.data.join("")+values.rquote; // FIXME: handle mglyph? svg.Add(this.SVGhandleVariant(variant,scale,text)); svg.Clean(); this.SVGhandleColor(svg); this.SVGsaveData(svg); return svg; } }); MathJax.Hub.Startup.signal.Post("SVG ms Ready"); MathJax.Ajax.loadComplete(SVG.autoloadDir+"/ms.js"); }); /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/fontdata.js * * Initializes the SVG OutputJax to use the MathJax TeX fonts * for displaying mathematics. * * --------------------------------------------------------------------- * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ (function (SVG,MML,AJAX,HUB) { var VERSION = "2.7.5"; var MAIN = "MathJax_Main", BOLD = "MathJax_Main-bold", ITALIC = "MathJax_Math-italic", AMS = "MathJax_AMS", SIZE1 = "MathJax_Size1", SIZE2 = "MathJax_Size2", SIZE3 = "MathJax_Size3", SIZE4 = "MathJax_Size4"; var H = "H", V = "V", EXTRAH = {load:"extra", dir:H}, EXTRAV = {load:"extra", dir:V}; var STDHW = [[1000,MAIN],[1200,SIZE1],[1800,SIZE2],[2400,SIZE3],[3000,SIZE4]]; var ARROWREP = [0x2212,MAIN,0,0,0,-.31,-.31]; // add depth for arrow extender var DARROWREP = [0x3D,MAIN,0,0,0,0,.1]; // add depth for arrow extender SVG.Augment({ FONTDATA: { version: VERSION, baselineskip: 1200, lineH: 800, lineD: 200, FONTS: { "MathJax_Main": "Main/Regular/Main.js", "MathJax_Main-bold": "Main/Bold/Main.js", "MathJax_Main-italic": "Main/Italic/Main.js", "MathJax_Math-italic": "Math/Italic/Main.js", "MathJax_Math-bold-italic": "Math/BoldItalic/Main.js", "MathJax_Caligraphic": "Caligraphic/Regular/Main.js", "MathJax_Size1": "Size1/Regular/Main.js", "MathJax_Size2": "Size2/Regular/Main.js", "MathJax_Size3": "Size3/Regular/Main.js", "MathJax_Size4": "Size4/Regular/Main.js", "MathJax_AMS": "AMS/Regular/Main.js", "MathJax_Fraktur": "Fraktur/Regular/Main.js", "MathJax_Fraktur-bold": "Fraktur/Bold/Main.js", "MathJax_SansSerif": "SansSerif/Regular/Main.js", "MathJax_SansSerif-bold": "SansSerif/Bold/Main.js", "MathJax_SansSerif-italic": "SansSerif/Italic/Main.js", "MathJax_Script": "Script/Regular/Main.js", "MathJax_Typewriter": "Typewriter/Regular/Main.js", "MathJax_Caligraphic-bold": "Caligraphic/Bold/Main.js" }, VARIANT: { "normal": {fonts:[MAIN,SIZE1,AMS], offsetG: 0x03B1, variantG: "italic", remap: {0x391:0x41, 0x392:0x42, 0x395:0x45, 0x396:0x5A, 0x397:0x48, 0x399:0x49, 0x39A:0x4B, 0x39C:0x4D, 0x39D:0x4E, 0x39F:0x4F, 0x3A1:0x50, 0x3A4:0x54, 0x3A7:0x58, 0x2016:0x2225, 0x2216:[0x2216,"-TeX-variant"], // \smallsetminus 0x210F:[0x210F,"-TeX-variant"], // \hbar 0x2032:[0x27,"sans-serif-italic"], // HACK: a smaller prime 0x29F8:[0x002F,MML.VARIANT.ITALIC]}}, "bold": {fonts:[BOLD,SIZE1,AMS], bold:true, offsetG: 0x03B1, variantG: "bold-italic", remap: {0x391:0x41, 0x392:0x42, 0x395:0x45, 0x396:0x5A, 0x397:0x48, 0x399:0x49, 0x39A:0x4B, 0x39C:0x4D, 0x39D:0x4E, 0x39F:0x4F, 0x3A1:0x50, 0x3A4:0x54, 0x3A7:0x58, 0x29F8:[0x002F,"bold-italic"], 0x2016:0x2225, 0x219A:"\u2190\u0338", 0x219B:"\u2192\u0338", 0x21AE:"\u2194\u0338", 0x21CD:"\u21D0\u0338", 0x21CE:"\u21D4\u0338", 0x21CF:"\u21D2\u0338", 0x2204:"\u2203\u0338", 0x2224:"\u2223\u0338", 0x2226:"\u2225\u0338", 0x2241:"\u223C\u0338", 0x2247:"\u2245\u0338", 0x226E:"<\u0338", 0x226F:">\u0338", 0x2270:"\u2264\u0338", 0x2271:"\u2265\u0338", 0x2280:"\u227A\u0338", 0x2281:"\u227B\u0338", 0x2288:"\u2286\u0338", 0x2289:"\u2287\u0338", 0x22AC:"\u22A2\u0338", 0x22AD:"\u22A8\u0338", // 0x22AE:"\u22A9\u0338", 0x22AF:"\u22AB\u0338", 0x22E0:"\u227C\u0338", 0x22E1:"\u227D\u0338"//, // 0x22EA:"\u22B2\u0338", 0x22EB:"\u22B3\u0338", // 0x22EC:"\u22B4\u0338", 0x22ED:"\u22B5\u0338" }}, "italic": {fonts:[ITALIC,"MathJax_Main-italic",MAIN,SIZE1,AMS], italic:true, remap: {0x391:0x41, 0x392:0x42, 0x395:0x45, 0x396:0x5A, 0x397:0x48, 0x399:0x49, 0x39A:0x4B, 0x39C:0x4D, 0x39D:0x4E, 0x39F:0x4F, 0x3A1:0x50, 0x3A4:0x54, 0x3A7:0x58}}, "bold-italic": {fonts:["MathJax_Math-bold-italic",BOLD,SIZE1,AMS], bold:true, italic:true, remap: {0x391:0x41, 0x392:0x42, 0x395:0x45, 0x396:0x5A, 0x397:0x48, 0x399:0x49, 0x39A:0x4B, 0x39C:0x4D, 0x39D:0x4E, 0x39F:0x4F, 0x3A1:0x50, 0x3A4:0x54, 0x3A7:0x58}}, "double-struck": {fonts:[AMS, MAIN]}, "fraktur": {fonts:["MathJax_Fraktur",MAIN,SIZE1,AMS]}, "bold-fraktur": {fonts:["MathJax_Fraktur-bold",BOLD,SIZE1,AMS], bold:true}, "script": {fonts:["MathJax_Script",MAIN,SIZE1,AMS]}, "bold-script": {fonts:["MathJax_Script",BOLD,SIZE1,AMS], bold:true}, "sans-serif": {fonts:["MathJax_SansSerif",MAIN,SIZE1,AMS]}, "bold-sans-serif": {fonts:["MathJax_SansSerif-bold",BOLD,SIZE1,AMS], bold:true}, "sans-serif-italic": {fonts:["MathJax_SansSerif-italic","MathJax_Main-italic",SIZE1,AMS], italic:true}, "sans-serif-bold-italic": {fonts:["MathJax_SansSerif-italic","MathJax_Main-italic",SIZE1,AMS], bold:true, italic:true}, "monospace": {fonts:["MathJax_Typewriter",MAIN,SIZE1,AMS]}, "-tex-caligraphic": {fonts:["MathJax_Caligraphic",MAIN], offsetA: 0x41, variantA: "italic"}, "-tex-oldstyle": {fonts:["MathJax_Caligraphic",MAIN]}, "-tex-mathit": {fonts:["MathJax_Main-italic",ITALIC,MAIN,SIZE1,AMS], italic:true, noIC: true, remap: {0x391:0x41, 0x392:0x42, 0x395:0x45, 0x396:0x5A, 0x397:0x48, 0x399:0x49, 0x39A:0x4B, 0x39C:0x4D, 0x39D:0x4E, 0x39F:0x4F, 0x3A1:0x50, 0x3A4:0x54, 0x3A7:0x58}}, "-TeX-variant": {fonts:[AMS,MAIN,SIZE1], // HACK: to get larger prime for \prime remap: { 0x2268: 0xE00C, 0x2269: 0xE00D, 0x2270: 0xE011, 0x2271: 0xE00E, 0x2A87: 0xE010, 0x2A88: 0xE00F, 0x2224: 0xE006, 0x2226: 0xE007, 0x2288: 0xE016, 0x2289: 0xE018, 0x228A: 0xE01A, 0x228B: 0xE01B, 0x2ACB: 0xE017, 0x2ACC: 0xE019, 0x03DC: 0xE008, 0x03F0: 0xE009, 0x2216:[0x2216,MML.VARIANT.NORMAL], // \setminus 0x210F:[0x210F,MML.VARIANT.NORMAL] // \hslash }}, "-largeOp": {fonts:[SIZE2,SIZE1,MAIN]}, "-smallOp": {fonts:[SIZE1,MAIN]}, "-tex-caligraphic-bold": {fonts:["MathJax_Caligraphic-bold","MathJax_Main-bold","MathJax_Main","MathJax_Math","MathJax_Size1"], bold:true, offsetA: 0x41, variantA: "bold-italic"}, "-tex-oldstyle-bold": {fonts:["MathJax_Caligraphic-bold","MathJax_Main-bold","MathJax_Main","MathJax_Math","MathJax_Size1"], bold:true} }, RANGES: [ {name: "alpha", low: 0x61, high: 0x7A, offset: "A", add: 32}, {name: "number", low: 0x30, high: 0x39, offset: "N"}, {name: "greek", low: 0x03B1, high: 0x03F6, offset: "G"} ], RULECHAR: 0x2212, REMAP: { 0xA: 0x20, // newline 0x00A0: 0x20, // non-breaking space 0x203E: 0x2C9, // overline 0x20D0: 0x21BC, 0x20D1: 0x21C0, // combining left and right harpoons 0x20D6: 0x2190, 0x20E1: 0x2194, // combining left arrow and lef-right arrow 0x20EC: 0x21C1, 0x20ED: 0x21BD, // combining low right and left harpoons 0x20EE: 0x2190, 0x20EF: 0x2192, // combining low left and right arrows 0x20F0: 0x2A, // combining asterisk 0xFE37: 0x23DE, 0xFE38: 0x23DF, // OverBrace, UnderBrace 0xB7: 0x22C5, // center dot 0x2B9: 0x2032, // prime, 0x3D2: 0x3A5, // Upsilon 0x2206: 0x394, // increment 0x2015: 0x2014, 0x2017: 0x5F, // horizontal bars 0x2022: 0x2219, 0x2044: 0x2F, // bullet, fraction slash 0x2305: 0x22BC, 0x2306: 0x2A5E, // barwedge, doublebarwedge 0x25AA: 0x25A0, 0x25B4: 0x25B2, // blacksquare, blacktriangle 0x25B5: 0x25B3, 0x25B8: 0x25B6, // triangle, blacktriangleright 0x25BE: 0x25BC, 0x25BF: 0x25BD, // blacktriangledown, triangledown 0x25C2: 0x25C0, // blacktriangleleft 0x2329: 0x27E8, 0x232A: 0x27E9, // langle, rangle 0x3008: 0x27E8, 0x3009: 0x27E9, // langle, rangle 0x2758: 0x2223, // VerticalSeparator 0x2A2F: 0xD7, // cross product 0x25FB: 0x25A1, 0x25FC: 0x25A0, // square, blacksquare // // Letter-like symbols (that appear elsewhere) // 0x2102: [0x0043,MML.VARIANT.DOUBLESTRUCK], // 0x210A: [0x0067,MML.VARIANT.SCRIPT], 0x210B: [0x0048,MML.VARIANT.SCRIPT], 0x210C: [0x0048,MML.VARIANT.FRAKTUR], 0x210D: [0x0048,MML.VARIANT.DOUBLESTRUCK], 0x210E: [0x0068,MML.VARIANT.ITALIC], 0x2110: [0x004A,MML.VARIANT.SCRIPT], 0x2111: [0x0049,MML.VARIANT.FRAKTUR], 0x2112: [0x004C,MML.VARIANT.SCRIPT], 0x2115: [0x004E,MML.VARIANT.DOUBLESTRUCK], 0x2119: [0x0050,MML.VARIANT.DOUBLESTRUCK], 0x211A: [0x0051,MML.VARIANT.DOUBLESTRUCK], 0x211B: [0x0052,MML.VARIANT.SCRIPT], 0x211C: [0x0052,MML.VARIANT.FRAKTUR], 0x211D: [0x0052,MML.VARIANT.DOUBLESTRUCK], 0x2124: [0x005A,MML.VARIANT.DOUBLESTRUCK], 0x2126: [0x03A9,MML.VARIANT.NORMAL], 0x2128: [0x005A,MML.VARIANT.FRAKTUR], 0x212C: [0x0042,MML.VARIANT.SCRIPT], 0x212D: [0x0043,MML.VARIANT.FRAKTUR], // 0x212F: [0x0065,MML.VARIANT.SCRIPT], 0x2130: [0x0045,MML.VARIANT.SCRIPT], 0x2131: [0x0046,MML.VARIANT.SCRIPT], 0x2133: [0x004D,MML.VARIANT.SCRIPT], // 0x2134: [0x006F,MML.VARIANT.SCRIPT], 0x2247: 0x2246, // wrong placement of this character 0x231C: 0x250C, 0x231D:0x2510, // wrong placement of \ulcorner, \urcorner 0x231E: 0x2514, 0x231F:0x2518, // wrong placement of \llcorner, \lrcorner // // compound symbols not in these fonts // 0x2204: "\u2203\u0338", // \not\exists 0x220C: "\u220B\u0338", // \not\ni 0x2244: "\u2243\u0338", // \not\simeq 0x2249: "\u2248\u0338", // \not\approx 0x2262: "\u2261\u0338", // \not\equiv 0x226D: "\u224D\u0338", // \not\asymp 0x2274: "\u2272\u0338", // \not\lesssim 0x2275: "\u2273\u0338", // \not\gtrsim 0x2278: "\u2276\u0338", // \not\lessgtr 0x2279: "\u2277\u0338", // \not\gtrless 0x2284: "\u2282\u0338", // \not\subset 0x2285: "\u2283\u0338", // \not\supset 0x22E2: "\u2291\u0338", // \not\sqsubseteq 0x22E3: "\u2292\u0338", // \not\sqsupseteq 0x2A0C: "\u222C\u222C", // quadruple integral 0x2033: "\u2032\u2032", // double prime 0x2034: "\u2032\u2032\u2032", // triple prime 0x2036: "\u2035\u2035", // double back prime 0x2037: "\u2035\u2035\u2035", // trile back prime 0x2057: "\u2032\u2032\u2032\u2032", // quadruple prime 0x20DB: "...", // combining three dots above (only works with mover/under) 0x20DC: "...." // combining four dots above (only works with mover/under) }, REMAPACCENT: { "\u2192":"\u20D7", "\u2032":"'", "\u2035":"`" }, REMAPACCENTUNDER: { }, PLANE1MAP: [ [0x1D400,0x1D419, 0x41, MML.VARIANT.BOLD], [0x1D41A,0x1D433, 0x61, MML.VARIANT.BOLD], [0x1D434,0x1D44D, 0x41, MML.VARIANT.ITALIC], [0x1D44E,0x1D467, 0x61, MML.VARIANT.ITALIC], [0x1D468,0x1D481, 0x41, MML.VARIANT.BOLDITALIC], [0x1D482,0x1D49B, 0x61, MML.VARIANT.BOLDITALIC], [0x1D49C,0x1D4B5, 0x41, MML.VARIANT.SCRIPT], // [0x1D4B6,0x1D4CF, 0x61, MML.VARIANT.SCRIPT], // [0x1D4D0,0x1D4E9, 0x41, MML.VARIANT.BOLDSCRIPT], // [0x1D4EA,0x1D503, 0x61, MML.VARIANT.BOLDSCRIPT], [0x1D504,0x1D51D, 0x41, MML.VARIANT.FRAKTUR], [0x1D51E,0x1D537, 0x61, MML.VARIANT.FRAKTUR], [0x1D538,0x1D551, 0x41, MML.VARIANT.DOUBLESTRUCK], // [0x1D552,0x1D56B, 0x61, MML.VARIANT.DOUBLESTRUCK], [0x1D56C,0x1D585, 0x41, MML.VARIANT.BOLDFRAKTUR], [0x1D586,0x1D59F, 0x61, MML.VARIANT.BOLDFRAKTUR], [0x1D5A0,0x1D5B9, 0x41, MML.VARIANT.SANSSERIF], [0x1D5BA,0x1D5D3, 0x61, MML.VARIANT.SANSSERIF], [0x1D5D4,0x1D5ED, 0x41, MML.VARIANT.BOLDSANSSERIF], [0x1D5EE,0x1D607, 0x61, MML.VARIANT.BOLDSANSSERIF], [0x1D608,0x1D621, 0x41, MML.VARIANT.SANSSERIFITALIC], [0x1D622,0x1D63B, 0x61, MML.VARIANT.SANSSERIFITALIC], // [0x1D63C,0x1D655, 0x41, MML.VARIANT.SANSSERIFBOLDITALIC], // [0x1D656,0x1D66F, 0x61, MML.VARIANT.SANSSERIFBOLDITALIC], [0x1D670,0x1D689, 0x41, MML.VARIANT.MONOSPACE], [0x1D68A,0x1D6A3, 0x61, MML.VARIANT.MONOSPACE], [0x1D6A8,0x1D6C1, 0x391, MML.VARIANT.BOLD], // [0x1D6C2,0x1D6E1, 0x3B1, MML.VARIANT.BOLD], [0x1D6E2,0x1D6FA, 0x391, MML.VARIANT.ITALIC], [0x1D6FC,0x1D71B, 0x3B1, MML.VARIANT.ITALIC], [0x1D71C,0x1D734, 0x391, MML.VARIANT.BOLDITALIC], [0x1D736,0x1D755, 0x3B1, MML.VARIANT.BOLDITALIC], [0x1D756,0x1D76E, 0x391, MML.VARIANT.BOLDSANSSERIF], // [0x1D770,0x1D78F, 0x3B1, MML.VARIANT.BOLDSANSSERIF], [0x1D790,0x1D7A8, 0x391, MML.VARIANT.SANSSERIFBOLDITALIC], // [0x1D7AA,0x1D7C9, 0x3B1, MML.VARIANT.SANSSERIFBOLDITALIC], [0x1D7CE,0x1D7D7, 0x30, MML.VARIANT.BOLD], // [0x1D7D8,0x1D7E1, 0x30, MML.VARIANT.DOUBLESTRUCK], [0x1D7E2,0x1D7EB, 0x30, MML.VARIANT.SANSSERIF], [0x1D7EC,0x1D7F5, 0x30, MML.VARIANT.BOLDSANSSERIF], [0x1D7F6,0x1D7FF, 0x30, MML.VARIANT.MONOSPACE] ], REMAPGREEK: { 0x391: 0x41, 0x392: 0x42, 0x395: 0x45, 0x396: 0x5A, 0x397: 0x48, 0x399: 0x49, 0x39A: 0x4B, 0x39C: 0x4D, 0x39D: 0x4E, 0x39F: 0x4F, 0x3A1: 0x50, 0x3A2: 0x398, 0x3A4: 0x54, 0x3A7: 0x58, 0x3AA: 0x2207, 0x3CA: 0x2202, 0x3CB: 0x3F5, 0x3CC: 0x3D1, 0x3CD: 0x3F0, 0x3CE: 0x3D5, 0x3CF: 0x3F1, 0x3D0: 0x3D6 }, RemapPlane1: function (n,variant) { for (var i = 0, m = this.PLANE1MAP.length; i < m; i++) { if (n < this.PLANE1MAP[i][0]) break; if (n <= this.PLANE1MAP[i][1]) { n = n - this.PLANE1MAP[i][0] + this.PLANE1MAP[i][2]; if (this.REMAPGREEK[n]) {n = this.REMAPGREEK[n]} variant = this.VARIANT[this.PLANE1MAP[i][3]]; break; } } return {n: n, variant: variant}; }, DELIMITERS: { 0x0028: // ( { dir: V, HW: STDHW, stretch: {top: [0x239B,SIZE4], ext: [0x239C,SIZE4], bot: [0x239D,SIZE4]} }, 0x0029: // ) { dir: V, HW: STDHW, stretch: {top:[0x239E,SIZE4], ext:[0x239F,SIZE4], bot:[0x23A0,SIZE4]} }, 0x002F: // / { dir: V, HW: STDHW }, 0x005B: // [ { dir: V, HW: STDHW, stretch: {top:[0x23A1,SIZE4], ext:[0x23A2,SIZE4], bot:[0x23A3,SIZE4]} }, 0x005C: // \ { dir: V, HW: STDHW }, 0x005D: // ] { dir: V, HW: STDHW, stretch: {top:[0x23A4,SIZE4], ext:[0x23A5,SIZE4], bot:[0x23A6,SIZE4]} }, 0x007B: // { { dir: V, HW: STDHW, stretch: {top:[0x23A7,SIZE4], mid:[0x23A8,SIZE4], bot:[0x23A9,SIZE4], ext:[0x23AA,SIZE4]} }, 0x007C: // | { dir: V, HW: [[1000,MAIN]], stretch: {ext:[0x2223,MAIN]} }, 0x007D: // } { dir: V, HW: STDHW, stretch: {top: [0x23AB,SIZE4], mid:[0x23AC,SIZE4], bot: [0x23AD,SIZE4], ext: [0x23AA,SIZE4]} }, 0x00AF: // macron { dir: H, HW: [[.59,MAIN]], stretch: {rep:[0xAF,MAIN]} }, 0x02C6: // wide hat { dir: H, HW: [[267+250,MAIN],[567+250,SIZE1],[1005+330,SIZE2],[1447+330,SIZE3],[1909,SIZE4]] }, 0x02DC: // wide tilde { dir: H, HW: [[333+250,MAIN],[555+250,SIZE1],[1000+330,SIZE2],[1443+330,SIZE3],[1887,SIZE4]] }, 0x2013: // en-dash { dir: H, HW: [[500,MAIN]], stretch: {rep:[0x2013,MAIN]} }, 0x2016: // vertical arrow extension { dir: V, HW: [[602,SIZE1],[1000,MAIN,null,0x2225]], stretch: {ext:[0x2225,MAIN]} }, 0x2190: // left arrow { dir: H, HW: [[1000,MAIN]], stretch: {left:[0x2190,MAIN], rep:ARROWREP, fuzz:300} }, 0x2191: // \uparrow { dir: V, HW: [[888,MAIN]], stretch: {top:[0x2191,SIZE1], ext:[0x23D0,SIZE1]} }, 0x2192: // right arrow { dir: H, HW: [[1000,MAIN]], stretch: {rep:ARROWREP, right:[0x2192,MAIN], fuzz:300} }, 0x2193: // \downarrow { dir: V, HW: [[888,MAIN]], stretch: {ext:[0x23D0,SIZE1], bot:[0x2193,SIZE1]} }, 0x2194: // left-right arrow { dir: H, HW: [[1000,MAIN]], stretch: {left:[0x2190,MAIN], rep:ARROWREP, right:[0x2192,MAIN], fuzz:300} }, 0x2195: // \updownarrow { dir: V, HW: [[1044,MAIN]], stretch: {top:[0x2191,SIZE1], ext:[0x23D0,SIZE1], bot:[0x2193,SIZE1]} }, 0x21D0: // left double arrow { dir: H, HW: [[1000,MAIN]], stretch: {left:[0x21D0,MAIN], rep:DARROWREP, fuzz:300} }, 0x21D1: // \Uparrow { dir: V, HW: [[888,MAIN]], stretch: {top:[0x21D1,SIZE1], ext:[0x2016,SIZE1]} }, 0x21D2: // right double arrow { dir: H, HW: [[1000,MAIN]], stretch: {rep:DARROWREP, right:[0x21D2,MAIN], fuzz:300} }, 0x21D3: // \Downarrow { dir: V, HW: [[888,MAIN]], stretch: {ext:[0x2016,SIZE1], bot:[0x21D3,SIZE1]} }, 0x21D4: // left-right double arrow { dir: H, HW: [[1000,MAIN]], stretch: {left:[0x21D0,MAIN], rep:DARROWREP, right:[0x21D2,MAIN], fuzz:300} }, 0x21D5: // \Updownarrow { dir: V, HW: [[1044,MAIN]], stretch: {top:[0x21D1,SIZE1], ext:[0x2016,SIZE1], bot:[0x21D3,SIZE1]} }, 0x2212: // horizontal line { dir: H, HW: [[.5,MAIN,0,0x2013]], stretch: {rep:ARROWREP, fuzz:300} }, 0x221A: // \surd { dir: V, HW: STDHW, stretch: {top:[0xE001,SIZE4], ext:[0xE000,SIZE4], bot:[0x23B7,SIZE4], fullExtenders:true} }, 0x2223: // \vert { dir: V, HW: [[1000,MAIN]], stretch: {ext:[0x2223,MAIN]} }, 0x2225: // \Vert { dir: V, HW: [[1000,MAIN]], stretch: {ext:[0x2225,MAIN]} }, 0x2308: // \lceil { dir: V, HW: STDHW, stretch: {top:[0x23A1,SIZE4], ext:[0x23A2,SIZE4]} }, 0x2309: // \rceil { dir: V, HW: STDHW, stretch: {top:[0x23A4,SIZE4], ext:[0x23A5,SIZE4]} }, 0x230A: // \lfloor { dir: V, HW: STDHW, stretch: {ext:[0x23A2,SIZE4], bot:[0x23A3,SIZE4]} }, 0x230B: // \rfloor { dir: V, HW: STDHW, stretch: {ext:[0x23A5,SIZE4], bot:[0x23A6,SIZE4]} }, 0x23AA: // \bracevert { dir: V, HW: [[320,SIZE4]], stretch: {top:[0x23AA,SIZE4], ext:[0x23AA,SIZE4], bot:[0x23AA,SIZE4]} }, 0x23B0: // \lmoustache { dir: V, HW: [[989,MAIN]], stretch: {top:[0x23A7,SIZE4], ext:[0x23AA,SIZE4], bot:[0x23AD,SIZE4]} }, 0x23B1: // \rmoustache { dir: V, HW: [[989,MAIN]], stretch: {top:[0x23AB,SIZE4], ext:[0x23AA,SIZE4], bot:[0x23A9,SIZE4]} }, 0x23D0: // vertical line extension { dir: V, HW: [[602,SIZE1],[1000,MAIN,null,0x2223]], stretch: {ext:[0x2223,MAIN]} }, 0x23DE: // horizontal brace down { dir: H, HW: [], stretch: {min:.9, left:[0xE150,SIZE4], mid:[[0xE153,0xE152],SIZE4], right:[0xE151,SIZE4], rep:[0xE154,SIZE4]} }, 0x23DF: // horizontal brace up { dir: H, HW: [], stretch: {min:.9, left:[0xE152,SIZE4], mid:[[0xE151,0xE150],SIZE4], right:[0xE153,SIZE4], rep:[0xE154,SIZE4]} }, 0x27E8: // \langle { dir: V, HW: STDHW }, 0x27E9: // \rangle { dir: V, HW: STDHW }, 0x27EE: // \lgroup { dir: V, HW: [[989,MAIN]], stretch: {top:[0x23A7,SIZE4], ext:[0x23AA,SIZE4], bot:[0x23A9,SIZE4]} }, 0x27EF: // \rgroup { dir: V, HW: [[989,MAIN]], stretch: {top:[0x23AB,SIZE4], ext:[0x23AA,SIZE4], bot:[0x23AD,SIZE4]} }, 0x002D: {alias: 0x2212, dir:H}, // minus 0x005E: {alias: 0x02C6, dir:H}, // wide hat 0x005F: {alias: 0x2013, dir:H}, // low line 0x007E: {alias: 0x02DC, dir:H}, // wide tilde 0x02C9: {alias: 0x00AF, dir:H}, // macron 0x0302: {alias: 0x02C6, dir:H}, // wide hat 0x0303: {alias: 0x02DC, dir:H}, // wide tilde 0x030C: {alias: 0x02C7, dir:H}, // wide caron 0x0332: {alias: 0x2013, dir:H}, // combining low line 0x2014: {alias: 0x2013, dir:H}, // em-dash 0x2015: {alias: 0x2013, dir:H}, // horizontal line 0x2017: {alias: 0x2013, dir:H}, // horizontal line 0x203E: {alias: 0x00AF, dir:H}, // over line 0x20D7: {alias: 0x2192, dir:H}, // combining over right arrow (vector arrow) 0x2215: {alias: 0x002F, dir:V}, // division slash 0x2329: {alias: 0x27E8, dir:V}, // langle 0x232A: {alias: 0x27E9, dir:V}, // rangle 0x23AF: {alias: 0x2013, dir:H}, // horizontal line extension 0x2500: {alias: 0x2013, dir:H}, // horizontal line 0x2758: {alias: 0x2223, dir:V}, // vertical separator 0x3008: {alias: 0x27E8, dir:V}, // langle 0x3009: {alias: 0x27E9, dir:V}, // rangle 0xFE37: {alias: 0x23DE, dir:H}, // horizontal brace down 0xFE38: {alias: 0x23DF, dir:H}, // horizontal brace up 0x003D: EXTRAH, // equal sign 0x219E: EXTRAH, // left two-headed arrow 0x21A0: EXTRAH, // right two-headed arrow 0x21A4: EXTRAH, // left arrow from bar 0x21A5: EXTRAV, // up arrow from bar 0x21A6: EXTRAH, // right arrow from bar 0x21A7: EXTRAV, // down arrow from bar 0x21B0: EXTRAV, // up arrow with top leftwards 0x21B1: EXTRAV, // up arrow with top right 0x21BC: EXTRAH, // left harpoon with barb up 0x21BD: EXTRAH, // left harpoon with barb down 0x21BE: EXTRAV, // up harpoon with barb right 0x21BF: EXTRAV, // up harpoon with barb left 0x21C0: EXTRAH, // right harpoon with barb up 0x21C1: EXTRAH, // right harpoon with barb down 0x21C2: EXTRAV, // down harpoon with barb right 0x21C3: EXTRAV, // down harpoon with barb left 0x21DA: EXTRAH, // left triple arrow 0x21DB: EXTRAH, // right triple arrow 0x23B4: EXTRAH, // top square bracket 0x23B5: EXTRAH, // bottom square bracket 0x23DC: EXTRAH, // top paren 0x23DD: EXTRAH, // bottom paren 0x23E0: EXTRAH, // top tortoise shell 0x23E1: EXTRAH, // bottom tortoise shell 0x2906: EXTRAH, // leftwards double arrow from bar 0x2907: EXTRAH, // rightwards double arrow from bar 0x294E: EXTRAH, // left barb up right barb up harpoon 0x294F: EXTRAV, // up barb right down barb right harpoon 0x2950: EXTRAH, // left barb dow right barb down harpoon 0x2951: EXTRAV, // up barb left down barb left harpoon 0x295A: EXTRAH, // leftwards harpoon with barb up from bar 0x295B: EXTRAH, // rightwards harpoon with barb up from bar 0x295C: EXTRAV, // up harpoon with barb right from bar 0x295D: EXTRAV, // down harpoon with barb right from bar 0x295E: EXTRAH, // leftwards harpoon with barb down from bar 0x295F: EXTRAH, // rightwards harpoon with barb down from bar 0x2960: EXTRAV, // up harpoon with barb left from bar 0x2961: EXTRAV, // down harpoon with barb left from bar 0x2312: {alias: 0x23DC, dir:H}, // arc 0x2322: {alias: 0x23DC, dir:H}, // frown 0x2323: {alias: 0x23DD, dir:H}, // smile 0x27F5: {alias: 0x2190, dir:H}, // long left arrow 0x27F6: {alias: 0x2192, dir:H}, // long right arrow 0x27F7: {alias: 0x2194, dir:H}, // long left-right arrow 0x27F8: {alias: 0x21D0, dir:H}, // long left double arrow 0x27F9: {alias: 0x21D2, dir:H}, // long right double arrow 0x27FA: {alias: 0x21D4, dir:H}, // long left-right double arrow 0x27FB: {alias: 0x21A4, dir:H}, // long left arrow from bar 0x27FC: {alias: 0x21A6, dir:H}, // long right arrow from bar 0x27FD: {alias: 0x2906, dir:H}, // long left double arrow from bar 0x27FE: {alias: 0x2907, dir:H} // long right double arrow from bar } } }); SVG.FONTDATA.FONTS['MathJax_Main'] = { directory: 'Main/Regular', family: 'MathJax_Main', id: 'MJMAIN', skew: { 0x131: 0.0278, 0x237: 0.0833, 0x2113: 0.111, 0x2118: 0.111, 0x2202: 0.0833 }, Ranges: [ [0x20,0x7F,"BasicLatin"], [0x100,0x17F,"LatinExtendedA"], [0x180,0x24F,"LatinExtendedB"], [0x2B0,0x2FF,"SpacingModLetters"], [0x300,0x36F,"CombDiacritMarks"], [0x370,0x3FF,"GreekAndCoptic"], [0x2100,0x214F,"LetterlikeSymbols"], [0x25A0,0x25FF,"GeometricShapes"], [0x2600,0x26FF,"MiscSymbols"], [0x2A00,0x2AFF,"SuppMathOperators"] ], // SPACE 0x20: [0,0,250,0,0,''], // LEFT PARENTHESIS 0x28: [750,250,389,94,333,'94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250'], // RIGHT PARENTHESIS 0x29: [750,250,389,55,294,'60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749'], // PLUS SIGN 0x2B: [583,82,778,56,722,'56 237T56 250T70 270H369V420L370 570Q380 583 389 583Q402 583 409 568V270H707Q722 262 722 250T707 230H409V-68Q401 -82 391 -82H389H387Q375 -82 369 -68V230H70Q56 237 56 250'], // COMMA 0x2C: [121,195,278,78,210,'78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17'], // FULL STOP 0x2E: [120,0,278,78,199,'78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60'], // SOLIDUS 0x2F: [750,250,500,56,444,'423 750Q432 750 438 744T444 730Q444 725 271 248T92 -240Q85 -250 75 -250Q68 -250 62 -245T56 -231Q56 -221 230 257T407 740Q411 750 423 750'], // DIGIT ZERO 0x30: [666,22,500,39,460,'96 585Q152 666 249 666Q297 666 345 640T423 548Q460 465 460 320Q460 165 417 83Q397 41 362 16T301 -15T250 -22Q224 -22 198 -16T137 16T82 83Q39 165 39 320Q39 494 96 585ZM321 597Q291 629 250 629Q208 629 178 597Q153 571 145 525T137 333Q137 175 145 125T181 46Q209 16 250 16Q290 16 318 46Q347 76 354 130T362 333Q362 478 354 524T321 597'], // DIGIT ONE 0x31: [666,0,500,83,427,'213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578'], // DIGIT TWO 0x32: [666,0,500,50,449,'109 429Q82 429 66 447T50 491Q50 562 103 614T235 666Q326 666 387 610T449 465Q449 422 429 383T381 315T301 241Q265 210 201 149L142 93L218 92Q375 92 385 97Q392 99 409 186V189H449V186Q448 183 436 95T421 3V0H50V19V31Q50 38 56 46T86 81Q115 113 136 137Q145 147 170 174T204 211T233 244T261 278T284 308T305 340T320 369T333 401T340 431T343 464Q343 527 309 573T212 619Q179 619 154 602T119 569T109 550Q109 549 114 549Q132 549 151 535T170 489Q170 464 154 447T109 429'], // DIGIT THREE 0x33: [665,22,500,42,457,'127 463Q100 463 85 480T69 524Q69 579 117 622T233 665Q268 665 277 664Q351 652 390 611T430 522Q430 470 396 421T302 350L299 348Q299 347 308 345T337 336T375 315Q457 262 457 175Q457 96 395 37T238 -22Q158 -22 100 21T42 130Q42 158 60 175T105 193Q133 193 151 175T169 130Q169 119 166 110T159 94T148 82T136 74T126 70T118 67L114 66Q165 21 238 21Q293 21 321 74Q338 107 338 175V195Q338 290 274 322Q259 328 213 329L171 330L168 332Q166 335 166 348Q166 366 174 366Q202 366 232 371Q266 376 294 413T322 525V533Q322 590 287 612Q265 626 240 626Q208 626 181 615T143 592T132 580H135Q138 579 143 578T153 573T165 566T175 555T183 540T186 520Q186 498 172 481T127 463'], // DIGIT FOUR 0x34: [677,0,500,28,471,'462 0Q444 3 333 3Q217 3 199 0H190V46H221Q241 46 248 46T265 48T279 53T286 61Q287 63 287 115V165H28V211L179 442Q332 674 334 675Q336 677 355 677H373L379 671V211H471V165H379V114Q379 73 379 66T385 54Q393 47 442 46H471V0H462ZM293 211V545L74 212L183 211H293'], // DIGIT FIVE 0x35: [666,22,500,50,449,'164 157Q164 133 148 117T109 101H102Q148 22 224 22Q294 22 326 82Q345 115 345 210Q345 313 318 349Q292 382 260 382H254Q176 382 136 314Q132 307 129 306T114 304Q97 304 95 310Q93 314 93 485V614Q93 664 98 664Q100 666 102 666Q103 666 123 658T178 642T253 634Q324 634 389 662Q397 666 402 666Q410 666 410 648V635Q328 538 205 538Q174 538 149 544L139 546V374Q158 388 169 396T205 412T256 420Q337 420 393 355T449 201Q449 109 385 44T229 -22Q148 -22 99 32T50 154Q50 178 61 192T84 210T107 214Q132 214 148 197T164 157'], // DIGIT SIX 0x36: [666,22,500,41,456,'42 313Q42 476 123 571T303 666Q372 666 402 630T432 550Q432 525 418 510T379 495Q356 495 341 509T326 548Q326 592 373 601Q351 623 311 626Q240 626 194 566Q147 500 147 364L148 360Q153 366 156 373Q197 433 263 433H267Q313 433 348 414Q372 400 396 374T435 317Q456 268 456 210V192Q456 169 451 149Q440 90 387 34T253 -22Q225 -22 199 -14T143 16T92 75T56 172T42 313ZM257 397Q227 397 205 380T171 335T154 278T148 216Q148 133 160 97T198 39Q222 21 251 21Q302 21 329 59Q342 77 347 104T352 209Q352 289 347 316T329 361Q302 397 257 397'], // DIGIT SEVEN 0x37: [676,22,500,55,485,'55 458Q56 460 72 567L88 674Q88 676 108 676H128V672Q128 662 143 655T195 646T364 644H485V605L417 512Q408 500 387 472T360 435T339 403T319 367T305 330T292 284T284 230T278 162T275 80Q275 66 275 52T274 28V19Q270 2 255 -10T221 -22Q210 -22 200 -19T179 0T168 40Q168 198 265 368Q285 400 349 489L395 552H302Q128 552 119 546Q113 543 108 522T98 479L95 458V455H55V458'], // DIGIT EIGHT 0x38: [666,22,500,43,457,'70 417T70 494T124 618T248 666Q319 666 374 624T429 515Q429 485 418 459T392 417T361 389T335 371T324 363L338 354Q352 344 366 334T382 323Q457 264 457 174Q457 95 399 37T249 -22Q159 -22 101 29T43 155Q43 263 172 335L154 348Q133 361 127 368Q70 417 70 494ZM286 386L292 390Q298 394 301 396T311 403T323 413T334 425T345 438T355 454T364 471T369 491T371 513Q371 556 342 586T275 624Q268 625 242 625Q201 625 165 599T128 534Q128 511 141 492T167 463T217 431Q224 426 228 424L286 386ZM250 21Q308 21 350 55T392 137Q392 154 387 169T375 194T353 216T330 234T301 253T274 270Q260 279 244 289T218 306L210 311Q204 311 181 294T133 239T107 157Q107 98 150 60T250 21'], // DIGIT NINE 0x39: [666,22,500,42,456,'352 287Q304 211 232 211Q154 211 104 270T44 396Q42 412 42 436V444Q42 537 111 606Q171 666 243 666Q245 666 249 666T257 665H261Q273 665 286 663T323 651T370 619T413 560Q456 472 456 334Q456 194 396 97Q361 41 312 10T208 -22Q147 -22 108 7T68 93T121 149Q143 149 158 135T173 96Q173 78 164 65T148 49T135 44L131 43Q131 41 138 37T164 27T206 22H212Q272 22 313 86Q352 142 352 280V287ZM244 248Q292 248 321 297T351 430Q351 508 343 542Q341 552 337 562T323 588T293 615T246 625Q208 625 181 598Q160 576 154 546T147 441Q147 358 152 329T172 282Q197 248 244 248'], // COLON 0x3A: [430,0,278,78,199,'78 370Q78 394 95 412T138 430Q162 430 180 414T199 371Q199 346 182 328T139 310T96 327T78 370ZM78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60'], // SEMICOLON 0x3B: [430,194,278,78,202,'78 370Q78 394 95 412T138 430Q162 430 180 414T199 371Q199 346 182 328T139 310T96 327T78 370ZM78 60Q78 85 94 103T137 121Q202 121 202 8Q202 -44 183 -94T144 -169T118 -194Q115 -194 106 -186T95 -174Q94 -171 107 -155T137 -107T160 -38Q161 -32 162 -22T165 -4T165 4Q165 5 161 4T142 0Q110 0 94 18T78 60'], // LESS-THAN SIGN 0x3C: [540,40,778,83,695,'694 -11T694 -19T688 -33T678 -40Q671 -40 524 29T234 166L90 235Q83 240 83 250Q83 261 91 266Q664 540 678 540Q681 540 687 534T694 519T687 505Q686 504 417 376L151 250L417 124Q686 -4 687 -5Q694 -11 694 -19'], // EQUALS SIGN 0x3D: [367,-133,778,56,722,'56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153'], // GREATER-THAN SIGN 0x3E: [540,40,778,82,694,'84 520Q84 528 88 533T96 539L99 540Q106 540 253 471T544 334L687 265Q694 260 694 250T687 235Q685 233 395 96L107 -40H101Q83 -38 83 -20Q83 -19 83 -17Q82 -10 98 -1Q117 9 248 71Q326 108 378 132L626 250L378 368Q90 504 86 509Q84 513 84 520'], // LEFT SQUARE BRACKET 0x5B: [750,250,278,118,255,'118 -250V750H255V710H158V-210H255V-250H118'], // REVERSE SOLIDUS 0x5C: [750,250,500,56,444,'56 731Q56 740 62 745T75 750Q85 750 92 740Q96 733 270 255T444 -231Q444 -239 438 -244T424 -250Q414 -250 407 -240Q404 -236 230 242T56 731'], // RIGHT SQUARE BRACKET 0x5D: [750,250,278,22,159,'22 710V750H159V-250H22V-210H119V710H22'], // CIRCUMFLEX ACCENT 0x5E: [694,-531,500,112,387,'112 560L249 694L257 686Q387 562 387 560L361 531Q359 532 303 581L250 627L195 580Q182 569 169 557T148 538L140 532Q138 530 125 546L112 560'], // LATIN SMALL LETTER A 0x61: [448,11,500,34,493,'137 305T115 305T78 320T63 359Q63 394 97 421T218 448Q291 448 336 416T396 340Q401 326 401 309T402 194V124Q402 76 407 58T428 40Q443 40 448 56T453 109V145H493V106Q492 66 490 59Q481 29 455 12T400 -6T353 12T329 54V58L327 55Q325 52 322 49T314 40T302 29T287 17T269 6T247 -2T221 -8T190 -11Q130 -11 82 20T34 107Q34 128 41 147T68 188T116 225T194 253T304 268H318V290Q318 324 312 340Q290 411 215 411Q197 411 181 410T156 406T148 403Q170 388 170 359Q170 334 154 320ZM126 106Q126 75 150 51T209 26Q247 26 276 49T315 109Q317 116 318 175Q318 233 317 233Q309 233 296 232T251 223T193 203T147 166T126 106'], // LATIN SMALL LETTER B 0x62: [695,11,556,20,522,'307 -11Q234 -11 168 55L158 37Q156 34 153 28T147 17T143 10L138 1L118 0H98V298Q98 599 97 603Q94 622 83 628T38 637H20V660Q20 683 22 683L32 684Q42 685 61 686T98 688Q115 689 135 690T165 693T176 694H179V543Q179 391 180 391L183 394Q186 397 192 401T207 411T228 421T254 431T286 439T323 442Q401 442 461 379T522 216Q522 115 458 52T307 -11ZM182 98Q182 97 187 90T196 79T206 67T218 55T233 44T250 35T271 29T295 26Q330 26 363 46T412 113Q424 148 424 212Q424 287 412 323Q385 405 300 405Q270 405 239 390T188 347L182 339V98'], // LATIN SMALL LETTER C 0x63: [448,12,444,34,415,'370 305T349 305T313 320T297 358Q297 381 312 396Q317 401 317 402T307 404Q281 408 258 408Q209 408 178 376Q131 329 131 219Q131 137 162 90Q203 29 272 29Q313 29 338 55T374 117Q376 125 379 127T395 129H409Q415 123 415 120Q415 116 411 104T395 71T366 33T318 2T249 -11Q163 -11 99 53T34 214Q34 318 99 383T250 448T370 421T404 357Q404 334 387 320'], // LATIN SMALL LETTER D 0x64: [695,11,556,34,535,'376 495Q376 511 376 535T377 568Q377 613 367 624T316 637H298V660Q298 683 300 683L310 684Q320 685 339 686T376 688Q393 689 413 690T443 693T454 694H457V390Q457 84 458 81Q461 61 472 55T517 46H535V0Q533 0 459 -5T380 -11H373V44L365 37Q307 -11 235 -11Q158 -11 96 50T34 215Q34 315 97 378T244 442Q319 442 376 393V495ZM373 342Q328 405 260 405Q211 405 173 369Q146 341 139 305T131 211Q131 155 138 120T173 59Q203 26 251 26Q322 26 373 103V342'], // LATIN SMALL LETTER E 0x65: [448,11,444,28,415,'28 218Q28 273 48 318T98 391T163 433T229 448Q282 448 320 430T378 380T406 316T415 245Q415 238 408 231H126V216Q126 68 226 36Q246 30 270 30Q312 30 342 62Q359 79 369 104L379 128Q382 131 395 131H398Q415 131 415 121Q415 117 412 108Q393 53 349 21T250 -11Q155 -11 92 58T28 218ZM333 275Q322 403 238 411H236Q228 411 220 410T195 402T166 381T143 340T127 274V267H333V275'], // LATIN SMALL LETTER F 0x66: [705,0,306,26,372,'273 0Q255 3 146 3Q43 3 34 0H26V46H42Q70 46 91 49Q99 52 103 60Q104 62 104 224V385H33V431H104V497L105 564L107 574Q126 639 171 668T266 704Q267 704 275 704T289 705Q330 702 351 679T372 627Q372 604 358 590T321 576T284 590T270 627Q270 647 288 667H284Q280 668 273 668Q245 668 223 647T189 592Q183 572 182 497V431H293V385H185V225Q185 63 186 61T189 57T194 54T199 51T206 49T213 48T222 47T231 47T241 46T251 46H282V0H273'], // LATIN SMALL LETTER G 0x67: [453,206,500,29,485,'329 409Q373 453 429 453Q459 453 472 434T485 396Q485 382 476 371T449 360Q416 360 412 390Q410 404 415 411Q415 412 416 414V415Q388 412 363 393Q355 388 355 386Q355 385 359 381T368 369T379 351T388 325T392 292Q392 230 343 187T222 143Q172 143 123 171Q112 153 112 133Q112 98 138 81Q147 75 155 75T227 73Q311 72 335 67Q396 58 431 26Q470 -13 470 -72Q470 -139 392 -175Q332 -206 250 -206Q167 -206 107 -175Q29 -140 29 -75Q29 -39 50 -15T92 18L103 24Q67 55 67 108Q67 155 96 193Q52 237 52 292Q52 355 102 398T223 442Q274 442 318 416L329 409ZM299 343Q294 371 273 387T221 404Q192 404 171 388T145 343Q142 326 142 292Q142 248 149 227T179 192Q196 182 222 182Q244 182 260 189T283 207T294 227T299 242Q302 258 302 292T299 343ZM403 -75Q403 -50 389 -34T348 -11T299 -2T245 0H218Q151 0 138 -6Q118 -15 107 -34T95 -74Q95 -84 101 -97T122 -127T170 -155T250 -167Q319 -167 361 -139T403 -75'], // LATIN SMALL LETTER H 0x68: [695,0,556,25,542,'41 46H55Q94 46 102 60V68Q102 77 102 91T102 124T102 167T103 217T103 272T103 329Q103 366 103 407T103 482T102 542T102 586T102 603Q99 622 88 628T43 637H25V660Q25 683 27 683L37 684Q47 685 66 686T103 688Q120 689 140 690T170 693T181 694H184V367Q244 442 328 442Q451 442 463 329Q464 322 464 190V104Q464 66 466 59T477 49Q498 46 526 46H542V0H534L510 1Q487 2 460 2T422 3Q319 3 310 0H302V46H318Q379 46 379 62Q380 64 380 200Q379 335 378 343Q372 371 358 385T334 402T308 404Q263 404 229 370Q202 343 195 315T187 232V168V108Q187 78 188 68T191 55T200 49Q221 46 249 46H265V0H257L234 1Q210 2 183 2T145 3Q42 3 33 0H25V46H41'], // LATIN SMALL LETTER I 0x69: [669,0,278,26,255,'69 609Q69 637 87 653T131 669Q154 667 171 652T188 609Q188 579 171 564T129 549Q104 549 87 564T69 609ZM247 0Q232 3 143 3Q132 3 106 3T56 1L34 0H26V46H42Q70 46 91 49Q100 53 102 60T104 102V205V293Q104 345 102 359T88 378Q74 385 41 385H30V408Q30 431 32 431L42 432Q52 433 70 434T106 436Q123 437 142 438T171 441T182 442H185V62Q190 52 197 50T232 46H255V0H247'], // LATIN SMALL LETTER J 0x6A: [669,205,306,-55,218,'98 609Q98 637 116 653T160 669Q183 667 200 652T217 609Q217 579 200 564T158 549Q133 549 116 564T98 609ZM28 -163Q58 -168 64 -168Q124 -168 135 -77Q137 -65 137 141T136 353Q132 371 120 377T72 385H52V408Q52 431 54 431L58 432Q62 432 70 432T87 433T108 434T133 436Q151 437 171 438T202 441T214 442H218V184Q217 -36 217 -59T211 -98Q195 -145 153 -175T58 -205Q9 -205 -23 -179T-55 -117Q-55 -94 -40 -79T-2 -64T36 -79T52 -118Q52 -143 28 -163'], // LATIN SMALL LETTER K 0x6B: [695,0,528,20,511,'36 46H50Q89 46 97 60V68Q97 77 97 91T97 124T98 167T98 217T98 272T98 329Q98 366 98 407T98 482T98 542T97 586T97 603Q94 622 83 628T38 637H20V660Q20 683 22 683L32 684Q42 685 61 686T98 688Q115 689 135 690T165 693T176 694H179V463L180 233L240 287Q300 341 304 347Q310 356 310 364Q310 383 289 385H284V431H293Q308 428 412 428Q475 428 484 431H489V385H476Q407 380 360 341Q286 278 286 274Q286 273 349 181T420 79Q434 60 451 53T500 46H511V0H505Q496 3 418 3Q322 3 307 0H299V46H306Q330 48 330 65Q330 72 326 79Q323 84 276 153T228 222L176 176V120V84Q176 65 178 59T189 49Q210 46 238 46H254V0H246Q231 3 137 3T28 0H20V46H36'], // LATIN SMALL LETTER L 0x6C: [695,0,278,26,263,'42 46H56Q95 46 103 60V68Q103 77 103 91T103 124T104 167T104 217T104 272T104 329Q104 366 104 407T104 482T104 542T103 586T103 603Q100 622 89 628T44 637H26V660Q26 683 28 683L38 684Q48 685 67 686T104 688Q121 689 141 690T171 693T182 694H185V379Q185 62 186 60Q190 52 198 49Q219 46 247 46H263V0H255L232 1Q209 2 183 2T145 3T107 3T57 1L34 0H26V46H42'], // LATIN SMALL LETTER M 0x6D: [443,0,833,25,819,'41 46H55Q94 46 102 60V68Q102 77 102 91T102 122T103 161T103 203Q103 234 103 269T102 328V351Q99 370 88 376T43 385H25V408Q25 431 27 431L37 432Q47 433 65 434T102 436Q119 437 138 438T167 441T178 442H181V402Q181 364 182 364T187 369T199 384T218 402T247 421T285 437Q305 442 336 442Q351 442 364 440T387 434T406 426T421 417T432 406T441 395T448 384T452 374T455 366L457 361L460 365Q463 369 466 373T475 384T488 397T503 410T523 422T546 432T572 439T603 442Q729 442 740 329Q741 322 741 190V104Q741 66 743 59T754 49Q775 46 803 46H819V0H811L788 1Q764 2 737 2T699 3Q596 3 587 0H579V46H595Q656 46 656 62Q657 64 657 200Q656 335 655 343Q649 371 635 385T611 402T585 404Q540 404 506 370Q479 343 472 315T464 232V168V108Q464 78 465 68T468 55T477 49Q498 46 526 46H542V0H534L510 1Q487 2 460 2T422 3Q319 3 310 0H302V46H318Q379 46 379 62Q380 64 380 200Q379 335 378 343Q372 371 358 385T334 402T308 404Q263 404 229 370Q202 343 195 315T187 232V168V108Q187 78 188 68T191 55T200 49Q221 46 249 46H265V0H257L234 1Q210 2 183 2T145 3Q42 3 33 0H25V46H41'], // LATIN SMALL LETTER N 0x6E: [443,0,556,25,542,'41 46H55Q94 46 102 60V68Q102 77 102 91T102 122T103 161T103 203Q103 234 103 269T102 328V351Q99 370 88 376T43 385H25V408Q25 431 27 431L37 432Q47 433 65 434T102 436Q119 437 138 438T167 441T178 442H181V402Q181 364 182 364T187 369T199 384T218 402T247 421T285 437Q305 442 336 442Q450 438 463 329Q464 322 464 190V104Q464 66 466 59T477 49Q498 46 526 46H542V0H534L510 1Q487 2 460 2T422 3Q319 3 310 0H302V46H318Q379 46 379 62Q380 64 380 200Q379 335 378 343Q372 371 358 385T334 402T308 404Q263 404 229 370Q202 343 195 315T187 232V168V108Q187 78 188 68T191 55T200 49Q221 46 249 46H265V0H257L234 1Q210 2 183 2T145 3Q42 3 33 0H25V46H41'], // LATIN SMALL LETTER O 0x6F: [448,10,500,28,471,'28 214Q28 309 93 378T250 448Q340 448 405 380T471 215Q471 120 407 55T250 -10Q153 -10 91 57T28 214ZM250 30Q372 30 372 193V225V250Q372 272 371 288T364 326T348 362T317 390T268 410Q263 411 252 411Q222 411 195 399Q152 377 139 338T126 246V226Q126 130 145 91Q177 30 250 30'], // LATIN SMALL LETTER P 0x70: [443,194,556,20,522,'36 -148H50Q89 -148 97 -134V-126Q97 -119 97 -107T97 -77T98 -38T98 6T98 55T98 106Q98 140 98 177T98 243T98 296T97 335T97 351Q94 370 83 376T38 385H20V408Q20 431 22 431L32 432Q42 433 61 434T98 436Q115 437 135 438T165 441T176 442H179V416L180 390L188 397Q247 441 326 441Q407 441 464 377T522 216Q522 115 457 52T310 -11Q242 -11 190 33L182 40V-45V-101Q182 -128 184 -134T195 -145Q216 -148 244 -148H260V-194H252L228 -193Q205 -192 178 -192T140 -191Q37 -191 28 -194H20V-148H36ZM424 218Q424 292 390 347T305 402Q234 402 182 337V98Q222 26 294 26Q345 26 384 80T424 218'], // LATIN SMALL LETTER Q 0x71: [442,194,528,33,535,'33 218Q33 308 95 374T236 441H246Q330 441 381 372L387 364Q388 364 404 403L420 442H457V156Q457 -132 458 -134Q462 -142 470 -145Q491 -148 519 -148H535V-194H527L504 -193Q480 -192 453 -192T415 -191Q312 -191 303 -194H295V-148H311Q339 -148 360 -145Q369 -141 371 -135T373 -106V-41V49Q313 -11 236 -11Q154 -11 94 53T33 218ZM376 300Q346 389 278 401Q275 401 269 401T261 402Q211 400 171 350T131 214Q131 137 165 82T253 27Q296 27 328 54T376 118V300'], // LATIN SMALL LETTER R 0x72: [443,0,392,20,364,'36 46H50Q89 46 97 60V68Q97 77 97 91T98 122T98 161T98 203Q98 234 98 269T98 328L97 351Q94 370 83 376T38 385H20V408Q20 431 22 431L32 432Q42 433 60 434T96 436Q112 437 131 438T160 441T171 442H174V373Q213 441 271 441H277Q322 441 343 419T364 373Q364 352 351 337T313 322Q288 322 276 338T263 372Q263 381 265 388T270 400T273 405Q271 407 250 401Q234 393 226 386Q179 341 179 207V154Q179 141 179 127T179 101T180 81T180 66V61Q181 59 183 57T188 54T193 51T200 49T207 48T216 47T225 47T235 46T245 46H276V0H267Q249 3 140 3Q37 3 28 0H20V46H36'], // LATIN SMALL LETTER S 0x73: [448,11,394,33,359,'295 316Q295 356 268 385T190 414Q154 414 128 401Q98 382 98 349Q97 344 98 336T114 312T157 287Q175 282 201 278T245 269T277 256Q294 248 310 236T342 195T359 133Q359 71 321 31T198 -10H190Q138 -10 94 26L86 19L77 10Q71 4 65 -1L54 -11H46H42Q39 -11 33 -5V74V132Q33 153 35 157T45 162H54Q66 162 70 158T75 146T82 119T101 77Q136 26 198 26Q295 26 295 104Q295 133 277 151Q257 175 194 187T111 210Q75 227 54 256T33 318Q33 357 50 384T93 424T143 442T187 447H198Q238 447 268 432L283 424L292 431Q302 440 314 448H322H326Q329 448 335 442V310L329 304H301Q295 310 295 316'], // LATIN SMALL LETTER T 0x74: [615,10,389,18,333,'27 422Q80 426 109 478T141 600V615H181V431H316V385H181V241Q182 116 182 100T189 68Q203 29 238 29Q282 29 292 100Q293 108 293 146V181H333V146V134Q333 57 291 17Q264 -10 221 -10Q187 -10 162 2T124 33T105 68T98 100Q97 107 97 248V385H18V422H27'], // LATIN SMALL LETTER U 0x75: [443,11,556,25,542,'383 58Q327 -10 256 -10H249Q124 -10 105 89Q104 96 103 226Q102 335 102 348T96 369Q86 385 36 385H25V408Q25 431 27 431L38 432Q48 433 67 434T105 436Q122 437 142 438T172 441T184 442H187V261Q188 77 190 64Q193 49 204 40Q224 26 264 26Q290 26 311 35T343 58T363 90T375 120T379 144Q379 145 379 161T380 201T380 248V315Q380 361 370 372T320 385H302V431Q304 431 378 436T457 442H464V264Q464 84 465 81Q468 61 479 55T524 46H542V0Q540 0 467 -5T390 -11H383V58'], // LATIN SMALL LETTER V 0x76: [431,11,528,19,508,'338 431Q344 429 422 429Q479 429 503 431H508V385H497Q439 381 423 345Q421 341 356 172T288 -2Q283 -11 263 -11Q244 -11 239 -2Q99 359 98 364Q93 378 82 381T43 385H19V431H25L33 430Q41 430 53 430T79 430T104 429T122 428Q217 428 232 431H240V385H226Q187 384 184 370Q184 366 235 234L286 102L377 341V349Q377 363 367 372T349 383T335 385H331V431H338'], // LATIN SMALL LETTER W 0x77: [431,11,722,18,703,'90 368Q84 378 76 380T40 385H18V431H24L43 430Q62 430 84 429T116 428Q206 428 221 431H229V385H215Q177 383 177 368Q177 367 221 239L265 113L339 328L333 345Q323 374 316 379Q308 384 278 385H258V431H264Q270 428 348 428Q439 428 454 431H461V385H452Q404 385 404 369Q404 366 418 324T449 234T481 143L496 100L537 219Q579 341 579 347Q579 363 564 373T530 385H522V431H529Q541 428 624 428Q692 428 698 431H703V385H697Q696 385 691 385T682 384Q635 377 619 334L559 161Q546 124 528 71Q508 12 503 1T487 -11H479Q460 -11 456 -4Q455 -3 407 133L361 267Q359 263 266 -4Q261 -11 243 -11H238Q225 -11 220 -3L90 368'], // LATIN SMALL LETTER X 0x78: [431,0,528,11,516,'201 0Q189 3 102 3Q26 3 17 0H11V46H25Q48 47 67 52T96 61T121 78T139 96T160 122T180 150L226 210L168 288Q159 301 149 315T133 336T122 351T113 363T107 370T100 376T94 379T88 381T80 383Q74 383 44 385H16V431H23Q59 429 126 429Q219 429 229 431H237V385Q201 381 201 369Q201 367 211 353T239 315T268 274L272 270L297 304Q329 345 329 358Q329 364 327 369T322 376T317 380T310 384L307 385H302V431H309Q324 428 408 428Q487 428 493 431H499V385H492Q443 385 411 368Q394 360 377 341T312 257L296 236L358 151Q424 61 429 57T446 50Q464 46 499 46H516V0H510H502Q494 1 482 1T457 2T432 2T414 3Q403 3 377 3T327 1L304 0H295V46H298Q309 46 320 51T331 63Q331 65 291 120L250 175Q249 174 219 133T185 88Q181 83 181 74Q181 63 188 55T206 46Q208 46 208 23V0H201'], // LATIN SMALL LETTER Y 0x79: [431,204,528,19,508,'69 -66Q91 -66 104 -80T118 -116Q118 -134 109 -145T91 -160Q84 -163 97 -166Q104 -168 111 -168Q131 -168 148 -159T175 -138T197 -106T213 -75T225 -43L242 0L170 183Q150 233 125 297Q101 358 96 368T80 381Q79 382 78 382Q66 385 34 385H19V431H26L46 430Q65 430 88 429T122 428Q129 428 142 428T171 429T200 430T224 430L233 431H241V385H232Q183 385 185 366L286 112Q286 113 332 227L376 341V350Q376 365 366 373T348 383T334 385H331V431H337H344Q351 431 361 431T382 430T405 429T422 429Q477 429 503 431H508V385H497Q441 380 422 345Q420 343 378 235T289 9T227 -131Q180 -204 113 -204Q69 -204 44 -177T19 -116Q19 -89 35 -78T69 -66'], // LATIN SMALL LETTER Z 0x7A: [431,0,444,28,401,'42 263Q44 270 48 345T53 423V431H393Q399 425 399 415Q399 403 398 402L381 378Q364 355 331 309T265 220L134 41L182 40H206Q254 40 283 46T331 77Q352 105 359 185L361 201Q361 202 381 202H401V196Q401 195 393 103T384 6V0H209L34 1L31 3Q28 8 28 17Q28 30 29 31T160 210T294 394H236Q169 393 152 388Q127 382 113 367Q89 344 82 264V255H42V263'], // LEFT CURLY BRACKET 0x7B: [750,250,500,65,434,'434 -231Q434 -244 428 -250H410Q281 -250 230 -184Q225 -177 222 -172T217 -161T213 -148T211 -133T210 -111T209 -84T209 -47T209 0Q209 21 209 53Q208 142 204 153Q203 154 203 155Q189 191 153 211T82 231Q71 231 68 234T65 250T68 266T82 269Q116 269 152 289T203 345Q208 356 208 377T209 529V579Q209 634 215 656T244 698Q270 724 324 740Q361 748 377 749Q379 749 390 749T408 750H428Q434 744 434 732Q434 719 431 716Q429 713 415 713Q362 710 332 689T296 647Q291 634 291 499V417Q291 370 288 353T271 314Q240 271 184 255L170 250L184 245Q202 239 220 230T262 196T290 137Q291 131 291 1Q291 -134 296 -147Q306 -174 339 -192T415 -213Q429 -213 431 -216Q434 -219 434 -231'], // VERTICAL LINE 0x7C: [750,249,278,119,159,'139 -249H137Q125 -249 119 -235V251L120 737Q130 750 139 750Q152 750 159 735V-235Q151 -249 141 -249H139'], // RIGHT CURLY BRACKET 0x7D: [750,250,500,65,434,'65 731Q65 745 68 747T88 750Q171 750 216 725T279 670Q288 649 289 635T291 501Q292 362 293 357Q306 312 345 291T417 269Q428 269 431 266T434 250T431 234T417 231Q380 231 345 210T298 157Q293 143 292 121T291 -28V-79Q291 -134 285 -156T256 -198Q202 -250 89 -250Q71 -250 68 -247T65 -230Q65 -224 65 -223T66 -218T69 -214T77 -213Q91 -213 108 -210T146 -200T183 -177T207 -139Q208 -134 209 3L210 139Q223 196 280 230Q315 247 330 250Q305 257 280 270Q225 304 212 352L210 362L209 498Q208 635 207 640Q195 680 154 696T77 713Q68 713 67 716T65 731'], // DIAERESIS 0xA8: [669,-554,500,95,405,'95 612Q95 633 112 651T153 669T193 652T210 612Q210 588 194 571T152 554L127 560Q95 577 95 612ZM289 611Q289 634 304 649T335 668Q336 668 340 668T346 669Q369 669 386 652T404 612T387 572T346 554Q323 554 306 570T289 611'], // NOT SIGN 0xAC: [356,-89,667,56,611,'56 323T56 336T70 356H596Q603 353 611 343V102Q598 89 591 89Q587 89 584 90T579 94T575 98T572 102L571 209V316H70Q56 323 56 336'], // MACRON 0xAF: [590,-544,500,69,430,'69 544V590H430V544H69'], // DEGREE SIGN 0xB0: [715,-542,500,147,352,'147 628Q147 669 179 692T244 715Q298 715 325 689T352 629Q352 592 323 567T249 542Q202 542 175 567T147 628ZM313 628Q313 660 300 669T259 678H253Q248 678 242 678T234 679Q217 679 207 674T192 659T188 644T187 629Q187 600 198 590Q210 579 250 579H265Q279 579 288 581T305 595T313 628'], // PLUS-MINUS SIGN 0xB1: [666,0,778,56,722,'56 320T56 333T70 353H369V502Q369 651 371 655Q376 666 388 666Q402 666 405 654T409 596V500V353H707Q722 345 722 333Q722 320 707 313H409V40H707Q722 32 722 20T707 0H70Q56 7 56 20T70 40H369V313H70Q56 320 56 333'], // ACUTE ACCENT 0xB4: [699,-505,500,203,393,'349 699Q367 699 380 686T393 656Q393 651 392 647T387 637T380 627T367 616T351 602T330 585T303 563L232 505L217 519Q203 533 204 533Q204 534 229 567T282 636T313 678L316 681Q318 684 321 686T328 692T337 697T349 699'], // MULTIPLICATION SIGN 0xD7: [491,-9,778,147,630,'630 29Q630 9 609 9Q604 9 587 25T493 118L389 222L284 117Q178 13 175 11Q171 9 168 9Q160 9 154 15T147 29Q147 36 161 51T255 146L359 250L255 354Q174 435 161 449T147 471Q147 480 153 485T168 490Q173 490 175 489Q178 487 284 383L389 278L493 382Q570 459 587 475T609 491Q630 491 630 471Q630 464 620 453T522 355L418 250L522 145Q606 61 618 48T630 29'], // DIVISION SIGN 0xF7: [537,36,778,56,721,'318 466Q318 500 339 518T386 537Q418 537 438 517T458 466Q458 438 440 417T388 396Q355 396 337 417T318 466ZM56 237T56 250T70 270H706Q721 262 721 250T706 230H70Q56 237 56 250ZM318 34Q318 68 339 86T386 105Q418 105 438 85T458 34Q458 6 440 -15T388 -36Q355 -36 337 -15T318 34'], // MODIFIER LETTER CIRCUMFLEX ACCENT 0x2C6: [694,-531,500,112,387,'112 560L249 694L257 686Q387 562 387 560L361 531Q359 532 303 581L250 627L195 580Q182 569 169 557T148 538L140 532Q138 530 125 546L112 560'], // CARON 0x2C7: [644,-513,500,114,385,'114 611L127 630L136 644Q138 644 193 612Q248 581 250 581L306 612Q361 644 363 644L385 611L318 562L249 513L114 611'], // MODIFIER LETTER MACRON 0x2C9: [590,-544,500,69,430,'69 544V590H430V544H69'], // MODIFIER LETTER ACUTE ACCENT 0x2CA: [699,-505,500,203,393,'349 699Q367 699 380 686T393 656Q393 651 392 647T387 637T380 627T367 616T351 602T330 585T303 563L232 505L217 519Q203 533 204 533Q204 534 229 567T282 636T313 678L316 681Q318 684 321 686T328 692T337 697T349 699'], // MODIFIER LETTER GRAVE ACCENT 0x2CB: [699,-505,500,106,296,'106 655Q106 671 119 685T150 699Q166 699 177 688Q190 671 222 629T275 561T295 533T282 519L267 505L196 563Q119 626 113 634Q106 643 106 655'], // BREVE 0x2D8: [694,-515,500,92,407,'250 515Q179 515 138 565T92 683V694H129V689Q129 688 129 683T130 675Q137 631 169 599T248 567Q304 567 337 608T370 689V694H407V683Q403 617 361 566T250 515'], // DOT ABOVE 0x2D9: [669,-549,500,190,309,'190 609Q190 637 208 653T252 669Q275 667 292 652T309 609Q309 579 292 564T250 549Q225 549 208 564T190 609'], // SMALL TILDE 0x2DC: [668,-565,500,83,416,'179 601Q164 601 151 595T131 584T111 565L97 577L83 588Q83 589 95 603T121 633T142 654Q165 668 187 668T253 650T320 632Q335 632 348 638T368 649T388 668L402 656L416 645Q375 586 344 572Q330 565 313 565Q292 565 248 583T179 601'], // EN DASH 0x2013: [285,-248,500,0,499,'0 248V285H499V248H0'], // EM DASH 0x2014: [285,-248,1000,0,999,'0 248V285H999V248H0'], // LEFT SINGLE QUOTATION MARK 0x2018: [694,-379,278,64,199,'64 494Q64 548 86 597T131 670T160 694Q163 694 172 685T182 672Q182 669 170 656T144 625T116 573T101 501Q101 489 102 489T107 491T120 497T138 500Q163 500 180 483T198 440T181 397T139 379Q110 379 87 405T64 494'], // RIGHT SINGLE QUOTATION MARK 0x2019: [694,-379,278,78,212,'78 634Q78 659 95 676T138 694Q166 694 189 668T212 579Q212 525 190 476T146 403T118 379Q114 379 105 388T95 401Q95 404 107 417T133 448T161 500T176 572Q176 584 175 584T170 581T157 576T139 573Q114 573 96 590T78 634'], // LEFT DOUBLE QUOTATION MARK 0x201C: [694,-379,500,128,466,'128 494Q128 528 137 560T158 616T185 658T209 685T223 694T236 685T245 670Q244 668 231 654T204 622T178 571T164 501Q164 489 165 489T170 491T183 497T201 500Q226 500 244 483T262 440T245 397T202 379Q173 379 151 405T128 494ZM332 494Q332 528 341 560T362 616T389 658T413 685T427 694T439 685T449 672Q449 669 437 656T411 625T383 573T368 501Q368 489 369 489T374 491T387 497T405 500Q430 500 448 483T466 440T449 397T406 379Q377 379 355 405T332 494'], // RIGHT DOUBLE QUOTATION MARK 0x201D: [694,-379,500,34,372,'34 634Q34 659 50 676T93 694Q121 694 144 668T168 579Q168 525 146 476T101 403T73 379Q69 379 60 388T50 401Q50 404 62 417T88 448T116 500T131 572Q131 584 130 584T125 581T112 576T94 573Q69 573 52 590T34 634ZM238 634Q238 659 254 676T297 694Q325 694 348 668T372 579Q372 525 350 476T305 403T277 379Q273 379 264 388T254 401Q254 404 266 417T292 448T320 500T335 572Q335 584 334 584T329 581T316 576T298 573Q273 573 256 590T238 634'], // DAGGER 0x2020: [705,216,444,54,389,'182 675Q195 705 222 705Q234 705 243 700T253 691T263 675L262 655Q262 620 252 549T240 454V449Q250 451 288 461T346 472T377 461T389 431Q389 417 379 404T346 390Q327 390 288 401T243 412H240V405Q245 367 250 339T258 301T261 274T263 225Q263 124 255 -41T239 -213Q236 -216 222 -216H217Q206 -216 204 -212T200 -186Q199 -175 199 -168Q181 38 181 225Q181 265 182 280T191 327T204 405V412H201Q196 412 157 401T98 390Q76 390 66 403T55 431T65 458T98 472Q116 472 155 462T205 449Q204 452 204 460T201 490T193 547Q182 619 182 655V675'], // DOUBLE DAGGER 0x2021: [705,205,444,54,389,'181 658Q181 705 222 705T263 658Q263 633 252 572T240 497Q240 496 241 496Q243 496 285 507T345 519Q365 519 376 508T388 478Q388 466 384 458T375 447T361 438H344Q318 438 282 448T241 459Q240 458 240 456Q240 449 251 384T263 297Q263 278 255 267T238 253T222 250T206 252T190 266T181 297Q181 323 192 383T204 458Q204 459 203 459Q198 459 162 449T101 438H84Q74 443 70 446T61 457T56 478Q56 497 67 508T99 519Q117 519 159 508T203 496Q204 496 204 499Q204 507 193 572T181 658ZM181 202Q181 249 222 249T263 202Q263 185 259 161T249 103T240 48V41H243Q248 41 287 52T346 63T377 52T389 22Q389 8 379 -5T346 -19Q327 -19 288 -8T243 3H240V-4Q243 -24 249 -58T259 -117T263 -158Q263 -177 255 -188T238 -202T222 -205T206 -203T190 -189T181 -158Q181 -141 185 -117T195 -59T204 -4V3H201Q196 3 157 -8T98 -19Q76 -19 66 -6T55 22T65 49T98 63Q117 63 156 52T201 41H204V48Q201 68 195 102T185 161T181 202'], // HORIZONTAL ELLIPSIS 0x2026: [120,0,1172,78,1093,'78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60ZM525 60Q525 84 542 102T585 120Q609 120 627 104T646 61Q646 36 629 18T586 0T543 17T525 60ZM972 60Q972 84 989 102T1032 120Q1056 120 1074 104T1093 61Q1093 36 1076 18T1033 0T990 17T972 60'], // PRIME 0x2032: [560,-43,275,30,262,'79 43Q73 43 52 49T30 61Q30 68 85 293T146 528Q161 560 198 560Q218 560 240 545T262 501Q262 496 260 486Q259 479 173 263T84 45T79 43'], // COMBINING RIGHT ARROW ABOVE 0x20D7: [714,-516,0,-471,-29,'-123 694Q-123 702 -118 708T-103 714Q-93 714 -88 706T-80 687T-67 660T-40 633Q-29 626 -29 615Q-29 606 -36 600T-53 590T-83 571T-121 531Q-135 516 -143 516T-157 522T-163 536T-152 559T-129 584T-116 595H-287L-458 596Q-459 597 -461 599T-466 602T-469 607T-471 615Q-471 622 -458 635H-99Q-123 673 -123 694'], // LEFTWARDS ARROW 0x2190: [511,11,1000,55,944,'944 261T944 250T929 230H165Q167 228 182 216T211 189T244 152T277 96T303 25Q308 7 308 0Q308 -11 288 -11Q281 -11 278 -11T272 -7T267 2T263 21Q245 94 195 151T73 236Q58 242 55 247Q55 254 59 257T73 264Q121 283 158 314T215 375T247 434T264 480L267 497Q269 503 270 505T275 509T288 511Q308 511 308 500Q308 493 303 475Q293 438 278 406T246 352T215 315T185 287T165 270H929Q944 261 944 250'], // UPWARDS ARROW 0x2191: [694,193,500,17,483,'27 414Q17 414 17 433Q17 437 17 439T17 444T19 447T20 450T22 452T26 453T30 454T36 456Q80 467 120 494T180 549Q227 607 238 678Q240 694 251 694Q259 694 261 684Q261 677 265 659T284 608T320 549Q340 525 363 507T405 479T440 463T467 455T479 451Q483 447 483 433Q483 413 472 413Q467 413 458 416Q342 448 277 545L270 555V-179Q262 -193 252 -193H250H248Q236 -193 230 -179V555L223 545Q192 499 146 467T70 424T27 414'], // RIGHTWARDS ARROW 0x2192: [511,11,1000,56,944,'56 237T56 250T70 270H835Q719 357 692 493Q692 494 692 496T691 499Q691 511 708 511H711Q720 511 723 510T729 506T732 497T735 481T743 456Q765 389 816 336T935 261Q944 258 944 250Q944 244 939 241T915 231T877 212Q836 186 806 152T761 85T740 35T732 4Q730 -6 727 -8T711 -11Q691 -11 691 0Q691 7 696 25Q728 151 835 230H70Q56 237 56 250'], // DOWNWARDS ARROW 0x2193: [694,194,500,17,483,'473 86Q483 86 483 67Q483 63 483 61T483 56T481 53T480 50T478 48T474 47T470 46T464 44Q428 35 391 14T316 -55T264 -168Q264 -170 263 -173T262 -180T261 -184Q259 -194 251 -194Q242 -194 238 -176T221 -121T180 -49Q169 -34 155 -21T125 2T95 20T67 33T44 42T27 47L21 49Q17 53 17 67Q17 87 28 87Q33 87 42 84Q158 52 223 -45L230 -55V312Q230 391 230 482T229 591Q229 662 231 676T243 693Q244 694 251 694Q264 692 270 679V-55L277 -45Q307 1 353 33T430 76T473 86'], // LEFT RIGHT ARROW 0x2194: [511,11,1000,55,944,'263 479Q267 501 271 506T288 511Q308 511 308 500Q308 493 303 475Q293 438 278 406T246 352T215 315T185 287T165 270H835Q729 349 696 475Q691 493 691 500Q691 511 711 511Q720 511 723 510T729 506T732 497T735 481T743 456Q765 389 816 336T935 261Q944 258 944 250Q944 244 939 241T915 231T877 212Q836 186 806 152T761 85T740 35T732 4Q730 -6 727 -8T711 -11Q691 -11 691 0Q691 7 696 25Q728 151 835 230H165Q167 228 182 216T211 189T244 152T277 96T303 25Q308 7 308 0Q308 -11 288 -11Q281 -11 278 -11T272 -7T267 2T263 21Q245 94 195 151T73 236Q58 242 55 247Q55 254 59 257T73 264Q144 292 194 349T263 479'], // UP DOWN ARROW 0x2195: [772,272,500,17,483,'27 492Q17 492 17 511Q17 515 17 517T17 522T19 525T20 528T22 530T26 531T30 532T36 534Q80 545 120 572T180 627Q210 664 223 701T238 755T250 772T261 762Q261 757 264 741T282 691T319 628Q352 589 390 566T454 536L479 529Q483 525 483 511Q483 491 472 491Q467 491 458 494Q342 526 277 623L270 633V-133L277 -123Q307 -77 353 -45T430 -2T473 8Q483 8 483 -11Q483 -15 483 -17T483 -22T481 -25T480 -28T478 -30T474 -31T470 -32T464 -34Q407 -49 364 -84T300 -157T270 -223T261 -262Q259 -272 250 -272Q242 -272 239 -255T223 -201T180 -127Q169 -112 155 -99T125 -76T95 -58T67 -45T44 -36T27 -31L21 -29Q17 -25 17 -11Q17 9 28 9Q33 9 42 6Q158 -26 223 -123L230 -133V633L223 623Q192 577 146 545T70 502T27 492'], // NORTH WEST ARROW 0x2196: [720,195,1000,29,944,'204 662Q257 662 301 676T369 705T394 720Q398 720 407 711T417 697Q417 688 389 671T310 639T212 623Q176 623 153 628Q151 628 221 557T546 232Q942 -164 943 -168Q944 -170 944 -174Q944 -182 938 -188T924 -195Q922 -195 916 -193Q912 -191 517 204Q440 281 326 394T166 553L121 598Q126 589 126 541Q126 438 70 349Q59 332 52 332Q48 332 39 341T29 355Q29 358 38 372T57 407T77 464T86 545Q86 583 78 614T63 663T55 683Q55 693 65 693Q73 693 82 688Q136 662 204 662'], // NORTH EAST ARROW 0x2197: [720,195,1000,55,971,'582 697Q582 701 591 710T605 720Q607 720 630 706T697 677T795 662Q830 662 863 670T914 686T934 694Q942 694 944 685Q944 680 936 663T921 615T913 545Q913 490 927 446T956 379T970 355Q970 351 961 342T947 332Q940 332 929 349Q874 436 874 541Q874 590 878 598L832 553Q787 508 673 395T482 204Q87 -191 83 -193Q77 -195 75 -195Q67 -195 61 -189T55 -174Q55 -170 56 -168Q58 -164 453 232Q707 487 777 557T847 628Q824 623 787 623Q689 623 599 679Q582 690 582 697'], // SOUTH EAST ARROW 0x2198: [695,220,1000,55,970,'55 675Q55 683 60 689T75 695Q77 695 83 693Q87 691 482 296Q532 246 605 174T717 62T799 -20T859 -80T878 -97Q874 -93 874 -41Q874 64 929 151Q940 168 947 168Q951 168 960 159T970 145Q970 143 956 121T928 54T913 -45Q913 -83 920 -114T936 -163T944 -185Q942 -194 934 -194Q932 -194 914 -186T864 -170T795 -162Q743 -162 698 -176T630 -205T605 -220Q601 -220 592 -211T582 -197Q582 -187 611 -170T691 -138T787 -123Q824 -123 847 -128Q848 -128 778 -57T453 268Q58 664 56 668Q55 670 55 675'], // SOUTH WEST ARROW 0x2199: [695,220,1000,29,944,'126 -41Q126 -92 121 -97Q121 -98 139 -80T200 -20T281 61T394 173T517 296Q909 690 916 693Q922 695 924 695Q932 695 938 689T944 674Q944 670 943 668Q942 664 546 268Q292 13 222 -57T153 -128Q176 -123 212 -123Q310 -123 400 -179Q417 -190 417 -197Q417 -201 408 -210T394 -220Q392 -220 369 -206T302 -177T204 -162Q131 -162 67 -194Q63 -195 59 -192T55 -183Q55 -180 62 -163T78 -115T86 -45Q86 10 72 54T44 120T29 145Q29 149 38 158T52 168Q59 168 70 151Q126 62 126 -41'], // RIGHTWARDS ARROW FROM BAR 0x21A6: [511,11,1000,54,944,'95 155V109Q95 83 92 73T75 63Q61 63 58 74T54 130Q54 140 54 180T55 250Q55 421 57 425Q61 437 75 437Q88 437 91 428T95 393V345V270H835Q719 357 692 493Q692 494 692 496T691 499Q691 511 708 511H711Q720 511 723 510T729 506T732 497T735 481T743 456Q765 389 816 336T935 261Q944 258 944 250Q944 244 939 241T915 231T877 212Q836 186 806 152T761 85T740 35T732 4Q730 -6 727 -8T711 -11Q691 -11 691 0Q691 7 696 25Q728 151 835 230H95V155'], // LEFTWARDS ARROW WITH HOOK 0x21A9: [511,11,1126,55,1070,'903 424T903 444T929 464Q976 464 1023 434T1070 347Q1070 316 1055 292T1016 256T971 237T929 230H165Q167 228 182 216T211 189T244 152T277 96T303 25Q308 7 308 0Q308 -11 288 -11Q281 -11 278 -11T272 -7T267 2T263 21Q245 94 195 151T73 236Q58 242 55 247Q55 254 59 257T73 264Q121 283 158 314T215 375T247 434T264 480L267 497Q269 503 270 505T275 509T288 511Q308 511 308 500Q308 493 303 475Q293 438 278 406T246 352T215 315T185 287T165 270H926Q929 270 941 271T960 275T978 280T998 290T1015 307Q1030 325 1030 347Q1030 355 1027 364T1014 387T983 411T929 424H928Q903 424 903 444'], // RIGHTWARDS ARROW WITH HOOK 0x21AA: [511,11,1126,55,1070,'55 347Q55 380 72 404T113 441T159 458T197 464Q222 464 222 444Q222 429 204 426T157 417T110 387Q95 369 95 347Q95 339 98 330T111 307T142 283T196 270H961Q845 357 818 493Q818 494 818 496T817 499Q817 511 834 511H837Q846 511 849 510T855 506T858 497T861 481T869 456Q891 389 942 336T1061 261Q1070 258 1070 250Q1070 244 1065 241T1041 231T1003 212Q962 186 932 152T887 85T866 35T858 4Q856 -6 853 -8T837 -11Q817 -11 817 0Q817 7 822 25Q854 151 961 230H196Q149 230 102 260T55 347'], // LEFTWARDS HARPOON WITH BARB UPWARDS 0x21BC: [511,-230,1000,55,944,'62 230Q56 236 55 244Q55 252 57 255T69 265Q114 292 151 326T208 391T243 448T265 491T273 509Q276 511 288 511Q304 511 306 505Q309 501 303 484Q293 456 279 430T251 383T223 344T196 313T173 291T156 276L148 270H929Q944 261 944 250T929 230H62'], // LEFTWARDS HARPOON WITH BARB DOWNWARDS 0x21BD: [270,11,1000,55,944,'55 256Q56 264 62 270H929Q944 261 944 250T929 230H148Q149 229 165 215T196 185T231 145T270 87T303 16Q309 -1 306 -5Q304 -11 288 -11Q279 -11 276 -10T269 -4T264 10T253 36T231 75Q172 173 69 235Q59 242 57 245T55 256'], // RIGHTWARDS HARPOON WITH BARB UPWARDS 0x21C0: [511,-230,1000,56,945,'691 500Q691 511 711 511Q720 511 723 510T730 504T735 490T746 464T768 425Q796 378 835 339T897 285T933 263Q941 258 942 256T944 245T937 230H70Q56 237 56 250T70 270H852Q802 308 762 364T707 455T691 500'], // RIGHTWARDS HARPOON WITH BARB DOWNWARDS 0x21C1: [270,11,1000,56,944,'56 237T56 250T70 270H937Q944 263 944 256Q944 251 944 250T943 246T940 242T933 238Q794 153 734 7Q729 -7 726 -9T711 -11Q695 -11 693 -5Q690 -1 696 16Q721 84 763 139T852 230H70Q56 237 56 250'], // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON 0x21CC: [671,11,1000,55,945,'691 660Q691 671 711 671Q720 671 723 670T730 664T735 650T746 624T768 585Q797 538 836 499T897 445T933 423Q941 418 942 416T944 405T937 390H70Q56 397 56 410T70 430H852Q802 468 762 524T707 615T691 660ZM55 256Q56 264 62 270H929Q944 261 944 250T929 230H148Q149 229 165 215T196 185T231 145T270 87T303 16Q309 -1 306 -5Q304 -11 288 -11Q279 -11 276 -10T269 -4T264 10T253 36T231 75Q172 173 69 235Q59 242 57 245T55 256'], // LEFTWARDS DOUBLE ARROW 0x21D0: [525,24,1000,56,945,'944 153Q944 140 929 133H318L328 123Q379 69 414 0Q419 -13 419 -17Q419 -24 399 -24Q388 -24 385 -23T377 -12Q332 77 253 144T72 237Q62 240 59 242T56 250T59 257T70 262T89 268T119 278T160 296Q303 366 377 512Q382 522 385 523T401 525Q419 524 419 515Q419 510 414 500Q379 431 328 377L318 367H929Q944 359 944 347Q944 336 930 328L602 327H274L264 319Q225 289 147 250Q148 249 165 241T210 217T264 181L274 173H930Q931 172 933 171T936 169T938 167T941 164T942 162T943 158T944 153'], // UPWARDS DOUBLE ARROW 0x21D1: [694,194,611,31,579,'228 -179Q227 -180 226 -182T223 -186T221 -189T218 -192T214 -193T208 -194Q196 -194 189 -181L188 125V430L176 419Q122 369 59 338Q46 330 40 330Q38 330 31 337V350Q31 362 33 365T46 374Q60 381 77 390T128 426T190 484T247 567T292 677Q295 688 298 692Q302 694 305 694Q313 694 318 677Q334 619 363 568T420 485T481 427T532 391T564 374Q575 368 577 365T579 350V337Q572 330 570 330Q564 330 551 338Q487 370 435 419L423 430L422 125V-181Q409 -194 401 -194Q397 -194 394 -193T388 -189T385 -184T382 -180V-177V475L373 487Q331 541 305 602Q304 601 300 591T290 571T278 548T260 519T238 488L229 476L228 148V-179'], // RIGHTWARDS DOUBLE ARROW 0x21D2: [525,24,1000,56,944,'580 514Q580 525 596 525Q601 525 604 525T609 525T613 524T615 523T617 520T619 517T622 512Q659 438 720 381T831 300T927 263Q944 258 944 250T935 239T898 228T840 204Q696 134 622 -12Q618 -21 615 -22T600 -24Q580 -24 580 -17Q580 -13 585 0Q620 69 671 123L681 133H70Q56 140 56 153Q56 168 72 173H725L735 181Q774 211 852 250Q851 251 834 259T789 283T735 319L725 327H72Q56 332 56 347Q56 360 70 367H681L671 377Q638 412 609 458T580 514'], // DOWNWARDS DOUBLE ARROW 0x21D3: [694,194,611,31,579,'401 694Q412 694 422 681V375L423 70L435 81Q487 130 551 162Q564 170 570 170Q572 170 579 163V150Q579 138 577 135T564 126Q541 114 518 99T453 48T374 -46T318 -177Q313 -194 305 -194T293 -178T272 -119T225 -31Q158 70 46 126Q35 132 33 135T31 150V163Q38 170 40 170Q46 170 59 162Q122 131 176 81L188 70V375L189 681Q199 694 208 694Q219 694 228 680V352L229 25L238 12Q279 -42 305 -102Q344 -23 373 13L382 25V678Q387 692 401 694'], // LEFT RIGHT DOUBLE ARROW 0x21D4: [526,25,1000,33,966,'308 524Q318 526 323 526Q340 526 340 514Q340 507 336 499Q326 476 314 454T292 417T274 391T260 374L255 368Q255 367 500 367Q744 367 744 368L739 374Q734 379 726 390T707 416T685 453T663 499Q658 511 658 515Q658 525 680 525Q687 524 690 523T695 519T701 507Q766 359 902 287Q921 276 939 269T961 259T966 250Q966 246 965 244T960 240T949 236T930 228T902 213Q763 137 701 -7Q697 -16 695 -19T690 -23T680 -25Q658 -25 658 -15Q658 -11 663 1Q673 24 685 46T707 83T725 109T739 126L744 132Q744 133 500 133Q255 133 255 132L260 126Q265 121 273 110T292 84T314 47T336 1Q341 -11 341 -15Q341 -25 319 -25Q312 -24 309 -23T304 -19T298 -7Q233 141 97 213Q83 221 70 227T51 235T41 239T35 243T34 250T35 256T40 261T51 265T70 273T97 287Q235 363 299 509Q305 522 308 524ZM792 319L783 327H216Q183 294 120 256L110 250L120 244Q173 212 207 181L216 173H783L792 181Q826 212 879 244L889 250L879 256Q826 288 792 319'], // UP DOWN DOUBLE ARROW 0x21D5: [772,272,611,31,579,'290 755Q298 772 305 772T318 757T343 706T393 633Q431 588 473 558T545 515T579 497V484Q579 464 570 464Q564 464 550 470Q485 497 423 550L422 400V100L423 -50Q485 3 550 30Q565 36 570 36Q579 36 579 16V3Q575 -1 549 -12T480 -53T393 -132Q361 -172 342 -208T318 -258T305 -272T293 -258T268 -208T217 -132Q170 -80 128 -51T61 -12T31 3V16Q31 36 40 36Q46 36 61 30Q86 19 109 6T146 -18T173 -38T188 -50V550Q186 549 173 539T147 519T110 495T61 470Q46 464 40 464Q31 464 31 484V497Q34 500 63 513T135 557T217 633Q267 692 290 755ZM374 598Q363 610 351 625T332 651T316 676T305 695L294 676Q282 657 267 636T236 598L228 589V-89L236 -98Q247 -110 259 -125T278 -151T294 -176T305 -195L316 -176Q328 -157 343 -136T374 -98L382 -89V589L374 598'], // FOR ALL 0x2200: [694,22,556,0,556,'0 673Q0 684 7 689T20 694Q32 694 38 680T82 567L126 451H430L473 566Q483 593 494 622T512 668T519 685Q524 694 538 694Q556 692 556 674Q556 670 426 329T293 -15Q288 -22 278 -22T263 -15Q260 -11 131 328T0 673ZM414 410Q414 411 278 411T142 410L278 55L414 410'], // PARTIAL DIFFERENTIAL 0x2202: [715,22,531,42,567,'202 508Q179 508 169 520T158 547Q158 557 164 577T185 624T230 675T301 710L333 715H345Q378 715 384 714Q447 703 489 661T549 568T566 457Q566 362 519 240T402 53Q321 -22 223 -22Q123 -22 73 56Q42 102 42 148V159Q42 276 129 370T322 465Q383 465 414 434T455 367L458 378Q478 461 478 515Q478 603 437 639T344 676Q266 676 223 612Q264 606 264 572Q264 547 246 528T202 508ZM430 306Q430 372 401 400T333 428Q270 428 222 382Q197 354 183 323T150 221Q132 149 132 116Q132 21 232 21Q244 21 250 22Q327 35 374 112Q389 137 409 196T430 306'], // THERE EXISTS 0x2203: [694,0,556,56,500,'56 661T56 674T70 694H487Q497 686 500 679V15Q497 10 487 1L279 0H70Q56 7 56 20T70 40H460V327H84Q70 334 70 347T84 367H460V654H70Q56 661 56 674'], // EMPTY SET 0x2205: [772,78,500,39,460,'331 696Q335 708 339 722T345 744T350 759T357 769T367 772Q374 772 381 767T388 754Q388 746 377 712L366 673L378 661Q460 575 460 344Q460 281 456 234T432 126T373 27Q319 -22 250 -22Q214 -22 180 -7Q168 -3 168 -4L159 -33Q148 -71 142 -75Q138 -78 132 -78Q124 -78 118 -72T111 -60Q111 -52 122 -18L133 21L125 29Q39 111 39 344Q39 596 137 675Q187 716 251 716Q265 716 278 714T296 710T315 703T331 696ZM276 676Q264 679 246 679Q196 679 159 631Q134 597 128 536T121 356Q121 234 127 174T151 80L234 366Q253 430 275 506T308 618L318 654Q318 656 294 669L276 676ZM181 42Q207 16 250 16Q291 16 324 47Q354 78 366 136T378 356Q378 470 372 528T349 616L348 613Q348 611 264 326L181 42'], // NABLA 0x2207: [683,33,833,46,786,'46 676Q46 679 51 683H781Q786 679 786 676Q786 674 617 326T444 -26Q439 -33 416 -33T388 -26Q385 -22 216 326T46 676ZM697 596Q697 597 445 597T193 596Q195 591 319 336T445 80L697 596'], // ELEMENT OF 0x2208: [541,41,667,84,583,'84 250Q84 372 166 450T360 539Q361 539 377 539T419 540T469 540H568Q583 532 583 520Q583 511 570 501L466 500Q355 499 329 494Q280 482 242 458T183 409T147 354T129 306T124 272V270H568Q583 262 583 250T568 230H124V228Q124 207 134 177T167 112T231 48T328 7Q355 1 466 0H570Q583 -10 583 -20Q583 -32 568 -40H471Q464 -40 446 -40T417 -41Q262 -41 172 45Q84 127 84 250'], // stix-negated (vert) set membership, variant 0x2209: [716,215,667,84,584,'196 25Q84 109 84 250Q84 372 166 450T360 539Q361 539 375 539T413 540T460 540L547 707Q550 716 563 716Q570 716 575 712T581 703T583 696T505 540H568Q583 532 583 520Q583 511 570 501L484 500L366 270H568Q583 262 583 250T568 230H346L247 38Q284 16 328 7Q355 1 466 0H570Q583 -10 583 -20Q583 -32 568 -40H471Q464 -40 447 -40T419 -41Q304 -41 228 3Q117 -211 115 -212Q111 -215 104 -215T92 -212T86 -204T84 -197Q84 -190 89 -183L196 25ZM214 61L301 230H124V228Q124 196 147 147T214 61ZM321 270L440 500Q353 499 329 494Q280 482 242 458T183 409T147 354T129 306T124 272V270H321'], // CONTAINS AS MEMBER 0x220B: [541,40,667,83,582,'83 520Q83 532 98 540H195Q202 540 220 540T249 541Q404 541 494 455Q582 374 582 250Q582 165 539 99T434 0T304 -39Q297 -40 195 -40H98Q83 -32 83 -20Q83 -10 96 0H200Q311 1 337 6Q369 14 401 28Q422 39 445 55Q484 85 508 127T537 191T542 228V230H98Q84 237 84 250T98 270H542V272Q542 280 539 295T527 336T497 391T445 445Q422 461 401 472Q386 479 374 483T347 491T325 495T298 498T273 499T239 500T200 500L96 501Q83 511 83 520'], // MINUS SIGN 0x2212: [270,-230,778,84,694,'84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250'], // MINUS-OR-PLUS SIGN 0x2213: [500,166,778,56,722,'56 467T56 480T70 500H707Q722 492 722 480T707 460H409V187H707Q722 179 722 167Q722 154 707 147H409V0V-93Q409 -144 406 -155T389 -166Q376 -166 372 -155T368 -105Q368 -96 368 -62T369 -2V147H70Q56 154 56 167T70 187H369V460H70Q56 467 56 480'], // DIVISION SLASH 0x2215: [750,250,500,56,444,'423 750Q432 750 438 744T444 730Q444 725 271 248T92 -240Q85 -250 75 -250Q68 -250 62 -245T56 -231Q56 -221 230 257T407 740Q411 750 423 750'], // SET MINUS 0x2216: [750,250,500,56,444,'56 731Q56 740 62 745T75 750Q85 750 92 740Q96 733 270 255T444 -231Q444 -239 438 -244T424 -250Q414 -250 407 -240Q404 -236 230 242T56 731'], // ASTERISK OPERATOR 0x2217: [465,-35,500,64,435,'229 286Q216 420 216 436Q216 454 240 464Q241 464 245 464T251 465Q263 464 273 456T283 436Q283 419 277 356T270 286L328 328Q384 369 389 372T399 375Q412 375 423 365T435 338Q435 325 425 315Q420 312 357 282T289 250L355 219L425 184Q434 175 434 161Q434 146 425 136T401 125Q393 125 383 131T328 171L270 213Q283 79 283 63Q283 53 276 44T250 35Q231 35 224 44T216 63Q216 80 222 143T229 213L171 171Q115 130 110 127Q106 124 100 124Q87 124 76 134T64 161Q64 166 64 169T67 175T72 181T81 188T94 195T113 204T138 215T170 230T210 250L74 315Q65 324 65 338Q65 353 74 363T98 374Q106 374 116 368T171 328L229 286'], // RING OPERATOR 0x2218: [444,-55,500,55,444,'55 251Q55 328 112 386T249 444T386 388T444 249Q444 171 388 113T250 55Q170 55 113 112T55 251ZM245 403Q188 403 142 361T96 250Q96 183 141 140T250 96Q284 96 313 109T354 135T375 160Q403 197 403 250Q403 313 360 358T245 403'], // BULLET OPERATOR 0x2219: [444,-55,500,55,444,'55 251Q55 328 112 386T249 444T386 388T444 249Q444 171 388 113T250 55Q170 55 113 112T55 251'], // SQUARE ROOT 0x221A: [800,200,833,71,853,'95 178Q89 178 81 186T72 200T103 230T169 280T207 309Q209 311 212 311H213Q219 311 227 294T281 177Q300 134 312 108L397 -77Q398 -77 501 136T707 565T814 786Q820 800 834 800Q841 800 846 794T853 782V776L620 293L385 -193Q381 -200 366 -200Q357 -200 354 -197Q352 -195 256 15L160 225L144 214Q129 202 113 190T95 178'], // PROPORTIONAL TO 0x221D: [442,11,778,56,722,'56 124T56 216T107 375T238 442Q260 442 280 438T319 425T352 407T382 385T406 361T427 336T442 315T455 297T462 285L469 297Q555 442 679 442Q687 442 722 437V398H718Q710 400 694 400Q657 400 623 383T567 343T527 294T503 253T495 235Q495 231 520 192T554 143Q625 44 696 44Q717 44 719 46H722V-5Q695 -11 678 -11Q552 -11 457 141Q455 145 454 146L447 134Q362 -11 235 -11Q157 -11 107 56ZM93 213Q93 143 126 87T220 31Q258 31 292 48T349 88T389 137T413 178T421 196Q421 200 396 239T362 288Q322 345 288 366T213 387Q163 387 128 337T93 213'], // INFINITY 0x221E: [442,11,1000,55,944,'55 217Q55 305 111 373T254 442Q342 442 419 381Q457 350 493 303L507 284L514 294Q618 442 747 442Q833 442 888 374T944 214Q944 128 889 59T743 -11Q657 -11 580 50Q542 81 506 128L492 147L485 137Q381 -11 252 -11Q166 -11 111 57T55 217ZM907 217Q907 285 869 341T761 397Q740 397 720 392T682 378T648 359T619 335T594 310T574 285T559 263T548 246L543 238L574 198Q605 158 622 138T664 94T714 61T765 51Q827 51 867 100T907 217ZM92 214Q92 145 131 89T239 33Q357 33 456 193L425 233Q364 312 334 337Q285 380 233 380Q171 380 132 331T92 214'], // ANGLE 0x2220: [694,0,722,55,666,'71 0L68 2Q65 3 63 5T58 11T55 20Q55 22 57 28Q67 43 346 361Q397 420 474 508Q595 648 616 671T647 694T661 688T666 674Q666 668 663 663Q662 662 627 622T524 503T390 350L120 41L386 40H653Q666 30 666 20Q666 8 651 0H71'], // DIVIDES 0x2223: [750,249,278,119,159,'139 -249H137Q125 -249 119 -235V251L120 737Q130 750 139 750Q152 750 159 735V-235Q151 -249 141 -249H139'], // PARALLEL TO 0x2225: [750,250,500,132,368,'133 736Q138 750 153 750Q164 750 170 739Q172 735 172 250T170 -239Q164 -250 152 -250Q144 -250 138 -244L137 -243Q133 -241 133 -179T132 250Q132 731 133 736ZM329 739Q334 750 346 750Q353 750 361 744L362 743Q366 741 366 679T367 250T367 -178T362 -243L361 -244Q355 -250 347 -250Q335 -250 329 -239Q327 -235 327 250T329 739'], // LOGICAL AND 0x2227: [598,22,667,55,611,'318 591Q325 598 333 598Q344 598 348 591Q349 590 414 445T545 151T611 -4Q609 -22 591 -22Q588 -22 586 -21T581 -20T577 -17T575 -13T572 -9T570 -4L333 528L96 -4Q87 -20 80 -21Q78 -22 75 -22Q57 -22 55 -4Q55 2 120 150T251 444T318 591'], // LOGICAL OR 0x2228: [598,22,667,55,611,'55 580Q56 587 61 592T75 598Q86 598 96 580L333 48L570 580Q579 596 586 597Q588 598 591 598Q609 598 611 580Q611 574 546 426T415 132T348 -15Q343 -22 333 -22T318 -15Q317 -14 252 131T121 425T55 580'], // stix-intersection, serifs 0x2229: [598,22,667,55,611,'88 -21T75 -21T55 -7V200Q55 231 55 280Q56 414 60 428Q61 430 61 431Q77 500 152 549T332 598Q443 598 522 544T610 405Q611 399 611 194V-7Q604 -22 591 -22Q582 -22 572 -9L570 405Q563 433 556 449T529 485Q498 519 445 538T334 558Q251 558 179 518T96 401Q95 396 95 193V-7Q88 -21 75 -21'], // stix-union, serifs 0x222A: [598,22,667,55,611,'591 598H592Q604 598 611 583V376Q611 345 611 296Q610 162 606 148Q605 146 605 145Q586 68 507 23T333 -22Q268 -22 209 -1T106 66T56 173Q55 180 55 384L56 585Q66 598 75 598Q85 598 95 585V378L96 172L98 162Q112 95 181 57T332 18Q415 18 487 58T570 175Q571 180 571 383V583Q579 598 591 598'], // INTEGRAL 0x222B: [716,216,417,55,472,'151 -112Q151 -150 106 -161Q106 -165 114 -172T134 -179Q155 -179 170 -146Q181 -120 188 -64T206 101T232 310Q256 472 277 567Q308 716 392 716Q434 716 453 681T472 613Q472 590 458 577T424 564Q404 564 390 578T376 612Q376 650 421 661Q421 663 418 667T407 675T393 679Q387 679 380 675Q360 665 350 619T326 438Q302 190 253 -57Q235 -147 201 -186Q174 -213 138 -216Q93 -216 74 -181T55 -113Q55 -91 69 -78T103 -64Q123 -64 137 -78T151 -112'], // TILDE OPERATOR 0x223C: [367,-133,778,55,722,'55 166Q55 241 101 304T222 367Q260 367 296 349T362 304T421 252T484 208T554 189Q616 189 655 236T694 338Q694 350 698 358T708 367Q722 367 722 334Q722 260 677 197T562 134H554Q517 134 481 152T414 196T355 248T292 293T223 311Q179 311 145 286Q109 257 96 218T80 156T69 133Q55 133 55 166'], // WREATH PRODUCT 0x2240: [583,83,278,55,222,'55 569Q55 583 83 583Q122 583 151 565T194 519T215 464T222 411Q222 360 194 304T139 193T111 89Q111 38 134 -7T195 -55Q222 -57 222 -69Q222 -83 189 -83Q130 -83 93 -33T55 90Q55 130 72 174T110 252T148 328T166 411Q166 462 144 507T83 555Q55 556 55 569'], // ASYMPTOTICALLY EQUAL TO 0x2243: [464,-36,778,55,722,'55 283Q55 356 103 409T217 463Q262 463 297 447T395 382Q431 355 446 344T493 320T554 307H558Q613 307 652 344T694 433Q694 464 708 464T722 432Q722 356 673 304T564 251H554Q510 251 465 275T387 329T310 382T223 407H219Q164 407 122 367Q91 333 85 295T76 253T69 250Q55 250 55 283ZM56 56Q56 71 72 76H706Q722 70 722 56Q722 44 707 36H70Q56 43 56 56'], // APPROXIMATELY EQUAL TO 0x2245: [589,-22,1000,55,722,'55 388Q55 463 101 526T222 589Q260 589 296 571T362 526T421 474T484 430T554 411Q616 411 655 458T694 560Q694 572 698 580T708 589Q722 589 722 556Q722 482 677 419T562 356H554Q517 356 481 374T414 418T355 471T292 515T223 533Q179 533 145 508Q109 479 96 440T80 378T69 355Q55 355 55 388ZM56 236Q56 249 70 256H707Q722 248 722 236Q722 225 708 217L390 216H72Q56 221 56 236ZM56 42Q56 57 72 62H708Q722 52 722 42Q722 30 707 22H70Q56 29 56 42'], // ALMOST EQUAL TO 0x2248: [483,-55,778,55,722,'55 319Q55 360 72 393T114 444T163 472T205 482Q207 482 213 482T223 483Q262 483 296 468T393 413L443 381Q502 346 553 346Q609 346 649 375T694 454Q694 465 698 474T708 483Q722 483 722 452Q722 386 675 338T555 289Q514 289 468 310T388 357T308 404T224 426Q164 426 125 393T83 318Q81 289 69 289Q55 289 55 319ZM55 85Q55 126 72 159T114 210T163 238T205 248Q207 248 213 248T223 249Q262 249 296 234T393 179L443 147Q502 112 553 112Q609 112 649 141T694 220Q694 249 708 249T722 217Q722 153 675 104T555 55Q514 55 468 76T388 123T308 170T224 192Q164 192 125 159T83 84Q80 55 69 55Q55 55 55 85'], // EQUIVALENT TO 0x224D: [484,-16,778,55,722,'55 464Q55 471 60 477T74 484Q80 484 108 464T172 420T268 376T389 356Q436 356 483 368T566 399T630 436T675 467T695 482Q701 484 703 484Q711 484 716 478T722 464Q722 454 707 442Q550 316 389 316Q338 316 286 329T195 362T124 402T76 437T57 456Q55 462 55 464ZM57 45Q66 58 109 88T230 151T381 183Q438 183 494 168T587 135T658 94T703 61T720 45Q722 39 722 36Q722 28 717 22T703 16Q697 16 669 36T606 80T510 124T389 144Q341 144 294 132T211 101T147 64T102 33T82 18Q76 16 74 16Q66 16 61 22T55 36Q55 39 57 45'], // APPROACHES THE LIMIT 0x2250: [670,-133,778,56,722,'56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153ZM329 610Q329 634 346 652T389 670Q413 670 431 654T450 611Q450 586 433 568T390 550T347 567T329 610'], // stix-not (vert) equals 0x2260: [716,215,778,56,722,'166 -215T159 -215T147 -212T141 -204T139 -197Q139 -190 144 -183L306 133H70Q56 140 56 153Q56 168 72 173H327L406 327H72Q56 332 56 347Q56 360 70 367H426Q597 702 602 707Q605 716 618 716Q625 716 630 712T636 703T638 696Q638 692 471 367H707Q722 359 722 347Q722 336 708 328L451 327L371 173H708Q722 163 722 153Q722 140 707 133H351Q175 -210 170 -212Q166 -215 159 -215'], // IDENTICAL TO 0x2261: [464,-36,778,56,722,'56 444Q56 457 70 464H707Q722 456 722 444Q722 430 706 424H72Q56 429 56 444ZM56 237T56 250T70 270H707Q722 262 722 250T707 230H70Q56 237 56 250ZM56 56Q56 71 72 76H706Q722 70 722 56Q722 44 707 36H70Q56 43 56 56'], // LESS-THAN OR EQUAL TO 0x2264: [636,138,778,83,694,'674 636Q682 636 688 630T694 615T687 601Q686 600 417 472L151 346L399 228Q687 92 691 87Q694 81 694 76Q694 58 676 56H670L382 192Q92 329 90 331Q83 336 83 348Q84 359 96 365Q104 369 382 500T665 634Q669 636 674 636ZM84 -118Q84 -108 99 -98H678Q694 -104 694 -118Q694 -130 679 -138H98Q84 -131 84 -118'], // GREATER-THAN OR EQUAL TO 0x2265: [636,138,778,82,694,'83 616Q83 624 89 630T99 636Q107 636 253 568T543 431T687 361Q694 356 694 346T687 331Q685 329 395 192L107 56H101Q83 58 83 76Q83 77 83 79Q82 86 98 95Q117 105 248 167Q326 204 378 228L626 346L360 472Q291 505 200 548Q112 589 98 597T83 616ZM84 -118Q84 -108 99 -98H678Q694 -104 694 -118Q694 -130 679 -138H98Q84 -131 84 -118'], // MUCH LESS-THAN 0x226A: [568,67,1000,56,944,'639 -48Q639 -54 634 -60T619 -67H618Q612 -67 536 -26Q430 33 329 88Q61 235 59 239Q56 243 56 250T59 261Q62 266 336 415T615 567L619 568Q622 567 625 567Q639 562 639 548Q639 540 633 534Q632 532 374 391L117 250L374 109Q632 -32 633 -34Q639 -40 639 -48ZM944 -48Q944 -54 939 -60T924 -67H923Q917 -67 841 -26Q735 33 634 88Q366 235 364 239Q361 243 361 250T364 261Q367 266 641 415T920 567L924 568Q927 567 930 567Q944 562 944 548Q944 540 938 534Q937 532 679 391L422 250L679 109Q937 -32 938 -34Q944 -40 944 -48'], // MUCH GREATER-THAN 0x226B: [567,67,1000,55,944,'55 539T55 547T60 561T74 567Q81 567 207 498Q297 449 365 412Q633 265 636 261Q639 255 639 250Q639 241 626 232Q614 224 365 88Q83 -65 79 -66Q76 -67 73 -67Q65 -67 60 -61T55 -47Q55 -39 61 -33Q62 -33 95 -15T193 39T320 109L321 110H322L323 111H324L325 112L326 113H327L329 114H330L331 115H332L333 116L334 117H335L336 118H337L338 119H339L340 120L341 121H342L343 122H344L345 123H346L347 124L348 125H349L351 126H352L353 127H354L355 128L356 129H357L358 130H359L360 131H361L362 132L363 133H364L365 134H366L367 135H368L369 136H370L371 137L372 138H373L374 139H375L376 140L378 141L576 251Q63 530 62 533Q55 539 55 547ZM360 539T360 547T365 561T379 567Q386 567 512 498Q602 449 670 412Q938 265 941 261Q944 255 944 250Q944 241 931 232Q919 224 670 88Q388 -65 384 -66Q381 -67 378 -67Q370 -67 365 -61T360 -47Q360 -39 366 -33Q367 -33 400 -15T498 39T625 109L626 110H627L628 111H629L630 112L631 113H632L634 114H635L636 115H637L638 116L639 117H640L641 118H642L643 119H644L645 120L646 121H647L648 122H649L650 123H651L652 124L653 125H654L656 126H657L658 127H659L660 128L661 129H662L663 130H664L665 131H666L667 132L668 133H669L670 134H671L672 135H673L674 136H675L676 137L677 138H678L679 139H680L681 140L683 141L881 251Q368 530 367 533Q360 539 360 547'], // PRECEDES 0x227A: [539,41,778,84,694,'84 249Q84 262 91 266T117 270Q120 270 126 270T137 269Q388 273 512 333T653 512Q657 539 676 539Q685 538 689 532T694 520V515Q689 469 672 431T626 366T569 320T500 286T435 265T373 249Q379 248 404 242T440 233T477 221T533 199Q681 124 694 -17Q694 -41 674 -41Q658 -41 653 -17Q646 41 613 84T533 154T418 197T284 220T137 229H114Q104 229 98 230T88 235T84 249'], // SUCCEEDS 0x227B: [539,41,778,83,694,'84 517Q84 539 102 539Q115 539 119 529T125 503T137 459T171 404Q277 275 640 269H661Q694 269 694 249T661 229H640Q526 227 439 214T283 173T173 98T124 -17Q118 -41 103 -41Q83 -41 83 -17Q88 29 105 67T151 132T208 178T277 212T342 233T404 249Q401 250 380 254T345 263T302 276T245 299Q125 358 92 468Q84 502 84 517'], // SUBSET OF 0x2282: [541,41,778,84,694,'84 250Q84 372 166 450T360 539Q361 539 370 539T395 539T430 540T475 540T524 540H679Q694 532 694 520Q694 511 681 501L522 500H470H441Q366 500 338 496T266 472Q244 461 224 446T179 404T139 337T124 250V245Q124 157 185 89Q244 25 328 7Q348 2 366 2T522 0H681Q694 -10 694 -20Q694 -32 679 -40H526Q510 -40 480 -40T434 -41Q350 -41 289 -25T172 45Q84 127 84 250'], // SUPERSET OF 0x2283: [541,40,778,83,693,'83 520Q83 532 98 540H251Q267 540 297 540T343 541Q427 541 488 525T605 455Q693 374 693 250Q693 165 650 99T545 0T415 -39Q407 -40 251 -40H98Q83 -32 83 -20Q83 -10 96 0H255H308H337Q412 0 439 4T512 28Q533 39 553 54T599 96T639 163T654 250Q654 341 592 411Q557 449 512 472Q468 491 439 495T335 500H306H255L96 501Q83 511 83 520'], // SUBSET OF OR EQUAL TO 0x2286: [637,138,778,84,694,'84 346Q84 468 166 546T360 635Q361 635 370 635T395 635T430 636T475 636T524 636H679Q694 628 694 616Q694 607 681 597L522 596H470H441Q366 596 338 592T266 568Q244 557 224 542T179 500T139 433T124 346V341Q124 253 185 185Q244 121 328 103Q348 98 366 98T522 96H681Q694 86 694 76Q694 64 679 56H526Q510 56 480 56T434 55Q350 55 289 71T172 141Q84 223 84 346ZM104 -131T104 -118T118 -98H679Q694 -106 694 -118T679 -138H118Q104 -131 104 -118'], // SUPERSET OF OR EQUAL TO 0x2287: [637,138,778,83,693,'83 616Q83 628 98 636H251Q267 636 297 636T343 637Q427 637 488 621T605 551Q693 470 693 346Q693 261 650 195T545 96T415 57Q407 56 251 56H98Q83 64 83 76Q83 86 96 96H255H308H337Q412 96 439 100T512 124Q533 135 553 150T599 192T639 259T654 346Q654 437 592 507Q557 545 512 568Q468 587 439 591T335 596H306H255L96 597Q83 607 83 616ZM84 -131T84 -118T98 -98H659Q674 -106 674 -118T659 -138H98Q84 -131 84 -118'], // MULTISET UNION 0x228E: [598,22,667,55,611,'591 598H592Q604 598 611 583V376Q611 345 611 296Q610 162 606 148Q605 146 605 145Q586 68 507 23T333 -22Q268 -22 209 -1T106 66T56 173Q55 180 55 384L56 585Q66 598 75 598Q85 598 95 585V378L96 172L98 162Q112 95 181 57T332 18Q415 18 487 58T570 175Q571 180 571 383V583Q579 598 591 598ZM313 406Q313 417 313 435T312 459Q312 483 316 493T333 503T349 494T353 461V406V325H515Q516 325 519 323T527 316T531 305T527 294T520 287T515 285H353V204V152Q353 127 350 117T333 107T316 117T312 152Q312 158 312 175T313 204V285H151Q150 285 147 287T139 294T135 305T139 316T146 323T151 325H313V406'], // SQUARE IMAGE OF OR EQUAL TO 0x2291: [636,138,778,84,714,'94 620Q98 632 110 636H699Q714 628 714 616T699 596H134V96H698Q714 90 714 76Q714 64 699 56H109Q104 59 95 69L94 344V620ZM84 -118Q84 -103 100 -98H698Q714 -104 714 -118Q714 -130 699 -138H98Q84 -131 84 -118'], // SQUARE ORIGINAL OF OR EQUAL TO 0x2292: [636,138,778,64,694,'64 603T64 616T78 636H668Q675 633 683 623V69Q675 59 668 56H78Q64 63 64 76Q64 91 80 96H643V596H78Q64 603 64 616ZM64 -118Q64 -108 79 -98H678Q694 -104 694 -118Q694 -130 679 -138H78Q64 -131 64 -118'], // stix-square intersection, serifs 0x2293: [598,0,667,61,605,'83 0Q79 0 76 1T71 3T67 6T65 9T63 13T61 16V301L62 585Q70 595 76 598H592Q602 590 605 583V15Q598 2 587 0Q583 0 580 1T575 3T571 6T569 9T567 13T565 16V558H101V15Q94 2 83 0'], // stix-square union, serifs 0x2294: [598,0,667,61,605,'77 0Q65 4 61 16V301L62 585Q72 598 81 598Q94 598 101 583V40H565V583Q573 598 585 598Q598 598 605 583V15Q602 10 592 1L335 0H77'], // stix-circled plus (with rim) 0x2295: [583,83,778,56,722,'56 250Q56 394 156 488T384 583Q530 583 626 485T722 250Q722 110 625 14T390 -83Q249 -83 153 14T56 250ZM364 542Q308 539 251 509T148 418T96 278V270H369V542H364ZM681 278Q675 338 650 386T592 462T522 509T458 535T412 542H409V270H681V278ZM96 222Q104 150 139 95T219 12T302 -29T366 -42H369V230H96V222ZM681 222V230H409V-42H412Q429 -42 456 -36T521 -10T590 37T649 113T681 222'], // CIRCLED MINUS 0x2296: [583,83,778,56,722,'56 250Q56 394 156 488T384 583Q530 583 626 485T722 250Q722 110 625 14T390 -83Q249 -83 153 14T56 250ZM681 278Q669 385 591 463T381 542Q283 542 196 471T96 278V270H681V278ZM275 -42T388 -42T585 32T681 222V230H96V222Q108 107 191 33'], // stix-circled times (with rim) 0x2297: [583,83,778,56,722,'56 250Q56 394 156 488T384 583Q530 583 626 485T722 250Q722 110 625 14T390 -83Q249 -83 153 14T56 250ZM582 471Q531 510 496 523Q446 542 381 542Q324 542 272 519T196 471L389 278L485 375L582 471ZM167 442Q95 362 95 250Q95 137 167 58L359 250L167 442ZM610 58Q682 138 682 250Q682 363 610 442L418 250L610 58ZM196 29Q209 16 230 2T295 -27T388 -42Q409 -42 429 -40T465 -33T496 -23T522 -11T544 1T561 13T574 22T582 29L388 222L196 29'], // CIRCLED DIVISION SLASH 0x2298: [583,83,778,56,722,'56 250Q56 394 156 488T384 583Q530 583 626 485T722 250Q722 110 625 14T390 -83Q249 -83 153 14T56 250ZM582 471Q581 472 571 480T556 491T539 502T517 514T491 525T460 534T424 539T381 542Q272 542 184 460T95 251Q95 198 113 150T149 80L167 58L582 471ZM388 -42Q513 -42 597 44T682 250Q682 363 610 442L196 29Q209 16 229 2T295 -27T388 -42'], // CIRCLED DOT OPERATOR 0x2299: [583,83,778,56,722,'56 250Q56 394 156 488T384 583Q530 583 626 485T722 250Q722 110 625 14T390 -83Q249 -83 153 14T56 250ZM682 250Q682 322 649 387T546 497T381 542Q272 542 184 459T95 250Q95 132 178 45T389 -42Q515 -42 598 45T682 250ZM311 250Q311 285 332 304T375 328Q376 328 382 328T392 329Q424 326 445 305T466 250Q466 217 445 195T389 172Q354 172 333 195T311 250'], // RIGHT TACK 0x22A2: [695,0,611,55,555,'55 678Q55 679 56 681T58 684T61 688T65 691T70 693T77 694Q88 692 95 679V367H540Q555 359 555 347Q555 334 540 327H95V15Q88 2 77 0Q73 0 70 1T65 3T61 6T59 9T57 13T55 16V678'], // LEFT TACK 0x22A3: [695,0,611,54,555,'515 678Q515 679 516 681T518 684T521 688T525 691T530 693T537 694Q548 692 555 679V15Q548 2 537 0Q533 0 530 1T525 3T521 6T519 9T517 13T515 16V327H71Q70 327 67 329T59 336T55 347T59 358T66 365T71 367H515V678'], // DOWN TACK 0x22A4: [668,0,778,55,723,'55 642T55 648T59 659T66 666T71 668H708Q723 660 723 648T708 628H409V15Q402 2 391 0Q387 0 384 1T379 3T375 6T373 9T371 13T369 16V628H71Q70 628 67 630T59 637'], // UP TACK 0x22A5: [669,0,778,54,723,'369 652Q369 653 370 655T372 658T375 662T379 665T384 667T391 668Q402 666 409 653V40H708Q723 32 723 20T708 0H71Q70 0 67 2T59 9T55 20T59 31T66 38T71 40H369V652'], // TRUE 0x22A8: [750,249,867,119,812,'139 -249H137Q125 -249 119 -235V251L120 737Q130 750 139 750Q152 750 159 735V367H796Q811 359 811 347Q811 336 797 328L479 327H161L159 328V172L161 173H797Q798 172 800 171T803 169T805 167T808 164T809 162T810 158T811 153Q811 140 796 133H159V-235Q151 -249 141 -249H139'], // DIAMOND OPERATOR 0x22C4: [488,-12,500,12,488,'242 486Q245 488 250 488Q256 488 258 486Q262 484 373 373T486 258T488 250T486 242T373 127T258 14Q256 12 250 12Q245 12 242 14Q237 16 127 126T14 242Q12 245 12 250T14 258Q16 263 126 373T242 486ZM439 250L250 439L61 250L250 61L439 250'], // DOT OPERATOR 0x22C5: [310,-190,278,78,199,'78 250Q78 274 95 292T138 310Q162 310 180 294T199 251Q199 226 182 208T139 190T96 207T78 250'], // STAR OPERATOR 0x22C6: [486,-16,500,3,497,'210 282Q210 284 225 381T241 480Q241 484 245 484Q249 486 251 486Q258 486 260 477T272 406Q275 390 276 380Q290 286 290 282L388 299Q484 314 487 314H488Q497 314 497 302Q497 297 434 266Q416 257 404 251L315 206L361 118Q372 98 383 75T401 40L407 28Q407 16 395 16Q394 16 392 16L390 17L250 159L110 17L108 16Q106 16 105 16Q93 16 93 28L99 40Q105 52 116 75T139 118L185 206L96 251Q6 296 4 300Q3 301 3 302Q3 314 12 314H13Q16 314 112 299L210 282'], // BOWTIE 0x22C8: [505,5,900,26,873,'833 50T833 250T832 450T659 351T487 250T658 150T832 50Q833 50 833 250ZM873 10Q866 -5 854 -5Q851 -5 845 -3L449 226L260 115Q51 -5 43 -5Q39 -5 35 -1T28 7L26 11V489Q33 505 43 505Q51 505 260 385L449 274L845 503Q851 505 853 505Q866 505 873 490V10ZM412 250L67 450Q66 450 66 250T67 50Q69 51 240 150T412 250'], // VERTICAL ELLIPSIS 0x22EE: [900,30,278,78,199,'78 30Q78 54 95 72T138 90Q162 90 180 74T199 31Q199 6 182 -12T139 -30T96 -13T78 30ZM78 440Q78 464 95 482T138 500Q162 500 180 484T199 441Q199 416 182 398T139 380T96 397T78 440ZM78 840Q78 864 95 882T138 900Q162 900 180 884T199 841Q199 816 182 798T139 780T96 797T78 840'], // MIDLINE HORIZONTAL ELLIPSIS 0x22EF: [310,-190,1172,78,1093,'78 250Q78 274 95 292T138 310Q162 310 180 294T199 251Q199 226 182 208T139 190T96 207T78 250ZM525 250Q525 274 542 292T585 310Q609 310 627 294T646 251Q646 226 629 208T586 190T543 207T525 250ZM972 250Q972 274 989 292T1032 310Q1056 310 1074 294T1093 251Q1093 226 1076 208T1033 190T990 207T972 250'], // DOWN RIGHT DIAGONAL ELLIPSIS 0x22F1: [820,-100,1282,133,1148,'133 760Q133 784 150 802T193 820Q217 820 235 804T254 761Q254 736 237 718T194 700T151 717T133 760ZM580 460Q580 484 597 502T640 520Q664 520 682 504T701 461Q701 436 684 418T641 400T598 417T580 460ZM1027 160Q1027 184 1044 202T1087 220Q1111 220 1129 204T1148 161Q1148 136 1131 118T1088 100T1045 117T1027 160'], // LEFT CEILING 0x2308: [750,250,444,174,422,'174 734Q178 746 190 750H298H369Q400 750 411 747T422 730T411 713T372 709Q365 709 345 709T310 710H214V-235Q206 -248 196 -250Q192 -250 189 -249T184 -247T180 -244T178 -241T176 -237T174 -234V734'], // RIGHT CEILING 0x2309: [750,250,444,21,269,'21 717T21 730T32 746T75 750H147H256Q266 742 269 735V-235Q262 -248 251 -250Q247 -250 244 -249T239 -247T235 -244T233 -241T231 -237T229 -234V710H133Q119 710 99 710T71 709Q43 709 32 713'], // LEFT FLOOR 0x230A: [751,251,444,174,423,'174 734Q174 735 175 737T177 740T180 744T184 747T189 749T196 750Q206 748 214 735V-210H310H373Q401 -210 411 -213T422 -230T411 -247T369 -251Q362 -251 338 -251T298 -250H190Q178 -246 174 -234V734'], // RIGHT FLOOR 0x230B: [751,250,444,21,269,'229 734Q229 735 230 737T232 740T235 744T239 747T244 749T251 750Q262 748 269 735V-235Q266 -240 256 -249L147 -250H77Q43 -250 32 -247T21 -230T32 -213T72 -209Q79 -209 99 -209T133 -210H229V734'], // stix-small down curve 0x2322: [388,-122,1000,55,944,'55 141Q55 149 72 174T125 234T209 303T329 360T478 388H526Q649 383 765 319Q814 291 858 250T923 179T944 141Q944 133 938 128T924 122Q914 124 912 125T902 139Q766 328 500 328Q415 328 342 308T225 258T150 199T102 148T84 124Q81 122 75 122Q55 127 55 141'], // stix-small up curve 0x2323: [378,-134,1000,55,944,'923 378Q944 378 944 358Q944 345 912 311T859 259Q710 134 500 134Q288 134 140 259Q55 336 55 358Q55 366 61 372T75 378Q78 378 84 376Q86 376 101 356T147 310T221 257T339 212T500 193Q628 193 734 236Q841 282 903 363Q914 378 923 378'], // UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION 0x23B0: [744,244,412,56,357,'357 741V726Q357 720 349 715Q261 655 242 539Q240 526 240 454T239 315T239 247Q240 235 240 124V40Q240 -17 233 -53T201 -130Q155 -206 78 -244H69H64Q58 -244 57 -243T56 -234Q56 -232 56 -231V-225Q56 -218 63 -215Q153 -153 170 -39Q172 -25 173 119V219Q173 245 174 249Q173 258 173 376V460Q173 515 178 545T201 611Q244 695 327 741L334 744H354L357 741'], // UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION 0x23B1: [744,244,412,55,357,'78 744Q153 706 196 640T239 492V376Q239 341 239 314T238 271T238 253Q239 251 239 223V119V49Q239 -39 254 -85Q263 -111 275 -134T301 -172T326 -197T346 -213T356 -221T357 -232V-241L354 -244H334Q264 -209 222 -146T174 -12Q173 -6 173 95Q173 134 173 191T174 250Q173 258 173 382V451Q173 542 159 585Q145 626 120 658T75 706T56 723V731Q56 741 57 742T66 744H78'], // MATHEMATICAL LEFT ANGLE BRACKET 0x27E8: [750,250,389,109,333,'333 -232Q332 -239 327 -244T313 -250Q303 -250 296 -240Q293 -233 202 6T110 250T201 494T296 740Q299 745 306 749L309 750Q312 750 313 750Q331 750 333 732Q333 727 243 489Q152 252 152 250T243 11Q333 -227 333 -232'], // MATHEMATICAL RIGHT ANGLE BRACKET 0x27E9: [750,250,389,55,279,'55 732Q56 739 61 744T75 750Q85 750 92 740Q95 733 186 494T278 250T187 6T92 -240Q85 -250 75 -250Q67 -250 62 -245T55 -232Q55 -227 145 11Q236 248 236 250T145 489Q55 727 55 732'], // MATHEMATICAL LEFT FLATTENED PARENTHESIS 0x27EE: [744,244,412,173,357,'357 741V726Q357 720 349 715Q261 655 242 539Q240 526 240 394V331Q240 259 239 250Q240 242 240 119V49Q240 -42 254 -85Q263 -111 275 -134T301 -172T326 -197T346 -213T356 -221T357 -232V-241L354 -244H334Q264 -209 222 -146T174 -12Q173 -6 173 95Q173 134 173 191T174 250Q173 260 173 376V460Q173 515 178 545T201 611Q244 695 327 741L334 744H354L357 741'], // MATHEMATICAL RIGHT FLATTENED PARENTHESIS 0x27EF: [744,244,412,55,240,'78 744Q153 706 196 640T239 492V376Q239 339 239 311T238 269T238 252Q240 236 240 124V40Q240 -18 233 -53T202 -130Q156 -206 79 -244H70H65Q58 -244 57 -242T56 -231T57 -220T64 -215Q153 -154 170 -39Q173 -18 174 119V247Q173 249 173 382V451Q173 542 159 585Q145 626 120 658T75 706T56 723V731Q56 741 57 742T66 744H78'], // LONG LEFTWARDS ARROW 0x27F5: [511,11,1609,55,1525,'165 270H1510Q1525 262 1525 250T1510 230H165Q167 228 182 216T211 189T244 152T277 96T303 25Q308 7 308 0Q308 -11 288 -11Q281 -11 278 -11T272 -7T267 2T263 21Q245 94 195 151T73 236Q58 242 55 247Q55 254 59 257T73 264Q121 283 158 314T215 375T247 434T264 480L267 497Q269 503 270 505T275 509T288 511Q308 511 308 500Q308 493 303 475Q293 438 278 406T246 352T215 315T185 287T165 270'], // LONG RIGHTWARDS ARROW 0x27F6: [511,11,1638,84,1553,'84 237T84 250T98 270H1444Q1328 357 1301 493Q1301 494 1301 496T1300 499Q1300 511 1317 511H1320Q1329 511 1332 510T1338 506T1341 497T1344 481T1352 456Q1374 389 1425 336T1544 261Q1553 258 1553 250Q1553 244 1548 241T1524 231T1486 212Q1445 186 1415 152T1370 85T1349 35T1341 4Q1339 -6 1336 -8T1320 -11Q1300 -11 1300 0Q1300 7 1305 25Q1337 151 1444 230H98Q84 237 84 250'], // LONG LEFT RIGHT ARROW 0x27F7: [511,11,1859,55,1803,'165 270H1694Q1578 357 1551 493Q1551 494 1551 496T1550 499Q1550 511 1567 511H1570Q1579 511 1582 510T1588 506T1591 497T1594 481T1602 456Q1624 389 1675 336T1794 261Q1803 258 1803 250Q1803 244 1798 241T1774 231T1736 212Q1695 186 1665 152T1620 85T1599 35T1591 4Q1589 -6 1586 -8T1570 -11Q1550 -11 1550 0Q1550 7 1555 25Q1587 151 1694 230H165Q167 228 182 216T211 189T244 152T277 96T303 25Q308 7 308 0Q308 -11 288 -11Q281 -11 278 -11T272 -7T267 2T263 21Q245 94 195 151T73 236Q58 242 55 247Q55 254 59 257T73 264Q121 283 158 314T215 375T247 434T264 480L267 497Q269 503 270 505T275 509T288 511Q308 511 308 500Q308 493 303 475Q293 438 278 406T246 352T215 315T185 287T165 270'], // LONG LEFTWARDS DOUBLE ARROW 0x27F8: [525,24,1609,56,1554,'274 173H1539Q1540 172 1542 171T1545 169T1547 167T1550 164T1551 162T1552 158T1553 153Q1553 140 1538 133H318L328 123Q379 69 414 0Q419 -13 419 -17Q419 -24 399 -24Q388 -24 385 -23T377 -12Q332 77 253 144T72 237Q62 240 59 242T56 250T59 257T70 262T89 268T119 278T160 296Q303 366 377 512Q382 522 385 523T401 525Q419 524 419 515Q419 510 414 500Q379 431 328 377L318 367H1538Q1553 359 1553 347Q1553 336 1539 328L1221 327H903L900 328L602 327H274L264 319Q225 289 147 250Q148 249 165 241T210 217T264 181L274 173'], // LONG RIGHTWARDS DOUBLE ARROW 0x27F9: [525,24,1638,56,1582,'1218 514Q1218 525 1234 525Q1239 525 1242 525T1247 525T1251 524T1253 523T1255 520T1257 517T1260 512Q1297 438 1358 381T1469 300T1565 263Q1582 258 1582 250T1573 239T1536 228T1478 204Q1334 134 1260 -12Q1256 -21 1253 -22T1238 -24Q1218 -24 1218 -17Q1218 -13 1223 0Q1258 69 1309 123L1319 133H70Q56 140 56 153Q56 168 72 173H1363L1373 181Q1412 211 1490 250Q1489 251 1472 259T1427 283T1373 319L1363 327H710L707 328L390 327H72Q56 332 56 347Q56 360 70 367H1319L1309 377Q1276 412 1247 458T1218 514'], // LONG LEFT RIGHT DOUBLE ARROW 0x27FA: [525,24,1858,56,1802,'1438 514Q1438 525 1454 525Q1459 525 1462 525T1467 525T1471 524T1473 523T1475 520T1477 517T1480 512Q1517 438 1578 381T1689 300T1785 263Q1802 258 1802 250T1793 239T1756 228T1698 204Q1554 134 1480 -12Q1476 -21 1473 -22T1458 -24Q1438 -24 1438 -17Q1438 -13 1443 0Q1478 69 1529 123L1539 133H318L328 123Q379 69 414 0Q419 -13 419 -17Q419 -24 399 -24Q388 -24 385 -23T377 -12Q332 77 253 144T72 237Q62 240 59 242T56 250T59 257T70 262T89 268T119 278T160 296Q303 366 377 512Q382 522 385 523T401 525Q419 524 419 515Q419 510 414 500Q379 431 328 377L318 367H1539L1529 377Q1496 412 1467 458T1438 514ZM274 173H1583L1593 181Q1632 211 1710 250Q1709 251 1692 259T1647 283T1593 319L1583 327H930L927 328L602 327H274L264 319Q225 289 147 250Q148 249 165 241T210 217T264 181L274 173'], // LONG RIGHTWARDS ARROW FROM BAR 0x27FC: [511,11,1638,54,1553,'95 155V109Q95 83 92 73T75 63Q61 63 58 74T54 130Q54 140 54 180T55 250Q55 421 57 425Q61 437 75 437Q88 437 91 428T95 393V345V270H1444Q1328 357 1301 493Q1301 494 1301 496T1300 499Q1300 511 1317 511H1320Q1329 511 1332 510T1338 506T1341 497T1344 481T1352 456Q1374 389 1425 336T1544 261Q1553 258 1553 250Q1553 244 1548 241T1524 231T1486 212Q1445 186 1415 152T1370 85T1349 35T1341 4Q1339 -6 1336 -8T1320 -11Q1300 -11 1300 0Q1300 7 1305 25Q1337 151 1444 230H95V155'], // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN 0x2AAF: [636,138,778,84,694,'84 346Q84 359 91 363T117 367Q120 367 126 367T137 366Q388 370 512 430T653 609Q657 636 676 636Q685 635 689 629T694 618V612Q689 566 672 528T626 463T569 417T500 383T435 362T373 346Q379 345 404 339T440 330T477 318T533 296Q592 266 630 223T681 145T694 78Q694 57 674 57Q662 57 657 67T652 92T640 135T606 191Q500 320 137 326H114Q104 326 98 327T88 332T84 346ZM84 -131T84 -118T98 -98H679Q694 -106 694 -118T679 -138H98Q84 -131 84 -118'], // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN 0x2AB0: [636,138,778,83,694,'84 614Q84 636 102 636Q115 636 119 626T125 600T137 556T171 501Q277 372 640 366H661Q694 366 694 346T661 326H640Q578 325 526 321T415 307T309 280T222 237T156 172T124 83Q122 66 118 62T103 57Q100 57 98 57T95 58T93 59T90 62T85 67Q83 71 83 80Q88 126 105 164T151 229T208 275T277 309T342 330T404 346Q401 347 380 351T345 360T302 373T245 396Q125 455 92 565Q84 599 84 614ZM84 -131T84 -118T98 -98H679Q694 -106 694 -118T679 -138H98Q84 -131 84 -118'] }; SVG.FONTDATA.FONTS['MathJax_Math-italic'] = { directory: 'Math/Italic', family: 'MathJax_Math', id: 'MJMATHI', style: 'italic', skew: { 0x41: 0.139, 0x42: 0.0833, 0x43: 0.0833, 0x44: 0.0556, 0x45: 0.0833, 0x46: 0.0833, 0x47: 0.0833, 0x48: 0.0556, 0x49: 0.111, 0x4A: 0.167, 0x4B: 0.0556, 0x4C: 0.0278, 0x4D: 0.0833, 0x4E: 0.0833, 0x4F: 0.0833, 0x50: 0.0833, 0x51: 0.0833, 0x52: 0.0833, 0x53: 0.0833, 0x54: 0.0833, 0x55: 0.0278, 0x58: 0.0833, 0x5A: 0.0833, 0x63: 0.0556, 0x64: 0.167, 0x65: 0.0556, 0x66: 0.167, 0x67: 0.0278, 0x68: -0.0278, 0x6C: 0.0833, 0x6F: 0.0556, 0x70: 0.0833, 0x71: 0.0833, 0x72: 0.0556, 0x73: 0.0556, 0x74: 0.0833, 0x75: 0.0278, 0x76: 0.0278, 0x77: 0.0833, 0x78: 0.0278, 0x79: 0.0556, 0x7A: 0.0556, 0x393: 0.0833, 0x394: 0.167, 0x398: 0.0833, 0x39B: 0.167, 0x39E: 0.0833, 0x3A0: 0.0556, 0x3A3: 0.0833, 0x3A5: 0.0556, 0x3A6: 0.0833, 0x3A8: 0.0556, 0x3A9: 0.0833, 0x3B1: 0.0278, 0x3B2: 0.0833, 0x3B4: 0.0556, 0x3B5: 0.0833, 0x3B6: 0.0833, 0x3B7: 0.0556, 0x3B8: 0.0833, 0x3B9: 0.0556, 0x3BC: 0.0278, 0x3BD: 0.0278, 0x3BE: 0.111, 0x3BF: 0.0556, 0x3C1: 0.0833, 0x3C2: 0.0833, 0x3C4: 0.0278, 0x3C5: 0.0278, 0x3C6: 0.0833, 0x3C7: 0.0556, 0x3C8: 0.111, 0x3D1: 0.0833, 0x3D5: 0.0833, 0x3F1: 0.0833, 0x3F5: 0.0556 }, // SPACE 0x20: [0,0,250,0,0,''], // SOLIDUS 0x2F: [716,215,778,139,638,'166 -215T159 -215T147 -212T141 -204T139 -197Q139 -190 144 -183Q157 -157 378 274T602 707Q605 716 618 716Q625 716 630 712T636 703T638 696Q638 691 406 241T170 -212Q166 -215 159 -215'], // LATIN CAPITAL LETTER A 0x41: [716,0,750,35,726,'208 74Q208 50 254 46Q272 46 272 35Q272 34 270 22Q267 8 264 4T251 0Q249 0 239 0T205 1T141 2Q70 2 50 0H42Q35 7 35 11Q37 38 48 46H62Q132 49 164 96Q170 102 345 401T523 704Q530 716 547 716H555H572Q578 707 578 706L606 383Q634 60 636 57Q641 46 701 46Q726 46 726 36Q726 34 723 22Q720 7 718 4T704 0Q701 0 690 0T651 1T578 2Q484 2 455 0H443Q437 6 437 9T439 27Q443 40 445 43L449 46H469Q523 49 533 63L521 213H283L249 155Q208 86 208 74ZM516 260Q516 271 504 416T490 562L463 519Q447 492 400 412L310 260L413 259Q516 259 516 260'], // LATIN CAPITAL LETTER B 0x42: [683,0,759,35,756,'231 637Q204 637 199 638T194 649Q194 676 205 682Q206 683 335 683Q594 683 608 681Q671 671 713 636T756 544Q756 480 698 429T565 360L555 357Q619 348 660 311T702 219Q702 146 630 78T453 1Q446 0 242 0Q42 0 39 2Q35 5 35 10Q35 17 37 24Q42 43 47 45Q51 46 62 46H68Q95 46 128 49Q142 52 147 61Q150 65 219 339T288 628Q288 635 231 637ZM649 544Q649 574 634 600T585 634Q578 636 493 637Q473 637 451 637T416 636H403Q388 635 384 626Q382 622 352 506Q352 503 351 500L320 374H401Q482 374 494 376Q554 386 601 434T649 544ZM595 229Q595 273 572 302T512 336Q506 337 429 337Q311 337 310 336Q310 334 293 263T258 122L240 52Q240 48 252 48T333 46Q422 46 429 47Q491 54 543 105T595 229'], // LATIN CAPITAL LETTER C 0x43: [705,22,715,50,760,'50 252Q50 367 117 473T286 641T490 704Q580 704 633 653Q642 643 648 636T656 626L657 623Q660 623 684 649Q691 655 699 663T715 679T725 690L740 705H746Q760 705 760 698Q760 694 728 561Q692 422 692 421Q690 416 687 415T669 413H653Q647 419 647 422Q647 423 648 429T650 449T651 481Q651 552 619 605T510 659Q484 659 454 652T382 628T299 572T226 479Q194 422 175 346T156 222Q156 108 232 58Q280 24 350 24Q441 24 512 92T606 240Q610 253 612 255T628 257Q648 257 648 248Q648 243 647 239Q618 132 523 55T319 -22Q206 -22 128 53T50 252'], // LATIN CAPITAL LETTER D 0x44: [683,0,828,33,803,'287 628Q287 635 230 637Q207 637 200 638T193 647Q193 655 197 667T204 682Q206 683 403 683Q570 682 590 682T630 676Q702 659 752 597T803 431Q803 275 696 151T444 3L430 1L236 0H125H72Q48 0 41 2T33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM703 469Q703 507 692 537T666 584T629 613T590 629T555 636Q553 636 541 636T512 636T479 637H436Q392 637 386 627Q384 623 313 339T242 52Q242 48 253 48T330 47Q335 47 349 47T373 46Q499 46 581 128Q617 164 640 212T683 339T703 469'], // LATIN CAPITAL LETTER E 0x45: [680,0,738,31,764,'492 213Q472 213 472 226Q472 230 477 250T482 285Q482 316 461 323T364 330H312Q311 328 277 192T243 52Q243 48 254 48T334 46Q428 46 458 48T518 61Q567 77 599 117T670 248Q680 270 683 272Q690 274 698 274Q718 274 718 261Q613 7 608 2Q605 0 322 0H133Q31 0 31 11Q31 13 34 25Q38 41 42 43T65 46Q92 46 125 49Q139 52 144 61Q146 66 215 342T285 622Q285 629 281 629Q273 632 228 634H197Q191 640 191 642T193 659Q197 676 203 680H757Q764 676 764 669Q764 664 751 557T737 447Q735 440 717 440H705Q698 445 698 453L701 476Q704 500 704 528Q704 558 697 578T678 609T643 625T596 632T532 634H485Q397 633 392 631Q388 629 386 622Q385 619 355 499T324 377Q347 376 372 376H398Q464 376 489 391T534 472Q538 488 540 490T557 493Q562 493 565 493T570 492T572 491T574 487T577 483L544 351Q511 218 508 216Q505 213 492 213'], // LATIN CAPITAL LETTER F 0x46: [680,0,643,31,749,'48 1Q31 1 31 11Q31 13 34 25Q38 41 42 43T65 46Q92 46 125 49Q139 52 144 61Q146 66 215 342T285 622Q285 629 281 629Q273 632 228 634H197Q191 640 191 642T193 659Q197 676 203 680H742Q749 676 749 669Q749 664 736 557T722 447Q720 440 702 440H690Q683 445 683 453Q683 454 686 477T689 530Q689 560 682 579T663 610T626 626T575 633T503 634H480Q398 633 393 631Q388 629 386 623Q385 622 352 492L320 363H375Q378 363 398 363T426 364T448 367T472 374T489 386Q502 398 511 419T524 457T529 475Q532 480 548 480H560Q567 475 567 470Q567 467 536 339T502 207Q500 200 482 200H470Q463 206 463 212Q463 215 468 234T473 274Q473 303 453 310T364 317H309L277 190Q245 66 245 60Q245 46 334 46H359Q365 40 365 39T363 19Q359 6 353 0H336Q295 2 185 2Q120 2 86 2T48 1'], // LATIN CAPITAL LETTER G 0x47: [705,22,786,50,760,'50 252Q50 367 117 473T286 641T490 704Q580 704 633 653Q642 643 648 636T656 626L657 623Q660 623 684 649Q691 655 699 663T715 679T725 690L740 705H746Q760 705 760 698Q760 694 728 561Q692 422 692 421Q690 416 687 415T669 413H653Q647 419 647 422Q647 423 648 429T650 449T651 481Q651 552 619 605T510 659Q492 659 471 656T418 643T357 615T294 567T236 496T189 394T158 260Q156 242 156 221Q156 173 170 136T206 79T256 45T308 28T353 24Q407 24 452 47T514 106Q517 114 529 161T541 214Q541 222 528 224T468 227H431Q425 233 425 235T427 254Q431 267 437 273H454Q494 271 594 271Q634 271 659 271T695 272T707 272Q721 272 721 263Q721 261 719 249Q714 230 709 228Q706 227 694 227Q674 227 653 224Q646 221 643 215T629 164Q620 131 614 108Q589 6 586 3Q584 1 581 1Q571 1 553 21T530 52Q530 53 528 52T522 47Q448 -22 322 -22Q201 -22 126 55T50 252'], // LATIN CAPITAL LETTER H 0x48: [683,0,831,31,888,'228 637Q194 637 192 641Q191 643 191 649Q191 673 202 682Q204 683 219 683Q260 681 355 681Q389 681 418 681T463 682T483 682Q499 682 499 672Q499 670 497 658Q492 641 487 638H485Q483 638 480 638T473 638T464 637T455 637Q416 636 405 634T387 623Q384 619 355 500Q348 474 340 442T328 395L324 380Q324 378 469 378H614L615 381Q615 384 646 504Q674 619 674 627T617 637Q594 637 587 639T580 648Q580 650 582 660Q586 677 588 679T604 682Q609 682 646 681T740 680Q802 680 835 681T871 682Q888 682 888 672Q888 645 876 638H874Q872 638 869 638T862 638T853 637T844 637Q805 636 794 634T776 623Q773 618 704 340T634 58Q634 51 638 51Q646 48 692 46H723Q729 38 729 37T726 19Q722 6 716 0H701Q664 2 567 2Q533 2 504 2T458 2T437 1Q420 1 420 10Q420 15 423 24Q428 43 433 45Q437 46 448 46H454Q481 46 514 49Q520 50 522 50T528 55T534 64T540 82T547 110T558 153Q565 181 569 198Q602 330 602 331T457 332H312L279 197Q245 63 245 58Q245 51 253 49T303 46H334Q340 38 340 37T337 19Q333 6 327 0H312Q275 2 178 2Q144 2 115 2T69 2T48 1Q31 1 31 10Q31 12 34 24Q39 43 44 45Q48 46 59 46H65Q92 46 125 49Q139 52 144 61Q147 65 216 339T285 628Q285 635 228 637'], // LATIN CAPITAL LETTER I 0x49: [683,0,440,26,504,'43 1Q26 1 26 10Q26 12 29 24Q34 43 39 45Q42 46 54 46H60Q120 46 136 53Q137 53 138 54Q143 56 149 77T198 273Q210 318 216 344Q286 624 286 626Q284 630 284 631Q274 637 213 637H193Q184 643 189 662Q193 677 195 680T209 683H213Q285 681 359 681Q481 681 487 683H497Q504 676 504 672T501 655T494 639Q491 637 471 637Q440 637 407 634Q393 631 388 623Q381 609 337 432Q326 385 315 341Q245 65 245 59Q245 52 255 50T307 46H339Q345 38 345 37T342 19Q338 6 332 0H316Q279 2 179 2Q143 2 113 2T65 2T43 1'], // LATIN CAPITAL LETTER J 0x4A: [683,22,555,57,633,'447 625Q447 637 354 637H329Q323 642 323 645T325 664Q329 677 335 683H352Q393 681 498 681Q541 681 568 681T605 682T619 682Q633 682 633 672Q633 670 630 658Q626 642 623 640T604 637Q552 637 545 623Q541 610 483 376Q420 128 419 127Q397 64 333 21T195 -22Q137 -22 97 8T57 88Q57 130 80 152T132 174Q177 174 182 130Q182 98 164 80T123 56Q115 54 115 53T122 44Q148 15 197 15Q235 15 271 47T324 130Q328 142 387 380T447 625'], // LATIN CAPITAL LETTER K 0x4B: [683,0,849,31,889,'285 628Q285 635 228 637Q205 637 198 638T191 647Q191 649 193 661Q199 681 203 682Q205 683 214 683H219Q260 681 355 681Q389 681 418 681T463 682T483 682Q500 682 500 674Q500 669 497 660Q496 658 496 654T495 648T493 644T490 641T486 639T479 638T470 637T456 637Q416 636 405 634T387 623L306 305Q307 305 490 449T678 597Q692 611 692 620Q692 635 667 637Q651 637 651 648Q651 650 654 662T659 677Q662 682 676 682Q680 682 711 681T791 680Q814 680 839 681T869 682Q889 682 889 672Q889 650 881 642Q878 637 862 637Q787 632 726 586Q710 576 656 534T556 455L509 418L518 396Q527 374 546 329T581 244Q656 67 661 61Q663 59 666 57Q680 47 717 46H738Q744 38 744 37T741 19Q737 6 731 0H720Q680 3 625 3Q503 3 488 0H478Q472 6 472 9T474 27Q478 40 480 43T491 46H494Q544 46 544 71Q544 75 517 141T485 216L427 354L359 301L291 248L268 155Q245 63 245 58Q245 51 253 49T303 46H334Q340 37 340 35Q340 19 333 5Q328 0 317 0Q314 0 280 1T180 2Q118 2 85 2T49 1Q31 1 31 11Q31 13 34 25Q38 41 42 43T65 46Q92 46 125 49Q139 52 144 61Q147 65 216 339T285 628'], // LATIN CAPITAL LETTER L 0x4C: [683,2,681,32,647,'228 637Q194 637 192 641Q191 643 191 649Q191 673 202 682Q204 683 217 683Q271 680 344 680Q485 680 506 683H518Q524 677 524 674T522 656Q517 641 513 637H475Q406 636 394 628Q387 624 380 600T313 336Q297 271 279 198T252 88L243 52Q243 48 252 48T311 46H328Q360 46 379 47T428 54T478 72T522 106T564 161Q580 191 594 228T611 270Q616 273 628 273H641Q647 264 647 262T627 203T583 83T557 9Q555 4 553 3T537 0T494 -1Q483 -1 418 -1T294 0H116Q32 0 32 10Q32 17 34 24Q39 43 44 45Q48 46 59 46H65Q92 46 125 49Q139 52 144 61Q147 65 216 339T285 628Q285 635 228 637'], // LATIN CAPITAL LETTER M 0x4D: [684,0,970,35,1051,'289 629Q289 635 232 637Q208 637 201 638T194 648Q194 649 196 659Q197 662 198 666T199 671T201 676T203 679T207 681T212 683T220 683T232 684Q238 684 262 684T307 683Q386 683 398 683T414 678Q415 674 451 396L487 117L510 154Q534 190 574 254T662 394Q837 673 839 675Q840 676 842 678T846 681L852 683H948Q965 683 988 683T1017 684Q1051 684 1051 673Q1051 668 1048 656T1045 643Q1041 637 1008 637Q968 636 957 634T939 623Q936 618 867 340T797 59Q797 55 798 54T805 50T822 48T855 46H886Q892 37 892 35Q892 19 885 5Q880 0 869 0Q864 0 828 1T736 2Q675 2 644 2T609 1Q592 1 592 11Q592 13 594 25Q598 41 602 43T625 46Q652 46 685 49Q699 52 704 61Q706 65 742 207T813 490T848 631L654 322Q458 10 453 5Q451 4 449 3Q444 0 433 0Q418 0 415 7Q413 11 374 317L335 624L267 354Q200 88 200 79Q206 46 272 46H282Q288 41 289 37T286 19Q282 3 278 1Q274 0 267 0Q265 0 255 0T221 1T157 2Q127 2 95 1T58 0Q43 0 39 2T35 11Q35 13 38 25T43 40Q45 46 65 46Q135 46 154 86Q158 92 223 354T289 629'], // LATIN CAPITAL LETTER N 0x4E: [683,0,803,31,888,'234 637Q231 637 226 637Q201 637 196 638T191 649Q191 676 202 682Q204 683 299 683Q376 683 387 683T401 677Q612 181 616 168L670 381Q723 592 723 606Q723 633 659 637Q635 637 635 648Q635 650 637 660Q641 676 643 679T653 683Q656 683 684 682T767 680Q817 680 843 681T873 682Q888 682 888 672Q888 650 880 642Q878 637 858 637Q787 633 769 597L620 7Q618 0 599 0Q585 0 582 2Q579 5 453 305L326 604L261 344Q196 88 196 79Q201 46 268 46H278Q284 41 284 38T282 19Q278 6 272 0H259Q228 2 151 2Q123 2 100 2T63 2T46 1Q31 1 31 10Q31 14 34 26T39 40Q41 46 62 46Q130 49 150 85Q154 91 221 362L289 634Q287 635 234 637'], // LATIN CAPITAL LETTER O 0x4F: [704,22,763,50,740,'740 435Q740 320 676 213T511 42T304 -22Q207 -22 138 35T51 201Q50 209 50 244Q50 346 98 438T227 601Q351 704 476 704Q514 704 524 703Q621 689 680 617T740 435ZM637 476Q637 565 591 615T476 665Q396 665 322 605Q242 542 200 428T157 216Q157 126 200 73T314 19Q404 19 485 98T608 313Q637 408 637 476'], // LATIN CAPITAL LETTER P 0x50: [683,0,642,33,751,'287 628Q287 635 230 637Q206 637 199 638T192 648Q192 649 194 659Q200 679 203 681T397 683Q587 682 600 680Q664 669 707 631T751 530Q751 453 685 389Q616 321 507 303Q500 302 402 301H307L277 182Q247 66 247 59Q247 55 248 54T255 50T272 48T305 46H336Q342 37 342 35Q342 19 335 5Q330 0 319 0Q316 0 282 1T182 2Q120 2 87 2T51 1Q33 1 33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM645 554Q645 567 643 575T634 597T609 619T560 635Q553 636 480 637Q463 637 445 637T416 636T404 636Q391 635 386 627Q384 621 367 550T332 412T314 344Q314 342 395 342H407H430Q542 342 590 392Q617 419 631 471T645 554'], // LATIN CAPITAL LETTER Q 0x51: [704,194,791,50,740,'399 -80Q399 -47 400 -30T402 -11V-7L387 -11Q341 -22 303 -22Q208 -22 138 35T51 201Q50 209 50 244Q50 346 98 438T227 601Q351 704 476 704Q514 704 524 703Q621 689 680 617T740 435Q740 255 592 107Q529 47 461 16L444 8V3Q444 2 449 -24T470 -66T516 -82Q551 -82 583 -60T625 -3Q631 11 638 11Q647 11 649 2Q649 -6 639 -34T611 -100T557 -165T481 -194Q399 -194 399 -87V-80ZM636 468Q636 523 621 564T580 625T530 655T477 665Q429 665 379 640Q277 591 215 464T153 216Q153 110 207 59Q231 38 236 38V46Q236 86 269 120T347 155Q372 155 390 144T417 114T429 82T435 55L448 64Q512 108 557 185T619 334T636 468ZM314 18Q362 18 404 39L403 49Q399 104 366 115Q354 117 347 117Q344 117 341 117T337 118Q317 118 296 98T274 52Q274 18 314 18'], // LATIN CAPITAL LETTER R 0x52: [683,21,759,33,755,'230 637Q203 637 198 638T193 649Q193 676 204 682Q206 683 378 683Q550 682 564 680Q620 672 658 652T712 606T733 563T739 529Q739 484 710 445T643 385T576 351T538 338L545 333Q612 295 612 223Q612 212 607 162T602 80V71Q602 53 603 43T614 25T640 16Q668 16 686 38T712 85Q717 99 720 102T735 105Q755 105 755 93Q755 75 731 36Q693 -21 641 -21H632Q571 -21 531 4T487 82Q487 109 502 166T517 239Q517 290 474 313Q459 320 449 321T378 323H309L277 193Q244 61 244 59Q244 55 245 54T252 50T269 48T302 46H333Q339 38 339 37T336 19Q332 6 326 0H311Q275 2 180 2Q146 2 117 2T71 2T50 1Q33 1 33 10Q33 12 36 24Q41 43 46 45Q50 46 61 46H67Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628Q287 635 230 637ZM630 554Q630 586 609 608T523 636Q521 636 500 636T462 637H440Q393 637 386 627Q385 624 352 494T319 361Q319 360 388 360Q466 361 492 367Q556 377 592 426Q608 449 619 486T630 554'], // LATIN CAPITAL LETTER S 0x53: [705,22,613,52,645,'308 24Q367 24 416 76T466 197Q466 260 414 284Q308 311 278 321T236 341Q176 383 176 462Q176 523 208 573T273 648Q302 673 343 688T407 704H418H425Q521 704 564 640Q565 640 577 653T603 682T623 704Q624 704 627 704T632 705Q645 705 645 698T617 577T585 459T569 456Q549 456 549 465Q549 471 550 475Q550 478 551 494T553 520Q553 554 544 579T526 616T501 641Q465 662 419 662Q362 662 313 616T263 510Q263 480 278 458T319 427Q323 425 389 408T456 390Q490 379 522 342T554 242Q554 216 546 186Q541 164 528 137T492 78T426 18T332 -20Q320 -22 298 -22Q199 -22 144 33L134 44L106 13Q83 -14 78 -18T65 -22Q52 -22 52 -14Q52 -11 110 221Q112 227 130 227H143Q149 221 149 216Q149 214 148 207T144 186T142 153Q144 114 160 87T203 47T255 29T308 24'], // LATIN CAPITAL LETTER T 0x54: [677,0,584,21,704,'40 437Q21 437 21 445Q21 450 37 501T71 602L88 651Q93 669 101 677H569H659Q691 677 697 676T704 667Q704 661 687 553T668 444Q668 437 649 437Q640 437 637 437T631 442L629 445Q629 451 635 490T641 551Q641 586 628 604T573 629Q568 630 515 631Q469 631 457 630T439 622Q438 621 368 343T298 60Q298 48 386 46Q418 46 427 45T436 36Q436 31 433 22Q429 4 424 1L422 0Q419 0 415 0Q410 0 363 1T228 2Q99 2 64 0H49Q43 6 43 9T45 27Q49 40 55 46H83H94Q174 46 189 55Q190 56 191 56Q196 59 201 76T241 233Q258 301 269 344Q339 619 339 625Q339 630 310 630H279Q212 630 191 624Q146 614 121 583T67 467Q60 445 57 441T43 437H40'], // LATIN CAPITAL LETTER U 0x55: [683,22,683,60,767,'107 637Q73 637 71 641Q70 643 70 649Q70 673 81 682Q83 683 98 683Q139 681 234 681Q268 681 297 681T342 682T362 682Q378 682 378 672Q378 670 376 658Q371 641 366 638H364Q362 638 359 638T352 638T343 637T334 637Q295 636 284 634T266 623Q265 621 238 518T184 302T154 169Q152 155 152 140Q152 86 183 55T269 24Q336 24 403 69T501 205L552 406Q599 598 599 606Q599 633 535 637Q511 637 511 648Q511 650 513 660Q517 676 519 679T529 683Q532 683 561 682T645 680Q696 680 723 681T752 682Q767 682 767 672Q767 650 759 642Q756 637 737 637Q666 633 648 597Q646 592 598 404Q557 235 548 205Q515 105 433 42T263 -22Q171 -22 116 34T60 167V183Q60 201 115 421Q164 622 164 628Q164 635 107 637'], // LATIN CAPITAL LETTER V 0x56: [683,22,583,52,769,'52 648Q52 670 65 683H76Q118 680 181 680Q299 680 320 683H330Q336 677 336 674T334 656Q329 641 325 637H304Q282 635 274 635Q245 630 242 620Q242 618 271 369T301 118L374 235Q447 352 520 471T595 594Q599 601 599 609Q599 633 555 637Q537 637 537 648Q537 649 539 661Q542 675 545 679T558 683Q560 683 570 683T604 682T668 681Q737 681 755 683H762Q769 676 769 672Q769 655 760 640Q757 637 743 637Q730 636 719 635T698 630T682 623T670 615T660 608T652 599T645 592L452 282Q272 -9 266 -16Q263 -18 259 -21L241 -22H234Q216 -22 216 -15Q213 -9 177 305Q139 623 138 626Q133 637 76 637H59Q52 642 52 648'], // LATIN CAPITAL LETTER W 0x57: [683,22,944,51,1048,'436 683Q450 683 486 682T553 680Q604 680 638 681T677 682Q695 682 695 674Q695 670 692 659Q687 641 683 639T661 637Q636 636 621 632T600 624T597 615Q597 603 613 377T629 138L631 141Q633 144 637 151T649 170T666 200T690 241T720 295T759 362Q863 546 877 572T892 604Q892 619 873 628T831 637Q817 637 817 647Q817 650 819 660Q823 676 825 679T839 682Q842 682 856 682T895 682T949 681Q1015 681 1034 683Q1048 683 1048 672Q1048 666 1045 655T1038 640T1028 637Q1006 637 988 631T958 617T939 600T927 584L923 578L754 282Q586 -14 585 -15Q579 -22 561 -22Q546 -22 542 -17Q539 -14 523 229T506 480L494 462Q472 425 366 239Q222 -13 220 -15T215 -19Q210 -22 197 -22Q178 -22 176 -15Q176 -12 154 304T131 622Q129 631 121 633T82 637H58Q51 644 51 648Q52 671 64 683H76Q118 680 176 680Q301 680 313 683H323Q329 677 329 674T327 656Q322 641 318 637H297Q236 634 232 620Q262 160 266 136L501 550L499 587Q496 629 489 632Q483 636 447 637Q428 637 422 639T416 648Q416 650 418 660Q419 664 420 669T421 676T424 680T428 682T436 683'], // LATIN CAPITAL LETTER X 0x58: [683,0,828,26,852,'42 0H40Q26 0 26 11Q26 15 29 27Q33 41 36 43T55 46Q141 49 190 98Q200 108 306 224T411 342Q302 620 297 625Q288 636 234 637H206Q200 643 200 645T202 664Q206 677 212 683H226Q260 681 347 681Q380 681 408 681T453 682T473 682Q490 682 490 671Q490 670 488 658Q484 643 481 640T465 637Q434 634 411 620L488 426L541 485Q646 598 646 610Q646 628 622 635Q617 635 609 637Q594 637 594 648Q594 650 596 664Q600 677 606 683H618Q619 683 643 683T697 681T738 680Q828 680 837 683H845Q852 676 852 672Q850 647 840 637H824Q790 636 763 628T722 611T698 593L687 584Q687 585 592 480L505 384Q505 383 536 304T601 142T638 56Q648 47 699 46Q734 46 734 37Q734 35 732 23Q728 7 725 4T711 1Q708 1 678 1T589 2Q528 2 496 2T461 1Q444 1 444 10Q444 11 446 25Q448 35 450 39T455 44T464 46T480 47T506 54Q523 62 523 64Q522 64 476 181L429 299Q241 95 236 84Q232 76 232 72Q232 53 261 47Q262 47 267 47T273 46Q276 46 277 46T280 45T283 42T284 35Q284 26 282 19Q279 6 276 4T261 1Q258 1 243 1T201 2T142 2Q64 2 42 0'], // LATIN CAPITAL LETTER Y 0x59: [683,-1,581,30,763,'66 637Q54 637 49 637T39 638T32 641T30 647T33 664T42 682Q44 683 56 683Q104 680 165 680Q288 680 306 683H316Q322 677 322 674T320 656Q316 643 310 637H298Q242 637 242 624Q242 619 292 477T343 333L346 336Q350 340 358 349T379 373T411 410T454 461Q546 568 561 587T577 618Q577 634 545 637Q528 637 528 647Q528 649 530 661Q533 676 535 679T549 683Q551 683 578 682T657 680Q684 680 713 681T746 682Q763 682 763 673Q763 669 760 657T755 643Q753 637 734 637Q662 632 617 587Q608 578 477 424L348 273L322 169Q295 62 295 57Q295 46 363 46Q379 46 384 45T390 35Q390 33 388 23Q384 6 382 4T366 1Q361 1 324 1T232 2Q170 2 138 2T102 1Q84 1 84 9Q84 14 87 24Q88 27 89 30T90 35T91 39T93 42T96 44T101 45T107 45T116 46T129 46Q168 47 180 50T198 63Q201 68 227 171L252 274L129 623Q128 624 127 625T125 627T122 629T118 631T113 633T105 634T96 635T83 636T66 637'], // LATIN CAPITAL LETTER Z 0x5A: [683,0,683,58,723,'58 8Q58 23 64 35Q64 36 329 334T596 635L586 637Q575 637 512 637H500H476Q442 637 420 635T365 624T311 598T266 548T228 469Q227 466 226 463T224 458T223 453T222 450L221 448Q218 443 202 443Q185 443 182 453L214 561Q228 606 241 651Q249 679 253 681Q256 683 487 683H718Q723 678 723 675Q723 673 717 649Q189 54 188 52L185 49H274Q369 50 377 51Q452 60 500 100T579 247Q587 272 590 277T603 282H607Q628 282 628 271Q547 5 541 2Q538 0 300 0H124Q58 0 58 8'], // LATIN SMALL LETTER A 0x61: [441,10,529,33,506,'33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328'], // LATIN SMALL LETTER B 0x62: [694,11,429,40,422,'73 647Q73 657 77 670T89 683Q90 683 161 688T234 694Q246 694 246 685T212 542Q204 508 195 472T180 418L176 399Q176 396 182 402Q231 442 283 442Q345 442 383 396T422 280Q422 169 343 79T173 -11Q123 -11 82 27T40 150V159Q40 180 48 217T97 414Q147 611 147 623T109 637Q104 637 101 637H96Q86 637 83 637T76 640T73 647ZM336 325V331Q336 405 275 405Q258 405 240 397T207 376T181 352T163 330L157 322L136 236Q114 150 114 114Q114 66 138 42Q154 26 178 26Q211 26 245 58Q270 81 285 114T318 219Q336 291 336 325'], // LATIN SMALL LETTER C 0x63: [442,12,433,34,430,'34 159Q34 268 120 355T306 442Q362 442 394 418T427 355Q427 326 408 306T360 285Q341 285 330 295T319 325T330 359T352 380T366 386H367Q367 388 361 392T340 400T306 404Q276 404 249 390Q228 381 206 359Q162 315 142 235T121 119Q121 73 147 50Q169 26 205 26H209Q321 26 394 111Q403 121 406 121Q410 121 419 112T429 98T420 83T391 55T346 25T282 0T202 -11Q127 -11 81 37T34 159'], // LATIN SMALL LETTER D 0x64: [694,10,520,33,523,'366 683Q367 683 438 688T511 694Q523 694 523 686Q523 679 450 384T375 83T374 68Q374 26 402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487H491Q506 153 506 145Q506 140 503 129Q490 79 473 48T445 8T417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157Q33 205 53 255T101 341Q148 398 195 420T280 442Q336 442 364 400Q369 394 369 396Q370 400 396 505T424 616Q424 629 417 632T378 637H357Q351 643 351 645T353 664Q358 683 366 683ZM352 326Q329 405 277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q233 26 290 98L298 109L352 326'], // LATIN SMALL LETTER E 0x65: [443,11,466,39,430,'39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353'], // LATIN SMALL LETTER F 0x66: [705,205,490,55,550,'118 -162Q120 -162 124 -164T135 -167T147 -168Q160 -168 171 -155T187 -126Q197 -99 221 27T267 267T289 382V385H242Q195 385 192 387Q188 390 188 397L195 425Q197 430 203 430T250 431Q298 431 298 432Q298 434 307 482T319 540Q356 705 465 705Q502 703 526 683T550 630Q550 594 529 578T487 561Q443 561 443 603Q443 622 454 636T478 657L487 662Q471 668 457 668Q445 668 434 658T419 630Q412 601 403 552T387 469T380 433Q380 431 435 431Q480 431 487 430T498 424Q499 420 496 407T491 391Q489 386 482 386T428 385H372L349 263Q301 15 282 -47Q255 -132 212 -173Q175 -205 139 -205Q107 -205 81 -186T55 -132Q55 -95 76 -78T118 -61Q162 -61 162 -103Q162 -122 151 -136T127 -157L118 -162'], // LATIN SMALL LETTER G 0x67: [442,205,477,10,480,'311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328'], // LATIN SMALL LETTER H 0x68: [694,11,576,48,555,'137 683Q138 683 209 688T282 694Q294 694 294 685Q294 674 258 534Q220 386 220 383Q220 381 227 388Q288 442 357 442Q411 442 444 415T478 336Q478 285 440 178T402 50Q403 36 407 31T422 26Q450 26 474 56T513 138Q516 149 519 151T535 153Q555 153 555 145Q555 144 551 130Q535 71 500 33Q466 -10 419 -10H414Q367 -10 346 17T325 74Q325 90 361 192T398 345Q398 404 354 404H349Q266 404 205 306L198 293L164 158Q132 28 127 16Q114 -11 83 -11Q69 -11 59 -2T48 16Q48 30 121 320L195 616Q195 629 188 632T149 637H128Q122 643 122 645T124 664Q129 683 137 683'], // LATIN SMALL LETTER I 0x69: [661,11,345,21,302,'184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287'], // LATIN SMALL LETTER J 0x6A: [661,204,412,-12,403,'297 596Q297 627 318 644T361 661Q378 661 389 651T403 623Q403 595 384 576T340 557Q322 557 310 567T297 596ZM288 376Q288 405 262 405Q240 405 220 393T185 362T161 325T144 293L137 279Q135 278 121 278H107Q101 284 101 286T105 299Q126 348 164 391T252 441Q253 441 260 441T272 442Q296 441 316 432Q341 418 354 401T367 348V332L318 133Q267 -67 264 -75Q246 -125 194 -164T75 -204Q25 -204 7 -183T-12 -137Q-12 -110 7 -91T53 -71Q70 -71 82 -81T95 -112Q95 -148 63 -167Q69 -168 77 -168Q111 -168 139 -140T182 -74L193 -32Q204 11 219 72T251 197T278 308T289 365Q289 372 288 376'], // LATIN SMALL LETTER K 0x6B: [694,11,521,48,503,'121 647Q121 657 125 670T137 683Q138 683 209 688T282 694Q294 694 294 686Q294 679 244 477Q194 279 194 272Q213 282 223 291Q247 309 292 354T362 415Q402 442 438 442Q468 442 485 423T503 369Q503 344 496 327T477 302T456 291T438 288Q418 288 406 299T394 328Q394 353 410 369T442 390L458 393Q446 405 434 405H430Q398 402 367 380T294 316T228 255Q230 254 243 252T267 246T293 238T320 224T342 206T359 180T365 147Q365 130 360 106T354 66Q354 26 381 26Q429 26 459 145Q461 153 479 153H483Q499 153 499 144Q499 139 496 130Q455 -11 378 -11Q333 -11 305 15T277 90Q277 108 280 121T283 145Q283 167 269 183T234 206T200 217T182 220H180Q168 178 159 139T145 81T136 44T129 20T122 7T111 -2Q98 -11 83 -11Q66 -11 57 -1T48 16Q48 26 85 176T158 471L195 616Q196 629 188 632T149 637H144Q134 637 131 637T124 640T121 647'], // LATIN SMALL LETTER L 0x6C: [695,12,298,38,266,'117 59Q117 26 142 26Q179 26 205 131Q211 151 215 152Q217 153 225 153H229Q238 153 241 153T246 151T248 144Q247 138 245 128T234 90T214 43T183 6T137 -11Q101 -11 70 11T38 85Q38 97 39 102L104 360Q167 615 167 623Q167 626 166 628T162 632T157 634T149 635T141 636T132 637T122 637Q112 637 109 637T101 638T95 641T94 647Q94 649 96 661Q101 680 107 682T179 688Q194 689 213 690T243 693T254 694Q266 694 266 686Q266 675 193 386T118 83Q118 81 118 75T117 65V59'], // LATIN SMALL LETTER M 0x6D: [443,11,878,21,857,'21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287'], // LATIN SMALL LETTER N 0x6E: [443,11,600,21,580,'21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287'], // LATIN SMALL LETTER O 0x6F: [441,11,485,34,476,'201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120'], // LATIN SMALL LETTER P 0x70: [443,194,503,-39,497,'23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102'], // LATIN SMALL LETTER Q 0x71: [442,194,446,33,460,'33 157Q33 258 109 349T280 441Q340 441 372 389Q373 390 377 395T388 406T404 418Q438 442 450 442Q454 442 457 439T460 434Q460 425 391 149Q320 -135 320 -139Q320 -147 365 -148H390Q396 -156 396 -157T393 -175Q389 -188 383 -194H370Q339 -192 262 -192Q234 -192 211 -192T174 -192T157 -193Q143 -193 143 -185Q143 -182 145 -170Q149 -154 152 -151T172 -148Q220 -148 230 -141Q238 -136 258 -53T279 32Q279 33 272 29Q224 -10 172 -10Q117 -10 75 30T33 157ZM352 326Q329 405 277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q233 26 290 98L298 109L352 326'], // LATIN SMALL LETTER R 0x72: [443,11,451,21,430,'21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287'], // LATIN SMALL LETTER S 0x73: [443,10,469,53,419,'131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289'], // LATIN SMALL LETTER T 0x74: [626,11,361,19,330,'26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26'], // LATIN SMALL LETTER U 0x75: [442,11,572,21,551,'21 287Q21 295 30 318T55 370T99 420T158 442Q204 442 227 417T250 358Q250 340 216 246T182 105Q182 62 196 45T238 27T291 44T328 78L339 95Q341 99 377 247Q407 367 413 387T427 416Q444 431 463 431Q480 431 488 421T496 402L420 84Q419 79 419 68Q419 43 426 35T447 26Q469 29 482 57T512 145Q514 153 532 153Q551 153 551 144Q550 139 549 130T540 98T523 55T498 17T462 -8Q454 -10 438 -10Q372 -10 347 46Q345 45 336 36T318 21T296 6T267 -6T233 -11Q189 -11 155 7Q103 38 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287'], // LATIN SMALL LETTER V 0x76: [443,11,485,21,467,'173 380Q173 405 154 405Q130 405 104 376T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Q21 294 29 316T53 368T97 419T160 441Q202 441 225 417T249 361Q249 344 246 335Q246 329 231 291T200 202T182 113Q182 86 187 69Q200 26 250 26Q287 26 319 60T369 139T398 222T409 277Q409 300 401 317T383 343T365 361T357 383Q357 405 376 424T417 443Q436 443 451 425T467 367Q467 340 455 284T418 159T347 40T241 -11Q177 -11 139 22Q102 54 102 117Q102 148 110 181T151 298Q173 362 173 380'], // LATIN SMALL LETTER W 0x77: [443,11,716,21,690,'580 385Q580 406 599 424T641 443Q659 443 674 425T690 368Q690 339 671 253Q656 197 644 161T609 80T554 12T482 -11Q438 -11 404 5T355 48Q354 47 352 44Q311 -11 252 -11Q226 -11 202 -5T155 14T118 53T104 116Q104 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Q21 293 29 315T52 366T96 418T161 441Q204 441 227 416T250 358Q250 340 217 250T184 111Q184 65 205 46T258 26Q301 26 334 87L339 96V119Q339 122 339 128T340 136T341 143T342 152T345 165T348 182T354 206T362 238T373 281Q402 395 406 404Q419 431 449 431Q468 431 475 421T483 402Q483 389 454 274T422 142Q420 131 420 107V100Q420 85 423 71T442 42T487 26Q558 26 600 148Q609 171 620 213T632 273Q632 306 619 325T593 357T580 385'], // LATIN SMALL LETTER X 0x78: [442,11,572,35,522,'52 289Q59 331 106 386T222 442Q257 442 286 424T329 379Q371 442 430 442Q467 442 494 420T522 361Q522 332 508 314T481 292T458 288Q439 288 427 299T415 328Q415 374 465 391Q454 404 425 404Q412 404 406 402Q368 386 350 336Q290 115 290 78Q290 50 306 38T341 26Q378 26 414 59T463 140Q466 150 469 151T485 153H489Q504 153 504 145Q504 144 502 134Q486 77 440 33T333 -11Q263 -11 227 52Q186 -10 133 -10H127Q78 -10 57 16T35 71Q35 103 54 123T99 143Q142 143 142 101Q142 81 130 66T107 46T94 41L91 40Q91 39 97 36T113 29T132 26Q168 26 194 71Q203 87 217 139T245 247T261 313Q266 340 266 352Q266 380 251 392T217 404Q177 404 142 372T93 290Q91 281 88 280T72 278H58Q52 284 52 289'], // LATIN SMALL LETTER Y 0x79: [443,205,490,21,497,'21 287Q21 301 36 335T84 406T158 442Q199 442 224 419T250 355Q248 336 247 334Q247 331 231 288T198 191T182 105Q182 62 196 45T238 27Q261 27 281 38T312 61T339 94Q339 95 344 114T358 173T377 247Q415 397 419 404Q432 431 462 431Q475 431 483 424T494 412T496 403Q496 390 447 193T391 -23Q363 -106 294 -155T156 -205Q111 -205 77 -183T43 -117Q43 -95 50 -80T69 -58T89 -48T106 -45Q150 -45 150 -87Q150 -107 138 -122T115 -142T102 -147L99 -148Q101 -153 118 -160T152 -167H160Q177 -167 186 -165Q219 -156 247 -127T290 -65T313 -9T321 21L315 17Q309 13 296 6T270 -6Q250 -11 231 -11Q185 -11 150 11T104 82Q103 89 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287'], // LATIN SMALL LETTER Z 0x7A: [442,11,465,35,468,'347 338Q337 338 294 349T231 360Q211 360 197 356T174 346T162 335T155 324L153 320Q150 317 138 317Q117 317 117 325Q117 330 120 339Q133 378 163 406T229 440Q241 442 246 442Q271 442 291 425T329 392T367 375Q389 375 411 408T434 441Q435 442 449 442H462Q468 436 468 434Q468 430 463 420T449 399T432 377T418 358L411 349Q368 298 275 214T160 106L148 94L163 93Q185 93 227 82T290 71Q328 71 360 90T402 140Q406 149 409 151T424 153Q443 153 443 143Q443 138 442 134Q425 72 376 31T278 -11Q252 -11 232 6T193 40T155 57Q111 57 76 -3Q70 -11 59 -11H54H41Q35 -5 35 -2Q35 13 93 84Q132 129 225 214T340 322Q352 338 347 338'], // GREEK CAPITAL LETTER GAMMA 0x393: [680,-1,615,31,721,'49 1Q31 1 31 10Q31 12 34 24Q39 43 44 45Q48 46 59 46H65Q92 46 125 49Q139 52 144 61Q146 66 215 342T285 622Q285 629 281 629Q273 632 228 634H197Q191 640 191 642T193 661Q197 674 203 680H714Q721 676 721 669Q721 664 708 557T694 447Q692 440 674 440H662Q655 445 655 454Q655 455 658 480T661 534Q661 572 652 592Q638 619 603 626T501 634H471Q398 633 393 630Q389 628 386 622Q385 619 315 341T245 60Q245 46 333 46H345Q366 46 366 35Q366 33 363 21T358 6Q356 1 339 1Q334 1 292 1T187 2Q122 2 88 2T49 1'], // GREEK CAPITAL LETTER THETA 0x398: [704,22,763,50,740,'740 435Q740 320 676 213T511 42T304 -22Q207 -22 138 35T51 201Q50 209 50 244Q50 346 98 438T227 601Q351 704 476 704Q514 704 524 703Q621 689 680 617T740 435ZM640 466Q640 523 625 565T583 628T532 658T479 668Q370 668 273 559T151 255Q150 245 150 213Q150 156 165 116T207 55T259 26T313 17Q385 17 451 63T561 184Q590 234 615 312T640 466ZM510 276Q510 278 512 288L515 298Q515 299 384 299H253L250 285Q246 271 244 268T231 265H227Q216 265 214 266T207 274Q207 278 223 345T244 416Q247 419 260 419H263Q280 419 280 408Q280 406 278 396L275 386Q275 385 406 385H537L540 399Q544 413 546 416T559 419H563Q574 419 576 418T583 410Q583 403 566 339Q549 271 544 267Q542 265 538 265H530H527Q510 265 510 276'], // GREEK CAPITAL LETTER LAMDA 0x39B: [716,0,694,35,670,'135 2Q114 2 90 2T60 1Q35 1 35 11Q35 28 42 40Q45 46 55 46Q119 46 151 94Q153 97 325 402T498 709Q505 716 526 716Q543 716 549 710Q550 709 560 548T580 224T591 57Q594 52 595 52Q603 47 638 46H663Q670 39 670 35Q669 12 657 0H644Q613 2 530 2Q497 2 469 2T424 2T405 1Q388 1 388 10Q388 15 391 24Q392 27 393 32T395 38T397 41T401 44T406 45T415 46Q473 46 487 64L472 306Q468 365 465 426T459 518L457 550Q456 550 328 322T198 88Q196 80 196 77Q196 49 243 46Q261 46 261 35Q261 34 259 22Q256 7 254 4T240 0Q237 0 211 1T135 2'], // GREEK CAPITAL LETTER XI 0x39E: [678,0,742,53,777,'222 668Q222 670 229 677H654Q677 677 705 677T740 678Q764 678 770 676T777 667Q777 662 764 594Q761 579 757 559T751 528L749 519Q747 512 729 512H717Q710 519 710 525Q712 532 715 559T719 591Q718 595 711 595Q682 598 486 598Q252 598 246 592Q239 587 228 552L216 517Q214 512 197 512H185Q178 517 178 522Q178 524 198 591T222 668ZM227 262Q218 262 215 262T209 266L207 270L227 356Q247 435 250 439Q253 443 260 443H267H280Q287 438 287 433Q287 430 285 420T280 402L278 393Q278 392 431 392H585L590 415Q595 436 598 439T612 443H628Q635 438 635 433Q635 431 615 351T594 268Q592 262 575 262H572Q556 262 556 272Q556 280 560 293L565 313H258L252 292Q248 271 245 267T230 262H227ZM60 0Q53 4 53 11Q53 14 68 89T84 169Q88 176 98 176H104H116Q123 169 123 163Q122 160 117 127T112 88Q112 80 243 80H351H454Q554 80 574 81T597 88V89Q603 100 610 121T622 157T630 174Q633 176 646 176H658Q665 171 665 166Q665 164 643 89T618 7Q616 2 607 1T548 0H335H60'], // GREEK CAPITAL LETTER PI 0x3A0: [681,0,831,31,887,'48 1Q31 1 31 10Q31 12 34 24Q39 43 44 45Q48 46 59 46H65Q92 46 125 49Q139 52 144 61Q146 66 215 342T285 622Q285 629 281 629Q273 632 228 634H197Q191 640 191 642T193 661Q197 674 203 680H541Q621 680 709 680T812 681Q841 681 855 681T877 679T886 676T887 670Q887 663 885 656Q880 637 875 635Q871 634 860 634H854Q827 634 794 631Q780 628 775 619Q773 614 704 338T634 58Q634 51 638 51Q646 48 692 46H723Q729 38 729 37T726 19Q722 6 716 0H701Q664 2 567 2Q533 2 504 2T458 2T437 1Q420 1 420 10Q420 15 423 24Q428 43 433 45Q437 46 448 46H454Q481 46 514 49Q528 52 533 61Q536 67 572 209T642 491T678 632Q678 634 533 634H388Q387 631 316 347T245 59Q245 55 246 54T253 50T270 48T303 46H334Q340 38 340 37T337 19Q333 6 327 0H312Q275 2 178 2Q144 2 115 2T69 2T48 1'], // GREEK CAPITAL LETTER SIGMA 0x3A3: [683,0,780,58,806,'65 0Q58 4 58 11Q58 16 114 67Q173 119 222 164L377 304Q378 305 340 386T261 552T218 644Q217 648 219 660Q224 678 228 681Q231 683 515 683H799Q804 678 806 674Q806 667 793 559T778 448Q774 443 759 443Q747 443 743 445T739 456Q739 458 741 477T743 516Q743 552 734 574T710 609T663 627T596 635T502 637Q480 637 469 637H339Q344 627 411 486T478 341V339Q477 337 477 336L457 318Q437 300 398 265T322 196L168 57Q167 56 188 56T258 56H359Q426 56 463 58T537 69T596 97T639 146T680 225Q686 243 689 246T702 250H705Q726 250 726 239Q726 238 683 123T639 5Q637 1 610 1Q577 0 348 0H65'], // GREEK CAPITAL LETTER UPSILON 0x3A5: [706,0,583,28,700,'45 535Q34 535 31 536T28 544Q28 554 39 578T70 631T126 683T206 705Q230 705 251 698T295 671T330 612T344 514Q344 477 342 473V472Q343 472 347 480T361 509T380 547Q471 704 596 704Q615 704 625 702Q659 692 679 663T700 595Q700 565 696 552T687 537T670 535Q656 535 653 536T649 543Q649 544 649 550T650 562Q650 589 629 605T575 621Q502 621 448 547T365 361Q290 70 290 60Q290 46 379 46H404Q410 40 410 39T408 19Q404 6 398 0H381Q340 2 225 2Q184 2 149 2T94 2T69 1Q61 1 58 1T53 4T51 10Q51 11 53 23Q54 25 55 30T56 36T58 40T60 43T62 44T67 46T73 46T82 46H89Q144 46 163 49T190 62L198 93Q206 124 217 169T241 262T262 350T274 404Q281 445 281 486V494Q281 621 185 621Q147 621 116 601T74 550Q71 539 66 537T45 535'], // GREEK CAPITAL LETTER PHI 0x3A6: [683,0,667,24,642,'356 624Q356 637 267 637H243Q237 642 237 645T239 664Q243 677 249 683H264Q342 681 429 681Q565 681 571 683H583Q589 677 589 674T587 656Q582 641 578 637H540Q516 637 504 637T479 633T463 630T454 623T448 613T443 597T438 576Q436 566 434 556T430 539L428 533Q442 533 472 526T543 502T613 451T642 373Q642 301 567 241T386 158L336 150Q332 150 331 146Q310 66 310 60Q310 46 399 46H424Q430 40 430 39T428 19Q424 6 418 0H401Q360 2 247 2Q207 2 173 2T119 2T95 1Q87 1 84 1T79 4T77 10Q77 11 79 23Q80 25 81 30T82 36T84 40T86 43T88 44T93 46T99 46T108 46H115Q170 46 189 49T216 62Q220 74 228 107L239 150L223 152Q139 164 82 205T24 311Q24 396 125 462Q207 517 335 533L346 578Q356 619 356 624ZM130 291Q130 203 241 188H249Q249 190 287 342L325 495H324Q313 495 291 491T229 466T168 414Q130 357 130 291ZM536 393Q536 440 507 463T418 496L341 187L351 189Q443 201 487 255Q536 314 536 393'], // GREEK CAPITAL LETTER PSI 0x3A8: [683,0,612,21,692,'216 151Q48 174 48 329Q48 361 56 403T65 458Q65 482 58 494T43 507T28 510T21 520Q21 528 23 534T29 544L32 546H72H94Q110 546 119 544T139 536T154 514T159 476V465Q159 445 149 399T138 314Q142 229 197 201Q223 187 226 190L233 218Q240 246 253 300T280 407Q333 619 333 625Q333 637 244 637H220Q214 642 214 645T216 664Q220 677 226 683H241Q321 681 405 681Q543 681 549 683H560Q566 677 566 674T564 656Q559 641 555 637H517Q448 636 436 628Q429 623 423 600T373 404L320 192Q370 201 419 248Q451 281 469 317T500 400T518 457Q529 486 542 505T569 532T594 543T621 546H644H669Q692 546 692 536Q691 509 676 509Q623 509 593 399Q587 377 579 355T552 301T509 244T446 195T359 159Q324 151 314 151Q311 151 310 150T298 106T287 60Q287 46 376 46H401Q407 40 407 39T405 19Q401 6 395 0H378Q337 2 224 2Q184 2 150 2T96 2T72 1Q64 1 61 1T56 4T54 10Q54 11 56 23Q57 25 58 30T59 36T61 40T63 43T65 44T70 46T76 46T85 46H92Q147 46 166 49T193 62L204 106Q216 149 216 151'], // GREEK CAPITAL LETTER OMEGA 0x3A9: [704,0,772,80,786,'125 84Q127 78 194 76H243V78Q243 122 208 215T165 350Q164 359 162 389Q162 522 272 610Q328 656 396 680T525 704Q628 704 698 661Q734 637 755 601T781 544T786 504Q786 439 747 374T635 226T537 109Q518 81 518 77Q537 76 557 76Q608 76 620 78T640 92Q646 100 656 119T673 155T683 172Q690 173 698 173Q718 173 718 162Q718 161 681 82T642 2Q639 0 550 0H461Q455 5 455 9T458 28Q472 78 510 149T584 276T648 402T677 525Q677 594 636 631T530 668Q476 668 423 641T335 568Q284 499 271 400Q270 388 270 348Q270 298 277 228T285 115Q285 82 280 49T271 6Q269 1 258 1T175 0H87Q83 3 80 7V18Q80 22 82 98Q84 156 85 163T91 172Q94 173 104 173T119 172Q124 169 124 126Q125 104 125 84'], // GREEK SMALL LETTER ALPHA 0x3B1: [442,11,640,34,603,'34 156Q34 270 120 356T309 442Q379 442 421 402T478 304Q484 275 485 237V208Q534 282 560 374Q564 388 566 390T582 393Q603 393 603 385Q603 376 594 346T558 261T497 161L486 147L487 123Q489 67 495 47T514 26Q528 28 540 37T557 60Q559 67 562 68T577 70Q597 70 597 62Q597 56 591 43Q579 19 556 5T512 -10H505Q438 -10 414 62L411 69L400 61Q390 53 370 41T325 18T267 -2T203 -11Q124 -11 79 39T34 156ZM208 26Q257 26 306 47T379 90L403 112Q401 255 396 290Q382 405 304 405Q235 405 183 332Q156 292 139 224T121 120Q121 71 146 49T208 26'], // GREEK SMALL LETTER BETA 0x3B2: [705,194,566,23,573,'29 -194Q23 -188 23 -186Q23 -183 102 134T186 465Q208 533 243 584T309 658Q365 705 429 705H431Q493 705 533 667T573 570Q573 465 469 396L482 383Q533 332 533 252Q533 139 448 65T257 -10Q227 -10 203 -2T165 17T143 40T131 59T126 65L62 -188Q60 -194 42 -194H29ZM353 431Q392 431 427 419L432 422Q436 426 439 429T449 439T461 453T472 471T484 495T493 524T501 560Q503 569 503 593Q503 611 502 616Q487 667 426 667Q384 667 347 643T286 582T247 514T224 455Q219 439 186 308T152 168Q151 163 151 147Q151 99 173 68Q204 26 260 26Q302 26 349 51T425 137Q441 171 449 214T457 279Q457 337 422 372Q380 358 347 358H337Q258 358 258 389Q258 396 261 403Q275 431 353 431'], // GREEK SMALL LETTER GAMMA 0x3B3: [441,216,518,11,543,'31 249Q11 249 11 258Q11 275 26 304T66 365T129 418T206 441Q233 441 239 440Q287 429 318 386T371 255Q385 195 385 170Q385 166 386 166L398 193Q418 244 443 300T486 391T508 430Q510 431 524 431H537Q543 425 543 422Q543 418 522 378T463 251T391 71Q385 55 378 6T357 -100Q341 -165 330 -190T303 -216Q286 -216 286 -188Q286 -138 340 32L346 51L347 69Q348 79 348 100Q348 257 291 317Q251 355 196 355Q148 355 108 329T51 260Q49 251 47 251Q45 249 31 249'], // GREEK SMALL LETTER DELTA 0x3B4: [717,10,444,36,451,'195 609Q195 656 227 686T302 717Q319 716 351 709T407 697T433 690Q451 682 451 662Q451 644 438 628T403 612Q382 612 348 641T288 671T249 657T235 628Q235 584 334 463Q401 379 401 292Q401 169 340 80T205 -10H198Q127 -10 83 36T36 153Q36 286 151 382Q191 413 252 434Q252 435 245 449T230 481T214 521T201 566T195 609ZM112 130Q112 83 136 55T204 27Q233 27 256 51T291 111T309 178T316 232Q316 267 309 298T295 344T269 400L259 396Q215 381 183 342T137 256T118 179T112 130'], // GREEK SMALL LETTER EPSILON 0x3B5: [452,23,466,27,428,'190 -22Q124 -22 76 11T27 107Q27 174 97 232L107 239L99 248Q76 273 76 304Q76 364 144 408T290 452H302Q360 452 405 421Q428 405 428 392Q428 381 417 369T391 356Q382 356 371 365T338 383T283 392Q217 392 167 368T116 308Q116 289 133 272Q142 263 145 262T157 264Q188 278 238 278H243Q308 278 308 247Q308 206 223 206Q177 206 142 219L132 212Q68 169 68 112Q68 39 201 39Q253 39 286 49T328 72T345 94T362 105Q376 103 376 88Q376 79 365 62T334 26T275 -8T190 -22'], // GREEK SMALL LETTER ZETA 0x3B6: [704,204,438,44,471,'296 643Q298 704 324 704Q342 704 342 687Q342 682 339 664T336 633Q336 623 337 618T338 611Q339 612 341 612Q343 614 354 616T374 618L384 619H394Q471 619 471 586Q467 548 386 546H372Q338 546 320 564L311 558Q235 506 175 398T114 190Q114 171 116 155T125 127T137 104T153 86T171 72T192 61T213 53T235 46T256 39L322 16Q389 -10 389 -80Q389 -119 364 -154T300 -202Q292 -204 274 -204Q247 -204 225 -196Q210 -192 193 -182T172 -167Q167 -159 173 -148Q180 -139 191 -139Q195 -139 221 -153T283 -168Q298 -166 310 -152T322 -117Q322 -91 302 -75T250 -51T183 -29T116 4T65 62T44 160Q44 287 121 410T293 590L302 595Q296 613 296 643'], // GREEK SMALL LETTER ETA 0x3B7: [443,216,497,21,503,'21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q156 442 175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336V326Q503 302 439 53Q381 -182 377 -189Q364 -216 332 -216Q319 -216 310 -208T299 -186Q299 -177 358 57L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287'], // GREEK SMALL LETTER THETA 0x3B8: [705,10,469,35,462,'35 200Q35 302 74 415T180 610T319 704Q320 704 327 704T339 705Q393 701 423 656Q462 596 462 495Q462 380 417 261T302 66T168 -10H161Q125 -10 99 10T60 63T41 130T35 200ZM383 566Q383 668 330 668Q294 668 260 623T204 521T170 421T157 371Q206 370 254 370L351 371Q352 372 359 404T375 484T383 566ZM113 132Q113 26 166 26Q181 26 198 36T239 74T287 161T335 307L340 324H145Q145 321 136 286T120 208T113 132'], // GREEK SMALL LETTER IOTA 0x3B9: [442,10,354,48,333,'139 -10Q111 -10 92 0T64 25T52 52T48 74Q48 89 55 109T85 199T135 375L137 384Q139 394 140 397T145 409T151 422T160 431T173 439T190 442Q202 442 213 435T225 410Q225 404 214 358T181 238T137 107Q126 74 126 54Q126 43 126 39T130 31T142 27H147Q206 27 255 78Q272 98 281 114T290 138T295 149T313 153Q321 153 324 153T329 152T332 149T332 143Q332 106 276 48T145 -10H139'], // GREEK SMALL LETTER KAPPA 0x3BA: [442,11,576,48,554,'83 -11Q70 -11 62 -4T51 8T49 17Q49 30 96 217T147 414Q160 442 193 442Q205 441 213 435T223 422T225 412Q225 401 208 337L192 270Q193 269 208 277T235 292Q252 304 306 349T396 412T467 431Q489 431 500 420T512 391Q512 366 494 347T449 327Q430 327 418 338T405 368Q405 370 407 380L397 375Q368 360 315 315L253 266L240 257H245Q262 257 300 251T366 230Q422 203 422 150Q422 140 417 114T411 67Q411 26 437 26Q484 26 513 137Q516 149 519 151T535 153Q554 153 554 144Q554 121 527 64T457 -7Q447 -10 431 -10Q386 -10 360 17T333 90Q333 108 336 122T339 146Q339 170 320 186T271 209T222 218T185 221H180L155 122Q129 22 126 16Q113 -11 83 -11'], // GREEK SMALL LETTER LAMDA 0x3BB: [694,12,583,47,557,'166 673Q166 685 183 694H202Q292 691 316 644Q322 629 373 486T474 207T524 67Q531 47 537 34T546 15T551 6T555 2T556 -2T550 -11H482Q457 3 450 18T399 152L354 277L340 262Q327 246 293 207T236 141Q211 112 174 69Q123 9 111 -1T83 -12Q47 -12 47 20Q47 37 61 52T199 187Q229 216 266 252T321 306L338 322Q338 323 288 462T234 612Q214 657 183 657Q166 657 166 673'], // GREEK SMALL LETTER MU 0x3BC: [442,216,603,23,580,'58 -216Q44 -216 34 -208T23 -186Q23 -176 96 116T173 414Q186 442 219 442Q231 441 239 435T249 423T251 413Q251 401 220 279T187 142Q185 131 185 107V99Q185 26 252 26Q261 26 270 27T287 31T302 38T315 45T327 55T338 65T348 77T356 88T365 100L372 110L408 253Q444 395 448 404Q461 431 491 431Q504 431 512 424T523 412T525 402L449 84Q448 79 448 68Q448 43 455 35T476 26Q485 27 496 35Q517 55 537 131Q543 151 547 152Q549 153 557 153H561Q580 153 580 144Q580 138 575 117T555 63T523 13Q510 0 491 -8Q483 -10 467 -10Q446 -10 429 -4T402 11T385 29T376 44T374 51L368 45Q362 39 350 30T324 12T288 -4T246 -11Q199 -11 153 12L129 -85Q108 -167 104 -180T92 -202Q76 -216 58 -216'], // GREEK SMALL LETTER NU 0x3BD: [442,2,494,45,530,'74 431Q75 431 146 436T219 442Q231 442 231 434Q231 428 185 241L137 51H140L150 55Q161 59 177 67T214 86T261 119T312 165Q410 264 445 394Q458 442 496 442Q509 442 519 434T530 411Q530 390 516 352T469 262T388 162T267 70T106 5Q81 -2 71 -2Q66 -2 59 -1T51 1Q45 5 45 11Q45 13 88 188L132 364Q133 377 125 380T86 385H65Q59 391 59 393T61 412Q65 431 74 431'], // GREEK SMALL LETTER XI 0x3BE: [704,205,438,21,443,'268 632Q268 704 296 704Q314 704 314 687Q314 682 311 664T308 635T309 620V616H315Q342 619 360 619Q443 619 443 586Q439 548 358 546H344Q326 546 317 549T290 566Q257 550 226 505T195 405Q195 381 201 364T211 342T218 337Q266 347 298 347Q375 347 375 314Q374 297 359 288T327 277T280 275Q234 275 208 283L195 286Q149 260 119 214T88 130Q88 116 90 108Q101 79 129 63T229 20Q238 17 243 15Q337 -21 354 -33Q383 -53 383 -94Q383 -137 351 -171T273 -205Q240 -205 202 -190T158 -167Q156 -163 156 -159Q156 -151 161 -146T176 -140Q182 -140 189 -143Q232 -168 274 -168Q286 -168 292 -165Q313 -151 313 -129Q313 -112 301 -104T232 -75Q214 -68 204 -64Q198 -62 171 -52T136 -38T107 -24T78 -8T56 12T36 37T26 66T21 103Q21 149 55 206T145 301L154 307L148 313Q141 319 136 323T124 338T111 358T103 382T99 413Q99 471 143 524T259 602L271 607Q268 618 268 632'], // GREEK SMALL LETTER OMICRON 0x3BF: [441,11,485,34,476,'201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120'], // GREEK SMALL LETTER PI 0x3C0: [431,11,570,19,573,'132 -11Q98 -11 98 22V33L111 61Q186 219 220 334L228 358H196Q158 358 142 355T103 336Q92 329 81 318T62 297T53 285Q51 284 38 284Q19 284 19 294Q19 300 38 329T93 391T164 429Q171 431 389 431Q549 431 553 430Q573 423 573 402Q573 371 541 360Q535 358 472 358H408L405 341Q393 269 393 222Q393 170 402 129T421 65T431 37Q431 20 417 5T381 -10Q370 -10 363 -7T347 17T331 77Q330 86 330 121Q330 170 339 226T357 318T367 358H269L268 354Q268 351 249 275T206 114T175 17Q164 -11 132 -11'], // GREEK SMALL LETTER RHO 0x3C1: [442,216,517,23,510,'58 -216Q25 -216 23 -186Q23 -176 73 26T127 234Q143 289 182 341Q252 427 341 441Q343 441 349 441T359 442Q432 442 471 394T510 276Q510 219 486 165T425 74T345 13T266 -10H255H248Q197 -10 165 35L160 41L133 -71Q108 -168 104 -181T92 -202Q76 -216 58 -216ZM424 322Q424 359 407 382T357 405Q322 405 287 376T231 300Q217 269 193 170L176 102Q193 26 260 26Q298 26 334 62Q367 92 389 158T418 266T424 322'], // GREEK SMALL LETTER FINAL SIGMA 0x3C2: [442,107,363,30,405,'31 207Q31 306 115 374T302 442Q341 442 373 430T405 400Q405 392 399 383T379 374Q373 375 348 390T296 405Q222 405 160 357T98 249Q98 232 103 218T112 195T132 175T154 159T186 141T219 122Q234 114 255 102T286 85T299 78L302 74Q306 71 308 69T315 61T322 51T328 40T332 25T334 8Q334 -31 305 -69T224 -107Q194 -107 163 -92Q156 -88 156 -80Q156 -73 162 -67T178 -61Q186 -61 190 -63Q209 -71 224 -71Q244 -71 253 -59T263 -30Q263 -25 263 -21T260 -12T255 -4T248 3T239 9T227 17T213 25T195 34T174 46Q170 48 150 58T122 74T97 90T70 112T51 137T36 169T31 207'], // GREEK SMALL LETTER SIGMA 0x3C3: [431,11,571,31,572,'184 -11Q116 -11 74 34T31 147Q31 247 104 333T274 430Q275 431 414 431H552Q553 430 555 429T559 427T562 425T565 422T567 420T569 416T570 412T571 407T572 401Q572 357 507 357Q500 357 490 357T476 358H416L421 348Q439 310 439 263Q439 153 359 71T184 -11ZM361 278Q361 358 276 358Q152 358 115 184Q114 180 114 178Q106 141 106 117Q106 67 131 47T188 26Q242 26 287 73Q316 103 334 153T356 233T361 278'], // GREEK SMALL LETTER TAU 0x3C4: [431,13,437,18,517,'39 284Q18 284 18 294Q18 301 45 338T99 398Q134 425 164 429Q170 431 332 431Q492 431 497 429Q517 424 517 402Q517 388 508 376T485 360Q479 358 389 358T299 356Q298 355 283 274T251 109T233 20Q228 5 215 -4T186 -13Q153 -13 153 20V30L203 192Q214 228 227 272T248 336L254 357Q254 358 208 358Q206 358 197 358T183 359Q105 359 61 295Q56 287 53 286T39 284'], // GREEK SMALL LETTER UPSILON 0x3C5: [443,10,540,21,523,'413 384Q413 406 432 424T473 443Q492 443 507 425T523 367Q523 334 508 270T468 153Q424 63 373 27T282 -10H268Q220 -10 186 2T135 36T111 78T104 121Q104 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Q21 299 34 333T82 404T161 441Q200 441 225 419T250 355Q248 336 247 334Q247 331 232 291T201 199T185 118Q185 68 211 47T275 26Q317 26 355 57T416 132T452 216T465 277Q465 301 457 318T439 343T421 361T413 384'], // GREEK SMALL LETTER PHI 0x3C6: [442,218,654,50,618,'92 210Q92 176 106 149T142 108T185 85T220 72L235 70L237 71L250 112Q268 170 283 211T322 299T370 375T429 423T502 442Q547 442 582 410T618 302Q618 224 575 152T457 35T299 -10Q273 -10 273 -12L266 -48Q260 -83 252 -125T241 -179Q236 -203 215 -212Q204 -218 190 -218Q159 -215 159 -185Q159 -175 214 -2L209 0Q204 2 195 5T173 14T147 28T120 46T94 71T71 103T56 142T50 190Q50 238 76 311T149 431H162Q183 431 183 423Q183 417 175 409Q134 361 114 300T92 210ZM574 278Q574 320 550 344T486 369Q437 369 394 329T323 218Q309 184 295 109L286 64Q304 62 306 62Q423 62 498 131T574 278'], // GREEK SMALL LETTER CHI 0x3C7: [443,204,626,24,600,'576 -125Q576 -147 547 -175T487 -204H476Q394 -204 363 -157Q334 -114 293 26L284 59Q283 58 248 19T170 -66T92 -151T53 -191Q49 -194 43 -194Q36 -194 31 -189T25 -177T38 -154T151 -30L272 102L265 131Q189 405 135 405Q104 405 87 358Q86 351 68 351Q48 351 48 361Q48 369 56 386T89 423T148 442Q224 442 258 400Q276 375 297 320T330 222L341 180Q344 180 455 303T573 429Q579 431 582 431Q600 431 600 414Q600 407 587 392T477 270Q356 138 353 134L362 102Q392 -10 428 -89T490 -168Q504 -168 517 -156T536 -126Q539 -116 543 -115T557 -114T571 -115Q576 -118 576 -125'], // GREEK SMALL LETTER PSI 0x3C8: [694,205,651,21,634,'161 441Q202 441 226 417T250 358Q250 338 218 252T187 127Q190 85 214 61Q235 43 257 37Q275 29 288 29H289L371 360Q455 691 456 692Q459 694 472 694Q492 694 492 687Q492 678 411 356Q329 28 329 27T335 26Q421 26 498 114T576 278Q576 302 568 319T550 343T532 361T524 384Q524 405 541 424T583 443Q602 443 618 425T634 366Q634 337 623 288T605 220Q573 125 492 57T329 -11H319L296 -104Q272 -198 272 -199Q270 -205 252 -205H239Q233 -199 233 -197Q233 -192 256 -102T279 -9Q272 -8 265 -8Q106 14 106 139Q106 174 139 264T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Q21 299 34 333T82 404T161 441'], // GREEK SMALL LETTER OMEGA 0x3C9: [443,12,622,15,604,'495 384Q495 406 514 424T555 443Q574 443 589 425T604 364Q604 334 592 278T555 155T483 38T377 -11Q297 -11 267 66Q266 68 260 61Q201 -11 125 -11Q15 -11 15 139Q15 230 56 325T123 434Q135 441 147 436Q160 429 160 418Q160 406 140 379T94 306T62 208Q61 202 61 187Q61 124 85 100T143 76Q201 76 245 129L253 137V156Q258 297 317 297Q348 297 348 261Q348 243 338 213T318 158L308 135Q309 133 310 129T318 115T334 97T358 83T393 76Q456 76 501 148T546 274Q546 305 533 325T508 357T495 384'], // GREEK THETA SYMBOL 0x3D1: [705,11,591,21,563,'537 500Q537 474 533 439T524 383L521 362Q558 355 561 351Q563 349 563 345Q563 321 552 318Q542 318 521 323L510 326Q496 261 459 187T362 51T241 -11Q100 -11 100 105Q100 139 127 242T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Q21 291 27 313T47 368T79 418Q103 442 134 442Q169 442 201 419T233 344Q232 330 206 228T180 98Q180 26 247 26Q292 26 332 90T404 260L427 349Q422 349 398 359T339 392T289 440Q265 476 265 520Q265 590 312 647T417 705Q463 705 491 670T528 592T537 500ZM464 564Q464 668 413 668Q373 668 339 622T304 522Q304 494 317 470T349 431T388 406T421 391T435 387H436L443 415Q450 443 457 485T464 564'], // GREEK PHI SYMBOL 0x3D5: [694,205,596,42,579,'409 688Q413 694 421 694H429H442Q448 688 448 686Q448 679 418 563Q411 535 404 504T392 458L388 442Q388 441 397 441T429 435T477 418Q521 397 550 357T579 260T548 151T471 65T374 11T279 -10H275L251 -105Q245 -128 238 -160Q230 -192 227 -198T215 -205H209Q189 -205 189 -198Q189 -193 211 -103L234 -11Q234 -10 226 -10Q221 -10 206 -8T161 6T107 36T62 89T43 171Q43 231 76 284T157 370T254 422T342 441Q347 441 348 445L378 567Q409 686 409 688ZM122 150Q122 116 134 91T167 53T203 35T237 27H244L337 404Q333 404 326 403T297 395T255 379T211 350T170 304Q152 276 137 237Q122 191 122 150ZM500 282Q500 320 484 347T444 385T405 400T381 404H378L332 217L284 29Q284 27 285 27Q293 27 317 33T357 47Q400 66 431 100T475 170T494 234T500 282'], // GREEK PI SYMBOL 0x3D6: [431,10,828,19,823,'206 -10Q158 -10 136 24T114 110Q114 233 199 349L205 358H184Q144 358 121 347Q108 340 95 330T75 312T61 295T53 285Q51 284 38 284Q19 284 19 294Q19 300 38 329T93 391T164 429Q171 431 532 431Q799 431 803 430Q823 423 823 402Q823 377 801 364Q790 358 766 358Q748 358 748 357Q748 355 749 348T752 327T754 297Q754 258 738 207T693 107T618 24T520 -10Q488 -10 466 2T432 36T416 77T411 120Q411 128 410 128T404 122Q373 71 323 31T206 -10ZM714 296Q714 316 707 358H251Q250 357 244 348T230 328T212 301T193 267T176 229T164 187T159 144Q159 62 222 62Q290 62 349 127T432 285Q433 286 434 288T435 291T437 293T440 294T444 294T452 294H466Q472 288 472 286Q472 285 464 244T456 170Q456 62 534 62Q604 62 659 139T714 296'], // GREEK RHO SYMBOL 0x3F1: [442,194,517,67,510,'205 -174Q136 -174 102 -153T67 -76Q67 -25 91 85T127 234Q143 289 182 341Q252 427 341 441Q343 441 349 441T359 442Q432 442 471 394T510 276Q510 169 431 80T253 -10Q226 -10 204 -2T169 19T146 44T132 64L128 73Q128 72 124 53T116 5T112 -44Q112 -68 117 -78T150 -95T236 -102Q327 -102 356 -111T386 -154Q386 -166 384 -178Q381 -190 378 -192T361 -194H348Q342 -188 342 -179Q342 -169 315 -169Q294 -169 264 -171T205 -174ZM424 322Q424 359 407 382T357 405Q322 405 287 376T231 300Q221 276 204 217Q188 152 188 116Q188 68 210 47T259 26Q297 26 334 62Q367 92 389 158T418 266T424 322'], // GREEK LUNATE EPSILON SYMBOL 0x3F5: [431,11,406,40,382,'227 -11Q149 -11 95 41T40 174Q40 262 87 322Q121 367 173 396T287 430Q289 431 329 431H367Q382 426 382 411Q382 385 341 385H325H312Q191 385 154 277L150 265H327Q340 256 340 246Q340 228 320 219H138V217Q128 187 128 143Q128 77 160 52T231 26Q258 26 284 36T326 57T343 68Q350 68 354 58T358 39Q358 36 357 35Q354 31 337 21T289 0T227 -11'] }; SVG.FONTDATA.FONTS[MAIN][0x2212][0] = SVG.FONTDATA.FONTS[MAIN][0x002B][0]; // minus is size SVG.FONTDATA.FONTS[MAIN][0x2212][1] = SVG.FONTDATA.FONTS[MAIN][0x002B][1]; // minus is size SVG.FONTDATA.FONTS[MAIN][0x22EE][0] += 400; // adjust height for \vdots SVG.FONTDATA.FONTS[MAIN][0x22F1][0] += 700; // adjust height for \ddots // // Add some spacing characters (more will come later) // MathJax.Hub.Insert(SVG.FONTDATA.FONTS[MAIN],{ 0x2000: [0,0,500,0,0,{space:1}], // en quad 0x2001: [0,0,1000,0,0,{space:1}], // em quad 0x2002: [0,0,500,0,0,{space:1}], // en space 0x2003: [0,0,1000,0,0,{space:1}], // em space 0x2004: [0,0,333,0,0,{space:1}], // 3-per-em space 0x2005: [0,0,250,0,0,{space:1}], // 4-per-em space 0x2006: [0,0,167,0,0,{space:1}], // 6-per-em space 0x2009: [0,0,167,0,0,{space:1}], // thin space 0x200A: [0,0,83,0,0,{space:1}], // hair space 0x200B: [0,0,0,0,0,{space:1}], // zero-width space 0xEEE0: [0,0,-575,0,0,{space:1}], 0xEEE1: [0,0,-300,0,0,{space:1}], 0xEEE8: [0,0,25,0,0,{space:1}] }); HUB.Register.StartupHook("SVG Jax Require",function () { HUB.Register.LoadHook(SVG.fontDir+"/Size4/Regular/Main.js",function () { SVG.FONTDATA.FONTS[SIZE4][0xE154][0] += 200; // adjust height for brace extender SVG.FONTDATA.FONTS[SIZE4][0xE154][1] += 200; // adjust depth for brace extender }); SVG.FONTDATA.FONTS[MAIN][0x2245][2] -= 222; // fix incorrect right bearing in font HUB.Register.LoadHook(SVG.fontDir+"/Main/Bold/MathOperators.js",function () { SVG.FONTDATA.FONTS[BOLD][0x2245][2] -= 106; // fix incorrect right bearing in font }); HUB.Register.LoadHook(SVG.fontDir+"/Typewriter/Regular/BasicLatin.js",function () { SVG.FONTDATA.FONTS['MathJax_Typewriter'][0x20][2] += 275; // fix incorrect width }); AJAX.loadComplete(SVG.fontDir + "/fontdata.js"); }); })(MathJax.OutputJax.SVG,MathJax.ElementJax.mml,MathJax.Ajax,MathJax.Hub); /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/fontdata-extra.js * * Adds extra stretchy characters to the TeX font data. * * --------------------------------------------------------------------- * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ (function (SVG) { var VERSION = "2.7.5"; var DELIMITERS = SVG.FONTDATA.DELIMITERS; var MAIN = "MathJax_Main", BOLD = "MathJax_Main-bold", AMS = "MathJax_AMS", SIZE1 = "MathJax_Size1", SIZE4 = "MathJax_Size4"; var H = "H", V = "V"; var ARROWREP = [0x2212,MAIN,0,0,0,-.31,-.31]; // add depth for arrow extender var DARROWREP = [0x3D,MAIN,0,0,0,0,.1]; // add depth for arrow extender var delim = { 0x003D: // equal sign { dir: H, HW: [[767,MAIN]], stretch: {rep:[0x003D,MAIN]} }, 0x219E: // left two-headed arrow { dir: H, HW: [[1000,AMS]], stretch: {left:[0x219E,AMS], rep:ARROWREP} }, 0x21A0: // right two-headed arrow { dir: H, HW: [[1000,AMS]], stretch: {right:[0x21A0,AMS], rep:ARROWREP} }, 0x21A4: // left arrow from bar { dir: H, HW: [], stretch: {min:1, left:[0x2190,MAIN], rep:ARROWREP, right:[0x2223,SIZE1,0,-.05,.9]} }, 0x21A5: // up arrow from bar { dir: V, HW: [], stretch: {min:.6, bot:[0x22A5,BOLD,0,0,.75], ext:[0x23D0,SIZE1], top:[0x2191,SIZE1]} }, 0x21A6: // right arrow from bar { dir: H, HW: [[1000,MAIN]], stretch: {left:[0x2223,SIZE1,-.09,-.05,.9], rep:ARROWREP, right:[0x2192,MAIN]} }, 0x21A7: // down arrow from bar { dir: V, HW: [], stretch: {min:.6, top:[0x22A4,BOLD,0,0,.75], ext:[0x23D0,SIZE1], bot:[0x2193,SIZE1]} }, 0x21B0: // up arrow with top leftwards { dir: V, HW: [[722,AMS]], stretch: {top:[0x21B0,AMS], ext:[0x23D0,SIZE1,.097]} }, 0x21B1: // up arrow with top right { dir: V, HW: [[722,AMS]], stretch: {top:[0x21B1,AMS,.27], ext:[0x23D0,SIZE1]} }, 0x21BC: // left harpoon with barb up { dir: H, HW: [[1000,MAIN]], stretch: {left:[0x21BC,MAIN], rep:ARROWREP} }, 0x21BD: // left harpoon with barb down { dir: H, HW: [[1000,MAIN]], stretch: {left:[0x21BD,MAIN], rep:ARROWREP} }, 0x21BE: // up harpoon with barb right { dir: V, HW: [[888,AMS]], stretch: {top:[0x21BE,AMS,.12,0,1.1], ext:[0x23D0,SIZE1]} }, 0x21BF: // up harpoon with barb left { dir: V, HW: [[888,AMS]], stretch: {top:[0x21BF,AMS,.12,0,1.1], ext:[0x23D0,SIZE1]} }, 0x21C0: // right harpoon with barb up { dir: H, HW: [[1000,MAIN]], stretch: {right:[0x21C0,MAIN], rep:ARROWREP} }, 0x21C1: // right harpoon with barb down { dir: H, HW: [[1000,MAIN]], stretch: {right:[0x21C1,MAIN], rep:ARROWREP} }, 0x21C2: // down harpoon with barb right { dir: V, HW: [[888,AMS]], stretch: {bot:[0x21C2,AMS,.12,0,1.1], ext:[0x23D0,SIZE1]} }, 0x21C3: // down harpoon with barb left { dir: V, HW: [[888,AMS]], stretch: {bot:[0x21C3,AMS,.12,0,1.1], ext:[0x23D0,SIZE1]} }, 0x21DA: // left triple arrow { dir: H, HW: [[1000,AMS]], stretch: {left:[0x21DA,AMS], rep:[0x2261,MAIN]} }, 0x21DB: // right triple arrow { dir: H, HW: [[1000,AMS]], stretch: {right:[0x21DB,AMS], rep:[0x2261,MAIN]} }, 0x23B4: // top square bracket { dir: H, HW: [], stretch: {min:.5, left:[0x250C,AMS,0,-.1], rep:[0x2212,MAIN,0,.325], right:[0x2510,AMS,0,-.1]} }, 0x23B5: // bottom square bracket { dir: H, HW: [], stretch: {min:.5, left:[0x2514,AMS,0,.26], rep:[0x2212,MAIN,0,0,0,.25], right:[0x2518,AMS,0,.26]} }, 0x23DC: // top paren { dir: H, HW: [[778,AMS,0,0x2322],[100,MAIN,0,0x2322]], stretch: {left:[0xE150,SIZE4], rep:[0xE154,SIZE4], right:[0xE151,SIZE4]} }, 0x23DD: // bottom paren { dir: H, HW: [[778,AMS,0,0x2323],[100,MAIN,0,0x2323]], stretch: {left:[0xE152,SIZE4], rep:[0xE154,SIZE4], right:[0xE153,SIZE4]} }, 0x23E0: // top tortoise shell { dir: H, HW: [], stretch: {min:1.25, left:[0x2CA,MAIN,-.1], rep:[0x2C9,MAIN,-.05,.13], right:[0x2CB,MAIN], fullExtenders:true} }, 0x23E1: // bottom tortoise shell { dir: H, HW: [], stretch: {min:1.5, left:[0x2CB,MAIN,-.1,.1], rep:[0x2C9,MAIN,-.1], right:[0x2CA,MAIN,-.1,.1], fullExtenders:true} }, 0x2906: // leftwards double arrow from bar { dir: H, HW: [], stretch: {min:1, left:[0x21D0,MAIN], rep:DARROWREP, right:[0x2223,SIZE1,0,-.1]} }, 0x2907: // rightwards double arrow from bar { dir: H, HW: [], stretch: {min:.7, left:[0x22A8,AMS,0,-.12], rep:DARROWREP, right:[0x21D2,MAIN]} }, 0x294E: // left barb up right barb up harpoon { dir: H, HW: [], stretch: {min:.5, left:[0x21BC,MAIN], rep:ARROWREP, right:[0x21C0,MAIN]} }, 0x294F: // up barb right down barb right harpoon { dir: V, HW: [], stretch: {min:.5, top:[0x21BE,AMS,.12,0,1.1], ext:[0x23D0,SIZE1], bot:[0x21C2,AMS,.12,0,1.1]} }, 0x2950: // left barb dow right barb down harpoon { dir: H, HW: [], stretch: {min:.5, left:[0x21BD,MAIN], rep:ARROWREP, right:[0x21C1,MAIN]} }, 0x2951: // up barb left down barb left harpoon { dir: V, HW: [], stretch: {min:.5, top:[0x21BF,AMS,.12,0,1.1], ext:[0x23D0,SIZE1], bot:[0x21C3,AMS,.12,0,1.1]} }, 0x295A: // leftwards harpoon with barb up from bar { dir: H, HW: [], stretch: {min:1, left:[0x21BC,MAIN], rep:ARROWREP, right:[0x2223,SIZE1,0,-.05,.9]} }, 0x295B: // rightwards harpoon with barb up from bar { dir: H, HW: [], stretch: {min:1, left:[0x2223,SIZE1,-.05,-.05,.9], rep:ARROWREP, right:[0x21C0,MAIN]} }, 0x295C: // up harpoon with barb right from bar { dir: V, HW: [], stretch: {min:.7, bot:[0x22A5,BOLD,0,0,.75], ext:[0x23D0,SIZE1], top:[0x21BE,AMS,.12,0,1.1]} }, 0x295D: // down harpoon with barb right from bar { dir: V, HW: [], stretch: {min:.7, top:[0x22A4,BOLD,0,0,.75], ext:[0x23D0,SIZE1], bot:[0x21C2,AMS,.12,0,1.1]} }, 0x295E: // leftwards harpoon with barb down from bar { dir: H, HW: [], stretch: {min:1, left:[0x21BD,MAIN], rep:ARROWREP, right:[0x2223,SIZE1,0,-.05,.9]} }, 0x295F: // rightwards harpoon with barb down from bar { dir: H, HW: [], stretch: {min:1, left:[0x2223,SIZE1,-.05,-.05,.9], rep:ARROWREP, right:[0x21C1,MAIN]} }, 0x2960: // up harpoon with barb left from bar { dir: V, HW: [], stretch: {min:.7, bot:[0x22A5,BOLD,0,0,.75], ext:[0x23D0,SIZE1], top:[0x21BF,AMS,.12,0,1.1]} }, 0x2961: // down harpoon with barb left from bar { dir: V, HW: [], stretch: {min:.7, top:[0x22A4,BOLD,0,0,.75], ext:[0x23D0,SIZE1], bot:[0x21C3,AMS,.12,0,1.1]} } }; for (var id in delim) {if (delim.hasOwnProperty(id)) {DELIMITERS[id] = delim[id]}}; MathJax.Ajax.loadComplete(SVG.fontDir + "/fontdata-extra.js"); })(MathJax.OutputJax.SVG); MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () { /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/Main.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'] = { directory: 'AMS/Regular', family: 'MathJax_AMS', id: 'MJAMS', Ranges: [ [0x80,0xFF,"Latin1Supplement"], [0x100,0x17F,"LatinExtendedA"], [0x2B0,0x2FF,"SpacingModLetters"], [0x300,0x36F,"CombDiacritMarks"], [0x370,0x3FF,"GreekAndCoptic"], [0x2000,0x206F,"GeneralPunctuation"], [0x2100,0x214F,"LetterlikeSymbols"], [0x2190,0x21FF,"Arrows"], [0x2200,0x22FF,"MathOperators"], [0x2300,0x23FF,"MiscTechnical"], [0x2460,0x24FF,"EnclosedAlphanum"], [0x2500,0x257F,"BoxDrawing"], [0x25A0,0x25FF,"GeometricShapes"], [0x2600,0x26FF,"MiscSymbols"], [0x2700,0x27BF,"Dingbats"], [0x2980,0x29FF,"MiscMathSymbolsB"], [0x2A00,0x2AFF,"SuppMathOperators"], [0xE000,0xF8FF,"PUA"] ], // SPACE 0x20: [0,0,250,0,0,''], // LATIN CAPITAL LETTER A 0x41: [701,1,722,17,703,'130 -1H63Q34 -1 26 2T17 17Q17 24 22 29T35 35Q49 35 64 44T88 66Q101 93 210 383Q331 693 335 697T346 701T357 697Q358 696 493 399Q621 104 633 83Q656 35 686 35Q693 35 698 30T703 17Q703 5 693 2T643 -1H541Q388 -1 386 1Q378 6 378 16Q378 24 383 29T397 35Q412 35 434 45T456 65Q456 93 428 170L419 197H197L195 179Q184 134 184 97Q184 82 186 71T190 55T198 45T205 39T214 36L219 35Q241 31 241 17Q241 5 233 2T196 -1H130ZM493 68Q493 51 481 35H619Q604 56 515 256Q486 321 468 361L348 637Q347 637 330 592T313 543Q313 538 358 436T448 219T493 68ZM404 235Q404 239 355 355T295 488L275 430Q241 348 208 232H306Q404 232 404 235ZM155 48Q151 55 148 88V117L135 86Q118 47 117 46L110 37L135 35H159Q157 41 155 48'], // LATIN CAPITAL LETTER B 0x42: [683,1,667,11,620,'11 665Q11 672 22 683H213Q407 681 431 677Q582 649 582 515Q582 488 573 468Q554 413 484 372L474 366H475Q620 317 620 178Q620 115 568 69T420 6Q393 1 207 -1H22Q11 10 11 18Q11 35 51 35Q79 37 88 39T102 52Q107 70 107 341T102 630Q97 640 88 643T51 648H46Q11 648 11 665ZM142 341Q142 129 141 88T134 37Q133 36 133 35H240L233 48L229 61V623L233 635L240 648H133L138 639Q142 621 142 341ZM284 370Q365 378 391 411T417 508Q417 551 406 581T378 624T347 643T320 648Q298 648 278 635Q267 628 266 611T264 492V370H284ZM546 515Q546 551 531 577T494 617T454 635T422 641L411 643L420 630Q439 604 445 579T452 510V504Q452 481 451 467T441 430T415 383Q420 383 439 391T483 413T527 455T546 515ZM585 185Q585 221 570 249T534 294T490 320T453 334T436 337L435 336L440 330Q445 325 452 315T467 288T479 246T484 188Q484 145 474 110T454 62T442 48Q442 47 444 47Q450 47 470 54T517 75T564 119T585 185ZM449 184Q449 316 358 332Q355 332 335 333T302 335H264V199Q266 68 270 57Q275 50 289 43Q300 37 324 37Q449 37 449 184'], // LATIN CAPITAL LETTER C 0x43: [702,19,722,39,684,'684 131Q684 125 672 109T633 71T573 29T489 -5T386 -19Q330 -19 276 -3T174 46T91 134T44 261Q39 283 39 341T44 421Q66 538 143 611T341 699Q344 699 364 700T395 701Q449 698 503 677T585 655Q603 655 611 662T620 678T625 694T639 702Q650 702 657 690V481L653 474Q640 467 628 472Q624 476 618 496T595 541Q562 587 507 625T390 663H381Q337 663 299 625Q212 547 212 336Q212 249 233 179Q274 30 405 30Q533 30 641 130Q658 147 666 147Q671 147 677 143T684 131ZM250 625Q264 643 261 643Q238 635 214 620T161 579T110 510T79 414Q74 384 74 341T79 268Q89 213 113 169T164 101T217 61T260 39L277 34Q270 41 264 48Q199 111 181 254Q178 281 178 344T181 434Q200 559 250 625ZM621 565V625Q617 623 613 623Q603 619 590 619H575L588 605Q608 583 610 579L621 565'], // LATIN CAPITAL LETTER D 0x44: [683,1,722,16,688,'16 666Q16 675 28 683H193Q329 683 364 682T430 672Q534 650 600 585T686 423Q688 406 688 352Q688 274 673 226Q641 130 565 72T381 1Q368 -1 195 -1H28Q16 5 16 16Q16 35 53 35Q68 36 75 37T87 42T95 52Q98 61 98 341T95 630Q91 640 83 643T53 648Q16 648 16 666ZM237 646Q237 648 184 648H128Q128 647 133 632Q136 620 136 341Q136 64 133 50L128 35H237L230 48L226 61V343Q228 620 231 633Q232 636 237 646ZM264 61Q278 40 310 35Q363 35 401 55T461 112T496 193T513 295Q515 333 515 349Q515 411 504 459Q481 598 373 641Q351 648 321 648Q304 648 292 643T277 635T264 621V61ZM461 628Q462 627 471 616T489 594T509 559T529 509T544 441T550 352Q550 165 479 75L468 59Q474 61 484 65T522 87T573 128T618 195T650 290Q654 322 654 354Q654 418 638 464T581 552Q559 576 529 595T480 621L461 628'], // LATIN CAPITAL LETTER E 0x45: [683,1,667,12,640,'12 666Q12 675 24 683H582Q590 680 593 672V588Q593 514 591 502T575 490Q567 490 563 495T555 517Q552 556 517 590Q486 623 445 634T340 648H282Q266 636 264 620T260 492V370H277Q329 375 358 391T404 439Q420 480 420 506Q420 529 436 529Q445 529 451 521Q455 517 455 361Q455 333 455 298T456 253Q456 217 453 207T437 197Q420 196 420 217Q420 240 406 270Q377 328 284 335H260V201Q261 174 261 134Q262 73 264 61T278 38Q281 36 282 35H331Q400 35 449 50Q571 93 602 179Q605 203 622 203Q629 203 634 197T640 183Q638 181 624 95T604 3L600 -1H24Q12 5 12 16Q12 35 51 35Q92 38 97 52Q102 60 102 341T97 632Q91 645 51 648Q12 648 12 666ZM137 341Q137 131 136 89T130 37Q129 36 129 35H235Q233 41 231 48L226 61V623L231 635L235 648H129Q132 641 133 638T135 603T137 517T137 341ZM557 603V648H504Q504 646 515 639Q527 634 542 619L557 603ZM420 317V397L406 383Q394 370 380 363L366 355Q373 350 382 346Q400 333 409 328L420 317ZM582 61L586 88Q585 88 582 83Q557 61 526 46L511 37L542 35H577Q577 36 578 39T580 49T582 61'], // LATIN CAPITAL LETTER F 0x46: [683,1,611,12,584,'584 499Q569 490 566 490Q558 490 552 497T546 515Q546 535 533 559Q526 574 506 593T469 621Q415 648 326 648Q293 648 287 647T275 641Q264 630 263 617Q262 609 260 492V370L275 372Q323 376 350 392T393 441Q409 473 409 506Q409 529 427 529Q437 529 442 519Q444 511 444 362Q444 212 442 206Q436 197 426 197Q409 197 409 217Q409 265 375 299Q346 328 280 335H260V206Q260 70 262 63Q265 46 276 41T326 35Q362 35 366 28Q377 17 366 3L360 -1H24Q12 5 12 16Q12 35 51 35Q92 38 97 52Q102 60 102 341T97 632Q91 645 51 648Q12 648 12 666Q12 675 24 683H573Q576 678 584 670V499ZM137 341Q137 131 136 89T130 37Q129 36 129 35H182Q233 35 233 39Q226 54 225 92T224 346L226 623L231 635L235 648H129Q132 641 133 638T135 603T137 517T137 341ZM549 603V648H495L506 641Q531 621 533 619L549 603ZM409 317V395L400 386Q390 376 375 366L357 355L373 346Q394 331 397 328L409 317'], // LATIN CAPITAL LETTER G 0x47: [702,19,778,39,749,'737 285Q749 277 749 268Q749 260 744 255T730 250Q695 250 677 217Q666 195 666 119Q666 52 664 50Q656 36 555 3Q483 -16 415 -19Q364 -19 348 -17Q226 -3 146 70T44 261Q39 283 39 341T44 421Q66 538 143 611T341 699Q344 699 364 700T395 701Q449 698 503 677T585 655Q603 655 611 662T620 678T625 694T639 702Q650 702 657 690V481L653 474Q640 467 628 472Q624 476 618 496T595 541Q562 587 507 625T390 663H381Q337 663 299 625Q213 547 213 337Q213 75 341 23Q357 19 397 19Q440 19 462 22T492 30T513 45V119Q513 184 506 203Q491 237 435 250Q421 250 415 257Q404 267 415 281L421 285H737ZM250 43Q250 45 243 55T225 87T203 139T185 224T177 343V361Q184 533 250 625Q264 643 261 643Q238 635 214 620T161 579T110 510T79 414Q74 384 74 341T79 268Q106 117 230 52L250 43ZM621 565V625Q617 623 613 623Q603 619 590 619H575L588 605Q608 583 610 579L621 565ZM655 250H517L524 241Q548 213 548 149V114V39Q549 39 562 44T592 55T615 63L630 70V134Q632 190 634 204T648 237Q655 245 655 250'], // LATIN CAPITAL LETTER H 0x48: [683,1,778,14,762,'14 666Q14 675 26 683H344L351 679Q361 665 351 655Q344 648 317 648Q287 645 282 641Q270 637 269 623T266 497V370H511V497Q511 519 510 553Q509 615 507 626T496 641H495Q489 645 459 648Q420 648 420 665Q420 672 426 679L433 683H751Q762 676 762 666Q762 648 724 648Q684 645 677 632Q675 626 675 341Q675 57 677 52Q684 38 724 35Q762 35 762 16Q762 6 751 -1H433L426 3Q420 10 420 17Q420 35 459 35Q501 38 506 52Q511 64 511 190V323H266V190Q266 60 271 52Q276 38 317 35Q342 35 351 28Q360 17 351 3L344 -1H26Q14 5 14 16Q14 35 53 35Q94 38 99 52Q104 60 104 341T99 632Q93 645 53 648Q14 648 14 666ZM233 341V553Q233 635 239 648H131Q134 641 135 638T137 603T139 517T139 341Q139 131 138 89T132 37Q131 36 131 35H239Q233 47 233 129V341ZM639 341V489Q639 548 639 576T640 620T642 639T646 648H537L542 639Q546 625 546 341Q546 130 545 88T538 37Q537 36 537 35H646Q643 41 643 42T641 55T639 84T639 140V341'], // LATIN CAPITAL LETTER I 0x49: [683,1,389,20,369,'20 666Q20 676 31 683H358Q369 676 369 666Q369 648 331 648Q288 645 282 632Q278 626 278 341Q278 57 282 50Q286 42 295 40T331 35Q369 35 369 16Q369 6 358 -1H31Q20 4 20 16Q20 35 58 35Q84 37 93 39T107 50Q113 60 113 341Q113 623 107 632Q101 645 58 648Q20 648 20 666ZM249 35Q246 40 246 41T244 54T242 83T242 139V341Q242 632 244 639L249 648H140Q146 634 147 596T149 341Q149 124 148 86T140 35H249'], // LATIN CAPITAL LETTER J 0x4A: [683,77,500,6,478,'79 103Q108 103 129 83T151 38Q151 9 130 -15Q116 -34 130 -37Q133 -39 157 -39Q208 -39 219 -8L226 3V305Q226 612 224 621Q220 636 211 641T166 647Q137 647 128 654Q119 665 128 679L135 683H466Q478 677 478 666Q478 647 439 647Q399 644 393 632Q388 620 388 347Q386 69 384 59Q364 -6 316 -39T184 -77H172Q102 -77 56 -48T6 30Q6 62 26 82T79 103ZM353 354Q353 556 354 596T361 645Q362 646 362 647H253Q257 639 258 628T261 547T262 312V-4L255 -17Q248 -29 250 -29Q253 -29 258 -28T277 -20T302 -5T327 22T348 65Q350 74 353 354ZM115 36Q115 47 105 57T79 67Q73 67 67 66T52 56T44 34Q44 9 62 -8Q66 -11 71 -15T81 -22T86 -24L90 -13Q100 3 102 5Q115 22 115 36'], // LATIN CAPITAL LETTER K 0x4B: [683,1,778,22,768,'22 666Q22 676 33 683H351L358 679Q368 665 358 655Q351 648 324 648Q288 645 280 637Q275 631 274 605T273 477L275 343L382 446Q473 530 492 553T512 599Q512 617 502 631T475 648Q455 651 455 666Q455 677 465 680T510 683H593H720Q732 676 732 666Q732 659 727 654T713 648Q670 648 589 581Q567 562 490 489T413 415Q413 413 554 245T711 61Q737 35 751 35Q758 35 763 29T768 15Q768 6 758 -1H624Q491 -1 486 3Q480 10 480 17Q480 25 487 30T506 35Q518 36 520 38T520 48L400 195L302 310L286 297L273 283V170Q275 65 277 57Q280 41 300 38Q302 37 324 35Q349 35 358 28Q367 17 358 3L351 -1H33Q22 4 22 16Q22 35 60 35Q101 38 106 52Q111 60 111 341T106 632Q100 645 60 648Q22 648 22 666ZM240 341V553Q240 635 246 648H138Q141 641 142 638T144 603T146 517T146 341Q146 131 145 89T139 37Q138 36 138 35H246Q240 47 240 129V341ZM595 632L615 648H535L542 637Q542 636 544 625T549 610V595L562 606Q565 608 577 618T595 632ZM524 226L386 388Q386 389 378 382T358 361Q330 338 330 333Q330 332 330 332L331 330L533 90Q558 55 558 41V35H684L671 50Q667 54 524 226'], // LATIN CAPITAL LETTER L 0x4C: [683,1,667,12,640,'12 666Q12 675 24 683H333L340 679Q350 665 340 655Q333 648 309 648Q287 646 279 643T266 630Q264 623 264 346Q264 68 266 57Q274 40 284 35H340Q413 37 460 55Q514 78 553 117T602 197Q605 221 622 221Q629 221 634 215T640 201Q638 194 625 105T611 12Q611 6 600 -1H24Q12 5 12 16Q12 35 51 35Q92 38 97 52Q102 60 102 341T97 632Q91 645 51 648Q12 648 12 666ZM137 341Q137 131 136 89T130 37Q129 36 129 35H237Q235 41 233 48L229 61L226 339Q226 621 229 628Q230 630 231 636T233 643V648H129Q132 641 133 638T135 603T137 517T137 341ZM580 48Q580 59 583 74T586 97Q586 98 585 97T579 92T571 86Q549 64 513 43L500 35H577L580 48'], // LATIN CAPITAL LETTER M 0x4D: [683,1,944,17,926,'18 666Q18 677 27 680T73 683H146Q261 683 266 679L465 215Q469 215 566 443Q663 676 668 681Q673 683 790 683H908L915 679Q924 664 915 655Q912 648 897 648Q851 639 835 606L833 346Q833 86 835 79Q838 69 849 58T873 41Q877 40 887 38T901 35Q926 35 926 16Q926 6 915 -1H604L597 3Q588 19 597 28Q600 35 615 35Q660 42 673 68L679 79V339Q679 409 679 443T679 520T679 580T677 597Q646 521 584 375T473 117T424 3Q416 -1 410 -1T401 1Q399 3 273 301L148 599L146 343Q146 86 148 79Q152 69 163 58T186 41Q190 40 200 38T215 35Q226 35 235 28Q244 17 235 3L228 -1H28Q17 4 17 17Q17 35 39 35Q84 42 97 68L104 79V639L88 641Q72 644 53 648Q34 648 26 651T18 666ZM457 166Q451 169 449 171T435 198T404 268T344 412L244 648H157L166 637Q169 633 293 346L413 66Q424 88 435 117L457 166ZM817 646Q817 648 766 648H715V72L708 57Q701 45 697 41L695 37Q695 35 757 35H819L813 46Q802 61 800 76Q797 105 797 346L799 612L804 626Q812 638 815 641L817 646ZM124 42Q119 42 119 38Q119 35 128 35Q132 35 132 36Q125 42 124 42'], // LATIN CAPITAL LETTER N 0x4E: [683,20,722,20,702,'20 664Q20 666 31 683H142Q256 683 258 681Q259 680 279 653T342 572T422 468L582 259V425Q582 451 582 490T583 541Q583 611 573 628T522 648Q500 648 493 654Q484 665 493 679L500 683H691Q702 676 702 666Q702 657 698 652Q688 648 680 648Q633 648 627 612Q624 601 624 294V-8Q616 -20 607 -20Q601 -20 596 -15Q593 -13 371 270L156 548L153 319Q153 284 153 234T152 167Q152 103 156 78T172 44T213 34Q236 34 242 28Q253 17 242 3L236 -1H36Q24 6 24 16Q24 34 56 34Q58 35 69 36T86 40T100 50T109 72Q111 83 111 345V603L96 619Q72 643 44 648Q20 648 20 664ZM413 419L240 648H120L136 628Q137 626 361 341T587 54L589 68Q589 78 589 121V192L413 419'], // LATIN CAPITAL LETTER O 0x4F: [701,19,778,34,742,'131 601Q180 652 249 676T387 701Q485 701 562 661Q628 629 671 575T731 448Q742 410 742 341T731 234Q707 140 646 81Q549 -19 389 -19Q228 -19 131 81Q57 155 37 274Q34 292 34 341Q34 392 37 410Q58 528 131 601ZM568 341Q568 613 437 659Q406 664 395 665Q329 665 286 625Q232 571 213 439Q210 408 210 341Q210 275 213 245Q232 111 286 57Q309 37 342 23Q357 19 389 19Q420 19 437 23Q469 38 491 57Q568 132 568 341ZM174 341Q174 403 177 441T197 535T249 639Q246 639 224 627T193 608Q189 606 183 601T169 589T155 577Q69 488 69 344Q69 133 231 52Q244 45 246 45Q248 45 246 48Q231 69 222 85T200 141T177 239Q174 269 174 341ZM708 341Q708 415 684 475T635 563T582 610Q578 612 565 619T546 630Q533 637 531 637Q530 637 530 636V635L531 634Q562 591 577 543Q602 471 602 341V316Q602 264 599 230T580 144T531 48L530 47V46Q530 45 531 45Q533 45 547 52T583 75T622 105Q708 195 708 341'], // LATIN CAPITAL LETTER P 0x50: [683,1,611,16,597,'16 666Q16 675 28 683H195Q334 683 370 682T437 672Q511 657 554 611T597 495Q597 343 404 309Q402 308 401 308Q381 303 319 303H261V181Q261 157 262 120Q262 60 267 50T304 36Q310 35 313 35Q352 35 352 17Q352 10 346 3L339 -1H28Q16 5 16 16Q16 35 53 35Q68 36 75 37T87 42T95 52Q98 61 98 341T95 630Q91 640 83 643T53 648Q16 648 16 666ZM235 35Q228 46 227 84Q226 129 226 337V621L230 635L237 648H128Q128 647 133 632Q136 620 136 341Q136 64 133 50L128 35H235ZM301 341H313Q339 341 354 344T389 362T417 410T426 498Q426 586 401 616T322 647Q301 647 293 643Q271 637 264 621Q261 617 261 479V341H301ZM429 350Q431 350 443 353T476 367T515 391T548 432T562 490Q562 550 524 592Q507 607 484 619Q481 621 448 635L433 639L439 621Q462 578 462 506Q462 448 454 413T437 366T428 350H429'], // LATIN CAPITAL LETTER Q 0x51: [701,181,778,34,742,'480 -10Q480 -13 486 -24T507 -50T541 -80T588 -104T648 -114Q666 -114 688 -110T714 -106Q724 -106 728 -114T729 -130Q723 -145 663 -163T548 -181Q503 -181 463 -169T395 -139T343 -97T307 -56T284 -19L280 -3L262 1Q188 24 131 81Q57 155 37 275Q34 292 34 342T37 410Q58 528 131 601Q179 652 248 676T388 701Q485 701 562 661Q698 595 731 448Q742 410 742 341T731 235Q707 141 646 81Q616 50 575 27T493 -5L480 -10ZM568 342Q568 613 437 659L395 666Q329 666 286 626Q232 570 213 439Q210 408 210 342T213 246Q231 113 286 57Q309 37 342 23Q357 19 389 19Q420 19 437 23Q469 38 491 57Q568 134 568 342ZM174 341V354Q174 393 175 419T183 484T205 561T246 635L249 639Q246 639 224 627T193 608Q189 606 183 601T169 589T155 577Q69 491 69 344Q69 133 231 52Q247 42 247 46Q247 46 246 48Q231 69 222 85T200 141T177 239Q174 269 174 341ZM708 341Q708 410 689 467T640 556T588 606T546 630Q532 638 531 638Q530 638 531 635Q563 590 577 543Q602 472 602 341V316Q602 264 599 230T580 144T531 48Q529 44 532 45T546 52Q575 68 596 84T642 128T683 200T706 299Q708 327 708 341ZM391 -17H333Q329 -15 326 -15Q324 -15 324 -17Q324 -21 362 -68Q424 -130 506 -143Q518 -144 544 -144Q569 -144 577 -143L589 -141L575 -139Q544 -127 509 -101T453 -37L442 -19L391 -17'], // LATIN CAPITAL LETTER R 0x52: [683,1,722,16,705,'17 665Q17 672 28 683H221Q415 681 439 677Q461 673 481 667T516 654T544 639T566 623T584 607T597 592T607 578T614 565T618 554L621 548Q626 530 626 497Q626 447 613 419Q578 348 473 326L455 321Q462 310 473 292T517 226T578 141T637 72T686 35Q705 30 705 16Q705 7 693 -1H510Q503 6 404 159L306 310H268V183Q270 67 271 59Q274 42 291 38Q295 37 319 35Q344 35 353 28Q362 17 353 3L346 -1H28Q16 5 16 16Q16 35 55 35Q96 38 101 52Q106 60 106 341T101 632Q95 645 55 648Q17 648 17 665ZM241 35Q238 42 237 45T235 78T233 163T233 337V621L237 635L244 648H133Q136 641 137 638T139 603T141 517T141 341Q141 131 140 89T134 37Q133 36 133 35H241ZM457 496Q457 540 449 570T425 615T400 634T377 643Q374 643 339 648Q300 648 281 635Q271 628 270 610T268 481V346H284Q327 346 375 352Q421 364 439 392T457 496ZM492 537T492 496T488 427T478 389T469 371T464 361Q464 360 465 360Q469 360 497 370Q593 400 593 495Q593 592 477 630L457 637L461 626Q474 611 488 561Q492 537 492 496ZM464 243Q411 317 410 317Q404 317 401 315Q384 315 370 312H346L526 35H619L606 50Q553 109 464 243'], // LATIN CAPITAL LETTER S 0x53: [702,12,556,28,528,'54 238Q72 238 72 212Q72 174 106 121Q113 110 132 90T166 59Q221 23 264 23Q315 23 348 41Q368 50 384 79Q393 102 393 129Q393 181 356 219T221 299Q120 343 74 390T28 501Q28 561 55 610Q98 682 212 699Q214 699 231 700T261 701Q309 698 340 687T408 675Q431 678 445 690T465 702Q474 702 481 690V497L477 490Q464 481 450 490Q446 500 446 501Q446 546 386 606T260 666Q215 666 182 639T148 565Q148 528 186 496T319 428Q352 414 370 405T418 379T468 338T506 284Q528 239 528 191Q528 102 456 46T266 -10Q211 -10 176 2T110 15Q86 9 73 -1T53 -12Q44 -12 37 -1V112V182Q37 214 40 226T54 238ZM446 619Q446 648 444 648Q439 646 435 644Q425 644 415 639H404L417 624Q435 606 439 601L446 592V619ZM124 619L128 635Q126 635 108 617Q64 576 64 502Q64 489 65 479T76 449T102 414T150 376T228 335Q335 291 381 245T427 128Q427 94 419 75L415 61Q421 61 448 88Q490 127 490 190Q490 233 475 264Q456 299 430 321Q402 349 369 367T287 404T204 441Q138 481 119 526Q113 544 113 565Q113 596 124 619ZM75 43Q76 43 90 46T110 50H119L106 64L74 101Q72 101 72 72T75 43'], // LATIN CAPITAL LETTER T 0x54: [683,1,667,33,635,'33 672Q36 680 44 683H624Q632 680 635 672V490L631 483Q621 479 617 479Q611 479 606 485T600 499Q600 525 584 552Q577 567 558 588T524 617Q479 642 426 646L415 648V355Q415 62 422 52Q425 42 434 40T473 35Q500 35 509 28Q518 17 509 3L502 -1H166L160 3Q149 17 160 28Q167 35 195 35Q224 37 234 39T249 52Q253 66 253 355V648L242 646Q192 642 144 617Q129 609 110 588T84 552Q69 527 69 499Q69 490 64 484T50 478Q39 478 33 490V672ZM113 639L126 648H69V597L84 612Q93 623 113 639ZM389 35Q382 46 381 86Q380 134 380 350V648H289V350Q289 199 288 131T286 53T280 35H389ZM600 597V648H542L555 639Q575 623 584 612L600 597'], // LATIN CAPITAL LETTER U 0x55: [683,19,722,16,709,'16 666Q16 677 28 683H341L348 679Q359 665 348 654Q342 648 315 648Q270 644 266 632Q262 627 262 598T261 399Q261 372 261 325T260 260Q260 149 274 99T339 30Q355 25 393 25Q430 25 457 33T494 49T519 72Q562 115 575 205Q576 219 576 379Q576 538 575 550Q568 597 550 622T506 648Q498 648 493 654T487 667T499 683H697Q709 675 709 667T704 654T690 648Q653 648 633 597Q624 573 622 546T619 377Q617 193 613 174Q596 95 544 41Q477 -19 355 -19H344Q275 -16 226 5T153 57T120 110T106 154Q101 172 99 399Q99 618 95 632Q88 644 53 648Q16 648 16 666ZM228 639L233 648H128Q128 647 133 632Q135 621 135 412Q135 197 137 185Q148 115 181 79Q209 51 235 41Q242 36 258 31T277 25Q276 27 268 38T254 59T241 92T228 145Q226 161 226 399Q226 632 228 639ZM604 621Q606 626 619 648H577L586 634Q587 632 591 625T595 614L597 608L604 621'], // LATIN CAPITAL LETTER V 0x56: [683,20,722,0,719,'316 683Q327 676 327 666Q327 648 302 648Q272 642 258 628Q249 621 249 608Q252 589 263 556T289 485T322 406T357 325T388 256T411 205L420 185Q423 185 473 317Q547 497 547 590Q547 621 541 632T516 648Q501 648 498 654Q488 664 498 679L504 683H607H660Q695 683 707 680T719 667Q719 660 714 654T700 648Q678 648 658 628L642 614L513 301Q484 231 449 148T397 25T380 -15Q373 -20 368 -20Q361 -20 358 -15Q354 -13 287 135T149 438T67 610Q45 648 18 648Q11 648 6 653T0 666Q0 677 9 680T59 683H164H316ZM216 614Q216 620 216 622T216 628T216 633T217 635T218 638T219 640T221 644T224 648H84L96 632Q118 592 236 330L367 43L387 88L404 132L380 185Q250 468 222 568Q216 590 216 614ZM576 645Q584 628 584 597L587 568L598 597Q609 624 618 637L624 648H600Q576 648 576 645'], // LATIN CAPITAL LETTER W 0x57: [683,19,1000,5,994,'785 664Q785 670 795 683H982Q994 675 994 665Q994 650 975 648Q953 643 939 619Q931 593 823 292T710 -15Q706 -19 699 -19T688 -15Q682 -6 639 107T555 328T513 437Q513 438 500 409T462 325T413 212Q315 -14 310 -17Q308 -19 302 -19T288 -15L57 619Q45 643 24 648Q5 650 5 665Q5 677 17 683H146H200Q256 683 270 681T285 666Q285 659 280 654T268 648Q253 648 239 634Q230 630 230 619Q230 598 264 481L362 192Q363 193 428 341T493 492Q493 496 473 546T446 608Q426 648 399 648Q392 648 387 653T382 667Q382 678 393 683H679Q690 670 690 665Q690 662 685 655T673 648Q653 648 633 632L622 625V610Q626 576 657 479T719 300T751 218Q754 218 779 294Q847 492 847 581Q847 648 802 648Q796 648 791 652T785 664ZM194 623Q194 630 199 648H82L90 632Q99 616 199 332L302 50Q303 50 322 94T342 141Q342 142 305 245T231 467T194 623ZM585 620Q585 634 593 648H530Q466 648 466 645Q479 632 595 323L699 54Q701 56 718 103T735 154L702 245Q585 562 585 620ZM884 572L890 587Q896 602 903 620T915 645Q915 648 893 648H868L875 634Q883 598 883 576Q883 572 884 572'], // LATIN CAPITAL LETTER X 0x58: [683,1,722,16,705,'22 666Q22 677 31 680T80 683H184H335Q346 675 346 667Q346 660 341 655Q335 648 315 648Q280 644 273 637Q273 630 300 583T356 492T386 448Q430 504 450 535T474 577T478 601Q478 620 469 634T444 648Q428 648 428 666Q428 678 436 680T488 683H559H630Q673 683 681 681T690 666Q690 648 673 648Q652 648 619 637Q571 615 517 550Q490 517 450 464T410 408Q415 399 501 273T617 106Q648 61 661 48T688 35Q705 35 705 16Q705 5 695 -1H539Q384 -1 379 3Q373 10 373 17Q373 27 380 31T408 35Q459 40 459 49Q459 59 418 129T335 259Q334 260 332 260Q328 260 273 197Q210 127 208 117Q199 104 199 82Q199 57 213 46T239 35Q247 35 252 29T257 15Q257 10 256 7T253 3T248 0L246 -1H28Q16 7 16 15T21 29T35 35Q61 35 117 88Q289 279 304 297Q307 303 255 377Q117 586 79 626Q60 648 39 648Q32 648 27 653T22 666ZM237 639V648H173Q113 647 113 646Q113 642 137 612Q186 546 302 373T453 139Q497 63 497 43Q497 39 495 35H559Q622 35 622 37Q622 38 583 94T486 233T373 399T277 552T237 639ZM553 637L566 648H504L508 637Q510 630 515 615V603L528 615Q529 616 539 625T553 637ZM170 46Q169 49 167 58T164 70V83L137 59L113 35H175Q175 38 170 46'], // LATIN CAPITAL LETTER Y 0x59: [683,1,722,16,704,'16 659T16 667T28 683H295Q306 676 306 666Q306 648 284 648Q258 648 255 641Q255 634 265 615T339 479Q418 339 421 339L455 394Q489 448 523 502L557 557Q560 566 560 582Q560 637 504 648Q489 648 486 655Q475 664 486 679L493 683H693Q704 675 704 667Q704 650 684 648Q672 645 653 623Q633 604 614 576T517 426L439 301V183Q442 62 444 59Q449 35 504 35Q521 35 528 30Q538 16 528 3L521 -1H195L188 3Q178 16 188 30Q195 35 213 35Q266 35 273 59Q274 61 277 163V261L75 621Q64 638 58 643T37 648Q28 648 22 653ZM219 637V648H101Q110 634 215 446L313 270V166Q310 59 306 48L301 35H415L410 48Q404 65 404 175V290L317 443Q230 601 226 612Q219 625 219 637ZM608 630L624 648H575Q584 632 588 623L595 610L608 630'], // LATIN CAPITAL LETTER Z 0x5A: [683,1,667,29,635,'39 -1Q29 9 29 12Q29 23 60 77T219 337L410 648H364Q261 648 210 628Q168 612 142 588T109 545T97 509T88 490Q85 489 80 489Q72 489 61 503L70 588Q72 607 75 628T79 662T81 675Q84 677 88 681Q90 683 341 683H592Q604 673 604 666Q604 662 412 348L221 37Q221 35 301 35Q406 35 446 48Q504 68 543 111T597 212Q602 239 617 239Q624 239 629 234T635 223Q635 215 621 113T604 8L597 1Q595 -1 317 -1H39ZM148 637L166 648H112V632Q111 629 110 622T108 612Q108 608 110 608T116 612T129 623T148 637ZM552 646Q552 648 504 648Q452 648 450 643Q448 639 266 343T77 37Q77 35 128 35H179L366 339L552 646ZM572 35Q581 89 581 97L561 77Q542 59 526 48L508 37L539 35H572'], // LATIN SMALL LETTER K 0x6B: [683,1,556,17,534,'519 443Q519 426 497 426Q458 422 361 335Q328 308 315 295Q307 289 310 286T383 193T466 88Q507 35 517 35Q534 35 534 16Q534 5 524 -1H304L297 3Q288 19 297 28Q300 35 317 35Q320 36 324 36T330 37T333 39Q334 39 334 40Q334 47 304 86T244 162L215 199Q212 202 206 199Q201 195 201 137V121Q201 35 230 35Q238 35 243 29T248 15Q248 4 237 -1H28L21 3Q17 13 17 17Q17 24 22 29T35 35Q55 35 61 70Q63 78 63 341T61 612Q55 648 35 648Q27 648 22 654T17 668Q17 678 26 682Q27 683 28 683H108H147Q156 683 162 683T174 683T182 683T187 682T191 681T194 680T197 678T201 675V461L204 246L244 281Q254 291 272 307Q317 349 326 360T339 386Q340 390 340 398Q340 426 321 426Q314 426 309 431T304 445Q304 456 315 461H508Q519 448 519 443ZM166 359V648H126Q89 648 89 645Q89 644 89 644T90 643T91 640T93 634T95 626Q99 612 99 341T95 57Q94 53 93 49T91 43T90 39L89 37Q89 35 133 35Q176 35 176 37Q175 38 175 39Q175 42 170 57Q166 70 166 359ZM410 423Q412 425 407 426Q404 426 393 426Q373 426 373 423Q374 422 375 417T377 410Q377 399 379 399Q406 419 410 423ZM460 37Q460 41 368 152L281 263Q280 263 259 246L239 228Q298 157 355 79Q370 61 370 41V35H417Q460 35 460 37'] }; MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/Main.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/Arrows.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // LEFTWARDS ARROW 0x2190: [437,-64,500,64,423,'292 419Q292 400 261 347T211 275H306Q319 275 338 275T364 276Q399 276 410 271T422 250T411 230T366 225H306H211Q214 222 232 197T271 136T292 82Q292 71 285 68T262 64H250H241Q221 64 216 67T205 83Q186 127 153 167T78 230Q64 238 64 250Q64 258 69 263T82 272T106 288T139 318Q162 342 177 365T198 402T209 425T223 436Q224 437 252 437H258Q292 437 292 419'], // RIGHTWARDS ARROW 0x2192: [437,-64,500,58,417,'188 417Q188 437 221 437H233Q256 437 263 434T275 417Q294 373 327 333T402 270Q417 261 417 250Q417 241 410 236T382 217T341 182Q315 155 299 128T275 85T263 66Q259 64 231 64H219Q197 64 191 72T193 100Q202 124 215 147T239 185T257 210T267 223L269 225H174H116Q80 225 69 229T58 250T70 271T114 276Q121 276 140 276T174 275H269L267 277Q266 280 257 291T233 325T205 374Q188 408 188 417'], // LEFTWARDS ARROW WITH STROKE 0x219A: [437,-60,1000,56,942,'942 250Q942 244 928 230H511L457 148Q440 124 420 93Q404 68 400 64T389 60Q381 60 375 66T368 81Q368 88 415 159L462 230H175L188 214Q210 188 235 145T264 85Q264 75 260 74T231 72L206 74L191 103Q169 142 164 150Q130 195 64 239Q56 244 56 250T64 261Q115 294 142 323T191 397L206 428H231Q255 428 259 426T264 414Q260 397 235 355T188 288L175 272L331 270Q488 270 491 272Q491 275 542 352T597 432Q602 437 609 437Q617 437 622 432T628 417T582 341L537 272L735 270H931Q942 257 942 250'], // RIGHTWARDS ARROW WITH STROKE 0x219B: [437,-60,1000,54,942,'54 250Q54 258 66 270H277L488 272L542 350Q596 431 602 435Q604 437 609 437Q617 437 622 432T628 417T582 341L537 272L608 270H751L822 272L808 288Q786 313 761 355T733 414Q733 424 737 426T766 428H793L806 397Q829 354 864 314Q896 284 928 263Q942 257 942 250T928 237Q887 208 864 185Q829 147 806 103L793 74L766 72Q742 72 738 73T733 85Q735 102 756 137T797 198L817 225L822 230H511L457 148Q440 124 420 93Q404 68 400 64T389 60Q381 60 375 66T368 81Q368 88 415 159L462 230H264L66 232Q54 239 54 250'], // LEFTWARDS TWO HEADED ARROW 0x219E: [417,-83,1000,56,944,'56 250Q103 277 142 322T199 417H221Q244 417 244 416Q244 414 237 397T208 344T158 278L151 270H276L285 277Q322 306 349 345T388 417H434Q434 413 424 392T393 338T349 279L340 270H634Q933 270 937 266L938 265Q944 259 944 250T938 235L937 234Q933 230 634 230H340L349 221Q372 196 393 163T424 108T434 83H388Q377 116 350 155T285 223L276 230H151L158 222Q186 191 207 156T236 104T244 84Q244 83 221 83H199Q181 133 142 178T56 250'], // RIGHTWARDS TWO HEADED ARROW 0x21A0: [417,-83,1000,55,943,'943 250Q895 221 856 177T801 83H778Q755 83 755 84Q755 86 762 103T791 156T841 222L848 230H723L714 223Q677 194 650 155T611 83H565Q565 87 575 108T606 162T650 221L659 230H365Q66 230 62 234L61 235Q55 241 55 250T61 265L62 266Q66 270 365 270H659L650 279Q627 304 606 337T575 392T565 417H611Q622 384 649 345T714 277L723 270H848L841 278Q813 309 792 344T763 396T755 416Q755 417 778 417H801Q817 367 856 323T943 250'], // LEFTWARDS ARROW WITH TAIL 0x21A2: [417,-83,1111,56,1031,'56 250Q103 277 142 322T199 417H221Q244 417 244 416Q244 414 237 397T208 344T158 278L151 270H873L882 277Q919 306 946 345T985 417H1031Q1031 413 1021 392T990 338T946 279L937 270V230L946 221Q969 196 990 163T1021 108T1031 83H985Q974 116 947 155T882 223L873 230H151L158 222Q186 191 207 156T236 104T244 84Q244 83 221 83H199Q181 133 142 178T56 250'], // RIGHTWARDS ARROW WITH TAIL 0x21A3: [417,-83,1111,79,1054,'1054 250Q1006 221 967 177T912 83H889Q866 83 866 84Q866 86 873 103T902 156T952 222L959 230H237L228 223Q191 194 164 155T125 83H79Q79 87 89 108T120 162T164 221L173 230V270L164 279Q141 304 120 337T89 392T79 417H125Q136 384 163 345T228 277L237 270H959L952 278Q924 309 903 344T874 396T866 416Q866 417 889 417H912Q928 367 967 323T1054 250'], // LEFTWARDS ARROW WITH LOOP 0x21AB: [576,41,1000,56,965,'56 250Q103 277 142 322T199 417H221Q244 417 244 416Q244 414 237 397T208 344T158 278L151 270H622V305Q622 356 624 388T635 460T661 521T709 559T785 575Q813 575 833 573T880 561T923 534T952 483T964 405Q964 374 959 350T942 307T918 276T884 255T847 242T804 235T760 231T713 230H662V-27Q654 -41 644 -41H642H640Q628 -41 622 -27V230H151L158 222Q186 191 207 156T236 104T244 84Q244 83 221 83H199Q181 133 142 178T56 250ZM924 403Q924 474 894 505T794 536Q758 536 734 526T696 500T675 453T665 395T662 319V270H699Q826 270 875 295T924 403'], // RIGHTWARDS ARROW WITH LOOP 0x21AC: [575,41,1000,35,943,'35 405Q35 454 48 489T86 542T137 567T195 575Q229 575 251 571T301 554T345 510T370 429Q377 384 377 305V270H848L841 278Q813 309 792 344T763 396T755 416Q755 417 778 417H801Q817 367 856 323T943 250Q896 221 857 177T801 83H778Q755 83 755 84Q755 86 762 103T791 156T841 222L848 230H377V-27Q369 -41 359 -41H357Q342 -41 337 -25V230H286Q247 231 225 232T169 238T115 255T75 284T45 333T35 405ZM75 406Q75 322 123 296T300 270H337V319Q335 432 317 477T240 534Q232 535 197 535Q140 535 108 507T75 406'], // LEFT RIGHT WAVE ARROW 0x21AD: [417,-83,1389,57,1331,'57 250Q159 311 200 417H246L242 407Q215 340 159 278L152 270H276L315 310Q354 349 358 351Q366 356 376 351Q378 350 455 273L530 196L606 273Q683 350 686 351Q694 354 703 351Q705 350 782 273L858 196L933 273Q1010 350 1012 351Q1022 356 1030 351Q1034 349 1073 310L1112 270H1236L1229 278Q1173 340 1146 407L1142 417H1188Q1233 306 1331 250Q1231 192 1188 83H1142L1146 93Q1173 160 1229 222L1236 230H1168Q1155 230 1139 230T1119 229Q1112 229 1108 229T1099 231T1092 233T1085 238T1078 245T1068 256T1056 269L1021 304L984 267Q948 230 910 191T867 149Q857 144 848 150Q844 151 770 227T694 304T618 228T540 150Q531 144 521 149Q517 152 479 191T404 267L367 304L332 269Q328 264 320 256T310 246T303 239T296 234T289 231T280 229T269 229Q265 229 249 229T220 230H152L159 222Q215 160 242 93L246 83H223L200 84L195 96Q152 190 57 250'], // LEFT RIGHT ARROW WITH STROKE 0x21AE: [437,-60,1000,56,942,'491 272Q491 275 542 352T597 432Q602 437 609 437Q617 437 622 432T628 417T582 341L537 272L608 270H751L822 272L808 288Q786 313 761 355T733 414Q733 424 737 426T766 428H793L806 397Q829 354 864 314Q896 284 928 263Q942 257 942 250T928 237Q887 208 864 185Q829 147 806 103L793 74L766 72Q742 72 738 73T733 85Q735 102 756 137T797 198L817 225L822 230H511L457 148Q440 124 420 93Q404 68 400 64T389 60Q381 60 375 66T368 81Q368 88 415 159L462 230H175L188 214Q210 188 235 145T264 85Q264 75 260 74T231 72L206 74L191 103Q169 142 164 150Q130 195 64 239Q56 244 56 250T64 261Q115 294 142 323T191 397L206 428H231Q255 428 259 426T264 414Q260 397 235 355T188 288L175 272L331 270Q488 270 491 272'], // UPWARDS ARROW WITH TIP LEFTWARDS 0x21B0: [722,0,500,56,444,'56 555Q74 567 79 570T107 592T141 625T170 667T198 722H221Q244 722 244 721Q244 718 236 699T207 647T161 587L151 576L291 575H292H293H294H296H297H298H299H300H301H302H304H305H306H307H308H309H310H311H312H314H315H316H317H318H319H320H321H322H323H324H325H327H328H329H330H331H332H333H334H335H336H337H338H339H340H341H342H343H345Q435 574 438 570L439 569L440 568Q444 564 444 287Q444 15 442 12Q436 0 424 0T406 12Q404 15 404 275V535H151L162 523Q187 495 207 462T236 410T244 389H198L193 402Q171 457 131 497T56 555'], // UPWARDS ARROW WITH TIP RIGHTWARDS 0x21B1: [722,0,500,55,443,'301 722Q339 618 443 555L437 551Q431 547 422 541T401 526T377 504T352 477T327 443T306 402L301 389H255Q255 392 263 410T291 461T337 523L348 535H95V275Q95 15 93 12Q87 0 75 0T57 12Q55 15 55 287Q55 564 59 568L60 569Q64 573 76 573T208 575L348 576L338 587Q314 613 294 646T264 698T255 721Q255 722 278 722H301'], // ANTICLOCKWISE TOP SEMICIRCLE ARROW 0x21B6: [461,1,1000,17,950,'361 210Q373 210 373 182V177Q373 155 370 151T348 139Q303 118 267 84T216 28T201 1Q197 -1 196 -1Q189 -1 184 8Q166 39 143 64T99 104T61 129T32 144T19 150Q17 152 17 179Q17 203 21 208Q28 210 39 206Q106 178 157 135L175 119V126Q179 130 179 155Q182 173 193 201Q228 305 312 374T510 459Q532 461 551 461H567Q678 461 784 386Q835 344 861 301Q902 245 926 173T950 32Q950 15 944 8Q930 -6 917 8Q910 12 910 43Q901 208 801 314T561 421Q453 421 359 359Q300 319 263 258T217 126L216 125Q216 124 216 123T217 122Q219 122 229 131T260 156T301 181Q314 189 336 199T361 210'], // CLOCKWISE TOP SEMICIRCLE ARROW 0x21B7: [460,1,1000,46,982,'972 209Q980 209 981 204T982 179Q982 155 979 151T957 139Q915 121 878 86T815 8Q808 -1 803 -1Q801 -1 797 1Q797 6 783 28T732 84T650 139L628 150Q626 152 626 177Q626 201 630 206Q636 210 637 210Q650 210 697 181Q727 166 764 137L784 119L782 132Q767 239 689 318T499 417Q474 421 442 421Q343 421 261 369T130 219Q86 121 86 28Q86 15 79 8Q73 1 66 1T53 8Q46 15 46 30Q46 102 77 192T186 361Q274 443 386 459Q396 460 426 460Q515 460 588 431T703 361T773 271T812 187T822 132Q822 123 825 123Q936 209 972 209'], // ANTICLOCKWISE OPEN CIRCLE ARROW 0x21BA: [650,83,778,56,722,'369 543T369 563T397 583Q408 583 440 579L454 577L464 581Q492 592 516 609T552 638T565 650Q604 638 607 637Q606 636 598 628T585 614T570 601T548 584T523 568L510 560L516 558Q522 555 527 553T541 546T559 536T580 523T603 506T626 485Q722 384 722 250Q722 106 622 12T387 -83Q253 -83 155 12T56 250Q56 357 110 433T235 545Q244 550 252 550Q270 550 270 531Q270 522 261 515T238 501T202 477T159 433Q95 352 95 250Q95 131 178 45T388 -42Q511 -42 596 43T682 250Q682 340 636 408T522 511Q495 526 488 526Q488 525 488 525T487 522T485 515L490 506Q505 481 516 451T531 404T535 384L532 385Q529 386 524 387T513 390L491 397L488 408Q472 483 413 542L399 543Q369 543 369 563'], // CLOCKWISE OPEN CIRCLE ARROW 0x21BB: [650,83,778,56,721,'170 637L213 650Q270 597 313 581L323 577L337 579Q369 583 380 583Q408 583 408 563T380 543H378L364 542Q305 483 289 408L286 397L264 390Q259 389 254 388T245 385L242 384Q242 387 246 403T261 450T287 506L292 515Q291 519 291 521T290 524T289 526Q284 526 265 517T216 486T160 434T114 354T95 249Q95 132 178 45T388 -42Q513 -42 597 44T682 250Q682 337 638 404T532 506Q529 508 525 510T519 514T515 516T511 519T509 522T508 526T507 531Q507 550 525 550Q533 550 542 545Q569 532 596 511T653 454T702 366T721 250Q721 151 672 74T547 -43T388 -83Q254 -83 155 12T56 250Q56 385 151 485Q164 498 179 509T205 528T228 542T247 551T260 558L267 560L254 568Q215 590 170 637'], // UPWARDS HARPOON WITH BARB RIGHTWARDS 0x21BE: [694,194,417,188,375,'188 258V694H208L215 682Q246 628 293 594T375 551V528Q375 505 374 505Q369 505 351 510T299 534T237 578L228 587V205Q228 -178 226 -182Q221 -194 208 -194T190 -182Q188 -178 188 258'], // UPWARDS HARPOON WITH BARB LEFTWARDS 0x21BF: [694,194,417,41,228,'41 551Q76 559 123 592T201 682L208 694H228V258Q228 -178 226 -182Q221 -194 208 -194T190 -182Q188 -178 188 205V587L179 578Q151 552 117 534T65 511T42 505Q41 505 41 528V551'], // DOWNWARDS HARPOON WITH BARB RIGHTWARDS 0x21C2: [694,194,417,188,375,'190 682Q195 694 208 694T226 683Q228 679 228 296V-87L237 -78Q265 -52 299 -34T351 -11T374 -5Q375 -5 375 -28V-51Q340 -60 293 -92T215 -182L208 -194H188V242Q188 678 190 682'], // DOWNWARDS HARPOON WITH BARB LEFTWARDS 0x21C3: [694,194,417,41,228,'188 295V573Q188 657 189 672T200 692Q206 694 208 694Q221 694 226 683Q228 679 228 242V-194H208L201 -182Q170 -128 123 -94T41 -51V-28Q41 -5 42 -5Q47 -5 65 -10T117 -34T179 -78L188 -87V295'], // RIGHTWARDS ARROW OVER LEFTWARDS ARROW 0x21C4: [667,0,1000,55,944,'943 500Q895 471 856 427T801 333H778Q755 333 755 334Q755 336 762 353T791 406T841 472L848 480H459Q70 480 67 482Q55 488 55 500T67 518Q70 520 459 520H848L841 528Q813 559 792 594T763 646T755 666Q755 667 778 667H801Q817 617 856 573T943 500ZM56 167Q102 194 141 238T198 333H221Q244 333 244 332Q221 265 161 198L151 187H539Q928 187 930 186Q944 182 944 167Q944 155 934 149Q930 147 541 147H151L160 137Q185 110 205 77T235 24T244 1Q244 0 221 0H199Q158 106 56 167'], // LEFTWARDS ARROW OVER RIGHTWARDS ARROW 0x21C6: [667,0,1000,55,944,'56 500Q103 527 142 572T199 667H221Q244 667 244 666Q244 664 237 647T208 594T158 528L151 520H539Q928 520 932 518Q944 513 944 500T932 482Q928 480 539 480H151L158 472Q186 441 207 406T236 354T244 334Q244 333 221 333H199Q181 383 142 428T56 500ZM943 167Q835 101 801 0H778Q755 0 755 1T758 9T765 25T771 39Q800 94 839 137L848 147H458Q68 147 66 149Q55 154 55 167Q55 182 69 186Q71 187 460 187H848L838 198Q811 228 791 261T762 314L755 332Q755 333 778 333H801Q841 227 943 167'], // LEFTWARDS PAIRED ARROWS 0x21C7: [583,83,1000,55,944,'930 437Q944 426 944 416T934 399Q930 397 540 397H150L159 387Q185 360 205 328T234 277T243 252Q243 237 217 191T159 113L150 103H540Q930 103 934 101Q944 94 944 84Q944 71 930 64L540 63H151Q180 34 203 -2T236 -61L244 -83H198Q178 -31 142 11T66 77L55 83L65 89Q157 145 197 246Q199 250 190 269Q150 359 65 411L55 417L66 423Q106 447 142 489T198 583H244Q202 488 151 437H930'], // UPWARDS PAIRED ARROWS 0x21C8: [694,193,833,83,749,'83 551Q190 590 250 694Q251 689 263 671T307 621T380 567Q409 551 416 551Q422 551 447 563T511 608T577 684L582 694Q642 591 749 551V528Q749 505 748 505Q745 505 724 515T669 546T612 590L602 599V-181Q595 -193 585 -193H582H581Q568 -193 565 -183L563 -179L562 209V598L552 589Q517 556 473 531T414 506H412Q411 506 393 514T361 530T324 553T280 589L270 598V-179Q255 -192 250 -193H247Q237 -193 230 -181V599L220 590Q197 567 164 546T110 515T84 505Q83 505 83 528V551'], // RIGHTWARDS PAIRED ARROWS 0x21C9: [583,83,1000,55,944,'55 416Q55 427 70 437H848Q819 466 796 502T764 561L755 583H801Q821 531 857 489T933 423L944 417L934 411Q843 355 802 254Q800 250 809 231Q849 141 934 89L944 83L933 77Q893 53 857 11T801 -83H755Q797 12 848 63H459L70 64Q55 70 55 84Q55 94 65 101Q69 103 459 103H849L840 113Q806 148 779 196T756 254Q756 255 760 264T770 286T786 315T809 351T840 387L849 397H459Q69 397 65 399Q55 406 55 416'], // DOWNWARDS PAIRED ARROWS 0x21CA: [694,194,833,83,749,'230 681Q240 694 251 694Q260 693 270 680V-98L280 -89Q297 -73 314 -60T348 -38T374 -24T397 -13T412 -6H414Q428 -6 473 -32T552 -89L562 -98V291L563 680Q570 693 582 693Q593 694 602 681V-99L612 -90Q635 -68 668 -47T723 -15T748 -5Q749 -5 749 -28V-51Q642 -91 582 -194L577 -184Q551 -141 512 -108T447 -63T416 -51T385 -63T321 -108T255 -184L250 -194Q189 -89 83 -51V-28Q83 -5 84 -5Q88 -5 109 -15T164 -46T220 -90L230 -99V681'], // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON 0x21CB: [514,14,1000,55,944,'195 504L198 514H221Q244 514 244 512Q244 508 239 490T215 437T171 376L162 367H545Q928 367 932 365Q944 360 944 347T932 329Q928 327 492 327H55V347L67 354Q113 379 146 420T195 504ZM67 171Q70 173 507 173H944V153L932 146Q839 95 804 -4L801 -14H778Q755 -14 755 -12Q768 59 828 124L837 133H454Q71 133 67 135Q55 140 55 153Q55 165 67 171'], // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON 0x21CC: [514,14,1000,55,944,'755 512Q755 514 778 514H801L804 503Q805 501 812 486T824 462T839 437T862 408T892 381T932 354L944 347V327H507Q70 327 67 329Q55 335 55 347T67 365Q70 367 454 367H837L828 376Q803 403 785 437T761 489T755 512ZM55 153V173H492Q928 173 932 171Q944 166 944 153T932 135Q928 133 545 133H162L171 124Q198 95 216 61T239 8L244 -12Q244 -14 221 -14H198L195 -4Q160 95 67 146L55 153'], // LEFTWARDS DOUBLE ARROW WITH STROKE 0x21CD: [535,35,1000,54,942,'397 525Q410 525 414 524T418 516Q418 506 394 467T331 381L319 367H473L624 369L657 445Q674 487 684 507T699 531T709 534Q717 534 722 528T728 516Q728 510 695 434Q689 418 683 402T672 377T668 367H928Q942 355 942 347Q942 341 928 327H791Q651 327 651 325Q649 324 620 251T586 174Q586 172 757 172H928Q942 158 942 152Q942 143 928 132H568L537 54Q510 -9 503 -22T486 -35Q479 -35 473 -29T466 -17T495 61L526 132H319L331 118Q364 81 391 37T418 -17Q418 -23 415 -24T401 -26Q398 -26 397 -26L384 -24L377 -13Q344 49 301 97T218 170T143 210T84 233T55 245Q54 253 59 256T86 267Q281 327 377 512L384 525H397ZM606 325Q606 327 439 327H275Q258 312 179 265L148 249Q228 206 262 181L275 172H544L575 247L606 325'], // LEFT RIGHT DOUBLE ARROW WITH STROKE 0x21CE: [534,37,1000,32,965,'395 -24T395 -19T417 57T440 132H255L266 116Q308 64 340 -6Q342 -17 337 -21Q335 -26 320 -26T302 -19Q302 -15 294 4T265 54T217 117T145 182T49 236Q30 243 33 254Q40 261 49 263Q98 283 142 315T214 379T263 442T293 493T302 519Q305 525 320 525T337 521Q342 516 340 505Q308 435 266 383L255 370L384 367H515Q561 522 569 530Q574 534 580 534Q587 534 594 528T602 516Q602 512 580 441T557 367H651L742 370L731 383Q689 435 657 505Q655 516 660 521Q662 525 677 525T695 519Q695 515 703 496T732 446T780 383T853 317T949 263Q967 258 964 245Q959 240 949 236Q897 215 852 182T779 116T731 52T703 3T695 -19Q692 -26 677 -26T660 -21Q655 -17 657 -6Q670 21 682 42T702 77T717 99T728 114T735 122T739 126T740 130T613 132H482L460 54Q440 -9 433 -23T415 -37Q408 -37 402 -31ZM502 325Q502 327 360 327H217L195 310Q173 291 120 256L111 250Q114 248 143 229T195 190L217 172H335L453 174L502 325ZM886 250Q885 251 865 263T831 286T802 310L780 327H544L535 299Q531 283 511 223L495 174L637 172H780L802 190Q843 225 877 243L886 250'], // RIGHTWARDS DOUBLE ARROW WITH STROKE 0x21CF: [534,36,1000,55,943,'346 174Q348 176 378 249T411 325Q411 327 239 327H68Q55 342 55 347Q55 354 68 367H428L459 445Q487 509 494 521T510 534Q517 534 524 527T531 516Q531 515 502 438L471 367H677L666 381Q631 421 605 463T578 516Q578 522 582 523T599 525H615L619 512Q659 437 714 383T812 309T896 272T942 254Q943 246 938 243T911 232Q718 172 619 -13L615 -24L599 -26Q578 -26 578 -17Q578 -11 587 6T617 53T666 118L677 132H373L339 54Q323 12 313 -8T298 -32T288 -35Q280 -35 275 -29T269 -17Q269 -14 298 57T328 132H68Q55 145 55 152Q55 156 56 158T62 165T68 172H206Q346 172 346 174ZM848 249Q763 297 735 318L722 327H455L422 252L391 174Q391 172 557 172H722L735 181Q773 210 819 234L848 249'], // LEFTWARDS TRIPLE ARROW 0x21DA: [611,111,1000,76,945,'944 54Q942 44 929 36H372Q372 34 377 26T395 -4T422 -58Q442 -109 442 -110T408 -111H374L370 -100Q282 124 87 243L76 250L87 257Q284 377 370 600L374 611H408Q442 611 442 610Q423 550 381 480Q380 478 379 475T376 471T374 468T372 465V464H929Q942 456 944 446Q944 442 943 439T941 434T938 430T935 428T931 426T928 424H344L336 414Q277 336 200 277L191 270H560Q929 270 933 268Q944 262 944 250Q944 237 933 232Q929 230 560 230H191L200 223Q279 162 336 86L344 76H928Q929 76 931 75T934 73T938 70T941 66T943 61T944 54'], // RIGHTWARDS TRIPLE ARROW 0x21DB: [611,111,1000,55,923,'56 250Q56 260 68 270H808L799 277Q720 338 663 414L655 424H363Q71 424 68 426Q55 432 55 444T68 462Q71 464 349 464H627Q627 466 622 474T604 504T577 558Q557 609 557 610T591 611H626L629 600Q717 376 912 257L923 250L912 243Q715 123 629 -100L626 -111H591Q557 -111 557 -110Q576 -50 618 20Q619 22 620 25T623 29T625 32T626 35L627 36H349Q71 36 68 38Q55 44 55 56T68 74Q71 76 363 76H655L663 86Q722 164 799 223L808 230H438L68 231Q56 236 56 250'], // RIGHTWARDS SQUIGGLE ARROW 0x21DD: [417,-83,1000,56,943,'76 230Q68 230 62 237T56 250Q56 257 63 264T91 291Q102 300 108 306L159 351Q168 356 177 351L218 316L303 239L353 195Q376 214 403 239L488 316L529 351Q538 356 546 351Q548 350 594 310L638 270H848L841 278Q813 309 792 344T763 396T755 416Q755 417 778 417H801Q817 367 856 323T943 250Q895 221 856 177T801 83H778Q755 83 755 84Q755 86 762 103T791 156T841 222L848 230H737Q625 230 622 232Q620 233 599 251T558 288L537 306Q537 305 451 228T362 149Q353 146 345 149Q341 150 255 227T169 306Q167 306 129 270Q123 265 115 257T102 245T93 237T84 232T76 230'], // LEFTWARDS DASHED ARROW 0x21E0: [437,-64,1334,64,1251,'292 419Q292 400 261 347T211 275H306H364Q400 275 411 271T422 250T411 230T366 225H306H211Q214 222 232 197T271 136T292 82Q292 71 285 68T262 64H250H241Q221 64 216 67T205 83Q186 127 153 167T78 230Q64 238 64 250Q64 258 69 263T82 272T106 288T139 318Q162 342 177 365T198 402T209 425T223 436Q224 437 252 437H258Q292 437 292 419ZM501 237T501 250T515 270H819Q834 262 834 250T819 230H515Q501 237 501 250ZM918 237T918 250T932 270H1236Q1251 262 1251 250T1236 230H932Q918 237 918 250'], // RIGHTWARDS DASHED ARROW 0x21E2: [437,-64,1334,84,1251,'84 237T84 250T98 270H402Q417 262 417 250T402 230H98Q84 237 84 250ZM501 237T501 250T515 270H819Q834 262 834 250T819 230H515Q501 237 501 250ZM1022 417Q1022 437 1055 437H1067Q1090 437 1097 434T1109 417Q1128 373 1161 333T1236 270Q1251 261 1251 250Q1251 241 1244 236T1216 217T1175 182Q1149 155 1133 128T1109 85T1097 66Q1093 64 1065 64H1053Q1031 64 1025 72T1027 100Q1036 124 1049 147T1073 185T1091 210T1101 223L1103 225H1008H950Q914 225 903 229T892 250T903 270T948 275H1008H1103L1101 277Q1100 280 1091 291T1067 325T1039 374Q1022 408 1022 417'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/Arrows.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/BoxDrawing.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // BOX DRAWINGS LIGHT DOWN AND RIGHT 0x250C: [694,-306,500,55,444,'76 306Q62 306 59 319T55 386V500V596Q55 664 57 676T68 692Q71 694 250 694Q428 694 432 692Q444 685 444 674Q444 665 432 656Q428 654 261 654H95V487Q95 355 95 336T90 312Q84 306 76 306'], // BOX DRAWINGS LIGHT DOWN AND LEFT 0x2510: [694,-306,500,55,445,'424 306Q418 306 413 310T406 318L404 321V654H238Q71 654 68 656Q55 662 55 674T68 692Q71 694 250 694H379Q432 694 438 688Q443 683 443 662T444 500T444 338T438 312Q432 306 424 306'], // BOX DRAWINGS LIGHT UP AND RIGHT 0x2514: [366,22,500,55,444,'55 172V287Q55 341 58 353T76 366Q88 366 95 351V18H261Q428 18 432 16Q444 9 444 -2Q444 -11 432 -20Q428 -22 250 -22H120Q67 -22 61 -16Q56 -11 56 10T55 172'], // BOX DRAWINGS LIGHT UP AND LEFT 0x2518: [366,22,500,55,444,'404 351Q410 366 424 366Q437 366 440 353T444 288V172V72Q444 8 443 -4T432 -20Q428 -22 250 -22Q71 -22 68 -20Q55 -14 55 -2T68 16Q71 18 238 18H404V351'], // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT 0x2571: [694,195,889,0,860,'19 -195Q13 -195 7 -188T0 -176Q0 -169 18 -151L822 683Q835 694 840 694T852 688T860 674Q860 667 810 614T460 252Q57 -167 44 -179Q27 -195 19 -195'], // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT 0x2572: [694,195,889,0,860,'0 675Q0 681 6 687T19 694Q27 694 44 678L460 247Q759 -62 809 -115T860 -175Q860 -183 852 -189T840 -195Q835 -195 822 -184L18 649Q0 667 0 675'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/BoxDrawing.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/CombDiacritMarks.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // COMBINING CIRCUMFLEX ACCENT 0x302: [845,-561,0,-2347,13,'-2332 561Q-2336 563 -2340 577T-2346 604L-2347 618Q-2347 625 -2340 628T-2310 635Q-2302 636 -2297 637Q-2270 641 -1712 745Q-1185 845 -1168 845Q-1166 845 -581 739L5 630Q13 630 13 618Q7 565 -1 561Q-4 561 -584 654Q-716 675 -867 699T-1092 736T-1166 748Q-1168 748 -1240 737T-1466 700T-1750 654Q-2330 561 -2332 561'], // COMBINING TILDE 0x303: [899,-628,0,-2332,-3,'-1529 788Q-1616 788 -1727 772T-1936 732T-2120 685T-2258 645T-2315 628Q-2322 628 -2322 632Q-2325 637 -2329 668T-2331 704Q-2331 713 -2297 732Q-2278 739 -2091 795Q-1711 898 -1507 898Q-1440 898 -1386 895Q-1324 887 -1277 872T-1146 819Q-1047 776 -977 758T-806 739Q-719 739 -608 755T-399 795T-215 842T-77 882T-20 899Q-13 899 -13 895Q-10 890 -6 860T-4 824Q-4 818 -37 795Q-60 787 -244 732Q-523 657 -735 632Q-771 629 -841 629Q-944 629 -1013 644T-1189 708Q-1285 751 -1356 769T-1529 788'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/CombDiacritMarks.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/Dingbats.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // CHECK MARK 0x2713: [706,34,833,84,749,'84 231Q84 244 114 264T170 285Q176 285 183 274T224 205Q267 129 268 129Q271 141 279 163T318 250T389 378T502 523T662 673Q702 706 732 706H734Q749 706 749 695Q749 682 730 666T660 607T559 505Q387 299 328 29Q324 0 295 -17T245 -34H241Q234 -34 225 -21T185 46Q166 79 154 101Q84 223 84 231'], // MALTESE CROSS 0x2720: [716,22,833,48,786,'195 702T195 706T201 716H632Q638 710 638 706T636 700T621 690Q436 581 427 374V357H430Q554 357 645 421Q682 447 711 483T755 542T770 567Q775 572 786 563V131Q777 125 774 125T762 139Q709 228 642 274T482 333Q452 337 430 337H427V320Q430 279 437 247T462 170T521 82T621 4Q630 -2 633 -4T637 -7T638 -12Q638 -16 632 -22H201Q195 -16 195 -12T197 -6T212 4Q397 113 406 320V337H403Q279 337 188 273Q151 247 122 211T78 152T63 127Q58 122 48 131V563Q54 569 59 569Q62 569 71 555Q124 466 191 420T351 361Q381 357 403 357H406V374Q403 415 396 447T371 525T312 613T212 690Q199 697 197 699'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/Dingbats.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/EnclosedAlphanum.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // CIRCLED LATIN CAPITAL LETTER S 0x24C8: [709,175,902,8,894,'451 -175Q328 -175 226 -115T66 47T8 267Q8 303 15 342T39 431T94 531T186 622Q239 663 307 686T424 709H440Q604 709 716 622Q757 592 788 555T838 482T869 414T886 350T892 301T894 267Q894 147 835 45T674 -116T451 -175ZM854 268Q854 375 802 467T657 614T450 670Q283 670 166 552T49 267Q49 99 167 -18T453 -136Q617 -136 735 -18T854 268ZM273 378Q273 430 309 474T409 527Q411 527 417 527T428 528Q498 528 549 484L567 505Q583 528 590 528H594Q600 528 606 522V350L600 344H586Q577 344 574 344T569 347T566 357Q542 491 432 491Q389 491 365 465T340 407Q340 391 344 378T358 356T377 340T400 328T421 321T443 316T459 313Q499 305 517 300T559 279T601 238Q629 195 629 148Q629 80 583 33T471 -14Q392 -14 330 30Q312 6 293 -13Q292 -14 285 -14Q279 -14 273 -8V77V138Q273 160 275 165T286 170H294H307Q313 164 313 158Q313 108 350 67T471 26Q512 26 537 54T562 119Q562 137 558 151T544 176T527 193T504 205T483 212T459 218T441 222Q391 232 368 241T318 273Q273 316 273 378'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/EnclosedAlphanum.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/GeneralPunctuation.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // REVERSED PRIME 0x2035: [560,-43,275,12,244,'12 501Q12 527 31 542T63 558Q73 560 77 560Q114 560 128 528Q133 518 188 293T244 61Q244 56 223 50T195 43Q192 43 190 45T102 263T14 486Q12 496 12 501'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/GeneralPunctuation.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/GeometricShapes.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // BLACK SQUARE 0x25A0: [689,0,778,55,722,'71 0Q59 4 55 16V346L56 676Q64 686 70 689H709Q719 681 722 674V15Q719 10 709 1L390 0H71'], // WHITE SQUARE 0x25A1: [689,0,778,55,722,'71 0Q59 4 55 16V346L56 676Q64 686 70 689H709Q719 681 722 674V15Q719 10 709 1L390 0H71ZM682 40V649H95V40H682'], // BLACK UP-POINTING TRIANGLE 0x25B2: [575,20,722,84,637,'99 -20Q84 -11 84 0Q84 5 148 145T278 424L342 563Q347 575 360 575Q368 575 375 570Q376 569 441 430T571 148T637 0Q637 -11 622 -20H99'], // WHITE UP-POINTING TRIANGLE 0x25B3: [575,20,722,84,637,'99 -20Q84 -11 84 0Q84 5 148 145T278 424L342 563Q347 575 360 575Q368 575 375 570Q376 569 441 430T571 148T637 0Q637 -11 622 -20H99ZM476 260L360 509L248 266Q137 24 135 22Q135 20 360 20Q586 20 586 21L476 260'], // BLACK RIGHT-POINTING TRIANGLE 0x25B6: [540,41,778,83,694,'83 523Q83 524 85 527T92 535T103 539Q107 539 389 406T680 268Q694 260 694 249Q694 239 687 234Q685 232 395 95L107 -41H101Q90 -40 83 -26V523'], // BLACK DOWN-POINTING TRIANGLE 0x25BC: [576,19,722,84,637,'84 556Q84 567 99 576H622Q637 567 637 556Q637 551 572 409T441 127T375 -14Q368 -19 360 -19H358Q349 -19 342 -7T296 92Q249 193 211 275Q84 550 84 556'], // WHITE DOWN-POINTING TRIANGLE 0x25BD: [576,19,722,84,637,'84 556Q84 567 99 576H622Q637 567 637 556Q637 551 572 409T441 127T375 -14Q368 -19 360 -19H358Q349 -19 342 -7T296 92Q249 193 211 275Q84 550 84 556ZM586 534Q586 536 361 536Q135 536 135 535L358 52L361 47L473 290Q584 532 586 534'], // BLACK LEFT-POINTING TRIANGLE 0x25C0: [539,41,778,83,694,'694 -26Q686 -40 676 -41H670L382 95Q92 232 90 234Q83 239 83 249Q83 262 96 267Q101 270 379 401T665 537Q671 539 674 539Q686 539 694 524V-26'], // LOZENGE 0x25CA: [716,132,667,56,611,'318 709Q325 716 332 716Q340 716 344 713T474 511Q611 298 611 292Q611 285 526 152Q494 103 474 72Q347 -128 344 -130Q340 -132 333 -132T322 -130Q319 -128 257 -31T131 169T60 278Q56 285 56 292Q56 298 60 305Q73 326 194 516T318 709ZM567 290T567 291T451 475T333 658L100 293Q100 288 215 108L333 -74Q334 -74 450 108'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/GeometricShapes.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/GreekAndCoptic.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // GREEK SMALL LETTER DIGAMMA 0x3DD: [605,85,778,55,719,'477 261Q477 257 473 256T455 253T417 251T348 250H235L155 -77L146 -82Q137 -85 109 -85Q55 -85 55 -77L139 261Q224 596 226 598Q229 603 239 603Q240 603 254 603T290 603T341 604T405 605T477 605Q656 603 687 602T719 596Q719 589 692 588T513 585H319L282 427L242 272Q242 270 351 270Q388 270 410 270T444 269T460 267T469 265T477 261'], // GREEK KAPPA SYMBOL 0x3F0: [434,6,667,37,734,'228 325Q170 322 156 316T127 309Q108 309 104 314Q99 319 99 322T108 341Q125 376 171 400T268 425H271Q302 425 319 396Q328 377 328 358Q328 332 324 314Q311 270 286 221Q274 194 274 192H275Q339 234 484 325T639 421Q669 434 691 434T723 425T734 406Q734 394 719 381Q715 376 644 330L575 287L566 267Q543 233 526 176Q520 160 515 143T508 115T506 105Q506 103 533 103Q585 103 607 110T641 118Q670 118 670 107Q670 100 661 85Q643 50 598 27T504 3Q465 3 450 36Q441 51 441 73Q441 84 444 96Q452 146 484 205L497 236L324 125Q143 12 135 10Q103 -6 77 -6Q61 -6 49 2T37 21Q37 36 49 46T124 96L195 141L204 156Q219 179 243 248T264 323Q264 325 228 325'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/GreekAndCoptic.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/Latin1Supplement.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // YEN SIGN 0xA5: [683,0,750,11,738,'515 0Q494 3 374 3Q256 3 235 0H224V46H257Q316 47 324 58Q327 62 327 137V213H133Q121 213 113 213T97 213T86 213T78 213T73 214T70 215T69 216T68 218T67 220Q64 225 66 231T73 240Q76 242 202 242H327V273L247 407H115Q81 407 75 408T67 414Q64 419 66 425T73 434Q76 436 153 436Q228 436 228 437Q227 440 173 530T115 623Q101 637 31 637H11V683H20Q66 681 153 681Q169 681 202 681T262 682L288 683H298V637H280Q230 636 230 621Q230 619 250 584Q255 576 264 561T286 526T305 494L340 437L403 436H467L513 514Q564 596 564 605Q564 608 560 616Q550 634 517 637H508V683H516Q531 680 633 680Q722 680 731 683H738V637H723Q644 632 617 595Q614 591 568 515T521 437T597 436T676 434Q681 432 683 426T682 414T671 409T589 407H503L422 273V242H547Q673 242 676 240Q681 238 683 232T682 220Q682 219 682 218T681 217T679 216T677 215T672 214T664 213T652 213T637 213T616 213H422V139V87Q422 64 425 58T441 49Q456 46 503 46H525V0H515ZM449 406Q449 407 403 407Q358 407 358 406L370 387Q381 368 392 350L404 331Q447 404 449 406'], // REGISTERED SIGN 0xAE: [709,176,947,32,915,'915 266Q915 140 852 38T689 -120T474 -175Q312 -175 188 -71T38 190Q32 220 32 266V287Q32 345 57 416T129 545Q192 624 282 666T464 709Q513 709 522 708Q599 698 665 666T776 590T853 493T900 387T915 287V266ZM875 285Q875 339 853 399T789 517T676 616T519 668Q510 669 465 669Q380 669 299 630T155 514T77 336Q72 312 72 285V266V256Q72 123 163 11Q290 -135 474 -135Q614 -135 727 -46Q875 81 875 266V285ZM276 457Q275 458 274 460T272 463T270 465T267 467T264 469T258 471T252 472T243 473T232 474T218 474H204V514H335Q477 514 499 510Q560 502 610 467T661 375Q661 362 658 350T648 327T635 308T618 292T601 280T583 269T568 262T554 256L547 253Q548 252 556 247T570 237T586 223T602 202T614 174Q616 169 626 123T638 72Q652 23 683 23Q715 23 720 68Q721 78 724 81T740 84T756 82T760 70Q760 47 747 25T715 -7Q700 -14 673 -14Q672 -14 662 -14T643 -12T619 -7T593 2T568 16T547 37T534 67Q531 80 531 97Q531 103 531 116T532 136Q532 218 472 236Q466 238 413 239H360V148L361 58Q366 47 375 44T418 40H432V0H424Q409 3 318 3T212 0H204V40H218Q242 40 253 42T268 47T276 58V457ZM376 473Q365 471 363 464T360 430V366V276H416Q421 276 434 276T453 276T469 277T486 279T501 282T517 287T529 294T542 305Q561 324 561 375Q561 424 545 444T482 472Q478 473 427 474Q415 474 403 474T384 474L376 473'], // LATIN SMALL LETTER ETH 0xF0: [749,21,556,42,509,'75 566V604Q75 624 79 629T102 635Q124 635 127 629T131 588L133 550L191 588L249 628L231 635Q176 654 124 657Q116 657 106 658L95 659Q94 661 94 687T95 715Q99 717 113 717Q195 717 282 679L309 668L331 681Q351 697 391 721Q428 748 435 748Q437 749 446 749Q470 749 473 746Q478 744 478 681V621Q466 615 456 615Q435 615 424 624L422 661V699L382 675L344 648Q353 639 366 630Q480 538 504 413Q509 393 509 333V313Q509 284 507 257T495 184T466 102T413 33T329 -16Q311 -21 275 -21Q226 -21 195 -10Q150 7 110 50T53 141Q42 179 42 227Q42 332 101 403T245 474Q282 474 314 461T359 436T380 415Q386 405 389 408Q389 426 378 475Q368 505 355 529T329 567T306 590T288 603L282 606L120 501Q116 500 102 500Q84 500 75 506V566ZM388 225Q388 376 309 410Q299 416 273 419Q216 419 191 390Q174 371 168 342T162 218Q162 112 184 79Q212 39 273 39Q312 39 342 62T380 121Q388 159 388 225'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/Latin1Supplement.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/LatinExtendedA.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // LATIN SMALL LETTER H WITH STROKE 0x127: [695,13,540,42,562,'182 599Q182 611 174 615T133 619Q118 619 114 621T109 630Q109 636 114 656T122 681Q125 685 202 688Q272 695 286 695Q304 695 304 684Q304 682 295 644T282 597Q282 592 360 592H399Q430 592 445 587T460 563Q460 552 451 541L442 535H266L251 468Q247 453 243 436T236 409T233 399Q233 395 244 404Q295 441 357 441Q405 441 445 417T485 333Q485 284 449 178T412 58T426 44Q447 44 466 68Q485 87 500 130L509 152H531H543Q562 152 562 144Q562 128 546 93T494 23T415 -13Q385 -13 359 3T322 44Q318 52 318 77Q318 99 352 196T386 337Q386 386 346 386Q318 386 286 370Q267 361 245 338T211 292Q207 287 193 235T162 113T138 21Q128 7 122 4Q105 -12 83 -12Q66 -12 54 -2T42 26L166 530Q166 534 161 534T129 535Q127 535 122 535T112 534Q74 534 74 562Q74 570 77 576T84 585T96 589T109 591T124 592T138 592L182 595V599'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/LatinExtendedA.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/LetterlikeSymbols.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // stix-/hbar - Planck's over 2pi 0x210F: [695,13,540,42,562,'150 475Q147 475 118 466T82 457Q73 457 64 467T54 487Q54 490 55 492Q63 506 64 506Q67 512 118 526Q162 541 169 546Q173 559 175 575Q181 596 181 604Q181 613 166 617Q164 617 153 618T135 619Q119 619 114 621T109 630Q109 636 114 656T122 681Q125 685 202 688Q272 695 286 695Q304 695 304 684Q304 682 291 628L278 577L386 612Q466 635 476 635T492 627T499 607Q499 593 489 586Q485 583 373 546L262 512Q262 511 248 455T233 397T236 397T244 404Q295 441 357 441Q405 441 445 417T485 333Q485 284 449 178T412 58T426 44Q447 44 466 68Q485 87 500 130L509 152H531H543Q562 152 562 144Q562 128 546 93T494 23T415 -13Q385 -13 359 3T322 44Q318 52 318 77Q318 99 352 196T386 337Q386 386 346 386Q318 386 286 370Q267 361 245 338T211 292Q207 287 193 235T162 113T138 21Q128 7 122 4Q105 -12 83 -12Q66 -12 54 -2T42 26Q42 45 98 257L151 475H150'], // INVERTED OHM SIGN 0x2127: [685,22,722,44,675,'126 584Q119 584 110 539T97 493Q95 490 73 490Q44 490 44 501Q44 515 62 590Q75 672 82 679Q84 684 177 684Q193 684 214 684T241 685Q265 685 271 682T277 664V648Q271 572 229 434T186 231Q186 173 203 132T247 70T302 42T360 33Q391 33 419 42T474 72T517 133T533 231Q533 297 491 437T442 648Q442 675 446 679Q448 684 542 684Q635 684 637 681Q640 678 657 594T675 501Q675 490 646 490Q624 490 622 493Q620 493 609 538T593 584Q591 585 585 585T569 586T551 588H513Q514 586 518 573T538 531T582 453Q647 340 660 277Q663 259 663 232Q663 194 657 177Q652 151 629 112T560 39Q495 -5 424 -19Q403 -22 360 -22Q318 -22 297 -19Q239 -8 193 18T120 74T80 131T62 177Q56 194 56 229Q56 281 74 328T137 453Q160 491 174 518T193 555T201 575T206 588H168Q160 587 150 587T134 586T126 584'], // TURNED CAPITAL F 0x2132: [695,1,556,55,497,'457 681Q471 695 477 695Q485 695 497 681V12L484 -1H68Q55 14 55 19T68 39H457V328H215L211 335Q198 346 211 359L217 368H457V681'], // BET SYMBOL 0x2136: [763,21,667,-22,687,'56 706V726Q56 763 76 763Q83 763 87 759T98 741Q108 726 116 721L127 717L340 715Q547 712 564 709Q575 705 587 692Q599 680 605 663L609 650V137H676Q687 124 687 115Q687 110 678 100T622 43L558 -21H-9Q-22 -6 -22 -1T-13 14T42 72L107 137H569V339Q569 541 567 546Q558 555 554 557L545 563H329Q118 566 101 569Q90 573 78 586Q54 610 54 661Q54 670 56 706'], // GIMEL SYMBOL 0x2137: [764,43,444,-22,421,'56 750Q68 764 76 764Q88 764 97 743T125 717Q131 715 240 715T358 713Q421 691 421 640Q421 608 399 588T358 566Q353 566 352 565T351 557L356 526Q356 488 379 346T402 97Q400 21 385 -12Q366 -43 351 -43Q335 -43 329 -10Q316 40 316 64Q316 67 315 67Q313 67 269 26L222 -21H-9Q-22 -7 -22 -1Q-22 4 -14 14T42 73L107 137H311V564H211H164Q115 564 93 573T60 615Q56 630 56 690V750'], // DALET SYMBOL 0x2138: [764,43,667,54,640,'62 757Q69 764 75 764Q87 764 97 741Q102 731 105 728T117 721L129 715H349Q569 715 580 710Q618 701 635 670Q640 661 640 639Q640 609 622 590Q617 583 604 575T580 566H573V553Q575 547 576 531T582 469T600 353Q624 205 624 104Q624 46 617 17T591 -32Q581 -43 573 -43Q550 -43 540 44Q535 73 533 319V564H322Q117 566 100 570Q90 573 77 586Q54 609 54 663Q54 689 55 706Q55 738 56 745T62 757'], // TURNED SANS-SERIF CAPITAL G 0x2141: [705,23,639,37,577,'239 665Q194 665 154 653T90 629T66 617Q59 617 53 623T46 637Q46 652 66 659Q129 695 197 701Q218 705 248 705Q293 705 335 693Q371 684 435 644Q543 562 573 417Q577 393 577 341Q577 290 573 266Q531 83 384 10Q346 -9 315 -16T234 -23H206Q202 -23 183 -23T152 -21T120 -18T88 -10T63 3T44 24L37 35V297L50 310H235Q248 297 248 290Q248 285 235 270H77V103Q77 88 77 80T77 63T78 50T80 43T82 38T85 35T89 32T95 30Q126 20 206 17Q289 17 330 30Q407 55 460 120T533 275Q538 305 538 342Q538 486 452 575T239 665'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/LetterlikeSymbols.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/MathOperators.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // COMPLEMENT 0x2201: [846,22,500,56,444,'404 269Q412 269 418 267T428 261T435 253T441 245L444 240V172Q444 103 443 96Q440 81 431 65T403 27T344 -7T250 -21T156 -8T97 27T69 65T58 96Q56 103 56 413Q56 722 58 729Q74 822 215 845Q221 846 229 846H243Q282 846 290 845Q422 826 443 729Q444 722 444 653V586L442 583Q441 580 440 578T436 573T430 567T423 562T415 558T404 556Q377 556 367 583Q364 590 364 654V719Q363 721 360 726T355 733Q326 766 250 766H249Q235 766 219 765T174 752T137 719V107Q145 83 178 71T251 58H254Q340 58 364 107V172Q364 176 364 187T363 204Q363 269 404 269'], // THERE DOES NOT EXIST 0x2204: [860,166,556,55,497,'55 676Q55 688 66 694H199L333 696L351 772Q364 827 370 843T386 860Q393 860 399 854T406 841Q406 836 391 765L375 696Q375 694 431 694H484Q491 688 497 681V12L493 5L486 1L353 -1H219L202 -79Q184 -153 180 -159Q175 -166 165 -166Q146 -166 146 -148Q146 -141 161 -76T177 -4Q177 -1 122 -1H68Q55 12 55 20T66 39H126L186 41L219 181Q226 215 234 251T246 305T251 325Q251 328 166 328H79Q68 345 68 347Q68 352 75 359L82 368H262L291 505Q298 539 306 575T319 630T324 650V654H68Q55 669 55 676ZM457 368V654H411Q366 654 366 652Q365 651 361 634T349 580T333 514Q303 373 302 372V368H457ZM457 39V328H375Q293 328 293 325Q292 322 260 183T228 41T344 39H457'], // EMPTY SET 0x2205: [587,3,778,55,720,'624 470Q624 468 639 446T668 382T683 291Q683 181 612 99T437 -1Q425 -2 387 -2T337 -1Q245 18 193 70L179 81L131 39Q96 8 89 3T75 -3Q55 -3 55 17Q55 24 61 30T111 73Q154 113 151 113Q151 114 140 130T115 177T95 241Q94 253 94 291T95 341Q112 431 173 495Q265 587 385 587Q410 587 437 581Q522 571 582 513L595 501L642 541Q689 586 695 586Q696 586 697 586T699 587Q706 587 713 583T720 568Q720 560 711 551T664 510Q651 499 642 490T628 475T624 470ZM564 477Q517 522 448 539Q428 546 375 546Q290 546 229 492T144 370Q133 332 133 279Q136 228 151 195Q157 179 168 160T184 141Q186 141 375 307T564 477ZM642 290Q642 318 637 343T625 386T611 416T598 436T593 444Q590 444 402 277T213 108Q213 104 231 89T293 55T392 37Q495 37 568 111T642 290'], // SMALL CONTAINS AS MEMBER 0x220D: [440,1,429,102,456,'154 -1Q122 -1 112 3T102 26Q102 63 158 63H178Q192 64 206 65T228 66T240 68Q301 85 324 146L329 157H244Q158 157 153 161Q149 162 145 169T140 183Q140 201 158 215L167 221H256L344 223L349 237Q352 262 352 287Q352 308 351 315Q341 352 315 368T256 385Q231 385 206 376T166 356T149 346Q143 346 138 364T132 388Q132 396 147 406Q198 440 252 440Q291 440 318 435Q421 404 451 301Q456 288 456 248V234Q456 151 391 86Q330 25 240 3Q212 -1 154 -1'], // MINUS SIGN 0x2212: [270,-230,500,84,417,'84 237T84 250T98 270H402Q417 262 417 250T402 230H98Q84 237 84 250'], // DOT PLUS 0x2214: [766,93,778,57,722,'339 717Q339 739 354 752T388 766Q410 766 424 751T439 716T424 681T390 666Q369 666 354 681T339 717ZM57 237T57 250T71 270H369V425L370 581Q380 594 389 594Q402 594 409 579V270H707Q722 262 722 250T707 230H409V-79Q401 -93 391 -93H389H387Q375 -93 369 -79V230H71Q57 237 57 250'], // SET MINUS 0x2216: [430,23,778,91,685,'91 404T91 410T97 423T111 430Q117 430 395 224Q676 13 678 10Q685 3 685 -3T678 -16T664 -23Q658 -23 380 184T98 397Q91 404 91 410'], // PROPORTIONAL TO 0x221D: [472,-28,778,56,722,'56 250Q56 346 122 409T276 472Q349 472 407 430T486 326L489 316Q490 317 493 326T501 345T514 367T531 393Q557 425 602 448T698 472Q722 472 722 452Q722 437 702 435T642 421T571 377Q520 323 520 250Q520 179 568 126T693 68Q722 66 722 48Q722 28 698 28Q636 28 576 67T493 174L490 184Q489 181 483 167T475 150T468 136T458 120T447 107T432 90T412 73Q350 28 277 28Q188 28 122 91T56 250ZM199 68T278 68T408 122T459 250Q459 322 414 370T308 430Q302 431 273 431Q204 431 150 380T96 250Q96 176 147 122'], // ANGLE 0x2220: [694,0,722,55,666,'71 0L68 2Q65 3 63 5T58 11T55 20Q55 22 57 28Q67 43 346 361Q397 420 474 508Q595 648 616 671T647 694T661 688T666 674Q666 668 663 663Q662 662 627 622T524 503T390 350L120 41L386 40H653Q666 30 666 20Q666 8 651 0H71'], // MEASURED ANGLE 0x2221: [714,20,722,55,666,'71 0L68 2Q65 3 63 5T58 11T55 20Q55 22 57 28Q64 38 348 373T638 712Q644 714 646 714Q653 714 659 709T666 694V693Q666 687 633 647Q619 631 576 580Q528 524 495 485Q336 296 329 289Q328 288 348 264T395 182T433 54L434 40H651Q666 32 666 20T651 0H436Q431 -20 416 -20Q400 -20 396 -4V0H71ZM394 40Q394 51 389 76T366 149T319 234L302 256L119 41L256 40H394'], // SPHERICAL ANGLE 0x2222: [551,51,722,55,666,'666 -32Q666 -51 646 -51Q639 -51 365 85L75 228Q55 238 55 250Q55 257 59 262T68 268L72 270L611 536Q642 551 647 551T659 547T666 532Q666 521 657 515L525 449Q525 448 535 424T556 352T566 250T556 148T536 77T525 51L657 -15Q666 -21 666 -32ZM526 250Q526 297 517 342T499 409T488 431Q487 431 304 341T121 250T304 159T488 69Q526 143 526 250'], // DIVIDES 0x2223: [430,23,222,91,131,'91 417Q104 430 111 430T131 417V-10Q116 -23 111 -23T91 -10V417'], // DOES NOT DIVIDE 0x2224: [750,252,278,-20,296,'118 737Q131 750 138 750L151 746L158 739V579L160 421L213 470Q269 519 276 519Q284 519 290 513T296 499V498Q296 493 291 488T244 445Q225 428 213 417L158 368V-239Q143 -252 136 -252L124 -248L120 -241L118 44V328L62 279Q4 231 0 230Q-8 230 -14 236T-20 250Q-20 257 -11 265T62 332L118 384V737'], // PARALLEL TO 0x2225: [431,23,389,55,331,'55 417Q69 431 76 431T95 419V-12Q84 -23 76 -23Q72 -23 69 -22T62 -16T55 -10V417ZM293 419Q300 431 310 431L324 424L331 417V-10Q316 -23 309 -23L297 -19L293 -12V419'], // NOT PARALLEL TO 0x2226: [751,250,500,-20,518,'131 737Q134 739 138 743T144 748T151 750T171 737V199L327 357V737Q340 750 347 750Q351 750 353 749T360 743T367 737V397L429 457Q493 518 498 519Q506 519 512 512T518 500Q518 489 442 417L367 339V-237Q352 -250 346 -250L333 -243L327 -237V301L171 143V-237Q156 -250 151 -250T131 -237V101L69 41Q24 -3 15 -12T0 -21Q-8 -21 -14 -14T-20 -2Q-20 5 -7 19T56 81L131 159V737'], // THEREFORE 0x2234: [471,82,667,24,643,'273 411Q273 437 291 454T334 471Q358 471 375 454T393 411T376 368T333 351Q307 351 290 368T273 411ZM84 38Q110 38 126 21T143 -22Q143 -46 127 -64T83 -82Q57 -82 41 -65T24 -22Q24 4 41 21T84 38ZM524 -22Q524 4 541 21T584 38Q608 38 625 21T643 -22Q643 -45 627 -63T583 -82Q557 -82 541 -65T524 -22'], // BECAUSE 0x2235: [471,82,667,23,644,'23 411Q23 437 41 454T84 471Q108 471 125 454T143 411T126 368T83 351Q57 351 40 368T23 411ZM523 411Q523 437 541 454T584 471Q608 471 625 454T643 411T626 368T583 351Q557 351 540 368T523 411ZM274 -22Q274 4 291 21T334 38Q356 38 374 22T392 -22T375 -65T333 -82Q307 -82 291 -65T274 -22'], // TILDE OPERATOR 0x223C: [365,-132,778,55,719,'73 132Q55 132 55 172Q55 220 79 272Q95 301 111 319Q148 353 195 363Q199 364 212 364Q262 364 294 350T408 272Q472 222 522 212Q537 208 555 208Q606 208 646 243Q671 268 680 296T691 342T702 365Q713 365 716 354T719 314Q714 236 664 179L660 176Q657 173 654 170T644 163T631 154T615 146T596 139T574 134T549 132Q510 132 465 156T386 211T307 265T223 290Q162 290 124 249T86 165Q86 155 82 144T73 132'], // REVERSED TILDE 0x223D: [367,-133,778,56,722,'222 133Q147 133 102 197T56 335Q56 362 66 365Q71 369 77 364Q83 356 84 335T90 298Q102 254 137 222T223 189Q258 189 292 206T355 250T413 301T477 346T550 367Q628 367 673 309T722 171Q722 133 708 133Q703 133 699 141T694 162Q694 220 655 265T555 311Q519 311 485 293T421 248T363 196T298 152T222 133'], // stix-not, vert, similar 0x2241: [467,-32,778,55,719,'220 366Q258 366 297 347T361 308T391 288Q394 288 464 370Q494 407 510 425T535 454T546 465T552 467H553Q560 467 566 461T573 448Q573 439 499 350Q424 266 424 261Q424 259 442 247T492 222T554 209Q607 209 646 243Q671 268 680 295T690 341T702 366Q719 366 719 314Q716 265 695 226Q682 199 664 179Q614 132 555 132Q517 132 477 151T412 190T383 210T347 172T278 89T233 37Q228 32 220 32Q210 32 206 38T201 48Q201 57 266 137Q272 144 275 148Q351 231 351 237Q351 239 333 251T283 276T221 289Q159 289 123 248T86 166Q86 156 82 145T73 132Q55 132 55 172Q55 220 79 272Q95 301 111 319Q161 366 220 366'], // MINUS TILDE 0x2242: [463,-34,778,55,720,'55 439T55 443T56 449T62 456T68 463H706Q720 449 720 443T706 423H68Q55 439 55 443ZM56 72Q56 112 73 152T130 225T224 257Q259 257 294 240T360 199T419 149T484 107T553 90Q603 90 643 125T691 223Q693 257 704 257Q717 257 717 221Q717 147 671 91T554 34Q517 34 481 51T414 93T355 142T291 184T222 201Q172 201 131 167T84 67Q81 34 71 34Q56 37 56 72'], // APPROXIMATELY BUT NOT ACTUALLY EQUAL TO 0x2246: [652,155,778,54,720,'55 417Q55 479 101 528T222 578Q259 578 294 564T393 507Q413 493 434 480T469 460T484 454L537 549Q587 639 595 647Q600 652 607 652Q615 652 621 647T628 634Q628 625 575 536Q524 446 524 443Q527 440 555 440Q603 440 644 469T691 547Q694 578 706 578T718 556Q718 555 718 551T717 545Q717 488 684 445T595 387Q582 384 558 384Q530 384 508 389L493 394L404 238L557 236H708Q720 224 720 217T706 196H379L291 43L499 41H708Q720 29 720 21T706 1H268L226 -71Q186 -143 179 -148Q173 -155 165 -155T152 -150T146 -137Q146 -133 184 -64L222 1H144L66 3L59 7Q54 14 54 20Q54 29 66 41H246L333 194Q333 196 202 196H68Q55 211 55 218T66 236H213L357 238L457 409L437 421Q432 423 393 450T307 500T222 523Q171 523 129 491T84 414Q82 383 70 383Q55 383 55 417'], // ALMOST EQUAL TO 0x2248: [481,-49,778,55,719,'55 326Q55 394 101 437T226 481Q268 479 313 460T392 419T469 379T555 361Q622 361 662 401Q686 423 688 450Q693 479 702 479H705Q719 479 719 442Q719 367 670 327T554 286Q512 286 466 304T386 345T307 385T220 404Q184 404 157 394T120 374L111 363Q86 339 86 317Q86 288 71 288Q55 288 55 326ZM55 90Q55 164 105 205T226 246Q269 243 314 224T392 183T470 144T558 126Q622 126 662 166Q686 187 688 214Q693 244 704 244Q716 244 719 210Q719 165 702 132T658 82T605 58T552 50T498 58T447 77T384 110Q322 146 302 152Q263 168 220 168Q179 168 144 152Q128 147 107 125T86 81Q86 52 71 52Q55 52 55 90'], // ALMOST EQUAL OR EQUAL TO 0x224A: [579,39,778,51,725,'220 523Q163 523 124 486T84 412Q81 383 69 383Q56 383 56 413Q56 441 67 470Q78 508 111 537T187 575Q203 579 219 579Q248 579 271 572Q304 565 393 508Q498 439 551 439Q620 439 662 486Q688 512 693 557Q693 565 697 572T707 579Q719 579 719 548Q719 483 673 434T550 384Q512 384 467 405T386 453T305 501T220 523ZM222 288Q164 288 124 251T84 177Q81 148 69 148Q56 148 56 178Q56 206 67 235Q78 274 111 302T187 339Q198 343 220 343Q244 343 259 341T308 322T393 272Q496 203 553 203Q612 203 651 241T691 312Q693 343 705 343Q719 343 719 313Q719 245 673 199Q626 148 552 148Q513 148 467 170T385 218T304 266T222 288ZM51 -19Q51 -6 62 -1H387Q713 -1 715 -3Q725 -10 725 -20Q725 -27 718 -34Q714 -38 672 -38T387 -39H62Q51 -25 51 -19'], // GEOMETRICALLY EQUIVALENT TO 0x224E: [492,-8,778,56,723,'245 367Q251 415 288 453T392 492Q445 492 485 456T532 367H707Q722 359 722 347Q722 334 711 331T665 327H608H509Q500 332 498 336Q496 338 493 363T472 411Q443 451 389 451H387Q335 451 305 411Q290 392 287 374T282 344T268 327H72Q56 332 56 347Q56 360 70 367H245ZM56 153Q56 168 72 173H268Q277 168 279 164Q281 162 284 137T305 89Q334 49 389 49H391Q442 49 472 89Q487 108 490 126T495 156T509 173H608H666Q701 173 711 170T722 153T707 133H532Q526 81 486 45T389 8Q331 8 291 45T245 133H70Q56 140 56 153'], // DIFFERENCE BETWEEN 0x224F: [492,-133,778,56,722,'245 367Q251 415 288 453T392 492Q445 492 485 456T532 367H707Q722 359 722 347Q722 334 711 331T665 327H608H509Q500 332 498 336Q496 338 493 363T472 411Q443 451 389 451H387Q335 451 305 411Q290 392 287 374T282 344T268 327H72Q56 332 56 347Q56 360 70 367H245ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153'], // GEOMETRICALLY EQUAL TO 0x2251: [609,108,778,56,722,'421 474T389 474T339 493T321 541Q321 566 337 587T391 609Q456 602 456 541Q456 512 439 493ZM56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153ZM421 -108T389 -108T339 -89T321 -41Q321 -16 337 5T391 27Q456 20 456 -41Q456 -70 439 -89'], // APPROXIMATELY EQUAL TO OR THE IMAGE OF 0x2252: [601,101,778,15,762,'15 541Q15 569 33 585T75 601T117 585T135 541Q135 514 118 498T75 481T32 498T15 541ZM56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153ZM642 -41Q642 -17 658 0T702 18Q726 18 744 3T762 -41Q762 -67 745 -84T702 -101Q676 -101 659 -85T642 -41'], // IMAGE OF OR APPROXIMATELY EQUAL TO 0x2253: [601,102,778,14,762,'642 541Q642 569 660 585T702 601T744 585T762 541Q762 515 745 498T702 481Q676 481 659 497T642 541ZM56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153ZM14 -41Q14 -15 31 1T74 18Q101 18 118 0T135 -41Q135 -64 118 -83T75 -102Q51 -102 33 -85T14 -41'], // RING IN EQUAL TO 0x2256: [367,-133,778,56,722,'56 347Q56 360 70 367H707Q722 359 722 347Q722 334 711 331T658 327H586H465L472 318Q496 288 496 250T472 182L465 173H586H663Q700 173 711 170T722 153T707 133H70Q56 140 56 153Q56 168 72 173H312L305 182Q281 212 281 250T305 318L312 327H72Q56 332 56 347ZM473 250Q473 265 472 273T460 297T428 327H349Q328 313 318 298T306 273T304 250Q304 235 305 227T317 203T349 173H428Q449 187 459 202T471 227T473 250'], // RING EQUAL TO 0x2257: [721,-133,778,56,722,'279 612Q279 656 310 688T388 721Q433 721 465 689T498 612Q498 573 470 538T389 503Q336 503 308 538T279 612ZM458 614Q458 637 452 651T433 672T411 679T383 680T352 675T333 664T324 647T321 629T320 611Q320 593 321 584T332 562T359 545Q366 543 389 543H391Q406 543 414 544T435 552T452 573T458 614ZM56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153'], // DELTA EQUAL TO 0x225C: [859,-133,778,56,723,'192 482H190Q187 483 185 484T181 488T177 493T175 501Q175 506 178 512Q184 523 278 687T375 853Q379 857 383 857Q385 857 387 858T390 859Q397 859 403 853Q405 851 499 687T600 512Q603 506 603 501Q603 488 587 482H192ZM548 523L389 798Q388 798 309 661T230 523T389 522T548 523ZM56 347Q56 360 70 367H708Q723 359 723 347Q723 336 709 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H709Q723 163 723 153Q723 140 708 133H70Q56 140 56 153'], // LESS-THAN OVER EQUAL TO 0x2266: [753,175,778,83,694,'674 753Q682 753 688 747T694 732T687 718Q686 717 417 589L151 463L399 345Q687 209 691 204Q694 198 694 193Q694 175 676 173H670L382 309Q92 446 90 448Q83 453 83 465Q84 476 96 482Q104 486 382 617T665 751Q669 753 674 753ZM84 39Q84 49 99 59H678Q694 53 694 39Q694 26 679 19H98Q84 26 84 39ZM83 -157Q83 -153 84 -150T86 -145T89 -141T92 -139T96 -137T99 -135H678Q694 -146 694 -155Q694 -168 679 -175H98Q84 -168 83 -157'], // GREATER-THAN OVER EQUAL TO 0x2267: [753,175,778,82,694,'83 733Q83 741 89 747T99 753Q107 753 253 685T543 548T687 478Q694 473 694 463T687 448Q685 446 395 309L107 173H101Q83 175 83 193Q83 194 83 196Q82 203 98 212Q117 222 248 284Q326 321 378 345L626 463L360 589Q291 622 200 665Q112 706 98 714T83 733ZM84 39Q84 49 99 59H678Q694 53 694 39Q694 26 679 19H98Q84 26 84 39ZM83 -157Q83 -153 84 -150T86 -145T89 -141T92 -139T96 -137T99 -135H678Q694 -146 694 -155Q694 -168 679 -175H98Q84 -168 83 -157'], // stix-less, vert, not double equals 0x2268: [752,286,778,82,694,'86 472Q93 477 381 614T673 752Q680 752 686 746T693 732T689 721Q686 715 418 590L151 461L418 332Q684 207 689 201Q693 195 693 190Q693 183 687 177T675 170Q668 170 380 307T86 450Q82 454 82 461Q82 467 86 472ZM82 33Q82 37 83 40T89 47T95 54H473L520 105Q569 156 571 156Q573 157 578 157Q586 157 592 151T598 136Q598 130 562 92L526 56L604 54H682Q693 43 693 35Q693 31 692 28T686 21T680 14H489L342 -139L513 -142H682Q693 -148 693 -160Q693 -167 680 -182H304L258 -230Q248 -240 237 -251T221 -268T211 -278T203 -284T197 -286Q189 -286 184 -280T178 -264Q178 -257 213 -219L249 -182H171L93 -179L86 -175Q82 -170 82 -163Q82 -155 95 -142H289L360 -64L433 14H262L93 16Q82 23 82 33'], // stix-gt, vert, not double equals 0x2269: [752,286,778,82,693,'89 745Q95 752 100 752Q106 752 394 615T689 472Q693 468 693 461T689 450Q684 445 396 308T100 170Q95 170 89 176T82 190Q82 195 86 201Q91 208 358 332L624 461L358 590Q90 715 86 721Q82 725 82 731Q82 739 89 745ZM82 33Q82 37 83 40T89 47T95 54H473L520 105Q569 156 571 156Q573 157 578 157Q586 157 592 151T598 136Q598 130 562 92L526 56L604 54H682Q693 43 693 35Q693 31 692 28T686 21T680 14H489L342 -139L513 -142H682Q693 -148 693 -160Q693 -167 680 -182H304L258 -230Q248 -240 237 -251T221 -268T211 -278T203 -284T197 -286Q189 -286 184 -280T178 -264Q178 -257 213 -219L249 -182H171L93 -179L86 -175Q82 -170 82 -163Q82 -155 95 -142H289L360 -64L433 14H262L93 16Q82 23 82 33'], // BETWEEN 0x226C: [751,251,500,74,425,'104 730Q104 749 123 749Q130 749 138 745Q186 717 237 671L250 659L261 670Q297 703 332 726T375 750T389 744T395 730Q395 721 390 717T364 699T322 668Q290 641 283 632Q280 628 281 627T293 612Q425 454 425 250Q425 144 388 51T293 -112Q282 -125 281 -126T283 -132Q306 -162 379 -209Q395 -219 395 -230Q395 -238 389 -244T375 -250T335 -228T262 -171L250 -159L238 -170Q202 -203 167 -226T124 -250T110 -244T104 -230Q104 -219 121 -209Q199 -156 216 -132Q219 -128 218 -127T206 -112Q74 46 74 250T206 612Q217 625 218 626T216 632Q199 656 121 709Q104 719 104 730ZM249 -94Q364 61 364 250Q364 430 265 574Q253 590 249 594L242 583Q134 439 134 250Q134 114 192 -1Q212 -44 242 -83L249 -94'], // stix-not, vert, less-than 0x226E: [709,209,778,82,693,'693 -14T693 -20T687 -33T675 -41Q667 -41 506 37L344 112Q342 112 262 -46Q184 -196 176 -205Q172 -209 168 -209T162 -208Q155 -208 151 -203T146 -190Q146 -178 171 -137Q193 -91 251 21L306 132L198 183Q142 208 118 220T88 238T82 249Q82 253 86 261Q92 267 278 357L464 443L529 572Q561 637 577 667T597 703T607 708Q615 708 622 702T629 688Q629 680 575 579L524 474Q524 473 545 482T598 508Q666 541 673 541T686 535T693 521Q693 512 679 504T589 459L493 414L360 150Q366 148 378 142T431 116T529 70Q686 -8 689 -10Q693 -14 693 -20ZM380 277L433 383Q432 385 292 319T151 250T237 209T324 170L380 277'], // stix-not, vert, greater-than 0x226F: [708,209,778,82,693,'82 514T82 520T89 533T100 541Q106 541 271 463Q434 386 435 386L515 543Q593 699 600 706Q604 708 607 708Q615 708 622 702T629 688T549 526Q509 445 491 407T473 368Q522 343 580 317Q636 291 660 278T688 261T693 250V249Q693 241 681 234T580 184Q533 161 502 146Q317 59 315 59Q312 56 246 -74Q197 -170 186 -189T168 -209Q164 -209 162 -208Q155 -208 151 -203T146 -190Q146 -187 200 -79L253 28L218 11Q182 -6 144 -23T100 -41Q95 -41 89 -35T82 -21Q82 -12 96 -4T186 41L284 88L349 217Q377 273 395 311T413 350Q413 351 253 428Q101 498 86 510Q82 514 82 520ZM624 250Q461 330 455 330Q454 331 453 329T448 321T441 308T430 287T416 259T398 223L342 114L624 250'], // stix-not, vert, less-than-or-equal 0x2270: [801,303,778,81,694,'82 -124Q82 -120 83 -117T89 -110T95 -103H220L284 50Q346 204 344 206L218 268Q153 297 123 313T87 333T82 344T86 355Q104 369 291 455Q491 552 491 553L542 673Q581 767 590 784T609 801Q616 801 622 795T629 781Q629 773 586 677Q546 581 546 577L609 606Q669 635 673 635Q680 635 686 629T693 615Q693 610 692 608T670 593T604 561L524 521L400 226L542 157Q617 123 649 107T687 85T694 72Q694 66 690 60T679 54Q664 54 526 121Q513 127 495 136T464 150T438 162T416 173T399 180T388 185L384 186Q383 186 322 41L262 -103H680Q682 -105 684 -108T688 -113T691 -118T693 -124Q693 -134 682 -141L464 -143H246L213 -219Q182 -292 178 -299Q172 -303 166 -303T153 -297T146 -283Q146 -282 174 -213T202 -143H146L93 -141Q82 -134 82 -124ZM418 370L466 495Q464 495 308 420T151 344T204 317T311 267T364 244Q364 247 418 370'], // stix-not, vert, greater-than-or-equal 0x2271: [801,303,778,82,694,'97 54Q82 54 82 72Q82 79 86 84Q95 91 222 153L351 215L398 324L442 433L258 519Q95 597 87 604Q82 608 82 615T88 628T102 635Q107 635 424 484L458 468L524 630Q593 789 597 795Q601 801 609 801Q616 801 622 795T629 781L562 615L493 450L589 406Q665 371 679 362T694 344Q694 339 693 337T677 326T631 302T538 257Q504 241 465 223T406 195T386 186Q384 185 322 39L262 -103H680Q682 -105 684 -108T688 -113T691 -118T693 -124Q693 -134 682 -141L464 -143H246L213 -219Q182 -292 178 -299Q172 -303 166 -303T153 -297T146 -283Q146 -282 174 -213T202 -143H146L93 -141Q82 -134 82 -124Q82 -120 83 -117T89 -110T95 -103H220L273 26Q326 156 326 157L218 106Q109 54 97 54ZM553 379Q480 412 480 415Q479 415 460 372T423 285T406 241Q408 240 516 291T624 344L553 379'], // stix-less-than or (contour) similar 0x2272: [732,228,778,56,722,'674 732Q682 732 688 726T694 711T687 697Q686 696 417 568L151 442L399 324Q687 188 691 183Q694 177 694 172Q694 154 676 152H670L382 288Q92 425 90 427Q83 432 83 444Q84 455 96 461Q104 465 382 596T665 730Q669 732 674 732ZM56 -194Q56 -107 106 -51T222 6Q260 6 296 -12T362 -56T420 -108T483 -153T554 -171Q616 -171 654 -128T694 -29Q696 6 708 6Q722 6 722 -26Q722 -102 676 -164T557 -227Q518 -227 481 -209T415 -165T358 -113T294 -69T223 -51Q163 -51 125 -93T83 -196Q81 -228 69 -228Q56 -228 56 -202V-194'], // stix-greater-than or (contour) similar 0x2273: [732,228,778,56,722,'90 697Q83 704 83 712T88 726T99 732Q107 732 253 664T543 527T687 457Q694 452 694 442T687 427Q685 425 395 288L107 152H101Q83 154 83 172Q83 173 83 175Q82 182 98 191Q117 201 248 263Q326 300 378 324L626 442L360 568Q91 696 90 697ZM56 -194Q56 -107 106 -51T222 6Q260 6 296 -12T362 -56T420 -108T483 -153T554 -171Q616 -171 654 -128T694 -29Q696 6 708 6Q722 6 722 -26Q722 -102 676 -164T557 -227Q518 -227 481 -209T415 -165T358 -113T294 -69T223 -51Q163 -51 125 -93T83 -196Q81 -228 69 -228Q56 -228 56 -202V-194'], // LESS-THAN OR GREATER-THAN 0x2276: [681,253,778,44,734,'734 181Q734 173 728 167T714 161Q711 161 386 280T54 404Q44 408 44 421Q44 432 52 437Q66 443 388 562T714 681Q721 681 727 675T734 661Q734 651 722 645Q711 639 462 546Q441 539 420 531L122 421L420 311L723 198Q734 192 734 181ZM44 247Q44 255 50 261T63 267Q66 267 391 148T723 24Q734 18 734 7T723 -10Q716 -14 391 -133T63 -253Q56 -253 50 -247T44 -233Q44 -223 55 -217Q67 -210 317 -118Q337 -110 357 -103L655 7L357 117L54 230Q44 236 44 247'], // GREATER-THAN OR LESS-THAN 0x2277: [681,253,778,83,694,'83 661Q83 668 88 674T104 681Q111 679 396 560Q686 437 687 436Q694 431 694 421T687 406Q686 405 543 344T253 222T101 161Q83 163 83 180Q83 194 95 199Q96 199 130 213T232 257T361 311L621 421L357 532Q307 553 233 584Q121 631 102 640T83 661ZM673 267Q694 267 694 248Q694 237 687 232Q684 229 420 118L156 7L416 -103L683 -215Q694 -222 694 -233Q694 -251 676 -253Q670 -253 524 -192T235 -70T90 -8Q83 -1 83 7Q83 19 94 24Q97 25 378 144T667 266Q669 267 673 267'], // PRECEDES OR EQUAL TO 0x227C: [580,153,778,83,694,'112 270Q83 270 83 290Q83 301 94 307Q98 310 118 310Q516 310 620 464Q635 486 642 510T651 548T657 571T675 580Q693 577 693 559V552Q684 472 628 410T465 314Q436 303 372 290Q373 290 388 287T425 278T465 266Q674 199 693 28L694 17Q688 5 683 3Q677 0 673 0Q656 0 653 24Q623 270 118 270H112ZM110 116Q83 116 83 136T110 156H113Q134 156 160 155T231 146T318 128T407 95T489 44T550 -30T583 -131Q583 -153 563 -153Q556 -153 553 -152T547 -145T542 -127Q531 -54 478 0Q425 53 333 83T123 116H110'], // SUCCEEDS OR EQUAL TO 0x227D: [580,154,778,83,694,'668 310Q694 310 694 290Q694 285 691 279Q684 271 664 270Q550 268 464 257T301 220T179 146T124 27Q119 0 103 0T83 16Q83 21 83 31T92 68T113 121T157 177T229 231Q295 268 405 290Q404 290 389 293T352 302T312 314Q138 371 96 500Q83 541 83 562Q83 568 89 574T103 580Q115 580 120 570T126 542T138 497T173 442Q289 310 659 310H668ZM194 -131Q201 -60 241 -6T343 82T477 133T628 155Q632 155 644 155T661 156Q685 155 690 147Q694 143 694 136Q694 132 693 129T689 124T685 120T681 117L656 116Q596 114 543 106T436 79T342 35T272 -33T235 -127Q231 -154 212 -154Q203 -153 199 -147T194 -136V-131'], // PRECEDES OR EQUIVALENT TO 0x227E: [732,228,778,56,722,'84 442Q84 455 91 459T117 463Q120 463 126 463T137 462Q388 466 512 526T653 705Q657 732 676 732Q685 731 689 725T694 714V708Q689 662 672 624T626 559T569 513T500 479T435 458T373 442Q379 441 404 435T440 426T477 414T533 392Q592 362 630 319T681 241T694 174Q694 153 674 153Q662 153 657 163T652 188T640 231T606 287Q500 416 137 422H114Q104 422 98 423T88 428T84 442ZM56 -194Q56 -107 106 -51T222 6Q260 6 296 -12T362 -56T420 -108T483 -153T554 -171Q616 -171 654 -128T694 -29Q696 6 708 6Q722 6 722 -26Q722 -102 676 -164T557 -227Q518 -227 481 -209T415 -165T358 -113T294 -69T223 -51Q163 -51 125 -93T83 -196Q81 -228 69 -228Q56 -228 56 -202V-194'], // SUCCEEDS OR EQUIVALENT TO 0x227F: [732,228,778,56,722,'84 710Q84 732 102 732Q115 732 119 722T125 696T137 652T171 597Q277 468 640 462H661Q694 462 694 442T661 422H640Q578 421 526 417T415 403T309 376T222 333T156 268T124 179Q122 162 118 158T103 153Q100 153 98 153T95 154T93 155T90 158T85 163Q83 167 83 176Q88 222 105 260T151 325T208 371T277 405T342 426T404 442Q401 443 380 447T345 456T302 469T245 492Q125 551 92 661Q84 695 84 710ZM56 -194Q56 -107 106 -51T222 6Q260 6 296 -12T362 -56T420 -108T483 -153T554 -171Q616 -171 654 -128T694 -29Q696 6 708 6Q722 6 722 -26Q722 -102 676 -164T557 -227Q518 -227 481 -209T415 -165T358 -113T294 -69T223 -51Q163 -51 125 -93T83 -196Q81 -228 69 -228Q56 -228 56 -202V-194'], // DOES NOT PRECEDE 0x2280: [705,208,778,82,693,'386 292Q388 292 439 393T543 598T598 703Q599 703 603 704T609 705Q616 705 622 699T629 685T533 494Q440 308 440 305Q451 310 462 312Q547 342 592 388T651 505Q654 525 658 532T673 539Q680 539 686 533T693 519Q693 495 678 450Q638 341 500 283Q433 259 418 259Q416 259 411 251T406 241T415 239Q482 224 544 190Q674 121 691 -10Q693 -28 691 -32Q684 -43 672 -43Q664 -43 658 -37Q656 -33 650 -6T634 47T589 109T500 168Q473 179 436 190T388 201H386L284 -1Q261 -45 232 -101T191 -181T178 -206Q176 -206 172 -207T166 -208Q160 -208 153 -202T146 -188Q146 -185 246 12Q344 206 344 210Q344 213 305 217T213 225T124 228H95Q82 241 82 248Q82 253 95 268H124Q172 268 236 273T343 283T386 292'], // stix-not (vert) succeeds 0x2281: [705,208,778,82,693,'103 -43Q96 -43 89 -39T82 -26L84 -10Q105 141 275 212Q342 236 355 236Q360 236 364 245L369 256H360Q284 280 275 283Q115 351 86 490Q82 507 82 517Q82 526 88 532T103 538Q110 538 115 534Q119 531 122 517T128 486T143 444T174 397T231 351T320 310Q371 292 389 292L491 496Q595 701 598 703Q599 703 603 704T609 705Q616 705 622 699T629 685Q629 684 531 485Q431 296 431 288Q431 278 520 273T651 268H680Q693 253 693 248Q693 241 680 228H651Q591 228 491 218T386 201L284 -1Q261 -45 232 -101T191 -181T178 -206Q176 -206 172 -207T166 -208Q160 -208 153 -202T146 -188Q146 -182 302 125L335 190L324 185Q313 185 289 172Q241 153 208 128T159 78T135 31T124 -11T118 -37Q112 -43 103 -43'], // stix-/nsubseteq N: not (vert) subset, equals 0x2288: [801,303,778,83,693,'146 -283Q146 -282 174 -213T202 -143H115Q102 -127 102 -123T115 -103H220L291 68L278 73Q203 101 153 157T86 288Q83 309 83 344Q83 380 86 399Q107 480 160 539Q222 601 298 621Q328 630 345 631T435 635L526 637L560 715Q587 778 593 789T609 801Q616 801 622 795T629 781Q629 780 625 771T614 742T600 706L571 637Q571 635 626 635H680Q693 620 693 613T689 601L682 597L618 595H553L449 346Q425 288 399 223T359 127T346 95H356Q365 95 381 95T417 94T463 93T515 93H682Q693 82 693 74T680 53H511Q420 55 335 55L329 57L262 -103H680Q682 -105 684 -108T688 -113T691 -118T693 -124Q693 -134 682 -141L464 -143H246L213 -219Q182 -292 178 -299Q172 -303 166 -303T153 -297T146 -283ZM509 590Q509 595 438 595Q354 595 318 586Q246 567 195 516T126 395Q123 378 123 344T126 293Q141 229 184 181T291 110L306 104L406 346L509 590'], // stix-/nsupseteq N: not (vert) superset, equals 0x2289: [801,303,778,82,691,'82 606T82 613T95 635H251H348Q408 635 435 632T502 615L515 608L520 617Q520 619 558 708Q584 774 591 787T609 801Q616 801 622 795T629 781Q629 775 562 615L551 590L569 577Q646 527 678 437Q691 398 691 344T678 250Q653 182 597 132T469 64Q427 53 366 53H326L295 -25L262 -103H660Q673 -118 673 -124Q673 -129 669 -136L662 -141L453 -143H246L213 -219Q182 -292 178 -299Q172 -303 166 -303T153 -297T146 -283Q146 -282 174 -213T202 -143H95Q82 -128 82 -123T95 -103H220L251 -25L284 53H189L93 55L86 59Q82 64 82 71T95 93H302L400 333Q498 569 498 573L444 590Q431 593 260 595L93 597L86 601Q82 606 82 613ZM652 344V354Q652 451 575 521Q571 526 557 538T537 551Q534 551 533 548Q533 543 438 319L344 95L371 93H386Q487 93 557 150T649 293Q652 309 652 344'], // stix-subset, not equals, variant 0x228A: [635,241,778,84,693,'693 72Q693 68 692 66T686 59T680 52H524Q398 52 367 53T309 63Q236 82 180 132T98 250Q84 288 84 343Q84 397 98 437Q126 515 193 568T346 632Q347 632 373 633T440 634T520 635H680Q693 620 693 615Q693 608 680 595H526Q364 595 353 592Q279 582 221 539T138 430Q124 392 124 343Q124 296 138 257Q163 192 221 149T353 95Q364 92 526 92H680Q693 79 693 72ZM102 -132T102 -125T115 -103H382L420 -68Q429 -60 438 -52T452 -39T463 -28T472 -20T478 -14T483 -10T487 -7T490 -6T493 -5T496 -5Q502 -5 508 -12T515 -28Q515 -34 513 -37Q512 -38 507 -42T492 -55T475 -70L440 -101L562 -103H682Q693 -114 693 -122T680 -143H395L355 -179Q289 -241 280 -241Q273 -241 267 -235T260 -221T265 -208T300 -174L335 -143H224L113 -141L106 -137Q102 -132 102 -125'], // stix-superset, not equals, variant 0x228B: [635,241,778,82,691,'82 615Q82 620 95 635H251Q378 635 409 634T469 623Q540 605 596 555T678 437Q691 397 691 343T678 250Q649 172 581 119T426 55Q415 52 251 52H95Q93 55 89 59T84 65T82 72Q82 79 95 92H249Q411 92 422 95Q496 105 554 148T638 257Q651 296 651 343Q651 391 638 430Q613 495 555 538T422 592Q411 595 249 595H95Q82 608 82 615ZM82 -132T82 -125T95 -103H380L420 -57Q452 -21 460 -14T474 -6Q482 -6 488 -12T495 -25T451 -81L433 -101L549 -103H662Q673 -114 673 -122T660 -143H395L355 -190Q311 -239 309 -239Q305 -241 302 -241Q294 -241 287 -235T280 -221T324 -163L342 -143H218L93 -141L86 -137Q82 -132 82 -125'], // SQUARE IMAGE OF 0x228F: [539,41,778,83,694,'83 523Q87 535 99 539H679Q694 531 694 519Q694 506 679 499H123V-1H678Q694 -7 694 -21Q694 -34 679 -41H98Q93 -38 84 -28L83 247V523'], // SQUARE ORIGINAL OF 0x2290: [539,41,778,64,714,'64 506T64 519T78 539H699Q706 536 714 526V-28Q706 -38 699 -41H78Q64 -34 64 -21Q64 -6 80 -1H674V499H78Q64 506 64 519'], // CIRCLED RING OPERATOR 0x229A: [583,82,778,57,721,'57 250Q57 327 87 392T166 497T270 560T382 582H394Q512 582 610 500Q721 401 721 250Q721 112 626 15T389 -82Q251 -82 154 13T57 250ZM682 129T682 250T596 457T390 543Q269 543 183 457T96 250Q96 132 180 45T389 -43Q511 -43 596 43ZM250 250Q250 316 295 352T384 388Q451 388 489 347T528 250Q528 192 487 152T389 112Q331 112 291 152T250 250ZM488 250Q488 290 460 319T389 349Q348 349 319 320T290 250Q290 208 320 180T389 151Q431 151 459 181T488 250'], // CIRCLED ASTERISK OPERATOR 0x229B: [583,82,778,57,721,'57 250Q57 327 87 392T166 497T270 560T382 582H394Q512 582 610 500Q721 401 721 250Q721 112 626 15T389 -82Q251 -82 154 13T57 250ZM682 129T682 250T596 457T390 543Q269 543 183 457T96 250Q96 132 180 45T389 -43Q511 -43 596 43ZM204 339Q204 357 215 366T238 375Q247 375 283 348Q300 336 311 328L368 286Q369 286 366 323T359 398T355 437Q357 456 379 465Q380 465 384 465T391 466Q403 465 412 457T423 437Q423 436 420 398T413 323T410 286L467 328Q476 334 486 341T501 353T513 361T523 368T529 372T535 374T541 375Q554 375 564 365T575 339Q575 325 566 318T519 292Q504 285 496 281L430 250L496 219Q552 192 559 188T572 175Q575 168 575 161Q575 148 566 137T541 126H538Q530 126 499 149Q480 163 467 172L410 214Q409 214 412 177T419 102T423 63Q423 59 421 54T411 43T389 36T368 42T357 54T355 63Q355 64 358 102T365 177T368 214L311 172Q302 165 293 159T279 148T268 140T260 134T254 131T250 128T246 127T242 126T238 126Q223 126 214 135T204 161T213 183T282 219L348 250L282 281Q226 308 219 312T206 325Q204 330 204 339'], // CIRCLED DASH 0x229D: [583,82,778,57,721,'57 250Q57 327 87 392T166 497T270 560T382 582H394Q512 582 610 500Q721 401 721 250Q721 112 626 15T389 -82Q251 -82 154 13T57 250ZM682 129T682 250T596 457T390 543Q269 543 183 457T96 250Q96 132 180 45T389 -43Q511 -43 596 43ZM223 250Q223 263 233 267T280 271Q289 271 325 271T389 270H490Q535 270 545 267T555 250Q555 241 549 235Q544 231 527 231T389 230Q239 230 235 232Q223 236 223 250'], // SQUARED PLUS 0x229E: [689,0,778,55,722,'71 0Q59 4 55 16V346L56 676Q64 686 70 689H709Q719 681 722 674V15Q719 10 709 1L390 0H71ZM369 365V649H95V365H369ZM682 365V649H409V365H682ZM369 40V325H95V40H369ZM682 40V325H409V40H682'], // SQUARED MINUS 0x229F: [689,0,778,55,722,'71 0Q59 4 55 16V346L56 676Q64 686 70 689H709Q719 681 722 674V15Q719 10 709 1L390 0H71ZM682 365V649H95V365H682ZM682 40V325H95V40H682'], // SQUARED TIMES 0x22A0: [689,0,778,55,722,'71 0Q59 4 55 16V346L56 676Q64 686 70 689H707Q714 686 722 676V13Q714 3 707 0H71ZM123 649Q147 625 214 555T335 430T389 374L654 649H123ZM95 70Q99 74 229 209T360 345L95 619V70ZM682 70V619L418 346Q417 344 549 207L682 70ZM654 41L400 304L388 315L123 41L256 40H522L654 41'], // SQUARED DOT OPERATOR 0x22A1: [689,0,778,55,722,'71 0Q59 4 55 16V346L56 676Q64 686 70 689H709Q719 681 722 674V15Q719 10 709 1L390 0H71ZM682 40V649H95V40H682ZM330 345Q330 371 347 388T390 405Q412 405 430 389T448 345Q448 317 430 301T389 285T348 301T330 345'], // TRUE 0x22A8: [695,0,611,55,556,'55 678Q55 679 56 681T58 684T61 688T65 691T70 693T77 694Q88 692 95 679V464H540Q554 456 555 446Q555 442 554 439T552 434T549 430T546 428T542 426T539 424H95V270H539Q540 270 542 269T545 267T549 264T552 260T554 255T555 248Q554 238 540 230H95V15Q88 2 77 0Q73 0 70 1T65 3T61 6T59 9T57 13T55 16V678'], // FORCES 0x22A9: [695,0,722,55,666,'55 678Q55 679 56 681T58 684T61 688T65 691T70 693T77 694Q88 692 95 679V15Q88 2 77 0Q73 0 70 1T65 3T61 6T59 9T57 13T55 16V678ZM249 678Q249 679 250 681T252 684T255 688T259 691T264 693T271 694Q282 692 289 679V367H651Q666 359 666 347Q666 334 651 327H289V15Q282 2 271 0Q267 0 264 1T259 3T255 6T253 9T251 13T249 16V678'], // TRIPLE VERTICAL BAR RIGHT TURNSTILE 0x22AA: [695,0,889,55,833,'55 678Q55 679 56 681T58 684T61 688T65 691T70 693T77 694Q88 692 95 679V15Q88 2 77 0Q73 0 70 1T65 3T61 6T59 9T57 13T55 16V678ZM237 678Q237 679 238 681T240 684T243 688T247 691T252 693T259 694Q270 692 277 679V15Q270 2 259 0Q255 0 252 1T247 3T243 6T241 9T239 13T237 16V678ZM419 678Q419 679 420 681T422 684T425 688T429 691T434 693T441 694Q452 692 459 679V367H818Q833 359 833 347Q833 334 818 327H459V15Q452 2 441 0Q437 0 434 1T429 3T425 6T423 9T421 13T419 16V678'], // DOES NOT PROVE 0x22AC: [696,1,611,-55,554,'56 681Q70 695 76 695T96 681V368H243L381 530Q521 692 525 692Q537 700 547 688Q554 682 554 674Q554 671 553 669T548 661T539 649T522 631T499 604T465 565T421 512Q296 373 296 368H416H476Q525 368 539 365T554 348Q554 334 543 328H261L96 141V12Q81 -1 75 -1Q65 -1 58 10L56 50V92L18 48Q7 37 -1 28T-13 14T-19 6T-23 1T-27 0T-33 -1Q-42 -1 -48 4T-55 19Q-55 24 -47 34T12 103L56 155V681ZM205 326Q205 328 152 328H96V263Q96 203 98 203Q99 203 123 231T174 290T205 326'], // NOT TRUE 0x22AD: [695,1,611,-55,554,'56 681Q70 695 76 695T96 681V466H327L425 579Q522 692 527 692Q529 693 534 693Q542 693 547 688T553 674Q553 668 549 663Q549 662 538 650T504 611T463 563L381 468L461 466H543Q554 453 554 446T541 426H345L209 272L376 270H543Q554 257 554 251T541 230H174L96 141V12Q81 -1 75 -1Q65 -1 58 10L56 50V92L18 48Q7 37 -1 28T-13 14T-19 6T-23 1T-27 0T-33 -1Q-42 -1 -48 4T-55 19Q-55 24 -47 34T12 103L56 155V681ZM267 399L292 426H96V270H158L201 321Q256 382 267 399ZM118 228L119 229Q119 230 109 230H96V201L107 212Q118 227 118 228'], // DOES NOT FORCE 0x22AE: [695,1,722,-55,665,'56 681Q70 695 77 695T96 683V428L98 175L252 323V681Q264 695 272 695Q278 695 292 681V526Q292 368 296 368Q298 368 447 510Q638 695 642 695H645Q651 695 658 688T665 673Q665 666 661 661Q659 660 639 641T578 582T505 512L356 370L505 368H654Q665 357 665 349Q665 343 652 328H314L303 317L292 308V12Q289 10 285 6T279 1T272 -1Q265 -1 252 12V139Q252 266 249 266L96 119V12Q80 -1 76 -1T70 0T63 6T56 12V79L29 55Q-26 -1 -35 -1Q-42 -1 -48 5T-55 19Q-55 25 -51 30T-15 66Q5 86 18 99L56 135V681'], // NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE 0x22AF: [695,1,722,-55,665,'56 681Q70 695 77 695T96 683V428L98 175L252 323V681Q264 695 272 695Q278 695 292 681V466H401L503 563L621 679Q637 695 645 695Q652 695 658 688T665 673Q665 670 663 666Q663 665 651 652T611 612T561 563L458 468L556 466H654Q665 455 665 447T652 426H416L294 308L292 288V270H652Q665 255 665 250T652 230H292V12Q289 10 285 6T279 1T272 -1Q265 -1 252 12V139Q252 266 249 266L96 119V12Q80 -1 76 -1T70 0T63 6T56 12V79L29 55Q-26 -1 -35 -1Q-42 -1 -48 5T-55 19Q-55 25 -51 30T-15 66Q5 86 18 99L56 135V681ZM358 426H292V361L325 392L358 426'], // NORMAL SUBGROUP OF 0x22B2: [539,41,778,83,694,'694 -26Q686 -40 676 -41H670L382 95Q92 232 90 234Q83 239 83 249Q83 262 96 267Q101 270 379 401T665 537Q671 539 674 539Q686 539 694 524V-26ZM654 11T654 249T653 487T402 369T151 249L275 190Q399 131 524 72T652 11Q654 11 654 249'], // CONTAINS AS NORMAL SUBGROUP 0x22B3: [540,41,778,83,694,'83 523Q83 524 85 527T92 535T103 539Q107 539 389 406T680 268Q694 260 694 249Q694 239 687 234Q685 232 395 95L107 -41H101Q90 -40 83 -26V523ZM376 368Q323 393 254 425T155 472L125 487Q123 487 123 249T125 11Q127 12 252 71T502 190L626 249L376 368'], // NORMAL SUBGROUP OF OR EQUAL TO 0x22B4: [636,138,778,83,695,'694 71Q686 58 676 56H670L382 192Q92 329 90 331Q83 336 83 346Q83 359 96 364Q101 367 379 498T665 634Q671 636 674 636Q686 636 694 621V71ZM654 108T654 346T653 584T402 466T151 346L275 287Q399 228 524 169T652 108Q654 108 654 346ZM83 -120Q83 -116 84 -113T86 -108T89 -104T92 -102T96 -100T99 -98H678Q679 -98 681 -99T684 -101T688 -104T691 -108T693 -113T694 -120Q692 -130 679 -138H98Q84 -130 83 -120'], // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO 0x22B5: [637,138,778,83,695,'83 620Q83 621 85 624T92 632T103 636Q107 636 389 503T680 365Q694 357 694 346Q694 336 687 331Q685 329 395 192L107 56H101Q90 58 83 71V620ZM376 465Q323 490 254 522T155 570L125 584Q123 584 123 346T125 108Q127 109 252 168T502 287L626 346L376 465ZM83 -120Q83 -116 84 -113T86 -108T89 -104T92 -102T96 -100T99 -98H678Q679 -98 681 -99T684 -101T688 -104T691 -108T693 -113T694 -120Q692 -130 679 -138H98Q84 -130 83 -120'], // MULTIMAP 0x22B8: [408,-92,1111,55,1055,'1055 250Q1055 190 1012 141T896 92Q858 92 828 106T781 140T755 180T741 214L738 228V230H405Q71 230 68 232Q55 238 55 250T68 268Q71 270 405 270H738V272L740 280Q742 287 745 297T754 321T771 348T796 374T832 396T881 408H891Q969 408 1012 360T1055 250ZM896 132Q948 132 981 166T1014 250Q1014 301 985 330T920 367Q914 368 891 368Q853 368 816 338T778 250Q778 198 812 165T896 132'], // INTERCALATE 0x22BA: [431,212,556,57,501,'318 -182Q302 -212 280 -212H278H275Q249 -212 239 -182L238 84V351H162L87 352Q57 362 57 391T84 429Q89 431 280 431H470L474 429Q477 427 479 426T484 423T490 417T495 410T499 402T500 391Q500 365 470 352L394 351H318V-182'], // XOR 0x22BB: [716,0,611,55,555,'56 697Q56 706 62 711T75 716Q86 716 90 709Q91 708 104 680T147 592T199 483L305 261L411 483Q443 548 481 629Q512 694 518 705T535 716Q543 716 549 710T555 700Q555 693 501 577T388 340T325 210Q316 194 305 194Q292 194 285 210Q282 219 224 339T111 574T56 697ZM55 14T55 20T59 31T66 38T71 40H540Q555 32 555 20T540 0H71Q70 0 67 2T59 9'], // NAND 0x22BC: [716,0,611,54,555,'55 698Q56 708 70 716H540Q554 708 555 698Q555 694 554 691T552 686T549 682T546 680T542 678T539 676H71Q70 676 68 677T65 679T61 682T58 686T56 691T55 698ZM555 18Q554 12 549 6T536 0H535Q525 0 515 17T459 132Q430 194 410 235L305 455L199 233Q176 185 147 125T105 36T90 7Q85 0 75 0Q63 0 58 11Q55 15 55 21Q58 31 170 266T285 507Q295 522 305 522T320 515Q322 513 439 268L555 24V18'], // DOT OPERATOR 0x22C5: [189,0,278,55,222,'71 0Q59 4 55 16V96L56 176Q59 180 66 187L70 189H209Q219 181 222 174V15Q219 10 209 1L140 0H71'], // DIVISION TIMES 0x22C7: [545,44,778,55,720,'366 543Q374 545 382 545Q405 545 419 538Q429 534 443 521T462 496Q466 478 466 467Q466 438 444 412Q422 390 388 390Q352 390 331 412Q311 434 311 467Q311 499 331 518Q345 533 366 543ZM146 472Q146 479 153 485T166 492Q171 492 187 476T279 385L386 278L495 385Q600 492 608 492Q615 492 621 486T628 472Q628 467 614 452T531 367L435 270H706Q720 256 720 250Q720 241 706 230H435L531 132Q600 63 614 48T628 27Q628 20 622 14T608 7Q600 7 495 114L386 221L279 114Q204 39 188 23T166 7Q159 7 153 13T146 27Q146 32 160 47T244 132L339 230H68Q55 243 55 250Q55 255 68 270H339L244 367Q175 436 161 451T146 472ZM466 34Q466 4 447 -20T388 -44Q353 -44 331 -22Q311 1 311 34Q311 66 331 85Q347 101 366 110Q374 112 382 112Q405 112 419 105Q429 100 443 87T462 63Q466 45 466 34'], // LEFT NORMAL FACTOR SEMIDIRECT PRODUCT 0x22C9: [492,-8,778,146,628,'146 479Q159 492 166 492Q171 492 189 475T279 386L386 279L495 386Q598 492 608 492Q615 492 621 486T628 472Q628 464 522 357L415 250L522 144Q628 37 628 28Q628 21 622 15T608 8Q599 8 495 115L386 221L279 115Q204 40 188 24T166 8Q162 8 160 9T153 15T146 21V479ZM186 77L359 250L186 424V77'], // RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT 0x22CA: [492,-8,778,146,628,'146 472Q146 479 152 485T166 492Q171 492 189 475T279 386L386 279L495 386Q598 492 608 492Q615 492 628 479V21Q615 8 608 8Q599 8 495 115L386 221L279 115Q204 40 188 24T166 8Q159 8 153 14T146 28Q146 37 253 144L359 250L253 357Q146 464 146 472ZM588 77V424L499 337L415 250L588 77'], // LEFT SEMIDIRECT PRODUCT 0x22CB: [694,23,778,55,722,'55 674Q55 682 62 688T76 694H77Q83 694 100 677T208 561Q320 440 410 342Q462 286 541 201Q677 55 699 30T722 -2Q722 -9 716 -15T701 -22T688 -17Q687 -15 542 141T394 301L388 306L240 146Q119 15 101 -3T75 -22T61 -16T55 -2Q55 4 67 19T158 117Q190 151 209 172L361 336L209 500Q62 657 57 667Q55 671 55 674'], // RIGHT SEMIDIRECT PRODUCT 0x22CC: [694,22,778,55,722,'84 -22T76 -22T62 -16T55 -2Q55 4 78 30T249 215Q321 293 367 342Q672 672 683 682Q695 694 702 694Q710 694 716 688T722 674Q722 668 710 653T619 555Q587 521 568 500L416 336L568 172Q715 15 720 5Q722 1 722 -2Q722 -9 716 -15T702 -22H700Q693 -22 671 1T537 146L389 306Q387 304 340 253T237 143T135 33L89 -17Q84 -22 76 -22'], // REVERSED TILDE EQUALS 0x22CD: [464,-36,778,56,722,'56 433Q56 464 71 464Q74 464 77 461Q82 454 82 438T91 397T123 347Q166 307 222 307Q264 307 308 331T386 385T465 438T556 463Q631 463 676 408T722 283Q722 250 708 250Q704 250 699 257Q695 265 693 286T682 330Q670 350 655 367Q612 407 556 407Q514 407 470 383T393 329T314 276T222 251Q148 251 102 306T56 433ZM57 56Q57 71 73 76H706Q722 70 722 56Q722 44 707 36H71Q57 43 57 56'], // CURLY LOGICAL OR 0x22CE: [578,21,760,83,676,'83 558Q83 566 89 572T104 578Q108 578 116 577T146 570T190 555T239 526T286 480Q308 453 325 420T351 358T367 304T376 265T380 251T381 253Q381 262 395 312Q428 434 492 499T642 576Q654 578 655 578Q664 578 670 572T676 558Q676 543 657 540T599 524T525 476Q406 362 400 29V8Q400 -21 380 -21Q369 -21 362 -11Q360 -7 360 12Q360 115 348 200T308 360T231 480T111 537Q83 540 83 558'], // CURLY LOGICAL AND 0x22CF: [578,22,760,83,676,'104 -22Q95 -22 89 -16T83 -2Q83 11 98 16T135 23T192 46T256 103Q360 233 360 549Q360 554 360 557T361 563T362 567T364 569T367 572T371 576Q377 578 380 578Q401 578 401 547Q401 543 401 537T400 527Q409 53 648 19Q676 16 676 -2Q676 -10 670 -16T655 -22Q654 -22 642 -20Q556 -9 492 56T395 244Q381 294 381 303Q381 305 380 305T374 275T352 201T310 110T234 27T117 -20Q105 -22 104 -22'], // DOUBLE SUBSET 0x22D0: [541,41,778,84,694,'84 250Q84 372 166 450T360 539Q361 539 370 539T395 539T430 540T475 540T524 540H679Q694 532 694 520Q694 511 681 501L522 500H470H441Q366 500 338 496T266 472Q244 461 224 446T179 404T139 337T124 250V245Q124 157 185 89Q244 25 328 7Q348 2 366 2T522 0H681Q694 -10 694 -20Q694 -32 679 -40H526Q510 -40 480 -40T434 -41Q350 -41 289 -25T172 45Q84 127 84 250ZM694 134Q694 123 679 114H425H384Q350 114 326 121T277 154Q238 193 238 251Q238 322 295 361Q318 378 339 382T412 387Q423 387 459 387T520 386H679Q694 377 694 366Q694 354 679 346H519Q493 346 458 346T411 347Q360 347 341 342T303 315Q278 287 278 250Q278 210 301 187T351 156Q358 154 519 154H679Q694 146 694 134'], // DOUBLE SUPERSET 0x22D1: [541,40,778,83,693,'83 520Q83 532 98 540H251Q267 540 297 540T343 541Q427 541 488 525T605 455Q693 374 693 250Q693 165 650 99T545 0T415 -39Q407 -40 251 -40H98Q83 -32 83 -20Q83 -10 96 0H255H308H337Q412 0 439 4T512 28Q533 39 553 54T599 96T639 163T654 250Q654 341 592 411Q557 449 512 472Q468 491 439 495T335 500H306H255L96 501Q83 511 83 520ZM83 366Q83 376 96 386H244Q280 386 317 386T378 386L402 387Q456 387 498 348T540 250Q540 203 512 168T446 120Q427 114 353 114H99Q84 120 84 134Q84 147 98 154H258Q284 154 319 154T366 153Q416 153 436 158T474 185Q500 214 500 250Q500 290 477 313T426 344Q419 346 258 346H98Q83 354 83 366'], // DOUBLE INTERSECTION 0x22D2: [598,22,667,55,611,'88 -21T75 -21T55 -7V200Q55 231 55 280Q56 414 60 428Q61 430 61 431Q77 500 152 549T332 598Q443 598 522 544T610 405Q611 399 611 194V-7Q604 -22 591 -22Q582 -22 572 -9L570 405Q563 433 556 449T529 485Q498 519 445 538T334 558Q251 558 179 518T96 401Q95 396 95 193V-7Q88 -21 75 -21ZM229 -21H227Q215 -21 209 -7V166Q209 304 209 327T215 363Q226 398 259 421T333 444Q380 444 414 416T455 347Q457 339 457 166V-7Q449 -21 439 -21H437H435Q423 -21 417 -7V164Q417 303 417 325T411 358Q387 403 333 403T255 358Q250 347 250 325T249 164V-7Q241 -21 231 -21H229'], // DOUBLE UNION 0x22D3: [598,22,667,55,611,'591 598H592Q604 598 611 583V376Q611 345 611 296Q610 162 606 148Q605 146 605 145Q586 68 507 23T333 -22Q268 -22 209 -1T106 66T56 173Q55 180 55 384L56 585Q66 598 75 598Q85 598 95 585V378L96 172L98 162Q112 95 181 57T332 18Q415 18 487 58T570 175Q571 180 571 383V583Q579 598 591 598ZM437 598Q450 598 457 583V410Q457 237 455 229Q448 189 414 161T333 132Q291 132 255 157T211 230Q209 237 209 412L210 585Q220 598 229 598Q242 598 249 583V412Q249 273 249 251T255 218Q279 173 333 173T411 218Q416 229 416 251T417 412V583Q425 598 437 598'], // PITCHFORK 0x22D4: [736,22,667,56,611,'76 -22Q64 -22 56 -7V176L57 360L59 370Q66 401 83 426T123 468T171 495T221 513T265 522T298 527L311 528H314V625L315 723Q325 736 334 736Q346 736 354 721V528H356L368 527Q380 526 399 523T441 515T490 498T537 472T578 433T606 379Q611 359 611 171V-7Q604 -21 591 -21T571 -7V170Q571 313 571 337T565 375Q555 408 526 432T461 467T402 482T365 487H354V-7Q347 -21 334 -21T314 -7V487H303Q251 484 207 467Q121 438 99 367L97 357L96 174V-9Q86 -22 76 -22'], // LESS-THAN WITH DOT 0x22D6: [541,41,778,82,694,'86 261Q92 267 381 404T673 541Q680 541 686 535T693 521T689 510Q684 504 418 379L151 250L418 121Q686 -4 689 -10Q693 -14 693 -21T687 -34T675 -41Q668 -41 380 96T86 239Q82 244 82 250Q82 257 86 261ZM610 250Q610 224 592 198T531 172Q498 172 475 195Q453 214 453 250Q453 308 513 328Q515 330 535 330Q569 328 589 304T610 250'], // GREATER-THAN WITH DOT 0x22D7: [541,41,778,82,693,'82 521Q82 529 89 535T100 541Q107 541 395 404T689 261Q693 257 693 250T689 239Q684 234 396 97T100 -41Q95 -41 89 -35T82 -21Q82 -12 96 -4Q118 9 358 121L624 250L358 379Q91 503 86 510Q82 514 82 521ZM165 250Q165 282 188 306T239 330Q262 330 275 323Q303 312 318 283Q322 272 322 250Q322 213 300 195Q277 172 246 172Q224 172 213 177Q165 200 165 250'], // VERY MUCH LESS-THAN 0x22D8: [568,67,1333,56,1277,'639 -48Q639 -54 634 -60T619 -67H618Q612 -67 536 -26Q430 33 329 88Q61 235 59 239Q56 243 56 250T59 261Q62 266 336 415T615 567L619 568Q622 567 625 567Q639 562 639 548Q639 540 633 534Q632 532 374 391L117 250L374 109Q632 -32 633 -34Q639 -40 639 -48ZM958 -48Q958 -54 953 -60T938 -67H937Q931 -67 855 -26Q749 33 648 88Q380 235 378 239Q375 243 375 250T378 261Q381 266 655 415T934 567L938 568Q941 567 944 567Q958 562 958 548Q958 540 952 534Q951 532 693 391L436 250L693 109Q951 -32 952 -34Q958 -40 958 -48ZM1277 -48Q1277 -54 1272 -60T1257 -67H1256Q1250 -67 1174 -26Q1068 33 967 88Q699 235 697 239Q694 243 694 250T697 261Q700 266 974 415T1253 567L1257 568Q1260 567 1263 567Q1277 562 1277 548Q1277 540 1271 534Q1270 532 1012 391L755 250L1012 109Q1270 -32 1271 -34Q1277 -40 1277 -48'], // VERY MUCH GREATER-THAN 0x22D9: [568,68,1333,55,1277,'75 -67Q65 -67 60 -61T55 -48Q55 -40 61 -34Q62 -32 329 109L595 250L329 391Q62 532 61 534Q55 540 55 548Q55 562 69 567H77Q81 567 222 493T506 342T653 264Q667 250 653 236Q649 234 504 157T220 7T77 -67H75ZM364 547Q364 563 381 567L384 568Q387 568 518 499T795 353T955 269Q967 261 967 250T955 231Q925 216 780 139T513 -3T383 -67Q373 -67 369 -60T364 -47Q364 -40 370 -34Q373 -31 639 109L904 250L639 391Q373 531 370 534Q364 540 364 547ZM674 538T674 548T681 562T693 567Q699 567 816 505Q915 453 993 412Q1050 382 1132 339Q1241 282 1259 271T1277 250Q1277 241 1263 232Q1246 221 985 84Q698 -67 692 -67Q674 -67 674 -47Q674 -38 680 -33Q683 -30 947 109L1213 250L947 391Q683 530 680 533Q674 538 674 548'], // stix-less, equal, slanted, greater 0x22DA: [886,386,778,83,674,'674 445Q674 438 669 432T655 425T369 531T90 640Q83 645 83 655Q83 668 95 673Q644 886 654 886Q662 886 668 880T674 866Q674 856 663 850Q649 843 411 751L160 655L407 560Q474 534 561 501Q646 469 660 462T674 445ZM84 250Q84 260 99 270H658Q674 264 674 250Q674 238 659 230H98Q84 237 84 250ZM83 55Q83 68 94 73Q98 76 104 76Q108 75 383 -30T664 -138Q674 -144 674 -155Q674 -165 667 -170Q664 -173 385 -279T104 -386Q85 -386 83 -368Q83 -354 92 -349Q93 -349 347 -251L597 -155L346 -59Q296 -40 223 -12Q118 28 101 36T83 55'], // stix-greater, equal, slanted, less 0x22DB: [886,386,778,83,674,'111 425T102 425T88 431T83 445V446Q83 455 96 461Q111 469 203 504Q287 536 350 560L597 655L346 751Q94 848 92 850Q83 856 83 866Q83 873 88 879T104 886Q109 885 386 779T667 670Q674 665 674 655T667 640Q665 638 388 532ZM84 250Q84 260 99 270H658Q674 264 674 250Q674 238 659 230H98Q84 237 84 250ZM653 76Q656 76 660 75T669 68T674 56Q674 46 665 40Q663 38 411 -59L160 -155L410 -251Q664 -349 665 -349Q674 -354 674 -368Q672 -386 654 -386Q650 -386 371 -279T90 -170Q83 -165 83 -155Q83 -144 93 -138Q645 76 653 76'], // EQUAL TO OR PRECEDES 0x22DE: [734,0,778,83,694,'113 424Q83 424 83 444Q83 453 96 464H121Q181 466 234 474T341 501T435 545T505 613T542 707Q545 734 564 734Q583 731 583 714Q583 658 560 613T500 538T414 486T321 453T229 434T156 426T113 424ZM112 270Q83 270 83 290Q83 301 94 307Q98 310 118 310Q624 310 653 556Q657 580 675 580Q693 577 693 559V552Q684 472 628 410T465 314Q436 303 372 290Q373 290 388 287T425 278T465 266Q674 199 693 28L694 17L692 14Q691 11 689 8T683 3T673 0Q657 0 653 24Q623 270 118 270H112'], // EQUAL TO OR SUCCEEDS 0x22DF: [734,0,778,83,694,'195 713Q195 725 201 729T214 734Q227 734 231 722T238 691T255 641T299 580Q405 474 656 464H681Q694 451 694 443Q694 424 670 424H664Q535 424 415 465T235 595Q195 657 195 713ZM668 310Q694 310 694 290Q694 285 691 279Q684 271 664 270Q550 268 464 257T301 220T179 146T124 27Q119 0 103 0T83 16Q83 21 83 31T92 68T113 121T157 177T229 231Q295 268 405 290Q404 290 389 293T352 302T312 314Q138 371 96 500Q83 541 83 562Q83 568 89 574T103 580Q115 580 120 570T126 542T138 497T173 442Q289 310 659 310H668'], // stix-not (vert) precedes or contour equals 0x22E0: [801,303,778,82,693,'82 344Q82 349 95 364H124Q266 364 398 390L429 397L509 595Q519 619 536 659Q581 766 590 783T609 801Q616 801 622 795T629 781Q629 776 553 595Q533 548 516 506T489 439T480 415Q482 415 505 426T538 444Q632 498 651 601Q654 621 658 628T673 635Q680 635 686 629T693 615Q693 591 678 546Q636 433 484 375L458 364L451 348Q443 332 443 329T455 324Q480 316 503 307T560 277T619 233T664 170T691 86Q693 68 691 64Q684 53 672 53Q664 53 658 59Q657 60 650 97T617 174T538 244Q515 257 476 273T428 289Q425 289 412 256Q381 179 344 90L262 -103H680Q682 -105 684 -108T688 -113T691 -118T693 -124Q693 -134 682 -141L464 -143H246L213 -219Q182 -292 178 -299Q172 -303 166 -303T153 -297T146 -283Q146 -282 174 -213T202 -143H95Q82 -128 82 -123T95 -103H220L302 97Q384 288 384 299Q384 302 341 308T235 319T124 324H95Q82 337 82 344ZM399 338Q403 338 406 346L409 353L375 344Q375 343 384 341T399 338'], // stix-not (vert) succeeds or contour equals 0x22E1: [801,303,778,82,694,'146 -283Q146 -282 174 -213T202 -143H95Q82 -127 82 -123T95 -103H220L300 93Q343 196 374 270Q385 294 386 299L373 295Q331 287 289 268Q241 249 208 224T159 174T135 127T124 85T118 59Q112 53 103 53Q91 53 84 64Q82 68 84 86Q96 185 174 248T375 337L400 344Q399 344 381 348T351 355T316 364T276 379T235 398T193 424T155 456T122 497T98 546Q82 587 82 615Q82 622 88 628T102 635Q112 635 116 628T124 601Q128 579 134 562T159 515T207 463T290 418T415 384L422 381L506 586Q571 744 584 772T609 801Q616 801 622 795T629 781T544 577Q525 529 504 478T473 402T462 375Q480 373 500 373Q579 364 651 364H680Q682 361 686 357T691 351T693 344Q693 337 680 324H651Q553 324 451 310L433 308L349 104L262 -101L473 -103H682Q694 -115 694 -123Q694 -133 682 -141L464 -143H246L213 -219Q182 -292 178 -299Q172 -303 166 -303T153 -297T146 -283'], // LESS-THAN BUT NOT EQUIVALENT TO 0x22E6: [730,359,778,55,719,'86 450Q93 455 380 592T673 730Q680 730 686 724T693 710Q693 702 688 699Q686 693 417 568L151 439L417 310Q685 185 688 179Q693 176 693 168Q693 161 687 155T675 148Q668 148 380 285T86 428Q74 438 86 450ZM55 -205Q55 -175 64 -142T92 -76T145 -22T222 -1Q288 -1 362 -66Q369 -72 372 -75T378 -79T382 -81T384 -79Q389 -74 439 21Q483 100 490 111T504 122Q510 122 518 118T526 103Q526 101 510 69T467 -12T419 -99L413 -112L433 -128Q498 -180 553 -180Q605 -180 646 -139Q672 -112 681 -77T693 -21T706 -1Q719 -1 719 -33Q719 -39 717 -57Q708 -141 655 -190Q625 -224 586 -232Q568 -237 551 -237Q487 -237 413 -172L391 -155Q391 -157 335 -255Q297 -325 286 -342T268 -359Q260 -359 254 -353T248 -339T304 -230L359 -126Q359 -124 337 -107T302 -81Q262 -57 221 -57Q170 -57 130 -93T84 -201Q82 -236 70 -236Q55 -236 55 -205'], // GREATER-THAN BUT NOT EQUIVALENT TO 0x22E7: [730,359,778,55,719,'88 723Q95 730 99 730Q106 730 394 593T688 450Q693 447 693 439T688 428Q683 423 395 286T99 148Q94 148 88 155T82 168Q82 175 86 179Q89 184 357 310L624 439L357 568Q88 694 86 699Q81 703 81 711T88 723ZM55 -205Q55 -175 64 -142T92 -76T145 -22T222 -1Q288 -1 362 -66Q369 -72 372 -75T378 -79T382 -81T384 -79Q389 -74 439 21Q483 100 490 111T504 122Q510 122 518 118T526 103Q526 101 510 69T467 -12T419 -99L413 -112L433 -128Q498 -180 553 -180Q605 -180 646 -139Q672 -112 681 -77T693 -21T706 -1Q719 -1 719 -33Q719 -39 717 -57Q708 -141 655 -190Q625 -224 586 -232Q568 -237 551 -237Q487 -237 413 -172L391 -155Q391 -157 335 -255Q297 -325 286 -342T268 -359Q260 -359 254 -353T248 -339T304 -230L359 -126Q359 -124 337 -107T302 -81Q262 -57 221 -57Q170 -57 130 -93T84 -201Q82 -236 70 -236Q55 -236 55 -205'], // PRECEDES BUT NOT EQUIVALENT TO 0x22E8: [730,359,778,55,719,'95 419Q81 433 81 439T95 459H124Q318 459 455 501Q515 521 556 550T615 607T641 659T652 702T659 725Q667 730 673 730Q680 730 686 724T693 710Q693 682 677 641Q668 616 654 594T622 554T586 522T545 497T504 477T464 462T428 452T397 444T375 439Q379 437 410 430T476 411T551 379T625 321T677 237Q693 196 693 168Q693 161 687 155T673 148Q662 148 658 154T651 181Q638 253 591 300T455 377Q318 419 124 419H95ZM55 -205Q55 -175 64 -142T92 -76T145 -22T222 -1Q288 -1 362 -66Q369 -72 372 -75T378 -79T382 -81T384 -79Q389 -74 439 21Q483 100 490 111T504 122Q510 122 518 118T526 103Q526 101 510 69T467 -12T419 -99L413 -112L433 -128Q498 -180 553 -180Q605 -180 646 -139Q672 -112 681 -77T693 -21T706 -1Q719 -1 719 -33Q719 -39 717 -57Q708 -141 655 -190Q625 -224 586 -232Q568 -237 551 -237Q487 -237 413 -172L391 -155Q391 -157 335 -255Q297 -325 286 -342T268 -359Q260 -359 254 -353T248 -339T304 -230L359 -126Q359 -124 337 -107T302 -81Q262 -57 221 -57Q170 -57 130 -93T84 -201Q82 -236 70 -236Q55 -236 55 -205'], // SUCCEEDS BUT NOT EQUIVALENT TO 0x22E9: [730,359,778,55,719,'679 459Q693 445 693 439Q693 430 679 419H651Q455 419 319 377Q231 347 184 300T124 181Q120 161 116 155T102 148Q95 148 89 154T82 168Q82 192 97 237Q111 275 137 306T188 355T249 391T307 414T361 429T399 439Q397 440 364 447T298 467T224 499T149 557T97 641Q82 686 82 710Q82 717 88 723T102 730L115 725Q118 722 124 697Q137 625 184 578T319 501Q456 459 651 459H679ZM55 -205Q55 -175 64 -142T92 -76T145 -22T222 -1Q288 -1 362 -66Q369 -72 372 -75T378 -79T382 -81T384 -79Q389 -74 439 21Q483 100 490 111T504 122Q510 122 518 118T526 103Q526 101 510 69T467 -12T419 -99L413 -112L433 -128Q498 -180 553 -180Q605 -180 646 -139Q672 -112 681 -77T693 -21T706 -1Q719 -1 719 -33Q719 -39 717 -57Q708 -141 655 -190Q625 -224 586 -232Q568 -237 551 -237Q487 -237 413 -172L391 -155Q391 -157 335 -255Q297 -325 286 -342T268 -359Q260 -359 254 -353T248 -339T304 -230L359 -126Q359 -124 337 -107T302 -81Q262 -57 221 -57Q170 -57 130 -93T84 -201Q82 -236 70 -236Q55 -236 55 -205'], // NOT NORMAL SUBGROUP OF 0x22EA: [707,208,778,82,693,'693 -30Q686 -41 673 -41Q661 -41 506 34L346 110L280 -44Q228 -162 216 -185T193 -208Q177 -208 173 -192Q173 -186 242 -30T311 128Q271 145 184 186T86 236Q82 240 82 246Q82 251 86 259Q96 267 271 350L449 434L506 565Q537 635 551 664T571 700T582 706Q587 706 593 701T600 690Q600 679 553 572Q504 463 504 461L586 501Q672 539 673 539Q679 539 693 525V-30ZM653 10V488L566 445L480 405L422 276Q415 260 405 236T388 199T376 171T368 151T366 145Q368 143 510 77T653 10ZM422 374Q422 376 420 376T285 313T151 248Q315 168 326 163Q415 356 422 374'], // DOES NOT CONTAIN AS NORMAL SUBGROUP 0x22EB: [706,208,778,82,693,'82 525Q96 539 102 539Q103 539 122 530T186 501T266 463L426 388Q428 388 495 541Q564 694 569 699Q573 706 581 706Q587 706 593 702T600 691Q600 676 533 528Q515 486 506 465T485 418T470 381T466 370Q466 369 575 316Q676 269 689 259Q693 253 693 248Q693 242 689 236Q688 235 506 145Q328 63 324 59Q324 50 266 -70Q224 -169 214 -188T193 -208Q177 -208 173 -192Q173 -183 222 -77Q244 -29 257 2T269 34L186 -6Q108 -43 99 -43Q93 -43 82 -30V525ZM271 416Q129 485 126 485H125Q122 485 122 250Q122 10 124 10L211 50L295 92L411 350Q411 351 271 416ZM624 248L449 332L440 319Q434 297 393 214Q353 121 353 119Q355 119 489 182T624 248'], // stix-not, vert, left triangle, equals 0x22EC: [802,303,778,82,693,'82 -123Q82 -114 93 -103H166L238 -101L293 50Q349 200 349 204L220 266Q166 291 140 304T100 325T84 336T82 344Q82 353 94 360Q112 372 282 453L473 541L482 568Q487 578 529 693Q559 785 569 795Q573 802 581 802Q587 802 593 797T599 786Q599 775 564 675L526 570Q526 568 561 584T633 617T673 635Q679 635 693 621V66Q686 54 679 54Q665 54 526 119Q491 137 458 153T405 177T386 184Q385 182 334 42T282 -101T482 -103H680Q682 -105 684 -108T688 -113T691 -118T693 -124Q693 -134 682 -141L473 -143H266L238 -219Q217 -278 210 -290T193 -303Q178 -303 173 -287Q173 -279 198 -214L222 -145Q222 -143 158 -143L93 -141L86 -136Q82 -131 82 -123ZM653 106V584L506 513L453 370Q442 339 428 300T407 243T400 224Q403 222 527 164T653 106ZM453 486Q453 488 451 488T300 417T151 344L224 308Q247 298 285 279T331 257L364 241L453 486'], // stix-not, vert, right triangle, equals 0x22ED: [801,303,778,82,693,'82 621Q96 635 102 635T249 568L420 486L449 473L469 533Q563 789 569 797Q573 801 581 801Q598 801 600 786Q602 781 544 617L484 455Q531 435 584 408Q677 364 689 355Q693 351 693 344Q693 339 692 337T676 325T631 302T538 257Q504 241 465 223T406 195T386 186Q384 184 333 44T282 -101Q282 -103 482 -103H680Q682 -105 684 -108T688 -113T691 -118T693 -124Q693 -134 682 -141L473 -143H266L238 -219Q217 -278 210 -290T193 -303Q178 -303 173 -287Q173 -279 198 -214L222 -145Q222 -143 158 -143L93 -141L86 -136Q82 -131 82 -123Q82 -114 93 -103H166L238 -101L333 159Q326 159 220 106Q110 54 97 54Q89 54 82 66V621ZM298 501Q155 567 142 575L122 584V344Q122 106 124 106Q125 106 180 132T291 185T351 213Q355 217 393 326L433 435Q433 436 298 501ZM549 381Q472 417 471 417L406 241Q408 240 516 291T624 344L549 381'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/MathOperators.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/MiscMathSymbolsB.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // BLACK LOZENGE 0x29EB: [716,132,667,56,611,'318 709Q325 716 332 716Q340 716 344 713T474 511Q611 298 611 292Q611 285 526 152Q494 103 474 72Q347 -128 344 -130Q340 -132 333 -132T322 -130Q319 -128 257 -31T131 169T60 278Q56 285 56 292Q56 298 60 305Q73 326 194 516T318 709'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/MiscMathSymbolsB.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/MiscSymbols.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // BLACK STAR 0x2605: [694,111,944,49,895,'367 395Q374 416 398 492T442 627T463 688Q463 692 467 692Q471 694 472 694Q478 694 484 680T523 562Q553 469 576 400L577 395H731H819Q872 395 883 394T895 384Q895 380 891 376T832 333Q794 305 767 285Q643 195 643 194L690 47Q737 -96 737 -103Q737 -111 727 -111Q721 -111 594 -18L472 71L350 -18Q223 -111 217 -111Q207 -111 207 -103Q207 -96 254 47L301 194Q301 195 241 239T118 328T51 378Q49 382 49 384Q49 392 58 393T110 395H213H367'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/MiscSymbols.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/MiscTechnical.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // stix-small down curve 0x2322: [378,-122,778,55,722,'77 122Q68 122 63 126T57 135T55 142Q55 151 68 176T111 235T177 302T271 356T388 378Q451 378 508 355T602 300T668 233T709 174T722 142Q722 124 704 122Q692 122 685 134T658 179T606 243Q511 338 390 338Q354 338 320 329Q251 312 193 263T97 141Q87 123 77 122'], // stix-small up curve 0x2323: [378,-142,778,54,722,'389 143Q324 143 266 164T171 215T107 277T67 330T55 358T60 371T77 378Q85 377 92 367T116 331T158 280Q256 182 389 182Q475 182 552 227T675 351Q688 378 704 378Q722 376 722 358Q722 352 710 330T670 276T605 215T511 164T389 143'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/MiscTechnical.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/PUA.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // ?? 0xE006: [430,23,222,-20,240,'91 417Q104 430 111 430T131 417V301L171 341Q201 373 207 378T220 384Q227 384 233 377T240 366Q240 357 187 299L131 244V-10Q116 -23 111 -23T91 -10V201L49 157Q20 127 14 121T0 115Q-8 115 -14 121T-20 132Q-20 139 17 178Q29 191 36 199L91 257V417'], // ?? 0xE007: [431,24,389,-20,407,'56 417Q68 431 76 431L89 426L96 419V317L98 215L193 273L291 330V375L293 419Q301 431 311 431Q331 431 331 388L333 355L356 370Q381 384 388 384Q394 384 400 377T407 363Q407 354 367 328L331 308V-10Q316 -23 310 -23Q300 -23 293 -12L291 135V284L98 168L96 77V-12Q84 -24 76 -24L62 -19L58 -12L56 66V144L31 128Q5 114 -2 114Q-8 114 -14 121T-20 136Q-20 142 -14 147T20 170L56 190V417'], // ?? 0xE008: [605,85,778,55,719,'477 261Q477 257 473 256T455 253T417 251T348 250H235L155 -77L146 -82Q137 -85 109 -85Q55 -85 55 -77L139 261Q224 596 226 598Q229 603 239 603Q240 603 254 603T290 603T341 604T405 605T477 605Q656 603 687 602T719 596Q719 589 692 588T513 585H319L282 427L242 272Q242 270 351 270Q388 270 410 270T444 269T460 267T469 265T477 261'], // ?? 0xE009: [434,6,667,37,734,'228 325Q170 322 156 316T127 309Q108 309 104 314Q99 319 99 322T108 341Q125 376 171 400T268 425H271Q302 425 319 396Q328 377 328 358Q328 332 324 314Q311 270 286 221Q274 194 274 192H275Q339 234 484 325T639 421Q669 434 691 434T723 425T734 406Q734 394 719 381Q715 376 644 330L575 287L566 267Q543 233 526 176Q520 160 515 143T508 115T506 105Q506 103 533 103Q585 103 607 110T641 118Q670 118 670 107Q670 100 661 85Q643 50 598 27T504 3Q465 3 450 36Q441 51 441 73Q441 84 444 96Q452 146 484 205L497 236L324 125Q143 12 135 10Q103 -6 77 -6Q61 -6 49 2T37 21Q37 36 49 46T124 96L195 141L204 156Q219 179 243 248T264 323Q264 325 228 325'], // ?? 0xE00C: [752,284,778,81,694,'86 472Q93 477 381 614T673 752Q680 752 686 746T693 732T689 721Q686 715 418 590L151 461L418 332Q684 207 689 201Q693 195 693 190Q693 183 687 177T675 170Q668 170 380 307T86 450Q82 454 82 461Q82 467 86 472ZM369 101V126Q369 156 382 156H384Q385 157 386 157Q409 157 409 115V98V54H680Q693 39 693 34T680 14H409V-142H680Q693 -155 693 -162Q693 -167 680 -182H409V-273Q396 -284 388 -284Q382 -284 369 -275V-182H95Q82 -167 82 -162Q82 -155 95 -142H369V14H95Q93 17 89 21T84 27T82 34T83 40T89 47T95 54H369V101'], // ?? 0xE00D: [752,284,778,81,693,'89 745Q95 752 100 752Q106 752 394 615T689 472Q693 468 693 461T689 450Q684 445 396 308T100 170Q95 170 89 176T82 190Q82 195 86 201Q91 208 358 332L624 461L358 590Q90 715 86 721Q82 725 82 731Q82 739 89 745ZM369 101V126Q369 156 382 156H384Q385 157 386 157Q409 157 409 115V98V54H680Q693 39 693 34T680 14H409V-142H680Q693 -155 693 -162Q693 -167 680 -182H409V-273Q396 -284 388 -284Q382 -284 369 -275V-182H95Q82 -167 82 -162Q82 -155 95 -142H369V14H95Q93 17 89 21T84 27T82 34T83 40T89 47T95 54H369V101'], // stix-not greater, double equals 0xE00E: [919,421,778,82,694,'97 172Q82 172 82 190Q82 197 86 201Q94 209 173 246T327 319T402 357Q405 360 434 448T462 539L278 628Q96 713 86 721Q82 725 82 732T88 745T102 752Q103 752 125 742T198 709T293 666Q342 642 385 622T453 590T478 579Q479 579 506 659T562 824T598 915Q602 919 609 919T622 913T629 901Q629 898 571 728Q546 656 531 608T518 559Q555 539 602 519Q664 488 679 479T694 461Q694 457 689 450Q680 443 616 413T494 356T435 326L389 190L342 57L513 55H682Q694 43 694 34Q694 28 689 21L682 17L506 15H329L322 -8Q320 -13 310 -41T295 -85L275 -141H680Q682 -143 684 -146T688 -151T691 -156T693 -162Q693 -172 682 -179L473 -181H262L220 -303Q192 -388 185 -404T166 -421Q160 -421 153 -415T146 -403Q146 -400 179 -302T220 -185Q220 -181 158 -181L93 -179L86 -174Q82 -169 82 -161Q82 -152 93 -141H164L233 -139L260 -63L286 15H189L93 17L86 21Q82 26 82 34Q82 44 93 55H198L300 57L342 179Q350 204 361 238T378 286T382 301L246 237Q111 172 97 172ZM624 461Q621 464 560 492Q512 518 503 518Q500 518 500 517Q499 513 488 479T465 413T453 379L624 461'], // stix-not greater-or-equal, slanted 0xE00F: [801,303,778,82,694,'97 54Q82 54 82 72Q82 79 86 84Q95 91 222 153L351 215L398 324L442 433L258 519Q95 597 87 604Q82 608 82 615T88 628T102 635Q107 635 424 484L458 468L524 630Q593 789 597 795Q601 801 609 801Q616 801 622 795T629 781L562 615L493 450L589 406Q665 371 679 362T694 344Q694 339 693 337T677 326T631 302T538 257Q504 241 465 223T406 195T386 186Q383 185 344 92T306 -3L486 81Q662 168 673 168Q680 168 686 162T693 148T689 137Q688 136 482 35L280 -59L233 -176Q184 -291 178 -299Q172 -303 166 -303T153 -297T146 -283Q146 -279 185 -186T224 -90Q225 -88 223 -88Q219 -88 193 -101Q109 -143 98 -143Q82 -138 82 -122Q82 -116 85 -113T108 -98T171 -67L249 -30L289 61Q297 81 307 107T321 144T326 157L218 106Q109 54 97 54ZM553 379Q480 412 480 415Q479 415 460 372T423 285T406 241Q408 240 516 291T624 344L553 379'], // stix-not less-or-equal, slanted 0xE010: [801,303,778,81,694,'102 168Q103 168 151 146T247 102T295 81Q299 85 322 144T344 206L218 268Q153 297 123 313T87 333T82 344T86 355Q104 369 291 455Q491 552 491 553L542 673Q581 767 590 784T609 801Q616 801 622 795T629 781Q629 773 586 677Q546 581 546 577L609 606Q669 635 673 635Q680 635 686 629T693 615Q693 610 692 608T670 593T604 561L524 521L400 226L542 157Q617 123 649 107T687 85T694 72Q694 66 690 60T679 54Q665 54 526 119Q394 186 386 186Q385 186 342 88L331 61L509 -23Q680 -105 688 -111Q693 -115 693 -122T688 -135T675 -141H673Q664 -141 491 -59Q320 21 316 21H315L249 -136Q183 -293 178 -299Q172 -303 166 -303T153 -297T146 -283Q146 -282 154 -261T181 -197T213 -119L280 41Q280 46 186 86Q157 101 121 119Q92 133 87 136T82 148Q82 155 88 161T102 168ZM418 370L466 495Q464 495 308 420T151 344T204 317T311 267T364 244Q364 247 418 370'], // stix-not less, double equals 0xE011: [919,421,778,82,694,'82 34Q82 44 93 55H198L300 57L342 179Q351 207 362 238T378 286T384 303T238 377Q109 435 86 450Q82 454 82 460T86 472Q90 476 302 579L511 679Q512 679 553 795Q569 842 577 866T592 903T600 917T608 919Q615 919 622 912T629 901Q629 899 595 799Q589 777 581 753T569 717T564 703L618 728Q666 752 673 752T686 746T693 732Q693 723 683 717T615 683L546 650L491 488Q464 410 450 368T438 326Q493 297 562 266Q660 219 677 209T694 190Q694 183 690 177T678 171Q664 171 546 228L424 286Q422 286 382 172L342 57L513 55H682Q694 43 694 34Q694 28 689 21L682 17L506 15H329L322 -8Q320 -13 310 -41T295 -85L275 -141H680Q682 -143 684 -146T688 -151T691 -156T693 -162Q693 -172 682 -179L473 -181H262L220 -303Q192 -388 185 -404T166 -421Q160 -421 153 -415T146 -403Q146 -400 179 -302T220 -185Q220 -181 158 -181L93 -179L86 -174Q82 -169 82 -161Q82 -152 93 -141H164L233 -139L260 -63L286 15H189L93 17L86 21Q82 26 82 34ZM495 623Q495 626 493 626T321 544T151 461L398 343Q399 343 405 360T423 415T446 483Q457 513 469 551T488 606T495 623'], // stix-not subset, double equals 0xE016: [828,330,778,82,694,'82 -6Q82 1 95 14H262L295 94Q331 171 331 174Q324 175 312 178T267 194T206 227T146 283T98 368Q84 406 84 461T98 554Q126 632 194 685T349 750Q360 752 480 752H591L604 783Q620 819 624 821Q631 828 640 828Q653 825 658 810Q658 808 646 781L635 754Q635 752 658 752Q680 752 686 746Q693 739 693 732Q693 728 692 726T686 719T680 712H615L506 466Q479 407 451 344T408 248T393 214Q393 210 535 210H680Q693 194 693 190T680 170H373L340 92L304 14H680Q693 1 693 -6Q693 -11 680 -26H286L253 -103L218 -179L451 -181H682Q694 -193 694 -201Q694 -212 682 -219L440 -221H200L178 -270Q160 -309 154 -319T139 -330Q122 -330 118 -312L155 -223Q155 -221 126 -221H95Q82 -206 82 -201T95 -181H175L206 -108Q237 -35 242 -30Q242 -26 169 -26H95Q82 -11 82 -6ZM571 710Q571 712 469 712Q443 712 416 712T371 711T351 710Q279 700 221 656T138 548Q124 508 124 461T138 374Q186 245 351 212L460 459Q571 709 571 710'], // ?? 0xE017: [752,332,778,81,694,'82 -14T82 -7T95 15H431L529 170H435Q341 170 333 175Q149 218 98 368Q84 406 84 461Q84 515 98 555Q126 633 193 686T346 750Q347 750 373 750T440 751T520 752H680Q693 739 693 732Q693 727 680 712H526Q364 712 353 710Q268 700 207 646T126 512Q123 496 123 461T126 410Q141 350 180 304T280 232Q312 217 344 214T464 210H555L589 261Q613 301 620 311T635 321Q644 321 650 315T657 301Q657 296 651 286T630 252T604 212Q604 210 642 210H680Q693 197 693 190Q693 186 692 184T686 177T680 170H578L526 92L478 17L580 15H682Q693 4 693 -4T680 -25H451L353 -179L518 -181H682Q694 -193 694 -201Q694 -211 682 -219L504 -221H326L293 -272Q257 -332 246 -332Q238 -332 232 -326T225 -313Q225 -310 226 -308Q226 -305 251 -265T278 -223Q278 -221 186 -221H95Q93 -218 89 -214T84 -208T82 -201T95 -181H306L404 -25H249L93 -23L86 -19Q82 -14 82 -7'], // stix-not superset, double equals 0xE018: [828,330,778,82,694,'82 732Q82 739 95 752H251H348Q420 752 460 744T551 708Q566 697 566 701Q618 815 624 821Q631 828 640 828Q653 825 658 810L600 677Q600 671 615 656T653 605T689 517Q692 496 692 461T689 406Q668 325 615 266Q572 221 513 196T391 170H373L340 92L304 14H680Q693 1 693 -6Q693 -11 680 -26H286L253 -103L218 -179L451 -181H682Q694 -193 694 -201Q694 -212 682 -219L440 -221H200L178 -270Q160 -309 154 -319T139 -330Q122 -330 118 -312L155 -223Q155 -221 126 -221H95Q82 -206 82 -201T95 -181H175L206 -108Q237 -35 242 -30Q242 -26 169 -26H95Q82 -11 82 -6Q82 1 95 14H262L295 92L331 170H95Q93 172 91 175T87 180T84 185T82 191Q82 199 93 210H220L349 212L549 659Q507 692 462 702T338 712H249H95Q82 727 82 732ZM652 473Q652 513 636 552T603 611T582 632Q581 632 487 422T393 210Q424 210 460 220T535 253T605 316T649 410Q652 427 652 461V473'], // ?? 0xE019: [752,333,778,82,693,'82 732Q82 739 95 752H251Q415 752 426 750Q539 736 615 657Q667 599 689 517Q692 496 692 461T689 406Q668 325 615 266Q522 170 382 170H355L326 95Q319 80 311 59T298 28T293 17Q293 15 486 15H680Q693 0 693 -6T680 -25H275L213 -179L449 -181H682Q693 -192 693 -199T680 -221H198L178 -270Q153 -333 139 -333Q132 -333 126 -327T119 -314T135 -266T153 -223Q153 -221 124 -221H95Q82 -207 82 -201T95 -181H171L233 -25H162L93 -23L86 -19Q82 -14 82 -7T95 15H251L313 170H202L93 172L86 177Q82 182 82 190Q82 199 93 210H211L329 212L349 261Q366 301 372 311T386 321Q392 321 399 315T407 302Q407 295 390 254T373 210Q374 209 377 209Q412 209 444 217Q512 231 564 273T638 377Q651 414 651 461Q651 509 638 548Q613 613 555 656T422 710Q411 712 249 712H95Q82 727 82 732'], // ?? 0xE01A: [634,255,778,84,694,'693 -115T693 -122T680 -144H315L269 -199Q221 -255 213 -255H212Q203 -255 197 -248T193 -231Q195 -225 229 -184L262 -144H186L113 -142L106 -137Q102 -130 102 -125Q102 -119 115 -104H298L426 52H386Q342 54 309 63Q236 79 180 129T98 249Q84 289 84 343Q84 398 98 436Q126 514 193 567T346 632Q347 632 373 632T440 633T520 634H680Q682 631 686 627T691 621T693 614T680 594H526Q364 594 353 592Q268 581 207 528T126 394Q123 378 123 343T126 292Q141 231 181 185T280 114Q329 92 415 92H462L506 147Q554 203 562 203H563Q572 203 578 196T582 178Q579 173 546 132L513 94L598 92H682Q693 81 693 73T680 52H480L349 -102L515 -104H682Q693 -115 693 -122'], // ?? 0xE01B: [634,254,778,82,691,'82 610T82 614T83 620T89 627T95 634H251Q378 634 409 633T469 623Q540 604 596 554T678 436Q691 397 691 343T678 249Q653 181 597 131T469 63Q427 52 362 52H315L213 -102L438 -104H662Q673 -115 673 -123Q673 -129 660 -144H186L151 -197Q114 -250 109 -253Q106 -254 104 -254Q100 -254 98 -253Q91 -253 87 -248T82 -235Q82 -230 109 -186L138 -144H115Q82 -144 82 -125Q82 -119 95 -104H166L266 49Q266 52 182 52H95Q82 65 82 72Q82 76 83 78T89 85T95 92H295L329 143Q365 195 369 198Q372 203 380 203Q385 203 391 197T398 185Q398 184 398 184L399 182Q399 175 369 129L344 94Q344 92 376 92Q402 92 422 94Q496 104 554 147T638 256Q651 295 651 343Q651 390 638 429Q613 494 555 537T422 592Q411 594 249 594H95Q82 610 82 614'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/PUA.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/SpacingModLetters.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // MODIFIER LETTER CIRCUMFLEX ACCENT 0x2C6: [845,-561,2333,-14,2346,'1 561Q-3 563 -6 577T-12 604L-14 618Q-14 625 -7 628T23 635Q31 636 36 637Q63 641 621 745Q1148 845 1165 845Q1167 845 1752 739L2338 630Q2346 630 2346 618Q2340 565 2332 561Q2329 561 1749 654Q1617 675 1466 699T1241 736T1167 748Q1165 748 1093 737T867 700T583 654Q3 561 1 561'], // SMALL TILDE 0x2DC: [899,-628,2333,1,2330,'804 788Q717 788 606 772T397 732T213 685T75 645T18 628Q11 628 11 632Q8 637 4 668T2 704Q2 713 36 732Q55 739 242 795Q622 898 826 898Q893 898 947 895Q1009 887 1056 872T1187 819Q1286 776 1356 758T1527 739Q1614 739 1725 755T1934 795T2118 842T2256 882T2313 899Q2320 899 2320 895Q2323 890 2327 860T2329 824Q2329 818 2296 795Q2273 787 2089 732Q1810 657 1598 632Q1562 629 1492 629Q1389 629 1320 644T1144 708Q1048 751 977 769T804 788'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/SpacingModLetters.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/AMS/Regular/SuppMathOperators.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_AMS'], { // LOGICAL AND WITH DOUBLE OVERBAR 0x2A5E: [813,97,611,54,555,'55 795Q56 806 70 813H540Q554 806 555 795Q555 791 554 788T552 783T549 779T546 777T542 775T539 773H71Q70 773 68 774T65 776T61 779T58 783T56 788T55 795ZM55 601Q56 612 70 619H540Q554 612 555 601Q555 597 554 594T552 589T549 585T546 583T542 581T539 579H71Q70 579 68 580T65 582T61 585T58 589T56 594T55 601ZM75 -97Q67 -97 61 -91T55 -81Q55 -71 169 166T285 410Q295 425 305 425Q311 425 316 422T323 414L325 410Q327 404 441 167T555 -81Q555 -85 549 -91T535 -97T520 -90Q519 -89 506 -61T463 27T411 136L305 358L199 136Q167 71 129 -10Q98 -75 92 -86T75 -97'], // LESS-THAN OR SLANTED EQUAL TO 0x2A7D: [636,138,778,83,694,'674 636Q682 636 688 630T694 615T687 601Q686 600 417 472L151 346L399 228Q687 92 691 87Q694 81 694 76Q694 58 676 56H670L382 192Q92 329 90 331Q83 336 83 348Q84 359 96 365Q104 369 382 500T665 634Q669 636 674 636ZM94 170Q102 172 104 172Q110 171 254 103T535 -30T678 -98Q694 -106 694 -118Q694 -136 676 -138H670L382 -2Q92 135 90 137Q83 142 83 154Q84 164 94 170'], // GREATER-THAN OR SLANTED EQUAL TO 0x2A7E: [636,138,778,82,694,'83 616Q83 624 89 630T99 636Q107 636 253 568T543 431T687 361Q694 356 694 346T687 331Q685 329 395 192L107 56H101Q83 58 83 76Q83 77 83 79Q82 86 98 95Q117 105 248 167Q326 204 378 228L626 346L360 472Q291 505 200 548Q112 589 98 597T83 616ZM674 172Q692 172 694 154Q694 142 687 137Q685 135 395 -2L107 -138H101Q83 -136 83 -118Q83 -106 96 -100Q100 -98 380 35T665 170T674 172'], // LESS-THAN OR APPROXIMATE 0x2A85: [762,290,778,55,722,'667 761Q669 762 673 762Q682 762 688 756T694 741Q694 731 687 727Q684 724 420 613L156 502L416 392Q476 367 544 338T647 295T682 280Q694 274 694 262Q694 244 676 242Q670 242 524 303T235 425T90 487Q83 493 83 501Q83 514 94 519Q97 520 378 639T667 761ZM55 -23Q55 43 103 90T223 138Q265 138 316 114Q342 100 393 68L443 36Q502 0 554 0Q609 0 650 32T694 109Q694 138 708 138Q710 138 713 136T719 127T722 108Q722 37 673 -9T557 -56Q514 -56 468 -35T387 13T308 60T223 82Q167 82 127 50T83 -27Q81 -56 69 -56Q55 -56 55 -23ZM55 -257Q55 -191 103 -144T223 -96Q265 -96 316 -120Q342 -134 393 -166L443 -198Q502 -234 554 -234Q609 -234 650 -202T694 -125Q694 -96 708 -96Q710 -96 713 -98T719 -107T722 -126Q722 -197 673 -243T557 -290Q514 -290 468 -269T387 -221T308 -174T223 -152Q167 -152 127 -184T83 -261Q80 -290 69 -290Q55 -290 55 -257'], // GREATER-THAN OR APPROXIMATE 0x2A86: [762,290,778,55,722,'90 727Q83 734 83 743Q83 751 89 756T104 762Q111 760 396 641Q686 518 687 517Q694 512 694 502T687 487Q686 486 543 425T253 303T101 242Q83 244 83 262Q83 274 95 280Q96 280 130 294T232 338T361 392L621 502L357 613Q93 724 90 727ZM55 -23Q55 43 103 90T223 138Q265 138 316 114Q342 100 393 68L443 36Q502 0 554 0Q609 0 650 32T694 109Q694 138 708 138Q710 138 713 136T719 127T722 108Q722 37 673 -9T557 -56Q514 -56 468 -35T387 13T308 60T223 82Q167 82 127 50T83 -27Q81 -56 69 -56Q55 -56 55 -23ZM55 -257Q55 -191 103 -144T223 -96Q265 -96 316 -120Q342 -134 393 -166L443 -198Q502 -234 554 -234Q609 -234 650 -202T694 -125Q694 -96 708 -96Q710 -96 713 -98T719 -107T722 -126Q722 -197 673 -243T557 -290Q514 -290 468 -269T387 -221T308 -174T223 -152Q167 -152 127 -184T83 -261Q80 -290 69 -290Q55 -290 55 -257'], // LESS-THAN AND SINGLE-LINE NOT EQUAL TO 0x2A87: [636,241,778,82,694,'380 497Q445 528 522 564T636 618T673 635Q680 635 686 628T693 615T689 603Q686 599 418 472L151 343L418 215Q686 88 689 83Q693 79 693 72T687 59T675 52Q669 52 381 189T86 332Q82 337 82 344Q82 350 86 355Q91 359 380 497ZM82 -130T82 -124T95 -103H380L431 -54Q476 -6 486 -6Q491 -6 498 -12T505 -27Q505 -28 505 -29T504 -32Q503 -33 498 -38T485 -53T469 -70L438 -103H680Q682 -106 686 -110T691 -116T693 -123Q693 -130 680 -143H398L346 -192Q300 -241 291 -241Q271 -241 271 -221Q271 -209 306 -179L340 -143H95Q82 -130 82 -124'], // GREATER-THAN AND SINGLE-LINE NOT EQUAL TO 0x2A88: [635,241,778,82,693,'82 614Q82 620 86 625T94 632T100 635Q106 635 394 498T689 355Q693 349 693 343Q693 338 689 332Q683 327 395 190T100 52Q95 52 89 58T82 72Q82 80 95 88Q114 99 358 215L624 343L358 472Q89 599 86 603Q82 607 82 614ZM82 -130T82 -124T95 -103H380L431 -54Q476 -6 486 -6Q491 -6 498 -12T505 -27Q505 -28 505 -29T504 -32Q503 -33 498 -38T485 -53T469 -70L438 -103H680Q682 -106 686 -110T691 -116T693 -123Q693 -130 680 -143H398L346 -192Q300 -241 291 -241Q271 -241 271 -221Q271 -209 306 -179L340 -143H95Q82 -130 82 -124'], // LESS-THAN AND NOT APPROXIMATE 0x2A89: [761,387,778,57,718,'86 512Q93 518 381 639T673 761Q680 761 686 755T693 741Q693 733 688 730Q685 723 419 612L155 501L419 390Q685 277 688 272Q693 269 693 261Q693 254 687 248T675 241Q669 241 381 362T86 490Q74 500 86 512ZM70 -59Q57 -59 57 -24Q57 40 104 87Q116 102 146 118Q186 136 231 136Q232 136 242 135T258 133T276 128T302 118T334 101T377 74Q386 69 396 63T411 53T417 50Q435 87 453 134Q491 223 495 227Q498 230 505 230Q513 230 519 225T526 212Q526 203 491 118T453 30Q453 22 489 10T553 -3Q589 -3 622 14Q653 28 669 50T688 90T694 122T706 136Q718 136 718 114Q718 113 718 109T717 103Q717 31 668 -14T554 -60Q529 -60 499 -50T451 -32T433 -24Q431 -24 404 -90T375 -157Q375 -159 402 -178T473 -218T553 -239Q599 -239 641 -211T691 -130Q694 -99 706 -99T718 -122Q718 -123 718 -127T717 -133Q717 -204 668 -249T559 -295Q512 -295 470 -275T355 -206L322 -290Q313 -310 304 -332T289 -367T282 -382Q277 -387 270 -387Q262 -387 255 -382T248 -368Q248 -361 322 -186Q311 -177 280 -166T222 -155Q189 -155 153 -173Q122 -186 106 -208T87 -248T82 -280T71 -294Q57 -294 57 -259Q57 -195 104 -148Q122 -126 155 -113T220 -99Q245 -99 276 -109T324 -127T342 -135Q397 -2 397 1Q386 10 367 23T302 58T222 80Q175 80 132 52T84 -28Q82 -59 70 -59'], // GREATER-THAN AND NOT APPROXIMATE 0x2A8A: [761,387,778,57,718,'86 730Q81 734 81 740Q81 747 88 754T99 761Q103 761 392 640T688 512Q693 509 693 501T688 490Q682 484 394 363T99 241Q94 241 88 248T82 261Q82 268 86 272Q89 277 355 390L619 501L355 612Q89 723 86 730ZM70 -59Q57 -59 57 -24Q57 40 104 87Q116 102 146 118Q186 136 231 136Q232 136 242 135T258 133T276 128T302 118T334 101T377 74Q386 69 396 63T411 53T417 50Q435 87 453 134Q491 223 495 227Q498 230 505 230Q513 230 519 225T526 212Q526 203 491 118T453 30Q453 22 489 10T553 -3Q589 -3 622 14Q653 28 669 50T688 90T694 122T706 136Q718 136 718 114Q718 113 718 109T717 103Q717 31 668 -14T554 -60Q529 -60 499 -50T451 -32T433 -24Q431 -24 404 -90T375 -157Q375 -159 402 -178T473 -218T553 -239Q599 -239 641 -211T691 -130Q694 -99 706 -99T718 -122Q718 -123 718 -127T717 -133Q717 -204 668 -249T559 -295Q512 -295 470 -275T355 -206L322 -290Q313 -310 304 -332T289 -367T282 -382Q277 -387 270 -387Q262 -387 255 -382T248 -368Q248 -361 322 -186Q311 -177 280 -166T222 -155Q189 -155 153 -173Q122 -186 106 -208T87 -248T82 -280T71 -294Q57 -294 57 -259Q57 -195 104 -148Q122 -126 155 -113T220 -99Q245 -99 276 -109T324 -127T342 -135Q397 -2 397 1Q386 10 367 23T302 58T222 80Q175 80 132 52T84 -28Q82 -59 70 -59'], // LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN 0x2A8B: [1003,463,778,83,694,'674 1003Q681 1003 687 999T694 983Q694 973 683 967Q669 959 420 868L162 772L422 676Q683 579 685 577Q694 571 694 560Q694 550 687 546T673 541Q669 542 384 647T93 755Q83 760 83 772Q83 783 91 788Q98 791 383 897T674 1003ZM84 354T84 367T98 387H679Q694 379 694 367Q694 354 679 347H98Q84 354 84 367ZM84 160T84 173T98 193H679Q694 185 694 173Q694 160 679 153H98Q84 160 84 173ZM94 -3Q102 -1 104 -1Q107 -2 392 -107T684 -215Q694 -219 694 -232Q694 -241 687 -247Q686 -248 395 -357Q106 -463 101 -463Q83 -461 83 -443Q83 -431 94 -426Q97 -423 357 -328L615 -232L355 -136Q94 -39 92 -37Q83 -31 83 -21Q83 -9 94 -3'], // GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN 0x2A8C: [1003,463,778,83,694,'104 541Q98 541 91 545T83 560Q83 571 92 577Q94 579 355 676L615 772L357 868Q108 959 94 967Q83 973 83 983Q83 989 87 996T104 1003Q109 1002 396 896T687 787Q694 781 694 772Q694 759 684 755Q678 752 393 647T104 541ZM84 367Q84 380 98 387H679Q694 379 694 367Q694 356 680 348L390 347H100Q84 352 84 367ZM84 173Q84 188 100 193H680Q694 183 694 173Q694 160 679 153H98Q84 160 84 173ZM674 -1Q682 -1 688 -6T694 -20Q694 -31 685 -37Q683 -39 422 -136L162 -232L420 -328Q680 -423 683 -426Q694 -431 694 -443Q694 -461 676 -463Q671 -463 382 -357Q91 -248 90 -247Q83 -242 83 -232Q83 -220 93 -215Q667 -1 674 -1'], // SLANTED EQUAL TO OR LESS-THAN 0x2A95: [636,138,778,83,694,'674 636Q682 636 688 631T694 616Q694 605 687 601Q685 599 395 462L107 326H101Q83 328 83 345Q83 358 96 365Q102 367 382 500T665 634Q671 636 674 636ZM674 442Q692 442 694 424Q694 412 687 407Q686 406 417 278L151 152L399 34Q687 -102 691 -107Q694 -113 694 -118Q694 -136 676 -138H670L382 -2Q92 135 90 137Q83 142 83 154Q84 165 96 171Q104 175 382 306T665 440Q669 442 674 442'], // SLANTED EQUAL TO OR GREATER-THAN 0x2A96: [636,138,778,83,694,'83 616Q83 624 89 630T99 636Q107 636 253 568T543 431T687 361Q694 354 694 346Q694 328 676 326H670L382 462Q317 493 226 535Q119 585 101 595T83 616ZM94 440Q102 442 104 442Q110 441 254 373T535 240T678 172Q679 172 680 171Q694 164 694 153T687 137Q685 135 395 -2L107 -138H101Q83 -136 83 -118Q83 -106 93 -101L128 -84Q163 -68 230 -36T361 26L626 152L360 278Q91 406 90 407Q83 412 83 424Q84 434 94 440'], // PRECEDES ABOVE NOT EQUAL TO 0x2AB5: [752,286,778,82,693,'653 734Q653 738 660 745T673 752T686 745T693 723Q672 555 466 485Q390 463 378 463Q373 463 373 461Q373 458 378 458Q390 458 466 436Q562 404 620 350Q682 283 693 198Q693 183 686 176Q681 170 674 170T660 176T653 187Q653 192 652 200T646 228T631 265T602 307T555 350Q435 431 151 441H95Q82 454 82 460T95 481H151Q165 482 197 483T238 485Q427 500 528 554T649 707Q653 729 653 734ZM82 33Q82 37 83 40T89 47T95 54H473L520 105Q569 156 571 156Q573 157 578 157Q586 157 592 151T598 136Q598 130 562 92L526 56L604 54H682Q693 43 693 35Q693 31 692 28T686 21T680 14H489L342 -139L513 -142H682Q693 -148 693 -160Q693 -167 680 -182H304L258 -230Q248 -240 237 -251T221 -268T211 -278T203 -284T197 -286Q189 -286 184 -280T178 -264Q178 -257 213 -219L249 -182H171L93 -179L86 -175Q82 -170 82 -163Q82 -155 95 -142H289L360 -64L433 14H262L93 16Q82 23 82 33'], // SUCCEEDS ABOVE NOT EQUAL TO 0x2AB6: [752,286,778,82,693,'693 466T693 460T680 441H624Q608 439 577 438T538 436Q349 421 248 367T126 214Q122 192 122 187Q122 183 116 177T102 170Q95 170 89 176Q82 183 82 198Q93 283 155 350Q213 404 309 436Q385 458 398 458Q402 458 402 461Q402 463 398 463Q385 463 309 485Q103 555 82 723Q82 738 89 745T102 752T115 745T122 734Q122 721 126 701T155 640T220 572Q340 490 624 481H680Q693 466 693 460ZM82 33Q82 37 83 40T89 47T95 54H473L520 105Q569 156 571 156Q573 157 578 157Q586 157 592 151T598 136Q598 130 562 92L526 56L604 54H682Q693 43 693 35Q693 31 692 28T686 21T680 14H489L342 -139L513 -142H682Q693 -148 693 -160Q693 -167 680 -182H304L258 -230Q248 -240 237 -251T221 -268T211 -278T203 -284T197 -286Q189 -286 184 -280T178 -264Q178 -257 213 -219L249 -182H171L93 -179L86 -175Q82 -170 82 -163Q82 -155 95 -142H289L360 -64L433 14H262L93 16Q82 23 82 33'], // PRECEDES ABOVE ALMOST EQUAL TO 0x2AB7: [761,294,778,57,717,'82 494T82 501T95 521H171Q405 527 511 569Q630 618 651 732Q652 734 653 740T655 748T658 754T663 759T672 761L686 754Q693 747 693 734Q684 668 648 623Q627 591 573 557T442 507L417 501Q428 496 442 494Q520 478 573 444T648 378Q684 333 693 267Q693 254 686 247Q673 234 659 245Q657 247 651 269Q630 383 511 432Q406 474 171 481H95Q82 494 82 501ZM70 -59Q57 -59 57 -26Q57 30 90 73T177 132Q191 136 226 136Q228 136 239 136T253 135T267 132T287 125T311 113T346 95T391 67Q462 20 502 5Q519 1 553 1Q586 1 602 5Q641 18 664 45T691 107Q694 136 704 136Q717 136 717 115V105Q717 39 671 -9T554 -58Q518 -58 481 -43T382 14Q302 63 273 74Q255 78 222 78Q188 78 173 74Q90 46 84 -28Q82 -59 70 -59ZM71 -294Q57 -294 57 -262Q57 -205 90 -162T177 -104Q191 -99 226 -99Q266 -103 277 -106Q310 -119 391 -168Q455 -212 502 -231Q519 -235 553 -235Q586 -235 602 -231Q640 -218 661 -195T686 -151T693 -115T704 -99Q717 -99 717 -121V-131Q717 -198 671 -246T556 -294Q519 -294 482 -279T382 -222Q307 -175 273 -162Q255 -157 222 -157Q188 -157 173 -162Q133 -175 110 -201T84 -264Q82 -294 71 -294'], // SUCCEEDS ABOVE ALMOST EQUAL TO 0x2AB8: [761,294,778,57,717,'693 501Q693 493 679 481H604Q369 474 264 432Q143 382 124 269Q116 246 115 245Q101 234 88 247Q82 254 82 267Q89 329 126 378Q147 410 201 444T333 494L357 501Q354 502 340 505T318 510T295 516T269 525T243 535T215 548T188 565Q142 599 126 623Q89 672 82 734Q82 761 102 761L115 756Q116 755 124 732Q143 619 264 569Q371 527 604 521H679Q693 507 693 501ZM70 -59Q57 -59 57 -26Q57 30 90 73T177 132Q191 136 226 136Q228 136 239 136T253 135T267 132T287 125T311 113T346 95T391 67Q462 20 502 5Q519 1 553 1Q586 1 602 5Q641 18 664 45T691 107Q694 136 704 136Q717 136 717 115V105Q717 39 671 -9T554 -58Q518 -58 481 -43T382 14Q302 63 273 74Q255 78 222 78Q188 78 173 74Q90 46 84 -28Q82 -59 70 -59ZM71 -294Q57 -294 57 -262Q57 -205 90 -162T177 -104Q191 -99 226 -99Q266 -103 277 -106Q310 -119 391 -168Q455 -212 502 -231Q519 -235 553 -235Q586 -235 602 -231Q640 -218 661 -195T686 -151T693 -115T704 -99Q717 -99 717 -121V-131Q717 -198 671 -246T556 -294Q519 -294 482 -279T382 -222Q307 -175 273 -162Q255 -157 222 -157Q188 -157 173 -162Q133 -175 110 -201T84 -264Q82 -294 71 -294'], // PRECEDES ABOVE NOT ALMOST EQUAL TO 0x2AB9: [761,337,778,57,718,'82 494T82 501T95 521H171Q256 523 317 528T441 548T543 584T613 644T651 732Q652 734 653 740T655 748T658 754T663 759T672 761L686 754Q693 747 693 734Q686 686 664 647T615 586T548 545T482 518T417 501Q419 500 451 493T517 471T590 434T657 367T693 267Q693 241 673 241Q664 241 659 245Q656 249 650 273T635 323T593 380T511 432Q406 474 171 481H95Q82 494 82 501ZM57 -26Q57 39 101 87T219 136Q254 136 277 130Q320 114 382 72Q419 50 424 45Q426 45 459 110Q496 178 497 179Q500 180 504 180Q509 180 517 175T526 161Q526 158 495 90L462 25Q462 21 502 5Q519 1 553 1Q586 1 602 5Q641 18 664 45T691 107Q694 136 706 136T718 115Q718 114 718 111T717 105Q717 39 671 -9T554 -58L459 -33Q450 -29 444 -27T437 -26L371 -155L391 -168Q485 -235 538 -235H553Q586 -235 602 -230Q683 -204 691 -128Q694 -99 706 -99T718 -120Q718 -121 718 -124T717 -130Q717 -199 670 -246T557 -294T393 -228Q353 -205 351 -201Q348 -201 315 -266Q294 -310 285 -323T268 -337Q259 -337 254 -331T248 -317Q248 -305 282 -246L313 -181Q313 -177 273 -161Q255 -157 222 -157Q188 -157 173 -161Q134 -174 113 -198T88 -242T82 -278T71 -294Q57 -294 57 -261Q57 -204 91 -161T179 -104Q195 -99 228 -99Q274 -102 315 -124Q337 -132 337 -130L404 -1L384 12Q319 58 273 74Q255 79 222 79Q188 79 173 74Q133 61 112 37T88 -7T82 -43T70 -59Q57 -59 57 -26'], // SUCCEEDS ABOVE NOT ALMOST EQUAL TO 0x2ABA: [761,337,778,57,718,'693 501Q693 493 679 481H604Q548 479 509 477T418 469T331 454T257 429T194 392T150 340T124 270Q117 247 115 245Q101 236 88 247Q82 254 82 267Q89 330 126 379Q147 411 202 444T333 494L357 501Q239 531 188 565Q142 599 126 623Q89 672 82 734Q82 761 102 761L115 756Q116 755 124 732Q133 678 166 640T241 579T349 544T470 527T604 521H679Q693 507 693 501ZM57 -26Q57 39 101 87T219 136Q254 136 277 130Q320 114 382 72Q419 50 424 45Q426 45 459 110Q496 178 497 179Q500 180 504 180Q509 180 517 175T526 161Q526 158 495 90L462 25Q462 21 502 5Q519 1 553 1Q586 1 602 5Q641 18 664 45T691 107Q694 136 706 136T718 115Q718 114 718 111T717 105Q717 39 671 -9T554 -58L459 -33Q450 -29 444 -27T437 -26L371 -155L391 -168Q485 -235 538 -235H553Q586 -235 602 -230Q683 -204 691 -128Q694 -99 706 -99T718 -120Q718 -121 718 -124T717 -130Q717 -199 670 -246T557 -294T393 -228Q353 -205 351 -201Q348 -201 315 -266Q294 -310 285 -323T268 -337Q259 -337 254 -331T248 -317Q248 -305 282 -246L313 -181Q313 -177 273 -161Q255 -157 222 -157Q188 -157 173 -161Q134 -174 113 -198T88 -242T82 -278T71 -294Q57 -294 57 -261Q57 -204 91 -161T179 -104Q195 -99 228 -99Q274 -102 315 -124Q337 -132 337 -130L404 -1L384 12Q319 58 273 74Q255 79 222 79Q188 79 173 74Q133 61 112 37T88 -7T82 -43T70 -59Q57 -59 57 -26'], // SUBSET OF ABOVE EQUALS SIGN 0x2AC5: [754,215,778,84,694,'84 463Q84 585 166 663T360 752Q361 752 370 752T395 752T430 752T475 753T524 753H679Q694 746 694 733Q694 724 681 714L522 713H470H441Q366 713 338 709T266 685Q244 674 224 659T179 617T139 550T124 463V458Q124 370 185 302Q244 238 328 220Q348 215 366 215T522 213H681Q694 203 694 193Q694 180 679 173H526Q510 173 480 173T434 172Q350 172 289 188T172 258Q84 340 84 463ZM84 -14T84 -1T98 19H679Q694 11 694 -1Q694 -14 679 -21H98Q84 -14 84 -1ZM84 -208T84 -195T98 -175H679Q694 -183 694 -195Q694 -208 679 -215H98Q84 -208 84 -195'], // SUPERSET OF ABOVE EQUALS SIGN 0x2AC6: [754,215,778,83,694,'83 733Q83 746 98 753H251Q267 753 297 753T343 754Q427 754 488 738T605 668Q693 587 693 463Q693 378 650 312T545 213T415 174Q407 173 251 173H98Q83 180 83 193Q83 203 96 213H255H308H337Q412 213 439 217T512 241Q533 252 553 267T599 309T639 376T654 463Q654 554 592 624Q557 662 512 685Q468 704 439 708T335 713H306H255L96 714Q83 724 83 733ZM84 -14T84 -1T98 19H679Q694 11 694 -1Q694 -14 679 -21H98Q84 -14 84 -1ZM84 -208T84 -195T98 -175H679Q694 -183 694 -195Q694 -208 679 -215H98Q84 -208 84 -195'], // stix-subset not double equals, variant 0x2ACB: [783,385,778,82,694,'693 221Q693 214 680 201H524Q398 201 367 202T309 212Q236 230 180 280T98 398Q84 438 84 492T98 585Q126 663 193 716T346 781Q347 781 373 781T440 782T520 783H680Q682 780 686 776T691 770T693 763T680 743H526Q364 743 353 741Q279 730 221 687T138 578Q124 540 124 492T138 405Q163 340 221 297T353 243Q364 241 526 241H680Q682 238 686 234T691 228T693 221ZM82 -48T82 -41T95 -19H462L513 41L569 105Q574 110 582 110T596 104T602 90Q602 87 600 83Q600 77 555 30L515 -17L600 -19H682Q693 -30 693 -38T680 -59H480L415 -137L349 -213L515 -215H682Q693 -226 693 -233T680 -255H313L260 -317Q224 -360 212 -372T192 -385Q184 -385 179 -377T173 -362Q174 -361 218 -306L260 -255H178L93 -253L86 -248Q82 -243 82 -235Q82 -226 93 -215H195L295 -213L362 -137L426 -59H260L93 -57L86 -53Q82 -48 82 -41'], // SUPERSET OF ABOVE NOT EQUAL TO 0x2ACC: [783,385,778,82,693,'82 759T82 763T83 769T89 776T95 783H251Q378 783 409 782T469 772Q540 753 596 703T678 585Q691 546 691 492T678 398Q649 320 581 267T426 203Q415 201 251 201H95Q82 214 82 221Q82 225 83 227T89 234T95 241H249Q411 241 422 243Q496 253 554 296T638 405Q651 444 651 492Q651 539 638 578Q613 643 555 686T422 741Q411 743 249 743H95Q82 759 82 763ZM82 -48T82 -41T95 -19H462L513 41L569 105Q574 110 582 110T596 104T602 90Q602 87 600 83Q600 77 555 30L515 -17L600 -19H682Q693 -30 693 -38T680 -59H480L415 -137L349 -213L515 -215H682Q693 -226 693 -233T680 -255H313L260 -317Q224 -360 212 -372T192 -385Q184 -385 179 -377T173 -362Q174 -361 218 -306L260 -255H178L93 -253L86 -248Q82 -243 82 -235Q82 -226 93 -215H195L295 -213L362 -137L426 -59H260L93 -57L86 -53Q82 -48 82 -41'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/AMS/Regular/SuppMathOperators.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/Main/Regular/BasicLatin.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_Main'], { // EXCLAMATION MARK 0x21: [716,-1,278,78,199,'78 661Q78 682 96 699T138 716T180 700T199 661Q199 654 179 432T158 206Q156 198 139 198Q121 198 119 206Q118 209 98 431T78 661ZM79 61Q79 89 97 105T141 121Q164 119 181 104T198 61Q198 31 181 16T139 1Q114 1 97 16T79 61'], // QUOTATION MARK 0x22: [694,-379,500,34,372,'34 634Q34 659 50 676T93 694Q121 694 144 668T168 579Q168 525 146 476T101 403T73 379Q69 379 60 388T50 401Q50 404 62 417T88 448T116 500T131 572Q131 584 130 584T125 581T112 576T94 573Q69 573 52 590T34 634ZM238 634Q238 659 254 676T297 694Q325 694 348 668T372 579Q372 525 350 476T305 403T277 379Q273 379 264 388T254 401Q254 404 266 417T292 448T320 500T335 572Q335 584 334 584T329 581T316 576T298 573Q273 573 256 590T238 634'], // NUMBER SIGN 0x23: [694,194,833,56,778,'56 347Q56 360 70 367H313L355 524Q394 676 401 686Q406 694 416 694Q434 694 436 676Q436 672 396 522Q355 374 355 369L354 367H543L585 524Q626 679 630 685Q636 694 646 694Q653 694 659 689T665 678Q665 668 626 522Q585 374 585 369L584 367H762Q777 359 777 347Q777 334 767 331T722 327H667H572L552 251L531 174Q531 173 647 173H720Q756 173 766 170T777 153T762 133H519L477 -24Q436 -179 432 -185Q426 -194 416 -194Q409 -194 403 -189T397 -177Q397 -167 436 -21Q477 125 477 131L478 133H289L247 -24Q206 -179 202 -185Q196 -194 186 -194Q179 -194 173 -189T167 -177Q167 -167 206 -21Q247 125 247 131L248 133H70Q56 140 56 153Q56 168 72 173H260L280 249L301 326Q301 327 186 327H72Q56 332 56 347ZM531 326Q531 327 437 327H342L322 251L301 174Q301 173 395 173H490L510 249L531 326'], // DOLLAR SIGN 0x24: [750,56,500,55,444,'162 187Q162 164 146 149T109 133H103V130Q108 115 115 105Q122 92 131 82T150 64T170 52T190 44T206 40T220 37L227 36V313Q190 320 162 335Q116 358 86 404T55 508Q55 567 85 614T165 685Q186 696 225 704H227V750H273V704L286 703Q369 690 413 631Q441 588 444 531Q444 514 443 509Q439 490 425 479T391 468Q368 468 353 483T337 522Q337 546 353 560T390 575L394 576V578Q386 599 372 614T342 637T314 649T288 656L273 658V408L288 405Q329 394 355 376Q396 348 420 300T444 199Q444 130 408 76T313 1Q286 -9 276 -9H273V-56H227V-10H221Q202 -6 193 -4T155 11T108 41T74 94T55 176V182Q55 227 95 238Q103 240 108 240Q129 240 145 226T162 187ZM225 657Q219 657 204 651T169 632T135 594T121 538Q121 512 131 491T156 457T187 435T213 423T227 420V539Q227 657 225 657ZM378 169Q378 230 339 265T274 301Q273 301 273 169V37Q324 50 351 87T378 169'], // PERCENT SIGN 0x25: [750,56,833,56,777,'465 605Q428 605 394 614T340 632T319 641Q332 608 332 548Q332 458 293 403T202 347Q145 347 101 402T56 548Q56 637 101 693T202 750Q241 750 272 719Q359 642 464 642Q580 642 650 732Q662 748 668 749Q670 750 673 750Q682 750 688 743T693 726Q178 -47 170 -52Q166 -56 160 -56Q147 -56 142 -45Q137 -36 142 -27Q143 -24 363 304Q469 462 525 546T581 630Q528 605 465 605ZM207 385Q235 385 263 427T292 548Q292 617 267 664T200 712Q193 712 186 709T167 698T147 668T134 615Q132 595 132 548V527Q132 436 165 403Q183 385 203 385H207ZM500 146Q500 234 544 290T647 347Q699 347 737 292T776 146T737 0T646 -56Q590 -56 545 0T500 146ZM651 -18Q679 -18 707 24T736 146Q736 215 711 262T644 309Q637 309 630 306T611 295T591 265T578 212Q577 200 577 146V124Q577 -18 647 -18H651'], // AMPERSAND 0x26: [716,22,778,42,727,'156 540Q156 620 201 668T302 716Q354 716 377 671T401 578Q401 505 287 386L274 373Q309 285 416 148L429 132L437 142Q474 191 543 309L562 341V349Q562 368 541 376T498 385H493V431H502L626 428Q709 428 721 431H727V385H712Q688 384 669 379T639 369T618 354T603 337T591 316T578 295Q537 223 506 176T464 117T454 104Q454 102 471 85T497 62Q543 24 585 24Q618 24 648 48T682 113V121H722V112Q721 94 714 75T692 32T646 -7T574 -22Q491 -19 414 42L402 51L391 42Q312 -22 224 -22Q144 -22 93 25T42 135Q42 153 46 169T55 197T74 225T96 249T125 278T156 308L195 347L190 360Q185 372 182 382T174 411T165 448T159 491T156 540ZM361 576Q361 613 348 646T305 679Q272 679 252 649T232 572Q232 497 255 426L259 411L267 420Q361 519 361 576ZM140 164Q140 103 167 64T240 24Q271 24 304 36T356 61T374 77Q295 156 235 262L220 292L210 310L193 293Q177 277 169 268T151 229T140 164'], // APOSTROPHE 0x27: [694,-379,278,78,212,'78 634Q78 659 95 676T138 694Q166 694 189 668T212 579Q212 525 190 476T146 403T118 379Q114 379 105 388T95 401Q95 404 107 417T133 448T161 500T176 572Q176 584 175 584T170 581T157 576T139 573Q114 573 96 590T78 634'], // ASTERISK 0x2A: [750,-320,500,64,436,'215 721Q216 732 225 741T248 750Q263 750 273 742T284 721L270 571L327 613Q383 654 388 657T399 660Q412 660 423 650T435 624T424 600T376 575Q363 569 355 566L289 534L355 504L424 470Q435 462 435 447Q435 431 424 420T399 409Q393 409 388 412T327 456L270 498L277 423L284 348Q280 320 250 320T215 348L229 498L172 456Q116 415 111 412T100 409Q87 409 76 420T64 447Q64 461 75 470L144 504L210 534L144 566Q136 570 122 576Q83 593 74 600T64 624Q64 639 75 649T100 660Q106 660 111 657T172 613L229 571Q229 578 222 643T215 721'], // HYPHEN-MINUS 0x2D: [252,-179,333,11,277,'11 179V252H277V179H11'], // QUESTION MARK 0x3F: [705,-1,472,55,417,'226 668Q190 668 162 656T124 632L114 621Q116 621 119 620T130 616T145 607T157 591T162 567Q162 544 147 529T109 514T71 528T55 566Q55 625 100 661T199 704Q201 704 210 704T224 705H228Q281 705 320 692T378 656T407 612T416 567Q416 503 361 462Q267 395 247 303Q242 279 242 241V224Q242 205 239 202T222 198T205 201T202 218V249Q204 320 220 371T255 445T292 491T315 537Q317 546 317 574V587Q317 604 315 615T304 640T277 661T226 668ZM162 61Q162 89 180 105T224 121Q247 119 264 104T281 61Q281 31 264 16T222 1Q197 1 180 16T162 61'], // COMMERCIAL AT 0x40: [705,11,778,56,722,'56 347Q56 429 86 498T164 612T270 680T386 705Q522 705 622 603T722 349Q722 126 608 126Q541 126 513 176Q512 177 512 179T510 182L509 183Q508 183 503 177T487 163T464 146T429 132T385 126Q311 126 251 186T190 347Q190 448 251 508T385 568Q426 568 460 548T509 511T531 479H555Q580 479 582 478Q586 477 587 468Q588 454 588 338V260Q588 200 593 182T619 163Q641 163 655 178T674 223T680 273T682 325V330Q682 426 647 500Q611 569 544 618T388 668Q271 668 184 577T96 347Q96 216 180 121T396 26Q421 26 446 28T493 34T535 43T573 52T605 63T629 72T647 80T657 84H716Q722 78 722 74Q722 65 675 45T547 7T392 -11Q255 -11 156 90T56 347ZM274 347Q274 266 308 214T390 162Q420 162 449 182T498 235L504 245V449L498 459Q453 532 387 532Q347 532 311 483T274 347'], // LATIN CAPITAL LETTER A 0x41: [716,0,750,32,717,'255 0Q240 3 140 3Q48 3 39 0H32V46H47Q119 49 139 88Q140 91 192 245T295 553T348 708Q351 716 366 716H376Q396 715 400 709Q402 707 508 390L617 67Q624 54 636 51T687 46H717V0H708Q699 3 581 3Q458 3 437 0H427V46H440Q510 46 510 64Q510 66 486 138L462 209H229L209 150Q189 91 189 85Q189 72 209 59T259 46H264V0H255ZM447 255L345 557L244 256Q244 255 345 255H447'], // LATIN CAPITAL LETTER B 0x42: [683,0,708,28,651,'131 622Q124 629 120 631T104 634T61 637H28V683H229H267H346Q423 683 459 678T531 651Q574 627 599 590T624 512Q624 461 583 419T476 360L466 357Q539 348 595 302T651 187Q651 119 600 67T469 3Q456 1 242 0H28V46H61Q103 47 112 49T131 61V622ZM511 513Q511 560 485 594T416 636Q415 636 403 636T371 636T333 637Q266 637 251 636T232 628Q229 624 229 499V374H312L396 375L406 377Q410 378 417 380T442 393T474 417T499 456T511 513ZM537 188Q537 239 509 282T430 336L329 337H229V200V116Q229 57 234 52Q240 47 334 47H383Q425 47 443 53Q486 67 511 104T537 188'], // LATIN CAPITAL LETTER C 0x43: [705,21,722,56,666,'56 342Q56 428 89 500T174 615T283 681T391 705Q394 705 400 705T408 704Q499 704 569 636L582 624L612 663Q639 700 643 704Q644 704 647 704T653 705H657Q660 705 666 699V419L660 413H626Q620 419 619 430Q610 512 571 572T476 651Q457 658 426 658Q322 658 252 588Q173 509 173 342Q173 221 211 151Q232 111 263 84T328 45T384 29T428 24Q517 24 571 93T626 244Q626 251 632 257H660L666 251V236Q661 133 590 56T403 -21Q262 -21 159 83T56 342'], // LATIN CAPITAL LETTER D 0x44: [683,0,764,27,708,'130 622Q123 629 119 631T103 634T60 637H27V683H228Q399 682 419 682T461 676Q504 667 546 641T626 573T685 470T708 336Q708 210 634 116T442 3Q429 1 228 0H27V46H60Q102 47 111 49T130 61V622ZM593 338Q593 439 571 501T493 602Q439 637 355 637H322H294Q238 637 234 628Q231 624 231 344Q231 62 232 59Q233 49 248 48T339 46H350Q456 46 515 95Q561 133 577 191T593 338'], // LATIN CAPITAL LETTER E 0x45: [680,0,681,25,652,'128 619Q121 626 117 628T101 631T58 634H25V680H597V676Q599 670 611 560T625 444V440H585V444Q584 447 582 465Q578 500 570 526T553 571T528 601T498 619T457 629T411 633T353 634Q266 634 251 633T233 622Q233 622 233 621Q232 619 232 497V376H286Q359 378 377 385Q413 401 416 469Q416 471 416 473V493H456V213H416V233Q415 268 408 288T383 317T349 328T297 330Q290 330 286 330H232V196V114Q232 57 237 52Q243 47 289 47H340H391Q428 47 452 50T505 62T552 92T584 146Q594 172 599 200T607 247T612 270V273H652V270Q651 267 632 137T610 3V0H25V46H58Q100 47 109 49T128 61V619'], // LATIN CAPITAL LETTER F 0x46: [680,0,653,25,610,'128 619Q121 626 117 628T101 631T58 634H25V680H582V676Q584 670 596 560T610 444V440H570V444Q563 493 561 501Q555 538 543 563T516 601T477 622T431 631T374 633H334H286Q252 633 244 631T233 621Q232 619 232 490V363H284Q287 363 303 363T327 364T349 367T372 373T389 385Q407 403 410 459V480H450V200H410V221Q407 276 389 296Q381 303 371 307T348 313T327 316T303 317T284 317H232V189L233 61Q240 54 245 52T270 48T333 46H360V0H348Q324 3 182 3Q51 3 36 0H25V46H58Q100 47 109 49T128 61V619'], // LATIN CAPITAL LETTER G 0x47: [705,22,785,56,735,'56 342Q56 428 89 500T174 615T283 681T391 705Q394 705 400 705T408 704Q499 704 569 636L582 624L612 663Q639 700 643 704Q644 704 647 704T653 705H657Q660 705 666 699V419L660 413H626Q620 419 619 430Q610 512 571 572T476 651Q457 658 426 658Q401 658 376 654T316 633T254 592T205 519T177 411Q173 369 173 335Q173 259 192 201T238 111T302 58T370 31T431 24Q478 24 513 45T559 100Q562 110 562 160V212Q561 213 557 216T551 220T542 223T526 225T502 226T463 227H437V273H449L609 270Q715 270 727 273H735V227H721Q674 227 668 215Q666 211 666 108V6Q660 0 657 0Q653 0 639 10Q617 25 600 42L587 54Q571 27 524 3T406 -22Q317 -22 238 22T108 151T56 342'], // LATIN CAPITAL LETTER H 0x48: [683,0,750,25,724,'128 622Q121 629 117 631T101 634T58 637H25V683H36Q57 680 180 680Q315 680 324 683H335V637H302Q262 636 251 634T233 622L232 500V378H517V622Q510 629 506 631T490 634T447 637H414V683H425Q446 680 569 680Q704 680 713 683H724V637H691Q651 636 640 634T622 622V61Q628 51 639 49T691 46H724V0H713Q692 3 569 3Q434 3 425 0H414V46H447Q489 47 498 49T517 61V332H232V197L233 61Q239 51 250 49T302 46H335V0H324Q303 3 180 3Q45 3 36 0H25V46H58Q100 47 109 49T128 61V622'], // LATIN CAPITAL LETTER I 0x49: [683,0,361,21,339,'328 0Q307 3 180 3T32 0H21V46H43Q92 46 106 49T126 60Q128 63 128 342Q128 620 126 623Q122 628 118 630T96 635T43 637H21V683H32Q53 680 180 680T328 683H339V637H317Q268 637 254 634T234 623Q232 620 232 342Q232 63 234 60Q238 55 242 53T264 48T317 46H339V0H328'], // LATIN CAPITAL LETTER J 0x4A: [683,22,514,25,465,'89 177Q115 177 133 160T152 112Q152 88 137 72T102 52Q99 51 101 49Q106 43 129 29Q159 15 190 15Q232 15 256 48T286 126Q286 127 286 142T286 183T286 238T287 306T287 378Q287 403 287 429T287 479T287 524T286 563T286 593T286 614V621Q281 630 263 633T182 637H154V683H166Q187 680 332 680Q439 680 457 683H465V637H449Q422 637 401 634Q393 631 389 623Q388 621 388 376T387 123Q377 61 322 20T194 -22Q188 -22 177 -21T160 -20Q96 -9 61 29T25 110Q25 144 44 160T89 177'], // LATIN CAPITAL LETTER K 0x4B: [683,0,778,25,736,'128 622Q121 629 117 631T101 634T58 637H25V683H36Q57 680 180 680Q315 680 324 683H335V637H313Q235 637 233 620Q232 618 232 462L233 307L379 449Q425 494 479 546Q518 584 524 591T531 607V608Q531 630 503 636Q501 636 498 636T493 637H489V683H499Q517 680 630 680Q704 680 716 683H722V637H708Q633 633 589 597Q584 592 495 506T406 419T515 254T631 80Q644 60 662 54T715 46H736V0H728Q719 3 615 3Q493 3 472 0H461V46H469Q515 46 515 72Q515 78 512 84L336 351Q332 348 278 296L232 251V156Q232 62 235 58Q243 47 302 46H335V0H324Q303 3 180 3Q45 3 36 0H25V46H58Q100 47 109 49T128 61V622'], // LATIN CAPITAL LETTER L 0x4C: [683,0,625,25,582,'128 622Q121 629 117 631T101 634T58 637H25V683H36Q48 680 182 680Q324 680 348 683H360V637H333Q273 637 258 635T233 622L232 342V129Q232 57 237 52Q243 47 313 47Q384 47 410 53Q470 70 498 110T536 221Q536 226 537 238T540 261T542 272T562 273H582V268Q580 265 568 137T554 5V0H25V46H58Q100 47 109 49T128 61V622'], // LATIN CAPITAL LETTER M 0x4D: [683,0,917,29,887,'132 622Q125 629 121 631T105 634T62 637H29V683H135Q221 683 232 682T249 675Q250 674 354 398L458 124L562 398Q666 674 668 675Q671 681 683 682T781 683H887V637H854Q814 636 803 634T785 622V61Q791 51 802 49T854 46H887V0H876Q855 3 736 3Q605 3 596 0H585V46H618Q660 47 669 49T688 61V347Q688 424 688 461T688 546T688 613L687 632Q454 14 450 7Q446 1 430 1T410 7Q409 9 292 316L176 624V606Q175 588 175 543T175 463T175 356L176 86Q187 50 261 46H278V0H269Q254 3 154 3Q52 3 37 0H29V46H46Q78 48 98 56T122 69T132 86V622'], // LATIN CAPITAL LETTER N 0x4E: [683,0,750,25,724,'42 46Q74 48 94 56T118 69T128 86V634H124Q114 637 52 637H25V683H232L235 680Q237 679 322 554T493 303L578 178V598Q572 608 568 613T544 627T492 637H475V683H483Q498 680 600 680Q706 680 715 683H724V637H707Q634 633 622 598L621 302V6L614 0H600Q585 0 582 3T481 150T282 443T171 605V345L172 86Q183 50 257 46H274V0H265Q250 3 150 3Q48 3 33 0H25V46H42'], // LATIN CAPITAL LETTER O 0x4F: [705,23,778,56,722,'56 340Q56 423 86 494T164 610T270 680T388 705Q521 705 621 601T722 341Q722 260 693 191T617 75T510 4T388 -22T267 3T160 74T85 189T56 340ZM467 647Q426 665 388 665Q360 665 331 654T269 620T213 549T179 439Q174 411 174 354Q174 144 277 61Q327 20 385 20H389H391Q474 20 537 99Q603 188 603 354Q603 411 598 439Q577 592 467 647'], // LATIN CAPITAL LETTER P 0x50: [684,0,681,27,624,'130 622Q123 629 119 631T103 634T60 637H27V683H214Q237 683 276 683T331 684Q419 684 471 671T567 616Q624 563 624 489Q624 421 573 372T451 307Q429 302 328 301H234V181Q234 62 237 58Q245 47 304 46H337V0H326Q305 3 182 3Q47 3 38 0H27V46H60Q102 47 111 49T130 61V622ZM507 488Q507 514 506 528T500 564T483 597T450 620T397 635Q385 637 307 637H286Q237 637 234 628Q231 624 231 483V342H302H339Q390 342 423 349T481 382Q507 411 507 488'], // LATIN CAPITAL LETTER Q 0x51: [705,193,778,56,728,'56 341Q56 499 157 602T388 705Q521 705 621 601T722 341Q722 275 703 218T660 127T603 63T555 25T525 9Q524 8 524 8H523Q524 5 526 -1T537 -21T555 -47T581 -67T615 -76Q653 -76 678 -56T706 -3Q707 10 716 10Q721 10 728 5L727 -13Q727 -88 697 -140T606 -193Q563 -193 538 -166T498 -83Q483 -23 483 -8L471 -11Q459 -14 435 -18T388 -22Q254 -22 155 81T56 341ZM607 339Q607 429 586 496T531 598T461 649T390 665T318 649T248 598T192 496T170 339Q170 143 277 57Q301 39 305 39L304 42Q304 44 304 46Q301 53 301 68Q301 101 325 128T391 155Q454 155 495 70L501 58Q549 91 578 164Q607 234 607 339ZM385 18Q404 18 425 23T459 33T472 40Q471 47 468 57T449 88T412 115Q398 117 386 117Q367 117 353 102T338 67Q338 48 351 33T385 18'], // LATIN CAPITAL LETTER R 0x52: [683,22,736,27,732,'130 622Q123 629 119 631T103 634T60 637H27V683H202H236H300Q376 683 417 677T500 648Q595 600 609 517Q610 512 610 501Q610 468 594 439T556 392T511 361T472 343L456 338Q459 335 467 332Q497 316 516 298T545 254T559 211T568 155T578 94Q588 46 602 31T640 16H645Q660 16 674 32T692 87Q692 98 696 101T712 105T728 103T732 90Q732 59 716 27T672 -16Q656 -22 630 -22Q481 -16 458 90Q456 101 456 163T449 246Q430 304 373 320L363 322L297 323H231V192L232 61Q238 51 249 49T301 46H334V0H323Q302 3 181 3Q59 3 38 0H27V46H60Q102 47 111 49T130 61V622ZM491 499V509Q491 527 490 539T481 570T462 601T424 623T362 636Q360 636 340 636T304 637H283Q238 637 234 628Q231 624 231 492V360H289Q390 360 434 378T489 456Q491 467 491 499'], // LATIN CAPITAL LETTER S 0x53: [705,22,556,55,500,'55 507Q55 590 112 647T243 704H257Q342 704 405 641L426 672Q431 679 436 687T446 700L449 704Q450 704 453 704T459 705H463Q466 705 472 699V462L466 456H448Q437 456 435 459T430 479Q413 605 329 646Q292 662 254 662Q201 662 168 626T135 542Q135 508 152 480T200 435Q210 431 286 412T370 389Q427 367 463 314T500 191Q500 110 448 45T301 -21Q245 -21 201 -4T140 27L122 41Q118 36 107 21T87 -7T78 -21Q76 -22 68 -22H64Q61 -22 55 -16V101Q55 220 56 222Q58 227 76 227H89Q95 221 95 214Q95 182 105 151T139 90T205 42T305 24Q352 24 386 62T420 155Q420 198 398 233T340 281Q284 295 266 300Q261 301 239 306T206 314T174 325T141 343T112 367T85 402Q55 451 55 507'], // LATIN CAPITAL LETTER T 0x54: [677,0,722,36,685,'36 443Q37 448 46 558T55 671V677H666V671Q667 666 676 556T685 443V437H645V443Q645 445 642 478T631 544T610 593Q593 614 555 625Q534 630 478 630H451H443Q417 630 414 618Q413 616 413 339V63Q420 53 439 50T528 46H558V0H545L361 3Q186 1 177 0H164V46H194Q264 46 283 49T309 63V339V550Q309 620 304 625T271 630H244H224Q154 630 119 601Q101 585 93 554T81 486T76 443V437H36V443'], // LATIN CAPITAL LETTER U 0x55: [683,22,750,25,724,'128 622Q121 629 117 631T101 634T58 637H25V683H36Q57 680 180 680Q315 680 324 683H335V637H302Q262 636 251 634T233 622L232 418V291Q232 189 240 145T280 67Q325 24 389 24Q454 24 506 64T571 183Q575 206 575 410V598Q569 608 565 613T541 627T489 637H472V683H481Q496 680 598 680T715 683H724V637H707Q634 633 622 598L621 399Q620 194 617 180Q617 179 615 171Q595 83 531 31T389 -22Q304 -22 226 33T130 192Q129 201 128 412V622'], // LATIN CAPITAL LETTER V 0x56: [683,22,750,19,730,'114 620Q113 621 110 624T107 627T103 630T98 632T91 634T80 635T67 636T48 637H19V683H28Q46 680 152 680Q273 680 294 683H305V637H284Q223 634 223 620Q223 618 313 372T404 126L490 358Q575 588 575 597Q575 616 554 626T508 637H503V683H512Q527 680 627 680Q718 680 724 683H730V637H723Q648 637 627 596Q627 595 515 291T401 -14Q396 -22 382 -22H374H367Q353 -22 348 -14Q346 -12 231 303Q114 617 114 620'], // LATIN CAPITAL LETTER W 0x57: [683,22,1028,18,1009,'792 683Q810 680 914 680Q991 680 1003 683H1009V637H996Q931 633 915 598Q912 591 863 438T766 135T716 -17Q711 -22 694 -22Q676 -22 673 -15Q671 -13 593 231L514 477L435 234Q416 174 391 92T358 -6T341 -22H331Q314 -21 310 -15Q309 -14 208 302T104 622Q98 632 87 633Q73 637 35 637H18V683H27Q69 681 154 681Q164 681 181 681T216 681T249 682T276 683H287H298V637H285Q213 637 213 620Q213 616 289 381L364 144L427 339Q490 535 492 546Q487 560 482 578T475 602T468 618T461 628T449 633T433 636T408 637H380V683H388Q397 680 508 680Q629 680 650 683H660V637H647Q576 637 576 619L727 146Q869 580 869 600Q869 605 863 612T839 627T794 637H783V683H792'], // LATIN CAPITAL LETTER X 0x58: [683,0,750,23,726,'270 0Q252 3 141 3Q46 3 31 0H23V46H40Q129 50 161 88Q165 94 244 216T324 339Q324 341 235 480T143 622Q133 631 119 634T57 637H37V683H46Q64 680 172 680Q297 680 318 683H329V637H324Q307 637 286 632T263 621Q263 618 322 525T384 431Q385 431 437 511T489 593Q490 595 490 599Q490 611 477 622T436 637H428V683H437Q455 680 566 680Q661 680 676 683H684V637H667Q585 634 551 599Q548 596 478 491Q412 388 412 387Q412 385 514 225T620 62Q628 53 642 50T695 46H726V0H717Q699 3 591 3Q466 3 445 0H434V46H440Q454 46 476 51T499 64Q499 67 463 124T390 238L353 295L350 292Q348 290 343 283T331 265T312 236T286 195Q219 88 218 84Q218 70 234 59T272 46H280V0H270'], // LATIN CAPITAL LETTER Y 0x59: [683,0,750,11,738,'518 0Q497 3 374 3Q253 3 232 0H221V46H254Q313 47 321 58Q324 62 324 167V273L221 446Q117 620 114 623Q106 631 91 634T31 637H11V683H20Q29 680 148 680Q273 680 294 683H305V637H287Q239 636 236 621Q236 619 321 475L407 332L483 460Q502 492 527 534Q563 594 563 604Q563 632 517 637H508V683H517H525Q533 683 545 683T571 682T600 681T626 681Q695 681 731 683H738V637H723Q640 633 613 588Q612 587 517 427L425 273V169V95Q425 66 428 59T444 49Q459 46 506 46H528V0H518'], // LATIN CAPITAL LETTER Z 0x5A: [683,0,611,55,560,'69 443Q69 452 74 554T80 683H549Q555 677 555 664Q555 649 554 648Q552 645 366 348T179 50T192 49T263 49H275H302Q333 49 353 50T401 59T447 78T482 115T507 173Q513 200 520 273V282H560V274Q560 272 552 143T543 8V0H302L61 1L58 3Q55 8 55 21V35Q59 43 153 193T340 489T432 637H343Q259 637 214 625T141 573Q109 523 109 445Q109 443 89 443H69'], // LOW LINE 0x5F: [-25,62,500,0,499,'0 -62V-25H499V-62H0'], // GRAVE ACCENT 0x60: [699,-505,500,106,296,'106 655Q106 671 119 685T150 699Q166 699 177 688Q190 671 222 629T275 561T295 533T282 519L267 505L196 563Q119 626 113 634Q106 643 106 655'], // TILDE 0x7E: [318,-215,500,83,416,'179 251Q164 251 151 245T131 234T111 215L97 227L83 238Q83 239 95 253T121 283T142 304Q165 318 187 318T253 300T320 282Q335 282 348 288T368 299T388 318L402 306L416 295Q375 236 344 222Q330 215 313 215Q292 215 248 233T179 251'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/Main/Regular/BasicLatin.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/Main/Regular/CombDiacritMarks.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_Main'], { // COMBINING GRAVE ACCENT 0x300: [699,-505,0,-394,-204,'-394 655Q-394 671 -381 685T-350 699Q-334 699 -323 688Q-310 671 -278 629T-225 561T-205 533T-218 519L-233 505L-304 563Q-381 626 -387 634Q-394 643 -394 655'], // COMBINING ACUTE ACCENT 0x301: [699,-505,0,-297,-107,'-151 699Q-133 699 -120 686T-107 656Q-107 651 -108 647T-113 637T-120 627T-133 616T-149 602T-170 585T-197 563L-268 505L-283 519Q-297 533 -296 533Q-296 534 -271 567T-218 636T-187 678L-184 681Q-182 684 -179 686T-172 692T-163 697T-151 699'], // COMBINING CIRCUMFLEX ACCENT 0x302: [694,-531,0,-388,-113,'-388 560L-251 694L-243 686Q-113 562 -113 560L-139 531Q-141 532 -197 581L-250 627L-305 580Q-318 569 -331 557T-352 538L-360 532Q-362 530 -375 546L-388 560'], // COMBINING TILDE 0x303: [668,-565,0,-417,-84,'-321 601Q-336 601 -349 595T-369 584T-389 565L-403 577L-417 588Q-417 589 -405 603T-379 633T-358 654Q-335 668 -313 668T-247 650T-180 632Q-165 632 -152 638T-132 649T-112 668L-98 656L-84 645Q-125 586 -156 572Q-170 565 -187 565Q-208 565 -252 583T-321 601'], // COMBINING MACRON 0x304: [590,-544,0,-431,-70,'-431 544V590H-70V544H-431'], // COMBINING BREVE 0x306: [694,-515,0,-408,-93,'-250 515Q-321 515 -362 565T-408 683V694H-371V689Q-371 688 -371 683T-370 675Q-363 631 -331 599T-252 567Q-196 567 -163 608T-130 689V694H-93V683Q-97 617 -139 566T-250 515'], // COMBINING DOT ABOVE 0x307: [669,-549,0,-310,-191,'-310 609Q-310 637 -292 653T-248 669Q-225 667 -208 652T-191 609Q-191 579 -208 564T-250 549Q-275 549 -292 564T-310 609'], // COMBINING DIAERESIS 0x308: [669,-554,0,-405,-95,'-405 612Q-405 633 -388 651T-347 669T-307 652T-290 612Q-290 588 -306 571T-348 554L-373 560Q-405 577 -405 612ZM-211 611Q-211 634 -196 649T-165 668Q-164 668 -160 668T-154 669Q-131 669 -114 652T-96 612T-113 572T-154 554Q-177 554 -194 570T-211 611'], // COMBINING RING ABOVE 0x30A: [715,-542,0,-353,-148,'-353 628Q-353 669 -321 692T-256 715Q-202 715 -175 689T-148 629Q-148 592 -177 567T-251 542Q-298 542 -325 567T-353 628ZM-187 628Q-187 660 -200 669T-241 678H-247Q-252 678 -258 678T-266 679Q-283 679 -293 674T-308 659T-312 644T-313 629Q-313 600 -302 590Q-290 579 -250 579H-235Q-221 579 -212 581T-195 595T-187 628'], // COMBINING DOUBLE ACUTE ACCENT 0x30B: [701,-510,0,-378,-80,'-292 701Q-278 701 -262 690T-246 658Q-246 649 -250 641Q-252 637 -297 574T-344 510L-378 528Q-378 530 -355 598T-327 676Q-316 701 -292 701ZM-126 701Q-112 701 -96 690T-80 658Q-80 649 -84 641Q-86 637 -131 574T-178 510L-212 528Q-212 530 -189 598T-161 676Q-150 701 -126 701'], // COMBINING CARON 0x30C: [644,-513,0,-386,-115,'-386 611L-373 630L-364 644Q-362 644 -307 612Q-252 581 -250 581L-194 612Q-139 644 -137 644L-115 611L-182 562L-251 513L-386 611'], // COMBINING LONG SOLIDUS OVERLAY 0x338: [716,215,0,-639,-140,'-612 -215T-619 -215T-631 -212T-637 -204T-639 -197Q-639 -190 -634 -183Q-621 -157 -400 274T-176 707Q-173 716 -160 716Q-153 716 -148 712T-142 703T-140 696Q-140 691 -372 241T-608 -212Q-612 -215 -619 -215'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/Main/Regular/CombDiacritMarks.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/Main/Regular/GeometricShapes.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_Main'], { // WHITE UP-POINTING TRIANGLE 0x25B3: [716,0,889,59,828,'75 0L72 2Q69 3 67 5T62 11T59 20Q59 24 62 30Q65 37 245 370T428 707Q428 708 430 710T436 714T444 716Q451 716 455 712Q459 710 644 368L828 27V20Q828 7 814 0H75ZM610 347L444 653Q443 653 278 347T113 40H775Q775 42 610 347'], // WHITE RIGHT-POINTING SMALL TRIANGLE 0x25B9: [505,5,500,26,473,'26 489Q33 505 43 505Q51 505 260 385Q464 266 471 259Q473 257 473 250Q473 242 469 239Q459 231 260 115Q51 -5 43 -5Q39 -5 35 -1T28 7L26 11V489ZM412 250L67 450Q66 450 66 250T67 50Q69 51 240 150T412 250'], // WHITE DOWN-POINTING TRIANGLE 0x25BD: [500,215,889,59,828,'59 480Q59 485 61 489T66 495T72 498L75 500H814Q828 493 828 480V474L644 132Q458 -210 455 -212Q451 -215 444 -215T433 -212Q429 -210 342 -49T164 282T64 466Q59 478 59 480ZM775 460H113Q113 459 278 153T444 -153T610 153T775 460'], // WHITE LEFT-POINTING SMALL TRIANGLE 0x25C3: [505,5,500,26,473,'473 10Q466 -5 454 -5Q451 -5 445 -3Q444 -3 343 56T140 173T35 234Q26 239 26 250T35 266Q40 269 240 384T445 503Q451 505 453 505Q466 505 473 490V10ZM433 50T433 250T432 450T259 351T87 250T258 150T432 50Q433 50 433 250'], // LARGE CIRCLE 0x25EF: [715,215,1000,56,944,'56 250Q56 353 95 442T196 589T335 681T491 715Q573 715 635 693Q694 673 747 635T846 543T917 412T944 250Q944 58 815 -78T500 -215Q457 -215 429 -210Q274 -183 165 -56T56 250ZM500 -176Q664 -176 784 -54T904 250Q904 418 799 536T543 674Q534 675 493 675Q425 675 357 647T229 567T133 432T96 250Q96 160 129 80T217 -56T346 -144T500 -176'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/Main/Regular/GeometricShapes.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/Main/Regular/GreekAndCoptic.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_Main'], { // GREEK CAPITAL LETTER GAMMA 0x393: [680,0,625,25,582,'128 619Q121 626 117 628T101 631T58 634H25V680H554V676Q556 670 568 560T582 444V440H542V444Q542 445 538 478T523 545T492 598Q454 634 349 634H334Q264 634 249 633T233 621Q232 618 232 339L233 61Q240 54 245 52T270 48T333 46H360V0H348Q324 3 182 3Q51 3 36 0H25V46H58Q100 47 109 49T128 61V619'], // GREEK CAPITAL LETTER DELTA 0x394: [716,0,833,46,786,'51 0Q46 4 46 7Q46 9 215 357T388 709Q391 716 416 716Q439 716 444 709Q447 705 616 357T786 7Q786 4 781 0H51ZM507 344L384 596L137 92L383 91H630Q630 93 507 344'], // GREEK CAPITAL LETTER THETA 0x398: [705,23,778,56,722,'56 340Q56 423 86 494T164 610T270 680T388 705Q521 705 621 601T722 341Q722 260 693 191T617 75T510 4T388 -22T267 3T160 74T85 189T56 340ZM610 339Q610 428 590 495T535 598T463 651T384 668Q332 668 289 638T221 566Q168 485 168 339Q168 274 176 235Q189 158 228 105T324 28Q356 16 388 16Q415 16 442 24T501 54T555 111T594 205T610 339ZM223 263V422H263V388H514V422H554V263H514V297H263V263H223'], // GREEK CAPITAL LETTER LAMDA 0x39B: [716,0,694,32,661,'320 708Q326 716 340 716H348H355Q367 716 372 708Q374 706 423 547T523 226T575 62Q581 52 591 50T634 46H661V0H653Q644 3 532 3Q411 3 390 0H379V46H392Q464 46 464 65Q463 70 390 305T316 539L246 316Q177 95 177 84Q177 72 198 59T248 46H253V0H245Q230 3 130 3Q47 3 38 0H32V46H45Q112 51 127 91Q128 92 224 399T320 708'], // GREEK CAPITAL LETTER XI 0x39E: [677,0,667,42,624,'47 509L55 676Q55 677 333 677T611 676L619 509Q619 508 599 508T579 510Q579 529 575 557T564 589Q550 594 333 594T102 589Q95 586 91 558T87 510Q87 508 67 508T47 509ZM139 260V445H179V394H487V445H527V260H487V311H179V260H139ZM50 0L42 180H62Q82 180 82 178Q82 133 89 105Q92 93 95 90T108 86Q137 83 333 83Q530 83 558 86Q568 87 571 90T577 105Q584 133 584 178Q584 180 604 180H624L616 0H50'], // GREEK CAPITAL LETTER PI 0x3A0: [680,0,750,25,724,'128 619Q121 626 117 628T101 631T58 634H25V680H724V634H691Q651 633 640 631T622 619V61Q628 51 639 49T691 46H724V0H713Q692 3 569 3Q434 3 425 0H414V46H447Q489 47 498 49T517 61V634H232V348L233 61Q239 51 250 49T302 46H335V0H324Q303 3 180 3Q45 3 36 0H25V46H58Q100 47 109 49T128 61V619'], // GREEK CAPITAL LETTER SIGMA 0x3A3: [683,0,722,55,666,'666 247Q664 244 652 126T638 4V0H351Q131 0 95 0T57 5V6Q54 12 57 17L73 36Q89 54 121 90T182 159L305 299L56 644L55 658Q55 677 60 681Q63 683 351 683H638V679Q640 674 652 564T666 447V443H626V447Q618 505 604 543T559 605Q529 626 478 631T333 637H294H189L293 494Q314 465 345 422Q400 346 400 340Q400 338 399 337L154 57Q407 57 428 58Q476 60 508 68T551 83T575 103Q595 125 608 162T624 225L626 251H666V247'], // GREEK CAPITAL LETTER UPSILON 0x3A5: [705,0,778,55,722,'55 551Q55 604 91 654T194 705Q240 705 277 681T334 624T367 556T385 498L389 474L392 488Q394 501 400 521T414 566T438 615T473 659T521 692T584 705Q620 705 648 689T691 647T714 597T722 551Q722 540 719 538T699 536Q680 536 677 541Q677 542 677 544T676 548Q676 576 650 596T588 616H582Q538 616 505 582Q466 543 454 477T441 318Q441 301 441 269T442 222V61Q448 55 452 53T478 48T542 46H569V0H557Q533 3 389 3T221 0H209V46H236Q256 46 270 46T295 47T311 48T322 51T328 54T332 57T337 61V209Q337 383 333 415Q313 616 189 616Q154 616 128 597T101 548Q101 540 97 538T78 536Q63 536 59 538T55 551'], // GREEK CAPITAL LETTER PHI 0x3A6: [683,0,722,55,665,'312 622Q310 623 307 625T303 629T297 631T286 634T270 635T246 636T211 637H184V683H196Q220 680 361 680T526 683H538V637H511Q468 637 447 635T422 631T411 622V533L425 531Q525 519 595 466T665 342Q665 301 642 267T583 209T506 172T425 152L411 150V61Q417 55 421 53T447 48T511 46H538V0H526Q502 3 361 3T196 0H184V46H211Q231 46 245 46T270 47T286 48T297 51T303 54T307 57T312 61V150H310Q309 151 289 153T232 166T160 195Q149 201 136 210T103 238T69 284T56 342Q56 414 128 467T294 530Q309 532 310 533H312V622ZM170 342Q170 207 307 188H312V495H309Q301 495 282 491T231 469T186 423Q170 389 170 342ZM415 188Q487 199 519 236T551 342Q551 384 539 414T507 459T470 481T434 491T415 495H410V188H415'], // GREEK CAPITAL LETTER PSI 0x3A8: [683,0,778,55,722,'340 622Q338 623 335 625T331 629T325 631T314 634T298 635T274 636T239 637H212V683H224Q248 680 389 680T554 683H566V637H539Q479 637 464 635T439 622L438 407Q438 192 439 192Q443 193 449 195T474 207T507 232T536 276T557 344Q560 365 562 417T573 493Q587 536 620 544Q627 546 671 546H715L722 540V515Q714 509 708 509Q680 505 671 476T658 392T644 307Q599 177 451 153L438 151V106L439 61Q446 54 451 52T476 48T539 46H566V0H554Q530 3 389 3T224 0H212V46H239Q259 46 273 46T298 47T314 48T325 51T331 54T335 57T340 61V151Q126 178 117 406Q115 503 69 509Q55 509 55 526Q55 541 59 543T86 546H107H120Q150 546 161 543T184 528Q198 514 204 493Q212 472 213 420T226 316T272 230Q287 216 303 207T330 194L339 192Q340 192 340 407V622'], // GREEK CAPITAL LETTER OMEGA 0x3A9: [705,0,722,44,677,'55 454Q55 503 75 546T127 617T197 665T272 695T337 704H352Q396 704 404 703Q527 687 596 615T666 454Q666 392 635 330T559 200T499 83V80H543Q589 81 600 83T617 93Q622 102 629 135T636 172L637 177H677V175L660 89Q645 3 644 2V0H552H488Q461 0 456 3T451 20Q451 89 499 235T548 455Q548 512 530 555T483 622T424 656T361 668Q332 668 303 658T243 626T193 560T174 456Q174 380 222 233T270 20Q270 7 263 0H77V2Q76 3 61 89L44 175V177H84L85 172Q85 171 88 155T96 119T104 93Q109 86 120 84T178 80H222V83Q206 132 162 199T87 329T55 454'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/Main/Regular/GreekAndCoptic.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/Main/Regular/LatinExtendedA.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_Main'], { // LATIN SMALL LETTER DOTLESS I 0x131: [443,0,278,26,255,'247 0Q232 3 143 3Q132 3 106 3T56 1L34 0H26V46H42Q70 46 91 49Q100 53 102 60T104 102V205V293Q104 345 102 359T88 378Q74 385 41 385H30V408Q30 431 32 431L42 432Q52 433 70 434T106 436Q123 437 142 438T171 441T182 442H185V62Q190 52 197 50T232 46H255V0H247'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/Main/Regular/LatinExtendedA.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/Main/Regular/LatinExtendedB.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_Main'], { // LATIN SMALL LETTER DOTLESS J 0x237: [443,205,306,-55,218,'28 -163Q58 -168 64 -168Q124 -168 135 -77Q137 -65 137 141T136 353Q132 371 120 377T72 385H52V408Q52 431 54 431L58 432Q62 432 70 432T87 433T108 434T133 436Q151 437 171 438T202 441T214 442H218V184Q217 -36 217 -59T211 -98Q195 -145 153 -175T58 -205Q9 -205 -23 -179T-55 -117Q-55 -94 -40 -79T-2 -64T36 -79T52 -118Q52 -143 28 -163'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/Main/Regular/LatinExtendedB.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/Main/Regular/LetterlikeSymbols.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_Main'], { // stix-/hbar - Planck's over 2pi 0x210F: [695,13,540,42,562,'182 599Q182 611 174 615T133 619Q118 619 114 621T109 630Q109 636 114 656T122 681Q125 685 202 688Q272 695 286 695Q304 695 304 684Q304 682 295 644T282 597Q282 592 360 592H399Q430 592 445 587T460 563Q460 552 451 541L442 535H266L251 468Q247 453 243 436T236 409T233 399Q233 395 244 404Q295 441 357 441Q405 441 445 417T485 333Q485 284 449 178T412 58T426 44Q447 44 466 68Q485 87 500 130L509 152H531H543Q562 152 562 144Q562 128 546 93T494 23T415 -13Q385 -13 359 3T322 44Q318 52 318 77Q318 99 352 196T386 337Q386 386 346 386Q318 386 286 370Q267 361 245 338T211 292Q207 287 193 235T162 113T138 21Q128 7 122 4Q105 -12 83 -12Q66 -12 54 -2T42 26L166 530Q166 534 161 534T129 535Q127 535 122 535T112 534Q74 534 74 562Q74 570 77 576T84 585T96 589T109 591T124 592T138 592L182 595V599'], // BLACK-LETTER CAPITAL I 0x2111: [705,10,722,55,693,'55 507Q55 589 116 647T260 705Q395 705 526 541Q542 522 549 517T567 512Q595 512 621 521T647 550Q647 553 647 555T650 558T653 560T657 561T661 561T665 561T670 561Q681 561 685 561T691 558T693 548Q693 515 657 495T565 475Q518 475 481 495T418 543T371 599T320 647T259 667Q194 667 148 622T102 508Q102 468 119 436T164 385T220 357T273 347Q282 347 284 344T287 329Q287 317 285 314T272 310Q193 310 124 364T55 507ZM420 312Q420 367 464 399T564 431Q613 431 651 406T693 336Q693 325 689 323T667 320Q654 320 651 322T647 335Q645 360 622 376T566 393H563Q557 393 551 391T537 381T523 356T517 312Q517 287 535 265T574 229T613 190T631 132Q628 74 586 37T487 -9Q478 -10 417 -10H387Q344 -10 310 4T215 69Q130 142 71 146Q59 146 57 149T55 163Q55 177 58 180T75 183H108Q177 183 207 170T306 93Q346 56 368 40T420 13Q474 25 503 60T533 136Q533 160 516 182T477 219T438 257T420 304V312'], // SCRIPT SMALL L 0x2113: [706,20,417,7,397,'345 104T349 104T361 95T369 80T352 59Q268 -20 206 -20Q170 -20 146 3T113 53T99 104L94 129Q94 130 79 116T48 86T28 70Q22 70 15 79T7 94Q7 98 12 103T58 147L91 179V185Q91 186 91 191T92 200Q92 282 128 400T223 612T336 705Q397 705 397 636V627Q397 453 194 233Q185 223 180 218T174 211T171 208T165 201L163 186Q159 142 159 123Q159 17 208 17Q228 17 253 30T293 56T335 94Q345 104 349 104ZM360 634Q360 655 354 661T336 668Q328 668 322 666T302 645T272 592Q252 547 229 467T192 330L179 273Q179 272 186 280T204 300T221 322Q327 453 355 590Q360 612 360 634'], // SCRIPT CAPITAL P 0x2118: [453,216,636,67,625,'300 74Q300 133 338 133Q350 133 356 126T363 109Q363 88 340 76Q340 71 342 62T358 39T393 26Q435 26 474 67T532 182T551 290Q551 325 535 349T484 373Q430 373 378 348T291 289T228 218T187 157T174 130Q254 30 265 10Q276 -15 276 -41Q276 -101 235 -158T142 -216Q112 -216 90 -195T67 -118Q67 -40 104 64L110 81Q81 118 81 174Q81 268 134 360T247 453Q252 453 255 451T258 447L259 445Q259 432 253 420Q251 416 242 416Q209 411 176 341T142 203Q142 193 143 184T146 170T149 165L158 180Q215 280 303 345T485 410Q548 410 586 368T625 255Q625 157 553 74T389 -10H383Q349 -10 325 14Q302 37 300 74ZM105 -123Q105 -134 106 -141T110 -158T122 -173T145 -178Q155 -178 160 -176Q184 -163 199 -132T214 -73Q214 -69 214 -66T213 -59T212 -53T209 -47T205 -41T199 -33T193 -25T184 -14T174 -1L165 10Q156 22 148 32L139 43Q138 43 130 15T113 -54T105 -123'], // BLACK-LETTER CAPITAL R 0x211C: [716,22,722,40,715,'300 614L306 620Q311 626 316 631T332 646T356 663T386 679T425 695T473 707T531 715Q534 715 543 715T557 716Q570 716 572 714Q574 713 644 580T715 444Q715 441 713 439Q712 438 677 419T602 379T549 354L550 348Q550 337 555 238T561 128Q561 122 560 115T559 101Q559 63 591 25L599 18L631 51Q665 85 671 85Q674 85 687 78T702 68Q704 63 702 59Q702 58 659 20T613 -21Q612 -22 598 -22Q556 -22 526 -8T484 27T466 66T461 101Q461 110 462 116T463 129Q463 135 458 232T452 331V338H343V280Q342 195 333 157Q316 64 267 12Q233 -22 193 -22Q155 -22 122 2T72 74Q72 76 70 76T67 74T60 74T48 82Q40 91 40 95Q40 100 42 102T57 109V113Q57 118 66 127T81 137Q88 137 93 123Q105 127 108 126Q111 124 118 117T127 107Q127 101 123 98T113 93T107 90Q107 86 115 71T143 37T189 15H192Q230 15 239 96Q244 135 244 334Q244 510 242 542Q236 584 233 596Q223 630 205 649T166 668Q136 668 118 642T100 584Q100 567 110 554T137 522T166 486Q194 446 194 401V389Q189 243 114 243Q91 243 77 260T59 294T55 322Q55 331 59 333T75 336T91 334T95 322Q95 280 113 280Q134 280 140 305T147 375V391Q147 417 139 435T101 487Q56 540 56 572V580Q56 630 86 667T169 704Q214 704 247 676T300 614ZM324 562Q326 555 330 539T336 515T340 484T343 427V384H424L505 385Q537 396 584 422L609 435Q610 435 594 465T550 550Q536 575 520 605T496 650L488 664L476 662Q348 633 324 562'], // ALEF SYMBOL 0x2135: [694,0,611,55,555,'55 613Q55 643 61 663T74 688T85 694Q94 694 94 681Q98 632 134 588L412 285Q416 311 430 397T447 509V519L438 526Q407 554 398 571T388 617T394 664T407 688T418 694Q425 694 427 684Q429 675 454 635T488 586Q490 584 496 579T513 563T537 540Q555 516 555 487Q555 460 549 441T537 416T528 409Q519 409 517 415T513 435T503 463Q492 481 490 481Q454 264 454 246Q454 237 479 212T529 152T555 79Q555 32 538 9Q531 1 524 1Q516 1 516 13Q512 62 476 106Q468 115 337 258T195 412L193 406Q191 401 189 394T183 377T176 352T171 322T167 284T165 240Q165 224 166 220Q171 199 211 152T252 70Q252 45 235 29T203 8T175 1Q170 0 115 0H79Q60 0 58 3T55 20Q55 31 58 34Q60 37 76 37Q112 39 126 46T140 70Q140 96 112 148T83 236Q83 281 102 334T140 419T159 452Q55 556 55 613'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/Main/Regular/LetterlikeSymbols.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/Main/Regular/MiscSymbols.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_Main'], { // BLACK SPADE SUIT 0x2660: [727,131,778,55,723,'181 -21Q134 -21 96 27T55 193Q55 224 58 247T82 317T143 410Q172 443 234 498Q282 543 314 598T360 687T380 725Q386 727 389 727Q395 727 398 725T406 716T413 702T423 677T439 641Q481 556 544 498Q633 420 678 353T723 204Q723 142 711 94T669 12T590 -21Q520 -21 490 8T459 66V70H409V62Q409 22 416 -17T430 -82T437 -112Q437 -131 407 -131Q403 -131 397 -131T389 -130T382 -130T372 -131Q341 -131 341 -111Q341 -107 348 -82T362 -18T369 62V70H319V66Q319 57 314 44T297 16T257 -10T191 -21H181'], // WHITE HEART SUIT 0x2661: [716,33,778,55,723,'55 490Q55 557 71 604T114 674T167 706T222 716Q279 716 322 684T389 605Q391 610 395 617T414 643T447 677T494 704T555 716Q642 716 682 652T723 490Q723 455 718 426T684 342T602 227Q573 196 537 161T485 110T449 63T412 -8Q408 -22 404 -27T389 -33Q382 -33 379 -31T372 -23T366 -8T355 18T335 54Q319 81 298 104T239 163T176 227Q102 310 79 371T55 490ZM198 674Q143 664 119 613T95 491Q95 415 137 346Q174 282 265 194T384 48L389 39Q391 42 397 54T406 71T415 86T427 104T442 122T464 146T491 172Q571 249 613 303Q683 396 683 487Q683 581 649 631Q613 676 556 676Q495 676 457 634T410 538Q407 514 390 514Q386 514 380 517Q372 520 369 536T355 581T319 635Q277 675 223 675H217H208L204 674Q200 674 198 674'], // WHITE DIAMOND SUIT 0x2662: [727,162,778,55,723,'370 714Q370 717 375 722T388 727Q398 727 403 721T417 697Q420 692 421 689Q536 465 709 304Q723 291 723 282T709 260Q529 93 406 -153Q402 -162 390 -162H389Q379 -162 376 -158T357 -125Q247 89 89 241L64 265Q55 272 55 282Q55 287 57 290T64 300T77 312T98 331T127 361Q197 435 258 523T344 663L370 714ZM655 299Q568 384 508 470T389 662L376 638Q362 613 341 577T289 497T215 399T123 299L105 282L123 265Q210 180 270 94T389 -98L402 -74Q416 -49 437 -13T489 67T563 165T655 265L673 282L655 299'], // BLACK CLUB SUIT 0x2663: [726,131,778,28,750,'213 532Q213 615 265 670T389 726Q461 726 513 671T565 532Q565 511 562 492T553 458T541 432T526 409T512 393T498 379L490 371L511 326Q512 326 516 330T528 341T546 353T572 363T606 368Q664 368 707 315T750 174Q750 87 699 33T579 -22Q567 -22 553 -20T517 -10T479 16T459 63V70H409V62Q409 22 416 -17T430 -82T437 -112Q437 -131 407 -131Q403 -131 397 -131T389 -130T382 -130T372 -131Q341 -131 341 -111Q341 -107 348 -82T362 -18T369 62V70H319V63Q315 25 281 2T197 -22Q132 -22 80 32T28 174Q28 255 69 311T175 368Q192 368 207 364T232 353T250 341T262 331T267 326L288 371L280 378Q272 385 267 391T253 407T238 430T226 457T217 492T213 532'], // MUSIC FLAT SIGN 0x266D: [750,22,389,55,332,'200 467Q254 467 293 428T332 321Q332 147 104 -11L88 -22H75Q62 -22 56 -16L55 362V647Q55 743 60 748Q63 750 76 750H83Q87 750 95 744V434L104 440Q144 467 200 467ZM237 322Q237 360 225 388T183 417Q158 417 134 407T101 378Q96 370 96 349T95 197V34Q152 91 194 167T237 322'], // MUSIC NATURAL SIGN 0x266E: [734,223,389,65,324,'65 721Q78 734 94 734Q100 734 104 727V444L116 449Q129 454 157 465T208 486Q313 527 314 527Q318 527 324 521V-210Q306 -223 294 -223Q289 -223 284 -216V-13L270 -18Q257 -24 231 -34T180 -54Q77 -96 74 -96T65 -90V721ZM104 13Q282 84 283 85Q284 85 284 252Q284 418 283 418L230 396L140 360L104 346V13'], // MUSIC SHARP SIGN 0x266F: [723,223,389,55,333,'101 -223Q94 -223 93 -217T91 -188V-151Q91 -88 90 -88Q87 -88 80 -92T68 -96Q62 -96 56 -90L55 -50V-22Q55 -8 58 -4T78 5L91 10V177Q91 343 90 343Q87 343 80 339T68 335Q62 335 56 341L55 381V409Q55 423 58 427T78 436L91 441V543V616Q91 643 93 648T106 656Q119 662 126 659Q130 657 130 645T131 554V456L257 503V607L258 710L260 712Q261 715 272 719T286 723Q293 723 295 715T297 671V617Q297 519 298 519Q301 519 307 522T319 526Q327 526 333 521V437L330 435Q328 432 312 427L297 421V254Q297 88 298 88Q301 88 307 91T319 95Q327 95 333 90V6L330 4Q328 1 312 -4L297 -10V-78V-122Q297 -145 295 -149T282 -156Q274 -160 268 -160Q257 -160 257 -130V-89V-25L131 -72V-210Q123 -215 116 -218T104 -222L101 -223ZM257 72V406L131 359V25L257 72'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/Main/Regular/MiscSymbols.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/Main/Regular/SpacingModLetters.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_Main'], { // RING ABOVE 0x2DA: [715,-542,500,147,352,'147 628Q147 669 179 692T244 715Q298 715 325 689T352 629Q352 592 323 567T249 542Q202 542 175 567T147 628ZM313 628Q313 660 300 669T259 678H253Q248 678 242 678T234 679Q217 679 207 674T192 659T188 644T187 629Q187 600 198 590Q210 579 250 579H265Q279 579 288 581T305 595T313 628'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/Main/Regular/SpacingModLetters.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/Main/Regular/SuppMathOperators.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.Hub.Insert( MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_Main'], { // AMALGAMATION OR COPRODUCT 0x2A3F: [683,0,750,28,721,'28 660Q28 676 31 679T46 683H50Q87 681 182 681Q217 681 247 681T294 682T315 682Q321 682 323 682T328 679T331 673T332 660Q332 643 328 640T304 637Q239 637 231 626Q229 620 229 334V46H520V334Q520 620 518 626Q510 637 445 637Q426 637 422 640T417 660Q417 675 420 678T432 682H435Q437 682 467 682T569 681T671 681T703 682Q714 682 717 679T721 660Q721 643 717 640T693 637Q628 637 620 626Q619 623 619 342Q619 60 620 57Q628 46 693 46Q714 46 717 43T721 23Q721 5 715 1Q713 0 374 0Q36 0 34 1Q28 5 28 23Q28 40 31 43T56 46Q121 46 129 57Q131 63 131 342Q131 620 129 626Q121 637 56 637Q35 637 32 640T28 660'] } ); MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/Main/Regular/SuppMathOperators.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/Size1/Regular/Main.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_Size1'] = { directory: 'Size1/Regular', family: 'MathJax_Size1', id: 'MJSZ1', // SPACE 0x20: [0,0,250,0,0,''], // LEFT PARENTHESIS 0x28: [850,349,458,152,422,'152 251Q152 646 388 850H416Q422 844 422 841Q422 837 403 816T357 753T302 649T255 482T236 250Q236 124 255 19T301 -147T356 -251T403 -315T422 -340Q422 -343 416 -349H388Q359 -325 332 -296T271 -213T212 -97T170 56T152 251'], // RIGHT PARENTHESIS 0x29: [850,349,458,35,305,'305 251Q305 -145 69 -349H56Q43 -349 39 -347T35 -338Q37 -333 60 -307T108 -239T160 -136T204 27T221 250T204 473T160 636T108 740T60 807T35 839Q35 850 50 850H56H69Q197 743 256 566Q305 425 305 251'], // SOLIDUS 0x2F: [850,349,578,55,522,'481 838Q489 850 500 850Q508 850 515 844T522 827Q521 824 311 248T96 -337Q90 -349 77 -349Q68 -349 62 -343T55 -326Q56 -323 266 253T481 838'], // LEFT SQUARE BRACKET 0x5B: [850,349,417,202,394,'202 -349V850H394V810H242V-309H394V-349H202'], // REVERSE SOLIDUS 0x5C: [850,349,578,54,522,'522 -326Q522 -337 515 -343T500 -349Q487 -349 481 -337Q477 -328 267 248T55 827Q54 835 60 842T76 850Q89 850 96 838Q100 829 310 253T522 -326'], // RIGHT SQUARE BRACKET 0x5D: [850,349,417,22,214,'22 810V850H214V-349H22V-309H174V810H22'], // LEFT CURLY BRACKET 0x7B: [851,349,583,105,477,'477 -343L471 -349H458Q432 -349 367 -325T273 -263Q258 -245 250 -212L249 -51Q249 -27 249 12Q248 118 244 128Q243 129 243 130Q220 189 121 228Q109 232 107 235T105 250Q105 256 105 257T105 261T107 265T111 268T118 272T128 276T142 283T162 291Q224 324 243 371Q243 372 244 373Q248 384 249 469Q249 475 249 489Q249 528 249 552L250 714Q253 728 256 736T271 761T299 789T347 816T422 843Q440 849 441 849H443Q445 849 447 849T452 850T457 850H471L477 844V830Q477 820 476 817T470 811T459 807T437 801T404 785Q353 760 338 724Q333 710 333 550Q333 526 333 492T334 447Q334 393 327 368T295 318Q257 280 181 255L169 251L184 245Q318 198 332 112Q333 106 333 -49Q333 -209 338 -223Q351 -255 391 -277T469 -309Q477 -311 477 -329V-343'], // RIGHT CURLY BRACKET 0x7D: [850,349,583,105,477,'110 849L115 850Q120 850 125 850Q151 850 215 826T309 764Q324 747 332 714L333 552Q333 528 333 489Q334 383 338 373Q339 372 339 371Q353 336 391 310T469 271Q477 268 477 251Q477 241 476 237T472 232T456 225T428 214Q357 179 339 130Q339 129 338 128Q334 117 333 32Q333 26 333 12Q333 -27 333 -51L332 -212Q328 -228 323 -240T302 -271T255 -307T175 -338Q139 -349 125 -349T108 -346T105 -329Q105 -314 107 -312T130 -304Q233 -271 248 -209Q249 -203 249 -49V57Q249 106 253 125T273 167Q307 213 398 245L413 251L401 255Q265 300 250 389Q249 395 249 550Q249 710 244 724Q224 774 112 811Q105 813 105 830Q105 845 110 849'], // MODIFIER LETTER CIRCUMFLEX ACCENT 0x2C6: [744,-551,556,-8,564,'279 669Q273 669 142 610T9 551L0 569Q-8 585 -8 587Q-8 588 -7 588L12 598Q30 608 66 628T136 666L277 744L564 587L555 569Q549 556 547 554T544 552Q539 555 410 612T279 669'], // SMALL TILDE 0x2DC: [722,-597,556,1,554,'374 597Q337 597 269 627T160 658Q101 658 34 606L24 597L12 611Q1 624 1 626Q1 627 27 648T55 671Q120 722 182 722Q219 722 286 692T395 661Q454 661 521 713L531 722L543 708Q554 695 554 693Q554 692 528 671T500 648Q434 597 374 597'], // COMBINING CIRCUMFLEX ACCENT 0x302: [744,-551,0,-564,8,'-277 669Q-283 669 -414 610T-547 551L-556 569Q-564 585 -564 587Q-564 588 -563 588L-544 598Q-526 608 -490 628T-420 666L-279 744L8 587L-1 569Q-7 556 -9 554T-12 552Q-17 555 -146 612T-277 669'], // COMBINING TILDE 0x303: [722,-597,0,-555,-2,'-182 597Q-219 597 -287 627T-396 658Q-455 658 -522 606L-532 597L-544 611Q-555 624 -555 626Q-555 627 -529 648T-501 671Q-436 722 -374 722Q-337 722 -270 692T-161 661Q-102 661 -35 713L-25 722L-13 708Q-2 695 -2 693Q-2 692 -28 671T-56 648Q-122 597 -182 597'], // DOUBLE VERTICAL LINE 0x2016: [602,0,778,257,521,'257 0V602H300V0H257ZM478 0V602H521V0H478'], // UPWARDS ARROW 0x2191: [600,0,667,112,555,'112 421L120 424Q127 427 136 430T161 441T191 458T224 481T260 510T295 546T328 591L333 600L340 589Q380 527 431 489T555 421V377L543 381Q445 418 368 492L355 504V0H312V504L299 492Q222 418 124 381L112 377V421'], // DOWNWARDS ARROW 0x2193: [600,0,667,112,555,'312 96V600H355V96L368 108Q445 182 543 219L555 223V179L546 176Q538 173 529 169T505 158T475 141T442 119T407 90T372 53T339 9L334 0L327 11Q287 73 236 111T112 179V223L124 219Q222 182 299 108L312 96'], // UPWARDS DOUBLE ARROW 0x21D1: [599,0,778,57,721,'142 329Q300 419 389 599Q389 598 399 579T420 541T452 494T497 438T558 383T636 329T708 294L721 289V246Q718 246 694 256T623 293T532 356L522 364L521 182V0H478V405L466 417Q436 450 389 516Q388 515 378 500T352 463T312 417L300 405V0H257V364L247 356Q202 320 155 293T82 256L57 246V289L70 294Q101 305 142 329'], // DOWNWARDS DOUBLE ARROW 0x21D3: [600,-1,778,57,721,'257 236V600H300V195L312 183Q342 150 389 84Q390 85 400 100T426 137T466 183L478 195V600H521V418L522 236L532 244Q576 280 623 307T696 344L721 354V311L708 306Q677 295 636 271Q478 181 389 1Q389 2 379 21T358 59T326 106T281 162T220 217T142 271T70 306L57 311V354Q60 354 83 345T154 308T247 244L257 236'], // N-ARY PRODUCT 0x220F: [750,250,944,55,888,'158 656Q147 684 131 694Q110 707 69 710H55V750H888V710H874Q840 708 820 698T795 678T786 656V-155Q798 -206 874 -210H888V-250H570V-210H584Q618 -208 638 -197T663 -178T673 -155V710H270V277L271 -155Q283 -206 359 -210H373V-250H55V-210H69Q103 -208 123 -197T148 -178T158 -155V656'], // N-ARY COPRODUCT 0x2210: [750,250,944,55,888,'158 656Q147 684 131 694Q110 707 69 710H55V750H373V710H359Q325 708 305 698T280 678T271 656L270 223V-210H673V656Q666 672 663 679T639 697T584 710H570V750H888V710H874Q840 708 820 698T795 678T786 656V-155Q798 -206 874 -210H888V-250H55V-210H69Q103 -208 123 -197T148 -178T158 -155V656'], // N-ARY SUMMATION 0x2211: [750,250,1056,56,999,'61 748Q64 750 489 750H913L954 640Q965 609 976 579T993 533T999 516H979L959 517Q936 579 886 621T777 682Q724 700 655 705T436 710H319Q183 710 183 709Q186 706 348 484T511 259Q517 250 513 244L490 216Q466 188 420 134T330 27L149 -187Q149 -188 362 -188Q388 -188 436 -188T506 -189Q679 -189 778 -162T936 -43Q946 -27 959 6H999L913 -249L489 -250Q65 -250 62 -248Q56 -246 56 -239Q56 -234 118 -161Q186 -81 245 -11L428 206Q428 207 242 462L57 717L56 728Q56 744 61 748'], // SQUARE ROOT 0x221A: [850,350,1000,111,1020,'263 249Q264 249 315 130T417 -108T470 -228L725 302Q981 837 982 839Q989 850 1001 850Q1008 850 1013 844T1020 832V826L741 243Q645 43 540 -176Q479 -303 469 -324T453 -348Q449 -350 436 -350L424 -349L315 -96Q206 156 205 156L171 130Q138 104 137 104L111 130L263 249'], // DIVIDES 0x2223: [627,15,333,144,188,'146 612Q151 627 166 627Q182 627 187 612Q188 610 188 306T187 0Q184 -15 166 -15Q149 -15 146 0V10Q146 19 146 35T146 73T146 122T145 179T145 241T145 306T145 370T145 433T145 489T146 538T146 576T146 602V612'], // PARALLEL TO 0x2225: [627,15,556,144,410,'146 612Q151 627 166 627Q182 627 187 612Q188 610 188 306T187 0Q184 -15 166 -15Q149 -15 146 0V10Q146 19 146 35T146 73T146 122T145 179T145 241T145 306T145 370T145 433T145 489T146 538T146 576T146 602V612ZM368 612Q373 627 388 627Q404 627 409 612Q410 610 410 306T409 0Q406 -15 389 -15Q371 -15 368 0V10Q368 19 368 35T368 73T368 122T367 179T367 241T367 306T367 370T367 433T367 489T368 538T368 576T368 602V612'], // INTEGRAL 0x222B: [805,306,472,55,610,'113 -244Q113 -246 119 -251T139 -263T167 -269Q186 -269 199 -260Q220 -247 232 -218T251 -133T262 -15T276 155T297 367Q300 390 305 438T314 512T325 580T340 647T361 703T390 751T428 784T479 804Q481 804 488 804T501 805Q552 802 581 769T610 695Q610 669 594 657T561 645Q542 645 527 658T512 694Q512 705 516 714T526 729T538 737T548 742L552 743Q552 745 545 751T525 762T498 768Q475 768 460 756T434 716T418 652T407 559T398 444T387 300T369 133Q349 -38 337 -102T303 -207Q256 -306 169 -306Q119 -306 87 -272T55 -196Q55 -170 71 -158T104 -146Q123 -146 138 -159T153 -195Q153 -206 149 -215T139 -230T127 -238T117 -242L113 -244'], // DOUBLE INTEGRAL 0x222C: [805,306,819,55,957,'113 -244Q113 -246 119 -251T139 -263T167 -269Q186 -269 199 -260Q220 -247 232 -218T251 -133T262 -15T276 155T297 367Q300 390 305 438T314 512T325 580T340 647T361 703T390 751T428 784T479 804Q481 804 488 804T501 805Q552 802 581 769T610 695Q610 669 594 657T561 645Q542 645 527 658T512 694Q512 705 516 714T526 729T538 737T548 742L552 743Q552 745 545 751T525 762T498 768Q475 768 460 756T434 716T418 652T407 559T398 444T387 300T369 133Q349 -38 337 -102T303 -207Q256 -306 169 -306Q119 -306 87 -272T55 -196Q55 -170 71 -158T104 -146Q123 -146 138 -159T153 -195Q153 -206 149 -215T139 -230T127 -238T117 -242L113 -244ZM460 -244Q460 -246 466 -251T486 -263T514 -269Q532 -269 546 -260Q567 -247 579 -218T598 -133T609 -15T623 155T644 367Q647 390 652 438T661 512T672 580T687 647T708 703T737 751T775 784T826 804Q828 804 835 804T848 805Q899 802 928 769T957 695Q957 669 941 657T908 645Q889 645 874 658T859 694Q859 705 863 714T873 729T885 737T895 742L899 743Q899 745 892 751T872 762T845 768Q822 768 807 756T781 716T765 652T754 559T745 444T734 300T716 133Q696 -38 684 -102T650 -207Q603 -306 516 -306Q466 -306 434 -272T402 -196Q402 -170 418 -158T451 -146Q470 -146 485 -159T500 -195Q500 -206 496 -215T486 -230T474 -238T464 -242L460 -244'], // TRIPLE INTEGRAL 0x222D: [805,306,1166,55,1304,'113 -244Q113 -246 119 -251T139 -263T167 -269Q186 -269 199 -260Q220 -247 232 -218T251 -133T262 -15T276 155T297 367Q300 390 305 438T314 512T325 580T340 647T361 703T390 751T428 784T479 804Q481 804 488 804T501 805Q552 802 581 769T610 695Q610 669 594 657T561 645Q542 645 527 658T512 694Q512 705 516 714T526 729T538 737T548 742L552 743Q552 745 545 751T525 762T498 768Q475 768 460 756T434 716T418 652T407 559T398 444T387 300T369 133Q349 -38 337 -102T303 -207Q256 -306 169 -306Q119 -306 87 -272T55 -196Q55 -170 71 -158T104 -146Q123 -146 138 -159T153 -195Q153 -206 149 -215T139 -230T127 -238T117 -242L113 -244ZM460 -244Q460 -246 466 -251T486 -263T514 -269Q532 -269 546 -260Q567 -247 579 -218T598 -133T609 -15T623 155T644 367Q647 390 652 438T661 512T672 580T687 647T708 703T737 751T775 784T826 804Q828 804 835 804T848 805Q899 802 928 769T957 695Q957 669 941 657T908 645Q889 645 874 658T859 694Q859 705 863 714T873 729T885 737T895 742L899 743Q899 745 892 751T872 762T845 768Q822 768 807 756T781 716T765 652T754 559T745 444T734 300T716 133Q696 -38 684 -102T650 -207Q603 -306 516 -306Q466 -306 434 -272T402 -196Q402 -170 418 -158T451 -146Q470 -146 485 -159T500 -195Q500 -206 496 -215T486 -230T474 -238T464 -242L460 -244ZM807 -244Q807 -246 813 -251T833 -263T861 -269Q880 -269 893 -260Q914 -247 926 -218T945 -133T956 -15T970 155T991 367Q994 390 999 438T1008 512T1019 580T1034 647T1055 703T1084 751T1122 784T1173 804Q1175 804 1182 804T1195 805Q1246 802 1275 769T1304 695Q1304 669 1288 657T1255 645Q1236 645 1221 658T1206 694Q1206 705 1210 714T1220 729T1232 737T1242 742L1246 743Q1246 745 1239 751T1219 762T1192 768Q1169 768 1154 756T1128 716T1112 652T1101 559T1092 444T1081 300T1063 133Q1043 -38 1031 -102T997 -207Q950 -306 863 -306Q813 -306 781 -272T749 -196Q749 -170 765 -158T798 -146Q817 -146 832 -159T847 -195Q847 -206 843 -215T833 -230T821 -238T811 -242L807 -244'], // CONTOUR INTEGRAL 0x222E: [805,306,472,55,610,'269 74L256 80Q244 85 227 97T191 128T161 179T148 250Q148 332 199 379T302 433L306 434L307 444Q309 456 313 495T321 553T331 607T345 664T365 712T393 756T431 785T479 804Q481 804 488 804T501 805Q552 802 581 769T610 695Q610 669 594 657T561 645Q542 645 527 658T512 694Q512 705 516 714T526 729T538 737T548 742L552 743Q552 745 545 751T525 762T498 768Q471 768 454 752T427 693T414 626T406 536Q405 530 405 527L397 425L404 422Q410 419 421 413T445 399T470 376T494 345T511 303T518 250Q518 205 502 169T460 112T410 80T364 66L360 65L359 55Q357 38 353 4T346 -43T340 -81T333 -118T326 -148T316 -179T303 -207Q256 -306 169 -306Q119 -306 87 -272T55 -196Q55 -170 71 -158T104 -146Q123 -146 138 -159T153 -195Q153 -206 149 -215T139 -230T127 -238T117 -242L113 -244Q113 -246 119 -251T139 -263T167 -269Q186 -269 199 -260Q231 -241 242 -183T266 33L269 74ZM272 122Q272 156 300 391Q300 392 299 392Q287 392 263 379T213 331T187 249Q187 211 205 180T239 137T272 116V122ZM366 107Q378 107 402 119T453 167T479 249Q479 340 394 383V377Q394 375 394 374T393 371T393 366T392 357T391 342T389 321T386 291T382 251T377 199T369 133Q366 112 366 107'], // N-ARY LOGICAL AND 0x22C0: [750,249,833,55,777,'119 -249T97 -249T65 -235T55 -207Q55 -201 56 -198Q58 -190 218 268T380 729Q392 750 416 750Q438 750 451 732Q453 728 534 498T695 36L775 -194Q777 -204 777 -208Q777 -222 767 -235T735 -249Q713 -249 700 -231Q696 -225 557 177L416 579L276 177Q136 -226 132 -231Q119 -249 97 -249'], // N-ARY LOGICAL OR 0x22C1: [750,249,833,55,777,'55 708Q55 729 68 739T96 750Q119 750 132 731Q136 726 276 323L416 -79L557 323Q696 725 700 731Q713 749 735 749Q756 749 766 736T777 708Q777 700 696 466T533 1T451 -232Q436 -249 416 -249Q402 -249 391 -241Q384 -236 380 -226Q368 -198 219 230Q55 697 55 708'], // N-ARY INTERSECTION 0x22C2: [750,249,833,54,777,'139 -217Q127 -241 114 -246Q106 -249 97 -249Q67 -249 57 -220Q55 -214 55 102Q55 152 55 221T54 312Q54 422 60 464T91 554Q120 612 165 654T257 714T337 741T392 749Q393 750 402 750Q414 750 422 749Q557 749 660 659T776 430Q777 422 777 102Q777 -214 775 -220Q765 -249 735 -249Q716 -249 708 -241T694 -217L692 428L690 441Q674 540 597 603T416 666H409Q388 666 364 662T294 638T212 581Q156 523 142 441L140 428L139 105V-217'], // N-ARY UNION 0x22C3: [750,249,833,55,777,'96 750Q103 750 109 748T120 744T127 737T133 730T137 723T139 718V395L140 73L142 60Q159 -43 237 -104T416 -166Q521 -166 597 -103T690 60L692 73L694 718Q708 749 735 749Q765 749 775 720Q777 714 777 398Q777 78 776 71Q766 -51 680 -140Q571 -249 416 -249H411Q261 -249 152 -140Q66 -51 56 71Q55 78 55 398Q55 714 57 720Q60 734 70 740Q80 750 96 750'], // LEFT CEILING 0x2308: [850,349,472,202,449,'202 -349V850H449V810H242V-349H202'], // RIGHT CEILING 0x2309: [850,349,472,22,269,'22 810V850H269V-349H229V810H22'], // LEFT FLOOR 0x230A: [850,349,472,202,449,'202 -349V850H242V-309H449V-349H202'], // RIGHT FLOOR 0x230B: [850,349,472,22,269,'229 -309V850H269V-349H22V-309H229'], // VERTICAL LINE EXTENSION (used to extend arrows) 0x23D0: [602,0,667,312,355,'312 0V602H355V0H312'], // MATHEMATICAL LEFT ANGLE BRACKET 0x27E8: [850,350,472,96,394,'373 850Q392 850 394 832Q394 825 267 538L139 250L267 -38Q394 -325 394 -332Q392 -350 375 -350Q361 -350 356 -338Q354 -331 289 -186T161 103T97 250T160 397T289 685T356 838Q362 850 373 850'], // MATHEMATICAL RIGHT ANGLE BRACKET 0x27E9: [850,350,472,77,375,'77 832Q77 837 82 843T98 850Q110 849 115 838Q117 831 182 686T310 397T374 250T311 103T182 -185T115 -338Q110 -350 96 -350Q79 -350 77 -332Q77 -325 204 -38L332 250L204 538Q77 825 77 832'], // N-ARY CIRCLED DOT OPERATOR 0x2A00: [750,250,1111,56,1054,'555 -250Q420 -250 306 -185T124 -4T56 250Q56 453 193 595T526 749Q528 750 539 750Q554 750 562 749Q688 749 800 687T983 508T1054 250Q1054 112 987 -3T806 -184T555 -250ZM555 -165Q672 -165 767 -108T916 44T970 250Q970 418 861 532T600 664Q591 665 548 665Q446 665 353 614T200 466T140 250V243Q140 88 248 -30Q262 -46 280 -62T338 -105T434 -148T555 -165ZM478 250Q478 288 503 307T551 326Q586 326 609 305T632 250Q632 217 610 196T555 174T500 196T478 250'], // N-ARY CIRCLED PLUS OPERATOR 0x2A01: [750,250,1111,56,1054,'555 -250Q420 -250 306 -185T124 -4T56 250Q56 453 193 595T526 749Q528 750 539 750Q554 750 562 749Q688 749 800 687T983 508T1054 250Q1054 112 987 -3T806 -184T555 -250ZM513 478Q513 664 512 664Q504 664 481 660T406 637T313 588Q281 564 255 537T211 483T181 431T161 382T150 342T144 310T141 292H513V478ZM798 588Q758 616 711 634T639 658T602 663L597 664V292H969Q969 293 967 309T960 341T949 381T930 430T900 482T856 537T798 588ZM513 -164V208H141Q142 205 144 189T149 160T158 125T173 83T196 39T229 -9Q249 -34 273 -55T318 -92T363 -119T405 -138T444 -150T475 -158T499 -162T513 -164ZM775 -103Q801 -87 823 -68T863 -30T894 10T919 49T937 88T950 123T959 154T964 180T968 198L969 208H597V-164Q599 -163 616 -161T647 -155T683 -145T728 -128T775 -103'], // N-ARY CIRCLED TIMES OPERATOR 0x2A02: [750,250,1111,56,1054,'555 -250Q420 -250 306 -185T124 -4T56 250Q56 453 193 595T526 749Q528 750 539 750Q554 750 562 749Q688 749 800 687T983 508T1054 250Q1054 112 987 -3T806 -184T555 -250ZM600 664Q591 665 548 665Q414 665 306 583L292 573L423 441L555 310L687 441L818 573L804 583Q714 650 600 664ZM364 118L495 250L364 382L232 513L223 500Q140 391 140 250Q140 107 223 0L232 -13L364 118ZM970 250Q970 389 887 501L878 512Q878 513 861 496T812 447T746 381L615 250L746 118L878 -13L887 0Q970 109 970 250ZM687 59L555 190L423 59L292 -73L306 -83Q416 -166 555 -166T804 -83L818 -73L687 59'], // N-ARY UNION OPERATOR WITH PLUS 0x2A04: [750,249,833,55,777,'96 750Q103 750 109 748T120 744T127 737T133 730T137 723T139 718V395L140 73L142 60Q159 -43 237 -104T416 -166Q521 -166 597 -103T690 60L692 73L694 718Q708 749 735 749Q765 749 775 720Q777 714 777 398Q777 78 776 71Q766 -51 680 -140Q571 -249 416 -249H411Q261 -249 152 -140Q66 -51 56 71Q55 78 55 398Q55 714 57 720Q60 734 70 740Q80 750 96 750ZM223 276Q223 282 224 287T227 296T232 302T238 308T243 313T250 316L254 319H374V376V406Q374 438 382 454T418 470Q443 467 450 453T458 410V376V319H579Q580 319 583 317T589 313T594 308T600 302T604 295T608 287T609 276Q609 253 587 241Q577 235 513 235H458V178Q458 176 458 166T459 148Q459 84 415 84Q401 84 390 93T375 117Q374 120 374 178V235H319Q317 235 307 235T290 234Q223 234 223 276'], // N-ARY SQUARE UNION OPERATOR 0x2A06: [750,249,833,55,777,'777 -217Q766 -244 745 -249H88Q64 -242 57 -220Q55 -214 55 250T57 720Q60 734 70 740Q80 750 96 750Q127 750 137 720Q139 714 139 274V-166H693V274Q693 714 695 720Q705 749 735 749Q766 749 775 719Q777 713 777 248V-217'] }; MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/Size1/Regular/Main.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/Size2/Regular/Main.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_Size2'] = { directory: 'Size2/Regular', family: 'MathJax_Size2', id: 'MJSZ2', // SPACE 0x20: [0,0,250,0,0,''], // LEFT PARENTHESIS 0x28: [1150,649,597,180,561,'180 96T180 250T205 541T266 770T353 944T444 1069T527 1150H555Q561 1144 561 1141Q561 1137 545 1120T504 1072T447 995T386 878T330 721T288 513T272 251Q272 133 280 56Q293 -87 326 -209T399 -405T475 -531T536 -609T561 -640Q561 -643 555 -649H527Q483 -612 443 -568T353 -443T266 -270T205 -41'], // RIGHT PARENTHESIS 0x29: [1150,649,597,35,417,'35 1138Q35 1150 51 1150H56H69Q113 1113 153 1069T243 944T330 771T391 541T416 250T391 -40T330 -270T243 -443T152 -568T69 -649H56Q43 -649 39 -647T35 -637Q65 -607 110 -548Q283 -316 316 56Q324 133 324 251Q324 368 316 445Q278 877 48 1123Q36 1137 35 1138'], // SOLIDUS 0x2F: [1150,649,811,56,754,'78 -649Q56 -646 56 -625Q56 -614 382 261T712 1140Q716 1150 732 1150Q754 1147 754 1126Q754 1116 428 240T98 -639Q94 -649 78 -649'], // LEFT SQUARE BRACKET 0x5B: [1150,649,472,224,455,'224 -649V1150H455V1099H275V-598H455V-649H224'], // REVERSE SOLIDUS 0x5C: [1150,649,811,54,754,'754 -625Q754 -649 731 -649Q715 -649 712 -639Q709 -635 383 242T55 1124Q54 1135 61 1142T80 1150Q92 1150 98 1140Q101 1137 427 262T754 -625'], // RIGHT SQUARE BRACKET 0x5D: [1150,649,472,16,247,'16 1099V1150H247V-649H16V-598H196V1099H16'], // LEFT CURLY BRACKET 0x7B: [1150,649,667,119,547,'547 -643L541 -649H528Q515 -649 503 -645Q324 -582 293 -466Q289 -449 289 -428T287 -200L286 42L284 53Q274 98 248 135T196 190T146 222L121 235Q119 239 119 250Q119 262 121 266T133 273Q262 336 284 449L286 460L287 701Q287 737 287 794Q288 949 292 963Q293 966 293 967Q325 1080 508 1148Q516 1150 527 1150H541L547 1144V1130Q547 1117 546 1115T536 1109Q480 1086 437 1046T381 950L379 940L378 699Q378 657 378 594Q377 452 374 438Q373 437 373 436Q350 348 243 282Q192 257 186 254L176 251L188 245Q211 236 234 223T287 189T340 135T373 65Q373 64 374 63Q377 49 378 -93Q378 -156 378 -198L379 -438L381 -449Q393 -504 436 -544T536 -608Q544 -611 545 -613T547 -629V-643'], // RIGHT CURLY BRACKET 0x7D: [1150,649,667,119,547,'119 1130Q119 1144 121 1147T135 1150H139Q151 1150 182 1138T252 1105T326 1046T373 964Q378 942 378 702Q378 469 379 462Q386 394 439 339Q482 296 535 272Q544 268 545 266T547 251Q547 241 547 238T542 231T531 227T510 217T477 194Q390 129 379 39Q378 32 378 -201Q378 -441 373 -463Q342 -580 165 -644Q152 -649 139 -649Q125 -649 122 -646T119 -629Q119 -622 119 -619T121 -614T124 -610T132 -607T143 -602Q195 -579 235 -539T285 -447Q286 -435 287 -199T289 51Q294 74 300 91T329 138T390 197Q412 213 436 226T475 244L489 250L472 258Q455 265 430 279T377 313T327 366T293 434Q289 451 289 472T287 699Q286 941 285 948Q279 978 262 1005T227 1048T184 1080T151 1100T129 1109L127 1110Q119 1113 119 1130'], // MODIFIER LETTER CIRCUMFLEX ACCENT 0x2C6: [772,-565,1000,-5,1004,'1004 603Q1004 600 999 583T991 565L960 574Q929 582 866 599T745 631L500 698Q497 698 254 631Q197 616 134 599T39 574L8 565Q5 565 0 582T-5 603L26 614Q58 624 124 646T248 687L499 772Q999 604 1004 603'], // SMALL TILDE 0x2DC: [750,-611,1000,0,999,'296 691Q258 691 216 683T140 663T79 639T34 619T16 611Q13 619 8 628L0 644L36 662Q206 749 321 749Q410 749 517 710T703 670Q741 670 783 678T859 698T920 722T965 742T983 750Q986 742 991 733L999 717L963 699Q787 611 664 611Q594 611 484 651T296 691'], // COMBINING CIRCUMFLEX ACCENT 0x302: [772,-565,0,-1005,4,'4 603Q4 600 -1 583T-9 565L-40 574Q-71 582 -134 599T-255 631L-500 698Q-503 698 -746 631Q-803 616 -866 599T-961 574L-992 565Q-995 565 -1000 582T-1005 603L-974 614Q-942 624 -876 646T-752 687L-501 772Q-1 604 4 603'], // COMBINING TILDE 0x303: [750,-611,0,-1000,-1,'-704 691Q-742 691 -784 683T-860 663T-921 639T-966 619T-984 611Q-987 619 -992 628L-1000 644L-964 662Q-794 749 -679 749Q-590 749 -483 710T-297 670Q-259 670 -217 678T-141 698T-80 722T-35 742T-17 750Q-14 742 -9 733L-1 717L-37 699Q-213 611 -336 611Q-405 611 -515 651T-704 691'], // N-ARY PRODUCT 0x220F: [950,450,1278,56,1221,'220 812Q220 813 218 819T214 829T208 840T199 853T185 866T166 878T140 887T107 893T66 896H56V950H1221V896H1211Q1080 896 1058 812V-311Q1076 -396 1211 -396H1221V-450H725V-396H735Q864 -396 888 -314Q889 -312 889 -311V896H388V292L389 -311Q405 -396 542 -396H552V-450H56V-396H66Q195 -396 219 -314Q220 -312 220 -311V812'], // N-ARY COPRODUCT 0x2210: [950,450,1278,56,1221,'220 812Q220 813 218 819T214 829T208 840T199 853T185 866T166 878T140 887T107 893T66 896H56V950H552V896H542Q411 896 389 812L388 208V-396H889V812Q889 813 887 819T883 829T877 840T868 853T854 866T835 878T809 887T776 893T735 896H725V950H1221V896H1211Q1080 896 1058 812V-311Q1076 -396 1211 -396H1221V-450H56V-396H66Q195 -396 219 -314Q220 -312 220 -311V812'], // N-ARY SUMMATION 0x2211: [950,450,1444,55,1388,'60 948Q63 950 665 950H1267L1325 815Q1384 677 1388 669H1348L1341 683Q1320 724 1285 761Q1235 809 1174 838T1033 881T882 898T699 902H574H543H251L259 891Q722 258 724 252Q725 250 724 246Q721 243 460 -56L196 -356Q196 -357 407 -357Q459 -357 548 -357T676 -358Q812 -358 896 -353T1063 -332T1204 -283T1307 -196Q1328 -170 1348 -124H1388Q1388 -125 1381 -145T1356 -210T1325 -294L1267 -449L666 -450Q64 -450 61 -448Q55 -446 55 -439Q55 -437 57 -433L590 177Q590 178 557 222T452 366T322 544L56 909L55 924Q55 945 60 948'], // SQUARE ROOT 0x221A: [1150,650,1000,111,1020,'1001 1150Q1017 1150 1020 1132Q1020 1127 741 244L460 -643Q453 -650 436 -650H424Q423 -647 423 -645T421 -640T419 -631T415 -617T408 -594T399 -560T385 -512T367 -448T343 -364T312 -259L203 119L138 41L111 67L212 188L264 248L472 -474L983 1140Q988 1150 1001 1150'], // INTEGRAL 0x222B: [1361,862,556,55,944,'114 -798Q132 -824 165 -824H167Q195 -824 223 -764T275 -600T320 -391T362 -164Q365 -143 367 -133Q439 292 523 655T645 1127Q651 1145 655 1157T672 1201T699 1257T733 1306T777 1346T828 1360Q884 1360 912 1325T944 1245Q944 1220 932 1205T909 1186T887 1183Q866 1183 849 1198T832 1239Q832 1287 885 1296L882 1300Q879 1303 874 1307T866 1313Q851 1323 833 1323Q819 1323 807 1311T775 1255T736 1139T689 936T633 628Q574 293 510 -5T410 -437T355 -629Q278 -862 165 -862Q125 -862 92 -831T55 -746Q55 -711 74 -698T112 -685Q133 -685 150 -700T167 -741Q167 -789 114 -798'], // DOUBLE INTEGRAL 0x222C: [1361,862,1084,55,1472,'114 -798Q132 -824 165 -824H167Q195 -824 223 -764T275 -600T320 -391T362 -164Q365 -143 367 -133Q439 292 523 655T645 1127Q651 1145 655 1157T672 1201T699 1257T733 1306T777 1346T828 1360Q884 1360 912 1325T944 1245Q944 1220 932 1205T909 1186T887 1183Q866 1183 849 1198T832 1239Q832 1287 885 1296L882 1300Q879 1303 874 1307T866 1313Q851 1323 833 1323Q819 1323 807 1311T775 1255T736 1139T689 936T633 628Q574 293 510 -5T410 -437T355 -629Q278 -862 165 -862Q125 -862 92 -831T55 -746Q55 -711 74 -698T112 -685Q133 -685 150 -700T167 -741Q167 -789 114 -798ZM642 -798Q660 -824 693 -824H695Q723 -824 751 -764T803 -600T848 -391T890 -164Q893 -143 895 -133Q967 292 1051 655T1173 1127Q1179 1145 1183 1157T1200 1201T1227 1257T1261 1306T1305 1346T1356 1360Q1412 1360 1440 1325T1472 1245Q1472 1220 1460 1205T1437 1186T1415 1183Q1394 1183 1377 1198T1360 1239Q1360 1287 1413 1296L1410 1300Q1407 1303 1402 1307T1394 1313Q1379 1323 1361 1323Q1347 1323 1335 1311T1303 1255T1264 1139T1217 936T1161 628Q1102 293 1038 -5T938 -437T883 -629Q806 -862 693 -862Q653 -862 620 -831T583 -746Q583 -711 602 -698T640 -685Q661 -685 678 -700T695 -741Q695 -789 642 -798'], // TRIPLE INTEGRAL 0x222D: [1361,862,1592,55,1980,'114 -798Q132 -824 165 -824H167Q195 -824 223 -764T275 -600T320 -391T362 -164Q365 -143 367 -133Q439 292 523 655T645 1127Q651 1145 655 1157T672 1201T699 1257T733 1306T777 1346T828 1360Q884 1360 912 1325T944 1245Q944 1220 932 1205T909 1186T887 1183Q866 1183 849 1198T832 1239Q832 1287 885 1296L882 1300Q879 1303 874 1307T866 1313Q851 1323 833 1323Q819 1323 807 1311T775 1255T736 1139T689 936T633 628Q574 293 510 -5T410 -437T355 -629Q278 -862 165 -862Q125 -862 92 -831T55 -746Q55 -711 74 -698T112 -685Q133 -685 150 -700T167 -741Q167 -789 114 -798ZM642 -798Q660 -824 693 -824H695Q723 -824 751 -764T803 -600T848 -391T890 -164Q893 -143 895 -133Q967 292 1051 655T1173 1127Q1179 1145 1183 1157T1200 1201T1227 1257T1261 1306T1305 1346T1356 1360Q1412 1360 1440 1325T1472 1245Q1472 1220 1460 1205T1437 1186T1415 1183Q1394 1183 1377 1198T1360 1239Q1360 1287 1413 1296L1410 1300Q1407 1303 1402 1307T1394 1313Q1379 1323 1361 1323Q1347 1323 1335 1311T1303 1255T1264 1139T1217 936T1161 628Q1102 293 1038 -5T938 -437T883 -629Q806 -862 693 -862Q653 -862 620 -831T583 -746Q583 -711 602 -698T640 -685Q661 -685 678 -700T695 -741Q695 -789 642 -798ZM1150 -798Q1168 -824 1201 -824H1203Q1231 -824 1259 -764T1311 -600T1356 -391T1398 -164Q1401 -143 1403 -133Q1475 292 1559 655T1681 1127Q1687 1145 1691 1157T1708 1201T1735 1257T1769 1306T1813 1346T1864 1360Q1920 1360 1948 1325T1980 1245Q1980 1220 1968 1205T1945 1186T1923 1183Q1902 1183 1885 1198T1868 1239Q1868 1287 1921 1296L1918 1300Q1915 1303 1910 1307T1902 1313Q1887 1323 1869 1323Q1855 1323 1843 1311T1811 1255T1772 1139T1725 936T1669 628Q1610 293 1546 -5T1446 -437T1391 -629Q1314 -862 1201 -862Q1161 -862 1128 -831T1091 -746Q1091 -711 1110 -698T1148 -685Q1169 -685 1186 -700T1203 -741Q1203 -789 1150 -798'], // CONTOUR INTEGRAL 0x222E: [1360,862,556,55,944,'114 -798Q132 -824 165 -824H167Q195 -824 223 -764T275 -600T320 -391T362 -164Q365 -143 367 -133Q382 -52 390 2Q314 40 276 99Q230 167 230 249Q230 363 305 436T484 519H494L503 563Q587 939 632 1087T727 1298Q774 1360 828 1360Q884 1360 912 1325T944 1245Q944 1220 932 1205T909 1186T887 1183Q866 1183 849 1198T832 1239Q832 1287 885 1296L882 1300Q879 1303 874 1307T866 1313Q851 1323 833 1323Q766 1323 688 929Q662 811 610 496Q770 416 770 249Q770 147 701 68T516 -21H506L497 -65Q407 -464 357 -623T237 -837Q203 -862 165 -862Q125 -862 92 -831T55 -746Q55 -711 74 -698T112 -685Q133 -685 150 -700T167 -741Q167 -789 114 -798ZM480 478Q460 478 435 470T380 444T327 401T287 335T271 249Q271 124 375 56L397 43L431 223L485 478H480ZM519 20Q545 20 578 33T647 72T706 144T730 249Q730 383 603 455Q603 454 597 421T582 343T569 276Q516 22 515 20H519'], // N-ARY LOGICAL AND 0x22C0: [950,450,1111,55,1055,'1055 -401Q1055 -419 1042 -434T1007 -450Q977 -450 963 -423Q959 -417 757 167L555 750L353 167Q151 -417 147 -423Q134 -450 104 -450Q84 -450 70 -436T55 -401Q55 -394 56 -390Q59 -381 284 270T512 925Q525 950 555 950Q583 950 597 926Q599 923 825 270T1054 -391Q1055 -394 1055 -401'], // N-ARY LOGICAL OR 0x22C1: [950,450,1111,55,1055,'55 900Q55 919 69 934T103 950Q134 950 147 924Q152 913 353 333L555 -250L757 333Q958 913 963 924Q978 950 1007 950Q1028 950 1041 935T1055 901Q1055 894 1054 891Q1052 884 826 231T597 -426Q583 -450 556 -450Q527 -450 512 -424Q510 -421 285 229T56 890Q55 893 55 900'], // N-ARY INTERSECTION 0x22C2: [949,451,1111,55,1055,'57 516Q68 602 104 675T190 797T301 882T423 933T542 949Q594 949 606 948Q780 928 901 815T1048 545Q1053 516 1053 475T1055 49Q1055 -406 1054 -410Q1051 -427 1037 -438T1006 -450T976 -439T958 -411Q957 -407 957 37Q957 484 956 494Q945 643 831 747T554 852Q481 852 411 826Q301 786 232 696T154 494Q153 484 153 37Q153 -407 152 -411Q148 -428 135 -439T104 -450T73 -439T56 -410Q55 -406 55 49Q56 505 57 516'], // N-ARY UNION 0x22C3: [950,449,1111,55,1055,'56 911Q58 926 71 938T103 950Q120 950 134 939T152 911Q153 907 153 463Q153 16 154 6Q165 -143 279 -247T556 -352Q716 -352 830 -248T956 6Q957 16 957 463Q957 907 958 911Q962 928 975 939T1006 950T1037 939T1054 911Q1055 906 1055 451Q1054 -5 1053 -16Q1029 -207 889 -328T555 -449Q363 -449 226 -331T62 -45Q57 -16 57 25T55 451Q55 906 56 911'], // LEFT CEILING 0x2308: [1150,649,528,224,511,'224 -649V1150H511V1099H275V-649H224'], // RIGHT CEILING 0x2309: [1150,649,528,16,303,'16 1099V1150H303V-649H252V1099H16'], // LEFT FLOOR 0x230A: [1150,649,528,224,511,'224 -649V1150H275V-598H511V-649H224'], // RIGHT FLOOR 0x230B: [1150,649,528,16,303,'252 -598V1150H303V-649H16V-598H252'], // MATHEMATICAL LEFT ANGLE BRACKET 0x27E8: [1150,649,611,112,524,'112 244V258L473 1130Q482 1150 498 1150Q511 1150 517 1142T523 1125V1118L344 685Q304 587 257 473T187 305L165 251L344 -184L523 -616V-623Q524 -634 517 -641T499 -649Q484 -649 473 -629L112 244'], // MATHEMATICAL RIGHT ANGLE BRACKET 0x27E9: [1150,649,611,85,498,'112 -649Q103 -649 95 -642T87 -623V-616L266 -184L445 251Q445 252 356 466T178 898T86 1123Q85 1134 93 1142T110 1150Q126 1150 133 1137Q134 1136 317 695L498 258V244L317 -194Q134 -635 133 -636Q126 -649 112 -649'], // N-ARY CIRCLED DOT OPERATOR 0x2A00: [949,449,1511,56,1454,'668 944Q697 949 744 949Q803 949 814 948Q916 937 1006 902T1154 826T1262 730T1336 638T1380 563Q1454 415 1454 250Q1454 113 1402 -14T1258 -238T1036 -391T755 -449Q608 -449 477 -392T255 -240T110 -16T56 250Q56 387 105 510T239 723T434 871T668 944ZM755 -352Q922 -352 1061 -269T1278 -48T1356 250Q1356 479 1202 652T809 850Q798 851 747 851Q634 851 527 806T337 682T204 491T154 251Q154 128 201 17T329 -176T521 -304T755 -352ZM665 250Q665 290 692 315T758 341Q792 339 818 315T845 250Q845 211 819 186T755 160Q716 160 691 186T665 250'], // N-ARY CIRCLED PLUS OPERATOR 0x2A01: [949,449,1511,56,1454,'668 944Q697 949 744 949Q803 949 814 948Q916 937 1006 902T1154 826T1262 730T1336 638T1380 563Q1454 415 1454 250Q1454 113 1402 -14T1258 -238T1036 -391T755 -449Q608 -449 477 -392T255 -240T110 -16T56 250Q56 387 105 510T239 723T434 871T668 944ZM706 299V850H704Q519 832 386 725T198 476Q181 433 169 379T156 300Q156 299 431 299H706ZM1116 732Q1054 778 982 807T871 842T810 849L804 850V299H1079Q1354 299 1354 300Q1354 311 1352 329T1336 402T1299 506T1228 620T1116 732ZM706 -350V201H431Q156 201 156 200Q156 189 158 171T174 98T211 -6T282 -120T395 -232Q428 -257 464 -277T527 -308T587 -328T636 -339T678 -346T706 -350ZM1354 200Q1354 201 1079 201H804V-350Q808 -349 838 -345T887 -338T940 -323T1010 -295Q1038 -282 1067 -265T1144 -208T1229 -121T1301 0T1349 158Q1354 188 1354 200'], // N-ARY CIRCLED TIMES OPERATOR 0x2A02: [949,449,1511,56,1454,'668 944Q697 949 744 949Q803 949 814 948Q916 937 1006 902T1154 826T1262 730T1336 638T1380 563Q1454 415 1454 250Q1454 113 1402 -14T1258 -238T1036 -391T755 -449Q608 -449 477 -392T255 -240T110 -16T56 250Q56 387 105 510T239 723T434 871T668 944ZM1143 709Q1138 714 1129 722T1086 752T1017 791T925 826T809 850Q798 851 747 851H728Q659 851 571 823T408 741Q367 713 367 709L755 320L1143 709ZM297 639Q296 639 282 622T247 570T205 491T169 382T154 250T168 118T204 9T247 -70T282 -122L297 -139L685 250L297 639ZM1213 -139Q1214 -139 1228 -122T1263 -70T1305 9T1341 118T1356 250T1342 382T1306 491T1263 570T1228 622L1213 639L825 250L1213 -139ZM367 -209Q373 -215 384 -224T434 -258T514 -302T622 -336T755 -352T887 -338T996 -302T1075 -259T1126 -224L1143 -209L755 180Q754 180 561 -14T367 -209'], // N-ARY UNION OPERATOR WITH PLUS 0x2A04: [950,449,1111,55,1055,'56 911Q58 926 71 938T103 950Q120 950 134 939T152 911Q153 907 153 463Q153 16 154 6Q165 -143 279 -247T556 -352Q716 -352 830 -248T956 6Q957 16 957 463Q957 907 958 911Q962 928 975 939T1006 950T1037 939T1054 911Q1055 906 1055 451Q1054 -5 1053 -16Q1029 -207 889 -328T555 -449Q363 -449 226 -331T62 -45Q57 -16 57 25T55 451Q55 906 56 911ZM507 554Q511 570 523 581T554 593Q571 593 585 582T603 554Q604 551 604 443V338H709Q817 338 820 337Q835 334 847 321T859 290Q859 254 819 241Q816 240 709 240H604V134Q604 48 604 34T598 11Q583 -15 555 -15Q526 -15 512 11Q507 20 507 34T506 134V240H401H344Q292 240 278 246Q251 259 251 290Q251 309 264 321T290 337Q293 338 401 338H506V443Q506 551 507 554'], // N-ARY SQUARE UNION OPERATOR 0x2A06: [950,450,1111,54,1056,'56 911Q60 927 72 938T103 950Q120 950 134 939T152 911Q153 907 153 277V-352H957V277Q957 907 958 911Q962 928 975 939T1006 950T1036 939T1054 911V891Q1054 871 1054 836T1054 754T1054 647T1055 525T1055 390T1055 250T1055 111T1055 -24T1055 -147T1054 -253T1054 -335T1054 -391V-411Q1047 -442 1016 -449Q1011 -450 552 -450L94 -449Q63 -439 56 -411V-391Q56 -371 56 -336T56 -254T56 -147T55 -25T55 110T55 250T55 389T55 524T55 647T56 753T56 835T56 891V911'] }; MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/Size2/Regular/Main.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/Size3/Regular/Main.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_Size3'] = { directory: 'Size3/Regular', family: 'MathJax_Size3', id: 'MJSZ3', // SPACE 0x20: [0,0,250,0,0,''], // LEFT PARENTHESIS 0x28: [1450,949,736,208,701,'701 -940Q701 -943 695 -949H664Q662 -947 636 -922T591 -879T537 -818T475 -737T412 -636T350 -511T295 -362T250 -186T221 17T209 251Q209 962 573 1361Q596 1386 616 1405T649 1437T664 1450H695Q701 1444 701 1441Q701 1436 681 1415T629 1356T557 1261T476 1118T400 927T340 675T308 359Q306 321 306 250Q306 -139 400 -430T690 -924Q701 -936 701 -940'], // RIGHT PARENTHESIS 0x29: [1450,949,736,34,527,'34 1438Q34 1446 37 1448T50 1450H56H71Q73 1448 99 1423T144 1380T198 1319T260 1238T323 1137T385 1013T440 864T485 688T514 485T526 251Q526 134 519 53Q472 -519 162 -860Q139 -885 119 -904T86 -936T71 -949H56Q43 -949 39 -947T34 -937Q88 -883 140 -813Q428 -430 428 251Q428 453 402 628T338 922T245 1146T145 1309T46 1425Q44 1427 42 1429T39 1433T36 1436L34 1438'], // SOLIDUS 0x2F: [1450,949,1044,55,988,'81 -949Q71 -949 63 -941T55 -921Q55 -917 56 -915Q59 -906 498 264T939 1438Q945 1450 960 1450Q972 1450 980 1441T988 1421Q982 1403 839 1020L398 -155Q107 -934 103 -938Q96 -949 81 -949'], // LEFT SQUARE BRACKET 0x5B: [1450,949,528,247,516,'247 -949V1450H516V1388H309V-887H516V-949H247'], // REVERSE SOLIDUS 0x5C: [1450,949,1044,56,988,'988 -922Q988 -933 980 -941T962 -949Q947 -949 940 -938Q936 -934 645 -155L204 1020Q56 1416 56 1424Q56 1433 62 1441T84 1450Q97 1448 103 1439Q107 1435 398 656L839 -519Q988 -918 988 -922'], // RIGHT SQUARE BRACKET 0x5D: [1450,949,528,11,280,'11 1388V1450H280V-949H11V-887H218V1388H11'], // LEFT CURLY BRACKET 0x7B: [1450,949,750,130,618,'618 -943L612 -949H582L568 -943Q472 -903 411 -841T332 -703Q327 -682 327 -653T325 -350Q324 -28 323 -18Q317 24 301 61T264 124T221 171T179 205T147 225T132 234Q130 238 130 250Q130 255 130 258T131 264T132 267T134 269T139 272T144 275Q207 308 256 367Q310 436 323 519Q324 529 325 851Q326 1124 326 1154T332 1205Q369 1358 566 1443L582 1450H612L618 1444V1429Q618 1413 616 1411L608 1406Q599 1402 585 1393T552 1372T515 1343T479 1305T449 1257T429 1200Q425 1180 425 1152T423 851Q422 579 422 549T416 498Q407 459 388 424T346 364T297 318T250 284T214 264T197 254L188 251L205 242Q290 200 345 138T416 3Q421 -18 421 -48T423 -349Q423 -397 423 -472Q424 -677 428 -694Q429 -697 429 -699Q434 -722 443 -743T465 -782T491 -816T519 -845T548 -868T574 -886T595 -899T610 -908L616 -910Q618 -912 618 -928V-943'], // RIGHT CURLY BRACKET 0x7D: [1450,949,750,131,618,'131 1414T131 1429T133 1447T148 1450H153H167L182 1444Q276 1404 336 1343T415 1207Q421 1184 421 1154T423 851L424 531L426 517Q434 462 460 415T518 339T571 296T608 274Q615 270 616 267T618 251Q618 241 618 238T615 232T608 227Q542 194 491 132T426 -15L424 -29L423 -350Q422 -622 422 -652T415 -706Q397 -780 337 -841T182 -943L167 -949H153Q137 -949 134 -946T131 -928Q131 -914 132 -911T144 -904Q146 -903 148 -902Q299 -820 323 -680Q324 -663 325 -349T327 -19Q355 145 541 241L561 250L541 260Q356 355 327 520Q326 537 325 850T323 1181Q315 1227 293 1267T244 1332T193 1374T151 1401T132 1413Q131 1414 131 1429'], // MODIFIER LETTER CIRCUMFLEX ACCENT 0x2C6: [772,-564,1444,-4,1447,'1439 564Q1434 564 1080 631T722 698Q719 698 362 631Q7 564 4 564L0 583Q-4 602 -4 603L720 772L1083 688Q1446 603 1447 603Q1447 602 1443 583L1439 564'], // SMALL TILDE 0x2DC: [749,-609,1444,1,1442,'1 643Q1 646 76 671T271 722T476 749Q555 749 626 736T742 706T856 676T999 662Q1088 662 1192 684T1363 727T1432 749Q1432 745 1437 731T1442 716Q1442 714 1381 693T1212 645T1012 611Q1000 610 955 610Q851 610 701 653T444 697Q355 697 251 676T80 632T11 610Q11 614 6 628T1 643'], // COMBINING CIRCUMFLEX ACCENT 0x302: [772,-564,0,-1448,3,'-5 564Q-9 564 -363 631T-722 698Q-725 698 -1082 631Q-1437 564 -1440 564L-1444 583Q-1448 602 -1448 603L-724 772L-361 688Q2 603 3 603Q3 602 -1 583L-5 564'], // COMBINING TILDE 0x303: [749,-609,0,-1443,-2,'-1443 643Q-1443 646 -1368 671T-1173 722T-968 749Q-889 749 -818 736T-702 706T-588 676T-445 662Q-356 662 -252 684T-81 727T-12 749Q-12 745 -7 731T-2 716Q-2 714 -63 693T-232 645T-432 611Q-444 610 -489 610Q-593 610 -743 653T-1000 697Q-1089 697 -1193 676T-1364 632T-1433 610Q-1433 614 -1438 628T-1443 643'], // SQUARE ROOT 0x221A: [1450,950,1000,111,1020,'424 -948Q422 -947 313 -434T202 80L170 31Q165 24 157 10Q137 -21 137 -21Q131 -16 124 -8L111 5L264 248L473 -720Q473 -717 727 359T983 1440Q989 1450 1001 1450Q1007 1450 1013 1445T1020 1433Q1020 1425 742 244T460 -941Q458 -950 439 -950H436Q424 -950 424 -948'], // LEFT CEILING 0x2308: [1450,949,583,246,571,'246 -949V1450H571V1388H308V-949H246'], // RIGHT CEILING 0x2309: [1450,949,583,11,336,'11 1388V1450H336V-949H274V1388H11'], // LEFT FLOOR 0x230A: [1450,949,583,246,571,'246 -949V1450H308V-887H571V-949H246'], // RIGHT FLOOR 0x230B: [1450,949,583,11,336,'274 -887V1450H336V-949H11V-887H274'], // MATHEMATICAL LEFT ANGLE BRACKET 0x27E8: [1450,950,750,126,654,'126 242V259L361 845Q595 1431 597 1435Q610 1450 624 1450Q634 1450 644 1443T654 1419V1411L422 831Q190 253 190 250T422 -331L654 -910V-919Q654 -936 644 -943T624 -950Q612 -950 597 -935Q595 -931 361 -345L126 242'], // MATHEMATICAL RIGHT ANGLE BRACKET 0x27E9: [1450,949,750,94,623,'94 1424Q94 1426 97 1432T107 1444T124 1450Q141 1450 152 1435Q154 1431 388 845L623 259V242L388 -345Q153 -933 152 -934Q142 -949 127 -949H125Q95 -949 95 -919V-910L327 -331Q559 247 559 250T327 831Q94 1411 94 1424'] }; MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/Size3/Regular/Main.js"); /************************************************************* * * MathJax/jax/output/SVG/fonts/TeX/svg/Size4/Regular/Main.js * * Copyright (c) 2011-2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ MathJax.OutputJax.SVG.FONTDATA.FONTS['MathJax_Size4'] = { directory: 'Size4/Regular', family: 'MathJax_Size4', id: 'MJSZ4', // SPACE 0x20: [0,0,250,0,0,''], // LEFT PARENTHESIS 0x28: [1750,1249,792,237,758,'758 -1237T758 -1240T752 -1249H736Q718 -1249 717 -1248Q711 -1245 672 -1199Q237 -706 237 251T672 1700Q697 1730 716 1749Q718 1750 735 1750H752Q758 1744 758 1741Q758 1737 740 1713T689 1644T619 1537T540 1380T463 1176Q348 802 348 251Q348 -242 441 -599T744 -1218Q758 -1237 758 -1240'], // RIGHT PARENTHESIS 0x29: [1750,1250,792,33,554,'33 1741Q33 1750 51 1750H60H65Q73 1750 81 1743T119 1700Q554 1207 554 251Q554 -707 119 -1199Q76 -1250 66 -1250Q65 -1250 62 -1250T56 -1249Q55 -1249 53 -1249T49 -1250Q33 -1250 33 -1239Q33 -1236 50 -1214T98 -1150T163 -1052T238 -910T311 -727Q443 -335 443 251Q443 402 436 532T405 831T339 1142T224 1438T50 1716Q33 1737 33 1741'], // SOLIDUS 0x2F: [1750,1249,1278,56,1221,'1166 1738Q1176 1750 1189 1750T1211 1742T1221 1721Q1221 1720 1221 1718T1220 1715Q1219 1708 666 238T111 -1237Q102 -1249 86 -1249Q74 -1249 65 -1240T56 -1220Q56 -1219 56 -1217T57 -1214Q58 -1207 611 263T1166 1738'], // LEFT SQUARE BRACKET 0x5B: [1750,1249,583,269,577,'269 -1249V1750H577V1677H342V-1176H577V-1249H269'], // REVERSE SOLIDUS 0x5C: [1750,1249,1278,56,1221,'56 1720Q56 1732 64 1741T85 1750Q104 1750 111 1738Q113 1734 666 264T1220 -1214Q1220 -1215 1220 -1217T1221 -1220Q1221 -1231 1212 -1240T1191 -1249Q1175 -1249 1166 -1237Q1164 -1233 611 237T57 1715Q57 1716 56 1718V1720'], // RIGHT SQUARE BRACKET 0x5D: [1750,1249,583,5,313,'5 1677V1750H313V-1249H5V-1176H240V1677H5'], // LEFT CURLY BRACKET 0x7B: [1750,1249,806,144,661,'661 -1243L655 -1249H622L604 -1240Q503 -1190 434 -1107T348 -909Q346 -897 346 -499L345 -98L343 -82Q335 3 287 87T157 223Q146 232 145 236Q144 240 144 250Q144 265 145 268T157 278Q242 333 288 417T343 583L345 600L346 1001Q346 1398 348 1410Q379 1622 600 1739L622 1750H655L661 1744V1727V1721Q661 1712 661 1710T657 1705T648 1700T630 1690T602 1668Q589 1659 574 1643T531 1593T484 1508T459 1398Q458 1389 458 1001Q458 614 457 605Q441 435 301 316Q254 277 202 251L250 222Q260 216 301 185Q443 66 457 -104Q458 -113 458 -501Q458 -888 459 -897Q463 -944 478 -988T509 -1060T548 -1114T580 -1149T602 -1167Q620 -1183 634 -1192T653 -1202T659 -1207T661 -1220V-1226V-1243'], // RIGHT CURLY BRACKET 0x7D: [1750,1249,806,144,661,'144 1727Q144 1743 146 1746T162 1750H167H183L203 1740Q274 1705 325 1658T403 1562T440 1478T456 1410Q458 1398 458 1001Q459 661 459 624T465 558Q470 526 480 496T502 441T529 395T559 356T588 325T615 301T637 284T654 273L660 269V266Q660 263 660 259T661 250V239Q661 236 661 234T660 232T656 229T649 224Q577 179 528 105T465 -57Q460 -86 460 -123T458 -499V-661Q458 -857 457 -893T447 -955Q425 -1048 359 -1120T203 -1239L183 -1249H168Q150 -1249 147 -1246T144 -1226Q144 -1213 145 -1210T153 -1202Q169 -1193 186 -1181T232 -1140T282 -1081T322 -1000T345 -897Q346 -888 346 -501Q346 -113 347 -104Q359 58 503 184Q554 226 603 250Q504 299 430 393T347 605Q346 614 346 1002Q346 1389 345 1398Q338 1493 288 1573T153 1703Q146 1707 145 1710T144 1727'], // MODIFIER LETTER CIRCUMFLEX ACCENT 0x2C6: [845,-561,1889,-14,1902,'5 561Q-4 561 -9 582T-14 618Q-14 623 -13 625Q-11 628 461 736T943 845Q945 845 1417 738T1896 628Q1902 628 1902 618Q1902 607 1897 584T1883 561Q1881 561 1412 654L945 750L476 654Q6 561 5 561'], // SMALL TILDE 0x2DC: [823,-582,1889,0,1885,'1212 583Q1124 583 1048 603T923 647T799 691T635 711Q524 711 375 679T120 615L16 583Q14 584 12 587T9 592Q-2 650 2 659Q2 669 38 687Q54 696 146 723T309 767Q527 823 666 823Q759 823 837 803T964 759T1088 715T1252 695Q1363 695 1512 727T1764 791T1871 823Q1872 822 1874 819T1878 814Q1885 783 1885 753Q1885 748 1884 747Q1884 738 1849 719Q1836 712 1740 682T1484 617T1212 583'], // COMBINING CIRCUMFLEX ACCENT 0x302: [845,-561,0,-1903,13,'-1884 561Q-1893 561 -1898 582T-1903 618Q-1903 623 -1902 625Q-1900 628 -1428 736T-946 845Q-944 845 -472 738T7 628Q13 628 13 618Q13 607 8 584T-6 561Q-8 561 -477 654L-944 750L-1413 654Q-1883 561 -1884 561'], // COMBINING TILDE 0x303: [823,-582,0,-1889,-4,'-677 583Q-765 583 -841 603T-966 647T-1090 691T-1254 711Q-1365 711 -1514 679T-1768 615L-1873 583Q-1875 584 -1877 587T-1880 592Q-1891 650 -1887 659Q-1887 669 -1851 687Q-1835 696 -1743 723T-1580 767Q-1362 823 -1223 823Q-1130 823 -1052 803T-925 759T-801 715T-637 695Q-526 695 -377 727T-125 791T-18 823Q-17 822 -15 819T-11 814Q-4 782 -4 753Q-4 748 -5 747Q-5 738 -40 719Q-53 712 -149 682T-405 617T-677 583'], // SQUARE ROOT 0x221A: [1750,1250,1000,111,1020,'983 1739Q988 1750 1001 1750Q1008 1750 1013 1745T1020 1733Q1020 1726 742 244T460 -1241Q458 -1250 439 -1250H436Q424 -1250 424 -1248L410 -1166Q395 -1083 367 -920T312 -601L201 44L137 -83L111 -57L187 96L264 247Q265 246 369 -357Q470 -958 473 -963L727 384Q979 1729 983 1739'], // LEFT CEILING 0x2308: [1750,1249,639,269,633,'269 -1249V1750H633V1677H342V-1249H269'], // RIGHT CEILING 0x2309: [1750,1249,639,5,369,'5 1677V1750H369V-1249H296V1677H5'], // LEFT FLOOR 0x230A: [1750,1249,639,269,633,'269 -1249V1750H342V-1176H633V-1249H269'], // RIGHT FLOOR 0x230B: [1750,1249,639,5,369,'296 -1176V1750H369V-1249H5V-1176H296'], // LEFT PARENTHESIS UPPER HOOK 0x239B: [1155,655,875,291,843,'837 1154Q843 1148 843 1145Q843 1141 818 1106T753 1002T667 841T574 604T494 299Q417 -84 417 -609Q417 -641 416 -647T411 -654Q409 -655 366 -655Q299 -655 297 -654Q292 -652 292 -643T291 -583Q293 -400 304 -242T347 110T432 470T574 813T785 1136Q787 1139 790 1142T794 1147T796 1150T799 1152T802 1153T807 1154T813 1154H819H837'], // LEFT PARENTHESIS EXTENSION 0x239C: [610,11,875,291,417,'413 -9Q412 -9 407 -9T388 -10T354 -10Q300 -10 297 -9Q294 -8 293 -5Q291 5 291 127V300Q291 602 292 605L296 609Q298 610 366 610Q382 610 392 610T407 610T412 609Q416 609 416 592T417 473V127Q417 -9 413 -9'], // LEFT PARENTHESIS LOWER HOOK 0x239D: [1165,644,875,291,843,'843 -635Q843 -638 837 -644H820Q801 -644 800 -643Q792 -635 785 -626Q684 -503 605 -363T473 -75T385 216T330 518T302 809T291 1093Q291 1144 291 1153T296 1164Q298 1165 366 1165Q409 1165 411 1164Q415 1163 416 1157T417 1119Q417 529 517 109T833 -617Q843 -631 843 -635'], // RIGHT PARENTHESIS UPPER HOOK 0x239E: [1154,655,875,31,583,'31 1143Q31 1154 49 1154H59Q72 1154 75 1152T89 1136Q190 1013 269 873T401 585T489 294T544 -8T572 -299T583 -583Q583 -634 583 -643T577 -654Q575 -655 508 -655Q465 -655 463 -654Q459 -653 458 -647T457 -609Q457 -58 371 340T100 1037Q87 1059 61 1098T31 1143'], // RIGHT PARENTHESIS EXTENSION 0x239F: [610,11,875,457,583,'579 -9Q578 -9 573 -9T554 -10T520 -10Q466 -10 463 -9Q460 -8 459 -5Q457 5 457 127V300Q457 602 458 605L462 609Q464 610 532 610Q548 610 558 610T573 610T578 609Q582 609 582 592T583 473V127Q583 -9 579 -9'], // RIGHT PARENTHESIS LOWER HOOK 0x23A0: [1165,644,875,31,583,'56 -644H50Q31 -644 31 -635Q31 -632 37 -622Q69 -579 100 -527Q286 -228 371 170T457 1119Q457 1161 462 1164Q464 1165 520 1165Q575 1165 577 1164Q582 1162 582 1153T583 1093Q581 910 570 752T527 400T442 40T300 -303T89 -626Q78 -640 75 -642T61 -644H56'], // LEFT SQUARE BRACKET UPPER CORNER 0x23A1: [1154,645,667,319,666,'319 -645V1154H666V1070H403V-645H319'], // LEFT SQUARE BRACKET EXTENSION 0x23A2: [602,0,667,319,403,'319 0V602H403V0H319'], // LEFT SQUARE BRACKET LOWER CORNER 0x23A3: [1155,644,667,319,666,'319 -644V1155H403V-560H666V-644H319'], // RIGHT SQUARE BRACKET UPPER CORNER 0x23A4: [1154,645,667,0,347,'0 1070V1154H347V-645H263V1070H0'], // RIGHT SQUARE BRACKET EXTENSION 0x23A5: [602,0,667,263,347,'263 0V602H347V0H263'], // RIGHT SQUARE BRACKET LOWER CORNER 0x23A6: [1155,644,667,0,347,'263 -560V1155H347V-644H0V-560H263'], // LEFT CURLY BRACKET UPPER HOOK 0x23A7: [899,10,889,383,718,'712 899L718 893V876V865Q718 854 704 846Q627 793 577 710T510 525Q510 524 509 521Q505 493 504 349Q504 345 504 334Q504 277 504 240Q504 -2 503 -4Q502 -8 494 -9T444 -10Q392 -10 390 -9Q387 -8 386 -5Q384 5 384 230Q384 262 384 312T383 382Q383 481 392 535T434 656Q510 806 664 892L677 899H712'], // LEFT CURLY BRACKET MIDDLE PIECE 0x23A8: [1160,660,889,170,504,'389 1159Q391 1160 455 1160Q496 1160 498 1159Q501 1158 502 1155Q504 1145 504 924Q504 691 503 682Q494 549 425 439T243 259L229 250L243 241Q349 175 421 66T503 -182Q504 -191 504 -424Q504 -600 504 -629T499 -659H498Q496 -660 444 -660T390 -659Q387 -658 386 -655Q384 -645 384 -425V-282Q384 -176 377 -116T342 10Q325 54 301 92T255 155T214 196T183 222T171 232Q170 233 170 250T171 268Q171 269 191 284T240 331T300 407T354 524T383 679Q384 691 384 925Q384 1152 385 1155L389 1159'], // LEFT CURLY BRACKET LOWER HOOK 0x23A9: [10,899,889,384,718,'718 -893L712 -899H677L666 -893Q542 -825 468 -714T385 -476Q384 -466 384 -282Q384 3 385 5L389 9Q392 10 444 10Q486 10 494 9T503 4Q504 2 504 -239V-310V-366Q504 -470 508 -513T530 -609Q546 -657 569 -698T617 -767T661 -812T699 -843T717 -856T718 -876V-893'], // CURLY BRACKET EXTENSION 0x23AA: [310,10,889,384,504,'384 150V266Q384 304 389 309Q391 310 455 310Q496 310 498 309Q502 308 503 298Q504 283 504 150Q504 32 504 12T499 -9H498Q496 -10 444 -10T390 -9Q386 -8 385 2Q384 17 384 150'], // RIGHT CURLY BRACKET UPPER HOOK 0x23AB: [899,10,889,170,504,'170 875Q170 892 172 895T189 899H194H211L222 893Q345 826 420 715T503 476Q504 467 504 230Q504 51 504 21T499 -9H498Q496 -10 444 -10Q402 -10 394 -9T385 -4Q384 -2 384 240V311V366Q384 469 380 513T358 609Q342 657 319 698T271 767T227 812T189 843T171 856T170 875'], // RIGHT CURLY BRACKET MIDDLE PIECE 0x23AC: [1160,660,889,384,718,'389 1159Q391 1160 455 1160Q496 1160 498 1159Q501 1158 502 1155Q504 1145 504 925V782Q504 676 511 616T546 490Q563 446 587 408T633 345T674 304T705 278T717 268Q718 267 718 250T717 232Q717 231 697 216T648 169T588 93T534 -24T505 -179Q504 -191 504 -425Q504 -600 504 -629T499 -659H498Q496 -660 444 -660T390 -659Q387 -658 386 -655Q384 -645 384 -424Q384 -191 385 -182Q394 -49 463 61T645 241L659 250L645 259Q539 325 467 434T385 682Q384 692 384 873Q384 1153 385 1155L389 1159'], // RIGHT CURLY BRACKET LOWER HOOK 0x23AD: [10,899,889,170,505,'384 -239V-57Q384 4 389 9Q391 10 455 10Q496 10 498 9Q501 8 502 5Q504 -5 504 -230Q504 -261 504 -311T505 -381Q505 -486 492 -551T435 -691Q357 -820 222 -893L211 -899H195Q176 -899 173 -896T170 -874Q170 -858 171 -855T184 -846Q262 -793 312 -709T378 -525Q378 -524 379 -522Q383 -493 384 -351Q384 -345 384 -334Q384 -276 384 -239'], // RADICAL SYMBOL BOTTOM 0x23B7: [935,885,1056,111,742,'742 -871Q740 -873 737 -876T733 -880T730 -882T724 -884T714 -885H702L222 569L180 484Q138 399 137 399Q131 404 124 412L111 425L265 736L702 -586V168L703 922Q713 935 722 935Q734 935 742 920V-871'], // MATHEMATICAL LEFT ANGLE BRACKET 0x27E8: [1750,1248,806,140,703,'140 242V260L386 994Q633 1729 635 1732Q643 1745 657 1749Q658 1749 662 1749T668 1750Q682 1749 692 1740T702 1714V1705L214 251L703 -1204L702 -1213Q702 -1230 692 -1239T667 -1248H664Q647 -1248 635 -1231Q633 -1228 386 -493L140 242'], // MATHEMATICAL RIGHT ANGLE BRACKET 0x27E9: [1750,1248,806,103,665,'103 1714Q103 1732 114 1741T137 1750Q157 1750 170 1732Q172 1729 419 994L665 260V242L419 -493Q172 -1228 170 -1231Q158 -1248 141 -1248H138Q123 -1248 113 -1239T103 -1213V-1204L591 251L103 1705V1714'], // stix-radical symbol vertical extender 0xE000: [625,14,1056,702,742,'722 -14H720Q708 -14 702 0V306L703 612Q713 625 722 625Q734 625 742 610V0Q734 -14 724 -14H722'], // stix-radical symbol top corner piece 0xE001: [605,14,1056,702,1076,'702 589Q706 601 718 605H1061Q1076 597 1076 585Q1076 572 1061 565H742V0Q734 -14 724 -14H722H720Q708 -14 702 0V589'], // stix-horizontal brace, down left piece 0xE150: [120,213,450,-24,460,'-18 -213L-24 -207V-172L-16 -158Q75 2 260 84Q334 113 415 119Q418 119 427 119T440 120Q454 120 457 117T460 98V60V25Q460 7 457 4T441 0Q308 0 193 -55T25 -205Q21 -211 18 -212T-1 -213H-18'], // stix-horizontal brace, down right piece 0xE151: [120,213,450,-10,474,'-10 60Q-10 104 -10 111T-5 118Q-1 120 10 120Q96 120 190 84Q375 2 466 -158L474 -172V-207L468 -213H451H447Q437 -213 434 -213T428 -209T423 -202T414 -187T396 -163Q331 -82 224 -41T9 0Q-4 0 -7 3T-10 25V60'], // stix-horizontal brace, upper left piece 0xE152: [333,0,450,-24,460,'-24 327L-18 333H-1Q11 333 15 333T22 329T27 322T35 308T54 284Q115 203 225 162T441 120Q454 120 457 117T460 95V60V28Q460 8 457 4T442 0Q355 0 260 36Q75 118 -16 278L-24 292V327'], // stix-horizontal brace, upper right piece 0xE153: [333,0,450,-10,474,'-10 60V95Q-10 113 -7 116T9 120Q151 120 250 171T396 284Q404 293 412 305T424 324T431 331Q433 333 451 333H468L474 327V292L466 278Q375 118 190 36Q95 0 8 0Q-5 0 -7 3T-10 24V60'], // stix-oblique open face capital letter A 0xE154: [120,0,400,-10,410,'-10 0V120H410V0H-10'] }; MathJax.Ajax.loadComplete(MathJax.OutputJax.SVG.fontDir+"/Size4/Regular/Main.js"); }); HUB.Browser.Select(MathJax.Message.browsers); if (BASE.AuthorConfig && typeof BASE.AuthorConfig.AuthorInit === "function") {BASE.AuthorConfig.AuthorInit()} HUB.queue = BASE.Callback.Queue(); HUB.queue.Push( ["Post",STARTUP.signal,"Begin"], ["Config",STARTUP], ["Cookie",STARTUP], ["Styles",STARTUP], ["Message",STARTUP], function () { // Do Jax and Extensions in parallel, but wait for them all to complete var queue = BASE.Callback.Queue( STARTUP.Jax(), STARTUP.Extensions() ); return queue.Push({}); }, ["Menu",STARTUP], STARTUP.onLoad(), function () {MathJax.isReady = true}, // indicates that MathJax is ready to process math ["Typeset",STARTUP], ["Hash",STARTUP], ["MenuZoom",STARTUP], ["Post",STARTUP.signal,"End"] ); })("MathJax"); }} /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var NONE = 'none'; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ // https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript // author Jeff Ward var UUID = (function() { const self = {}, lut = []; for (let i = 0; i < 256; i++) { lut[i] = (i < 16 ? '0' : '') + (i).toString(16); } self.generate = function() { const d0 = Math.random() * 0xffffffff | 0 , d1 = Math.random() * 0xffffffff | 0 , d2 = Math.random() * 0xffffffff | 0 , d3 = Math.random() * 0xffffffff | 0; return lut[d0 & 0xff] + lut[d0 >> 8 & 0xff] + lut[d0 >> 16 & 0xff] + lut[d0 >> 24 & 0xff] + '-' + lut[d1 & 0xff] + lut[d1 >> 8 & 0xff] + '-' + lut[d1 >> 16 & 0x0f | 0x40] + lut[d1 >> 24 & 0xff] + '-' + lut[d2 & 0x3f | 0x80] + lut[d2 >> 8 & 0xff] + '-' + lut[d2 >> 16 & 0xff] + lut[d2 >> 24 & 0xff] + lut[d3 & 0xff] + lut[d3 >> 8 & 0xff] + lut[d3 >> 16 & 0xff] + lut[d3 >> 24 & 0xff]; } return self; })(); /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var ToolUtil = (function() { return { enableLineProps: function(s, base) { const c = s.find('.wb-prop-color'), w = s.find('.wb-prop-width'), o = this.enableOpacity(s, base); s.find('.wb-prop-fill').prop('disabled', true); s.find('.wb-prop-b, .wb-prop-i, .wb-prop-lock-color, .wb-prop-lock-fill').button("disable"); c.val(base.stroke.color).prop('disabled', false); w.val(base.stroke.width).prop('disabled', false); return {c: c, w: w, o: o}; } , enableAllProps: function(s, base) { const c = s.find('.wb-prop-color'), w = s.find('.wb-prop-width') , f = s.find('.wb-prop-fill') , lc = s.find('.wb-prop-lock-color'), lf = s.find('.wb-prop-lock-fill'); this.enableOpacity(s, base); s.find('.wb-prop-b, .wb-prop-i').button("disable"); lc.button("enable").button('option', 'icon', base.stroke.enabled ? 'ui-icon-unlocked' : 'ui-icon-locked'); lf.button("enable").button('option', 'icon', base.fill.enabled ? 'ui-icon-unlocked' : 'ui-icon-locked'); c.val(base.stroke.color).prop('disabled', !base.stroke.enabled); w.val(base.stroke.width).prop('disabled', false); f.val(base.fill.color).prop('disabled', !base.fill.enabled); } , enableOpacity: function(s, base) { const o = s.find('.wb-prop-opacity') o.val(100 * base.opacity).prop('disabled', false); return o; } , disableAllProps: function(s) { s.find('[class^="wb-prop"]').prop('disabled', true); if (!!s.find('.wb-prop-b').button("instance")) { s.find('.wb-prop-b, .wb-prop-i, .wb-prop-lock-color, .wb-prop-lock-fill').button("disable"); } } , addDeletedItem: function(canvas, o) { if ("Presentation" === o.fileType) { fabric.Image.fromURL(o._src, function(img) { const sz = img.getOriginalSize(); img.width = sz.width; img.height = sz.height; img.scaleX = img.scaleY = canvas.width / (canvas.getZoom() * sz.width); canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); }); } else { fabric.Image.fromURL(o._src || o.src, function(img) { const sz = img.getOriginalSize(); img.width = sz.width; img.height = sz.height; img.scaleX = img.scaleY = (o.scaleX || 1.) * o.width / sz.width; img.type = 'image'; img.videoStatus = function() {}; canvas.add(img); canvas.requestRenderAll(); }, o); } } , filter: function(_o, props) { return props.reduce(function(result, key) { result[key] = _o[key]; return result; }, {}); } }; })(); /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var Player = (function() { const player = {}, mainColor = '#ff6600', rad = 20; function _sendStatus(g, _paused, _pos) { g.status.paused = _paused; g.status.pos = _pos; wbAction('videoStatus', JSON.stringify({ wbId: g.canvas.wbId , uid: g.uid , status: { paused: _paused , pos: _pos } })); } player.create = function(canvas, _o, wb) { const vid = $('<video>').hide() .attr('class', 'wb-video slide-' + canvas.slide) .attr('id', 'wb-video-' + _o.uid) .attr("width", _o.width) .attr("height", _o.height) .prop('controls', true) .append($('<source>') .attr('type', 'video/mp4') .attr('src', _o._src)); $('#wb-tab-' + canvas.wbId).append(vid); fabric.Image.fromURL(_o._poster, function(poster) { poster.scaleX = poster.scaleY = _o.width / poster.getOriginalSize().width; const video = new fabric.Image(vid[0], {visible: false, objectCaching: false}); vid[0].onseeked = function() { canvas.requestRenderAll(); }; if (typeof(_o.status) === 'undefined') { _o.status = {paused: true}; } let playable = false; const trg = new fabric.Triangle({ left: 2.7 * rad , top: _o.height - 2.5 * rad , visible: _o.status.paused , angle: 90 , width: rad , height: rad , stroke: mainColor , fill: mainColor }); const rectPause1 = new fabric.Rect({ left: 1.6 * rad , top: _o.height - 2.5 * rad , visible: !_o.status.paused , width: rad / 3 , height: rad , stroke: mainColor , fill: mainColor }); const rectPause2 = new fabric.Rect({ left: 2.1 * rad , top: _o.height - 2.5 * rad , visible: !_o.status.paused , width: rad / 3 , height: rad , stroke: mainColor , fill: mainColor }); const play = new fabric.Group([ new fabric.Circle({ left: rad , top: _o.height - 3 * rad , radius: rad , stroke: mainColor , strokeWidth: 2 , fill: null }) , trg, rectPause1, rectPause2] , { objectCaching: false , visible: false }); const cProgress = new fabric.Rect({ left: 3.5 * rad , top: _o.height - 1.5 * rad , visible: false , width: _o.width - 5 * rad , height: rad / 2 , stroke: mainColor , fill: null , rx: 5 , ry: 5 }); const isDone = function() { return video.getElement().currentTime === video.getElement().duration; }; const updateProgress = function() { progress.set('width', (video.getElement().currentTime * cProgress.width) / video.getElement().duration); canvas.requestRenderAll(); }; const progress = new fabric.Rect({ left: 3.5 * rad , top: _o.height - 1.5 * rad , visible: false , width: 0 , height: rad / 2 , stroke: mainColor , fill: mainColor , rx: 5 , ry: 5 }); let request; const opts = $.extend({ subTargetCheck: true , objectCaching: false , omType: 'Video' , selectable: canvas.selection }, ToolUtil.filter(_o, ['fileId', 'fileType', 'slide', 'uid', '_poster', '_src', 'width', 'height', 'status'])); const group = new fabric.Group([video, poster, play, progress, cProgress], opts); const updateControls = function() { video.visible = true; poster.visible = false; trg.visible = group.status.paused; rectPause1.visible = !group.status.paused; rectPause2.visible = !group.status.paused; canvas.requestRenderAll(); }; const render = function () { if (isDone()) { _sendStatus(group, true, video.getElement().duration); updateControls(); } updateProgress(); if (group.status.paused) { cancelAnimationFrame(request); canvas.requestRenderAll(); } else { request = fabric.util.requestAnimFrame(render); } }; cProgress.on({ 'mousedown': function (evt) { const _ptr = canvas.getPointer(evt.e, true) , ptr = canvas._normalizePointer(group, _ptr) , l = ptr.x - cProgress.aCoords.bl.x + group.width / 2; _sendStatus(group, group.status.paused, l * video.getElement().duration / cProgress.width) } }); play.on({ /* * https://github.com/kangax/fabric.js/issues/4115 * 'mouseover': function() { circle1.set({strokeWidth: 4}); canvas.requestRenderAll(); } , 'mouseout': function() { circle1.set({ left: pos.left , top: pos.top , strokeWidth: 2 }); canvas.requestRenderAll(); } , */'mousedown': function () { play.set({ left: pos.left + 3 , top: pos.top + 3 }); canvas.requestRenderAll(); } , 'mouseup': function () { play.set({ left: pos.left , top: pos.top }); if (isDone()) { video.getElement().currentTime = 0; } _sendStatus(group, !group.status.paused, video.getElement().currentTime) updateControls(); } }); group.on({ 'mouseover': function() { play.visible = playable; cProgress.visible = playable; progress.visible = playable; canvas.requestRenderAll(); } , 'mouseout': function() { play.visible = false; cProgress.visible = false; progress.visible = false; canvas.requestRenderAll(); } }); group.setPlayable = function(_r) { playable = _r !== NONE; }; group.videoStatus = function(_status) { group.status = _status; updateControls(); video.getElement().currentTime = group.status.pos; updateProgress(); if (group.status.paused) { video.getElement().pause(); } else { const prom = video.getElement().play(); if (prom !== undefined) { prom.then(function() { fabric.util.requestAnimFrame(render); }).catch(function(err) { if ('NotAllowedError' === err.name) { VideoUtil.askPermission(function() { video.getElement().play(); fabric.util.requestAnimFrame(render); }); } }); } else { fabric.util.requestAnimFrame(render); } } } group.setPlayable(wb.getRole()); canvas.add(group); canvas.requestRenderAll(); player.modify(group, _o); const pos = {left: play.left, top: play.top}; }); }; player.modify = function(g, _o) { const opts = $.extend({ angle: 0 , left: 10 , scaleX: 1 , scaleY: 1 , top: 10 }, ToolUtil.filter(_o, ['angle', 'left', 'scaleX', 'scaleY', 'top'])); g.set(opts).setCoords(); g.canvas.requestRenderAll(); }; return player; })(); /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var Base = function() { const base = {}; base.objectCreated = function(o, canvas) { o.uid = UUID.generate(); o.slide = canvas.slide; canvas.trigger("wb:object:created", o); return o.uid; } return base; }; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var Pointer = function(wb, s, sBtn) { return { activate: function() { wb.eachCanvas(function(canvas) { canvas.selection = true; canvas.forEachObject(function(o) { o.selectable = true; }); }); ToolUtil.disableAllProps(s); sBtn.addClass('disabled'); } , deactivate: function() { wb.eachCanvas(function(canvas) { canvas.selection = false; canvas.forEachObject(function(o) { o.selectable = false; }); }); } }; }; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var APointer = function(wb, s, sBtn) { const pointer = Base(); pointer.user = ''; pointer.create = function(canvas, o) { fabric.Image.fromURL('./css/images/pointer.png', function(img) { const scale = 1. / wb.getZoom(); img.set({ left:15 , originX: 'right' , originY: 'top' }); const circle1 = new fabric.Circle({ radius: 20 , stroke: '#ff6600' , strokeWidth: 2 , fill: 'rgba(0,0,0,0)' , originX: 'center' , originY: 'center' }); const circle2 = new fabric.Circle({ radius: 6 , stroke: '#ff6600' , strokeWidth: 2 , fill: 'rgba(0,0,0,0)' , originX: 'center' , originY: 'center' }); const text = new fabric.Text(o.user, { fontSize: 12 , left: 10 , originX: 'left' , originY: 'bottom' }); const group = new fabric.Group([circle1, circle2, img, text], { left: o.x - 20 , top: o.y - 20 , scaleX: scale , scaleY: scale }); canvas.add(group); group.uid = o.uid; group.loaded = !!o.loaded; const count = 3; function go(_cnt) { if (_cnt < 0) { canvas.remove(group); return; } circle1.set({radius: 3}); circle2.set({radius: 6}); circle1.animate( 'radius', '20' , { onChange: canvas.renderAll.bind(canvas) , duration: 1000 , onComplete: function() {go(_cnt - 1);} }); circle2.animate( 'radius', '20' , { onChange: canvas.renderAll.bind(canvas) , duration: 1000 }); } go(count); }); } pointer.mouseUp = function(o) { const canvas = this , ptr = canvas.getPointer(o.e); if (pointer.user === '') { pointer.user = $('.room.sidebar .user.list .current .name').text(); } const obj = { type: 'pointer' , x: ptr.x , y: ptr.y , user: pointer.user }; obj.uid = pointer.objectCreated(obj, canvas); pointer.create(canvas, obj); } pointer.activate = function() { wb.eachCanvas(function(canvas) { canvas.selection = false; canvas.on('mouse:up', pointer.mouseUp); }); ToolUtil.disableAllProps(s); sBtn.addClass('disabled'); } pointer.deactivate = function() { wb.eachCanvas(function(canvas) { canvas.off('mouse:up', pointer.mouseUp); }); }; return pointer; }; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var ShapeBase = function() { const base = Base(); base.fill = {enabled: true, color: '#FFFF33'}; base.stroke = {enabled: true, color: '#FF6600', width: 5}; base.opacity = 1; return base; }; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var Text = function(wb, s, sBtn) { const text = ShapeBase(); text.obj = null; text.fabricType = 'i-text'; text.fill.color = '#000000'; text.stroke.enabled = false; text.stroke.width = 50; //fontSize text.stroke.color = '#000000'; text.style = {bold: false, italic: false}; text.createTextObj = function(canvas, pointer) { return new fabric.IText('', { left: pointer.x , top: pointer.y , padding: 7 , fill: text.fill.enabled ? text.fill.color : 'rgba(0,0,0,0)' , stroke: text.stroke.enabled ? text.stroke.color : 'rgba(0,0,0,0)' , fontSize: text.stroke.width , fontFamily: text.fontFamily , opacity: text.opacity }); }; text._onMouseDown = function() { text.obj.enterEditing(); }; text._onActivate = function() { WbArea.removeDeleteHandler(); }; text._onDeactivate = function() { WbArea.addDeleteHandler(); }; text.mouseDown = function(o) { const canvas = this , pointer = canvas.getPointer(o.e) , ao = canvas.getActiveObject(); if (!!ao && text.fabricType === ao.type) { text.obj = ao; } else { text.obj = text.createTextObj(canvas, pointer); if (text.style.bold) { text.obj.fontWeight = 'bold' } if (text.style.italic) { text.obj.fontStyle = 'italic' } canvas.add(text.obj).setActiveObject(text.obj); } text._onMouseDown(); }; text.activate = function() { wb.eachCanvas(function(canvas) { canvas.on('mouse:down', text.mouseDown); canvas.on('mouse:dblclick', text.doubleClick); canvas.selection = true; canvas.forEachObject(function(o) { if (text.fabricType === o.type) { o.selectable = true; o.editable = true; } }); }); text.fontFamily = $('#wb-text-style-block').css('font-family'); ToolUtil.enableAllProps(s, text); const b = s.find('.wb-prop-b').button("enable"); if (text.style.bold) { b.addClass('ui-state-active selected'); } else { b.removeClass('ui-state-active selected'); } const i = s.find('.wb-prop-i').button("enable"); if (text.style.italic) { i.addClass('ui-state-active selected'); } else { i.removeClass('ui-state-active selected'); } text._onActivate(); VideoUtil.highlight(sBtn.removeClass('disabled'), 5); }; text.deactivate = function() { wb.eachCanvas(function(canvas) { canvas.off('mouse:down', text.mouseDown); canvas.off('mouse:dblclick', text.doubleClick); canvas.selection = false; canvas.forEachObject(function(o) { if (text.fabricType === o.type) { o.selectable = false; o.editable = false; } }); }); text._onDeactivate(); }; return text; }; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var Whiteout = function(wb, s, sBtn) { const wout = ShapeBase(wb); wout.fill.color = '#FFFFFF'; wout.stroke.color = '#FFFFFF'; wout.stroke.width = 25; wout.activate = function() { wb.eachCanvas(function(canvas) { canvas.isDrawingMode = true; canvas.freeDrawingBrush.width = wout.stroke.width; canvas.freeDrawingBrush.color = wout.stroke.color; canvas.freeDrawingBrush.opacity = wout.opacity; }); ToolUtil.disableAllProps(s); sBtn.addClass('disabled'); }; wout.deactivate = function() { wb.eachCanvas(function(canvas) { canvas.isDrawingMode = false; }); }; return wout; }; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var Textbox = function(wb, s, sBtn) { const text = Text(wb, s, sBtn); text.fabricType = 'textbox'; text.createTextObj = function(canvas, pointer) { return new fabric.Textbox('', { left: pointer.x , top: pointer.y , padding: 7 , fill: text.fill.enabled ? text.fill.color : 'rgba(0,0,0,0)' , stroke: text.stroke.enabled ? text.stroke.color : 'rgba(0,0,0,0)' //, strokeWidth: text.stroke.width , fontSize: text.stroke.width , fontFamily: text.fontFamily , opacity: text.opacity , breakWords: true , width: canvas.width / 4 , lockScalingX: false , lockScalingY: true }); }; text.doubleClick = function(e) { const canvas = this , ao = e.target; if (!!ao && text.fabricType === ao.type) { text.obj = ao; text.obj.enterEditing(); WbArea.removeDeleteHandler(); } }; text._onMouseDown = function() { WbArea.addDeleteHandler(); }; text._onActivate = function() { WbArea.addDeleteHandler(); }; text._onDeactivate = function() { WbArea.addDeleteHandler(); }; return text; }; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var StaticTMath = (function() { function tex2svg(tex, callback, _errCallback) { const errCallback = _errCallback || function() {}; let error = false; const wrapper = $('<div>').html('\\[' + tex + '\\]'); MathJax.Hub.Register.MessageHook('TeX Jax - parse error', function(message) { errCallback(message[1]); error = true; }); MathJax.Hub.Register.MessageHook('Math Processing Error', function(message) { errCallback(message[2]); error = true; }); MathJax.Hub.Queue(['Typeset', MathJax.Hub, wrapper[0]]); MathJax.Hub.Queue(function() { if (!error) { const mjOut = wrapper[0].getElementsByTagName('svg')[0]; callback(mjOut); } }); } function create(o, canvas, callback, errCallback) { tex2svg(o.formula, function(svg) { fabric.parseSVGDocument(svg, function(objects, options) { const opts = $.extend({}, o, options) , obj = objects.length === 1 ? new fabric.Group(objects, opts) : fabric.util.groupSVGElements(objects, opts); obj.selectable = canvas.selection; obj.type = 'group'; if (typeof(callback) === 'function') { callback(obj); } canvas.add(obj).requestRenderAll(); }); }, errCallback); } function highlight(el) { el.addClass('ui-state-highlight', 2000, function() { el.focus(); el.removeClass('ui-state-highlight', 2000); }); } return { tex2svg: tex2svg , create: create , highlight: highlight } })(); var TMath = function(wb, s, sBtn) { const math = ShapeBase(); math.obj = null; function _enableUpdate(upd, obj) { upd.data('uid', obj.uid); upd.data('slide', obj.slide); upd.button('enable'); } function _updateDisabled(cnvs) { const canvas = cnvs || wb.getCanvas() , ao = canvas.getActiveObject() , fml = wb.getFormula() , ta = fml.find('textarea') , upd = fml.find('.update-btn'); if (!!ao && ao.omType === 'Math') { _enableUpdate(upd, ao); ta.val(ao.formula); return false; } else { upd.button('disable'); return true; } } math.mouseDown = function(o) { const canvas = this , pointer = canvas.getPointer(o.e) , fml = wb.getFormula() , ta = fml.find('textarea') , upd = fml.find('.update-btn'); fml.show(); if (_updateDisabled(canvas)) { const err = fml.find('.status'); err.text(''); if (ta.val().trim() === '') { StaticTMath.highlight(ta); return; } StaticTMath.create( { scaleX: 10 , scaleY: 10 , left: pointer.x , top: pointer.y , omType: 'Math' , formula: ta.val() } , canvas , function(obj) { math.obj = obj; math.objectCreated(math.obj, canvas); if (wb.getRole() !== NONE) { canvas.setActiveObject(math.obj); } _enableUpdate(upd, math.obj); } , function(msg) { err.text(msg); StaticTMath.highlight(err); } ); } }; math.activate = function() { wb.eachCanvas(function(canvas) { canvas.on('mouse:down', math.mouseDown); canvas.selection = true; canvas.forEachObject(function(o) { if (o.omType === 'Math') { o.selectable = true; } }); }); _updateDisabled(); ToolUtil.disableAllProps(s); sBtn.addClass('disabled'); }; math.deactivate = function() { wb.eachCanvas(function(canvas) { canvas.off('mouse:down', math.mouseDown); canvas.selection = false; canvas.forEachObject(function(o) { if (o.omType === 'Math') { o.selectable = false; } }); }); wb.getFormula().find('.update-btn').button('disable'); }; return math; }; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var Paint = function(wb, s, sBtn) { const paint = ShapeBase(wb); paint.activate = function() { wb.eachCanvas(function(canvas) { canvas.isDrawingMode = true; canvas.freeDrawingBrush.width = paint.stroke.width; canvas.freeDrawingBrush.color = paint.stroke.color; canvas.freeDrawingBrush.opacity = paint.opacity; }); ToolUtil.enableLineProps(s, paint).o.prop('disabled', true); VideoUtil.highlight(sBtn.removeClass('disabled'), 5); }; paint.deactivate = function() { wb.eachCanvas(function(canvas) { canvas.isDrawingMode = false; }); }; return paint; }; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var Shape = function(wb, sBtn) { const shape = ShapeBase(wb); shape.obj = null; shape.isDown = false; shape.orig = {x: 0, y: 0}; shape.add2Canvas = function(canvas) { canvas.add(shape.obj); } shape.mouseDown = function(o) { const canvas = this , pointer = canvas.getPointer(o.e); shape.isDown = true; shape.orig = {x: pointer.x, y: pointer.y}; shape.createShape(canvas); shape.add2Canvas(canvas); }; shape.mouseMove = function(o) { const canvas = this; if (!shape.isDown) return; const pointer = canvas.getPointer(o.e); shape.updateShape(pointer); canvas.requestRenderAll(); }; shape.updateCreated = function(o) { return o; }; shape.mouseUp = function(o) { const canvas = this; shape.isDown = false; shape.obj.setCoords(); shape.obj.selectable = false; canvas.requestRenderAll(); shape.objectCreated(shape.obj, canvas); }; shape.internalActivate = function() {}; shape.activate = function() { wb.eachCanvas(function(canvas) { canvas.on({ 'mouse:down': shape.mouseDown , 'mouse:move': shape.mouseMove , 'mouse:up': shape.mouseUp }); }); shape.internalActivate(); VideoUtil.highlight(sBtn.removeClass('disabled'), 5); }; shape.deactivate = function() { wb.eachCanvas(function(canvas) { canvas.off({ 'mouse:down': shape.mouseDown , 'mouse:move': shape.mouseMove , 'mouse:up': shape.mouseUp }); }); }; return shape; }; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var Line = function(wb, s, sBtn) { const line = Shape(wb, sBtn); line.createShape = function() { line.obj = new fabric.Line([line.orig.x, line.orig.y, line.orig.x, line.orig.y], { strokeWidth: line.stroke.width , fill: line.stroke.color , stroke: line.stroke.color , opacity: line.opacity }); return line.obj; }; line.internalActivate = function() { ToolUtil.enableLineProps(s, line); }; line.updateShape = function(pointer) { line.obj.set({ x2: pointer.x, y2: pointer.y }); }; return line; }; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var ULine = function(wb, s, sBtn) { const uline = Line(wb, s, sBtn); uline.stroke.width = 20; uline.opacity = .5; return uline; }; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var Rect = function(wb, s, sBtn) { const rect = Shape(wb, sBtn); rect.createShape = function() { rect.obj = new fabric.Rect({ strokeWidth: rect.stroke.width , fill: rect.fill.enabled ? rect.fill.color : 'rgba(0,0,0,0)' , stroke: rect.stroke.enabled ? rect.stroke.color : 'rgba(0,0,0,0)' , opacity: rect.opacity , left: rect.orig.x , top: rect.orig.y , width: 0 , height: 0 }); return rect.obj; }; rect.internalActivate = function() { ToolUtil.enableAllProps(s, rect); }; rect.updateShape = function(pointer) { if (rect.orig.x > pointer.x) { rect.obj.set({ left: pointer.x }); } if (rect.orig.y > pointer.y) { rect.obj.set({ top: pointer.y }); } rect.obj.set({ width: Math.abs(rect.orig.x - pointer.x) , height: Math.abs(rect.orig.y - pointer.y) }); }; return rect; }; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var Ellipse = function(wb, s, sBtn) { const ellipse = Rect(wb, s, sBtn); ellipse.createShape = function() { ellipse.obj = new fabric.Ellipse({ strokeWidth: ellipse.stroke.width , fill: ellipse.fill.enabled ? ellipse.fill.color : 'rgba(0,0,0,0)' , stroke: ellipse.stroke.enabled ? ellipse.stroke.color : 'rgba(0,0,0,0)' , opacity: ellipse.opacity , left: ellipse.orig.x , top: ellipse.orig.y , rx: 0 , ry: 0 , originX: 'center' , originY: 'center' }); return ellipse.obj; }; ellipse.updateShape = function(pointer) { ellipse.obj.set({ rx: Math.abs(ellipse.orig.x - pointer.x) , ry: Math.abs(ellipse.orig.y - pointer.y) }); }; return ellipse; }; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var Arrow = function(wb, s, sBtn) { const arrow = Line(wb, s, sBtn); arrow.stroke.width = 20; arrow.createShape = function(canvas) { arrow.obj = new fabric.Polygon([ {x: 0, y: 0}, {x: 0, y: 0}, {x: 0, y: 0}, {x: 0, y: 0}, {x: 0, y: 0}, {x: 0, y: 0}, {x: 0, y: 0}] , { left: arrow.orig.x , top: arrow.orig.y , angle: 0 , strokeWidth: 2 , fill: arrow.fill.enabled ? arrow.fill.color : 'rgba(0,0,0,0)' , stroke: arrow.stroke.enabled ? arrow.stroke.color : 'rgba(0,0,0,0)' , opacity: arrow.opacity }); return arrow.obj; }; arrow.updateShape = function(pointer) { const dx = pointer.x - arrow.orig.x , dy = pointer.y - arrow.orig.y , d = Math.sqrt(dx * dx + dy * dy) , sw = arrow.stroke.width , hl = sw * 3 , h = 1.5 * sw , points = [ {x: 0, y: sw}, {x: Math.max(0, d - hl), y: sw}, {x: Math.max(0, d - hl), y: h}, {x: d, y: 3 * sw / 4}, {x: Math.max(0, d - hl), y: 0}, {x: Math.max(0, d - hl), y: sw / 2}, {x: 0, y: sw / 2}]; arrow.obj.set({ points: points , angle: Math.atan2(dy, dx) * 180 / Math.PI , width: d , height: h , maxX: d , maxY: h , pathOffset: { x: d / 2, y: h / 2 } }); }; arrow.internalActivate = function() { ToolUtil.enableAllProps(s, arrow); }; return arrow; }; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ if (!String.prototype.endsWith) { String.prototype.endsWith = function(search, this_len) { if (this_len === undefined || this_len > this.length) { this_len = this.length; } return this.substring(this_len - search.length, this_len) === search; }; } var Clipart = function(wb, btn, s, sBtn) { const art = Shape(wb, sBtn); art.add2Canvas = function() {} art.createShape = function(canvas) { const imgSrc = btn.data('image') , opts = { left: art.orig.x , top: art.orig.y , scaleX: 0. , scaleY: 0. , omType: 'Clipart' , _src: imgSrc , opacity: art.opacity }; if (imgSrc.toLowerCase().endsWith('svg')) { fabric.loadSVGFromURL(imgSrc, function(elements) { art.orig.width = 32; art.orig.height = 32; art.obj = fabric.util.groupSVGElements(elements, opts); canvas.add(art.obj); }); } else { fabric.Image.fromURL(imgSrc, function(img) { art.orig.width = img.width; art.orig.height = img.height; art.obj = img.set(opts); canvas.add(art.obj); }); } }; art.updateShape = function(pointer) { if (!art.obj) { return; // not ready } const dx = pointer.x - art.orig.x , dy = pointer.y - art.orig.y , d = Math.sqrt(dx * dx + dy * dy) , scale = d / art.obj.width; art.obj.set({ scaleX: scale , scaleY: scale , angle: Math.atan2(dy, dx) * 180 / Math.PI }); }; art.internalActivate = function() { ToolUtil.disableAllProps(s); ToolUtil.enableOpacity(s, art); }; return art; }; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var Wb = function() { const ACTIVE = 'active', BUMPER = 100, wb = {id: -1, name: ''}, canvases = [] , area = $('.room.wb.area .wb-area .tabs.ui-tabs'), bar = area.find('.wb-tabbar') , extraProps = ['uid', 'fileId', 'fileType', 'count', 'slide', 'omType', '_src', 'formula']; let a, t, z, s, f, mode, slide = 0, width = 0, height = 0 , zoom = 1., zoomMode = 'pageWidth', role = null, scrollTimeout = null; function _getBtn(m) { return !!t ? t.find('.om-icon.' + (m || mode) + ':not(.stub)') : null; } function _cleanActive() { !!t && t.find('.om-icon.' + ACTIVE).removeClass(ACTIVE); } function _setActive() { !!t && t.find('.om-icon.' + mode).addClass(ACTIVE); } function _btnClick(toolType) { const b = _getBtn(); if (b.length && b.hasClass(ACTIVE)) { b.data().deactivate(); } _cleanActive(); _getBtn('string' === typeof(toolType) && !!toolType ? toolType : $(this).data('toolType')).data().activate(); _setActive(); } function _initToolBtn(m, def, obj) { const btn = _getBtn(m); if (!btn || btn.length === 0) { return; } btn.data({ obj: obj , toolType: m , activate: function() { if (!btn.hasClass(ACTIVE)) { mode = m; obj.activate(); } } , deactivate: function() { obj.deactivate(); } }).click(_btnClick); if (def) { btn.data().activate(); } } function _setCurrent(c, _cur) { const hndl = c.find('a') , cur = _cur || c.find('div.om-icon.big:first') c.attr('title', cur.attr('title')); hndl.find('.om-icon').remove(); hndl.prepend(cur.clone().addClass('stub').data('toolType', cur.data('toolType'))); } function _initGroupHandle(c) { c.find('a').off().click(function(e) { e.stopImmediatePropagation() const stub = $(this).find('.stub'); if (stub.hasClass(ACTIVE)) { $(this).dropdown('toggle') } else { _btnClick(stub.data('toolType')); stub.addClass(ACTIVE); } }); _setCurrent(c); } function _initGroup(__id, e) { const c = OmUtil.tmpl(__id); e.after(c); c.find('.om-icon').each(function() { const cur = $(this); cur.click(function() { _setCurrent(c, cur); }); }); return c; } function _initTexts(sBtn) { const c = _initGroup('#wb-area-texts', _getBtn('apointer')); _initToolBtn('text', false, Text(wb, s, sBtn)); _initToolBtn('textbox', false, Textbox(wb, s, sBtn)); _initGroupHandle(c); } function _initDrawings(sBtn) { const c = _initGroup('#wb-area-drawings', t.find('.texts')); _initToolBtn('eraser', false, Whiteout(wb, s, sBtn)); _initToolBtn('paint', false, Paint(wb, s, sBtn)); _initToolBtn('line', false, Line(wb, s, sBtn)); _initToolBtn('uline', false, ULine(wb, s, sBtn)); _initToolBtn('rect', false, Rect(wb, s, sBtn)); _initToolBtn('ellipse', false, Ellipse(wb, s, sBtn)); _initToolBtn('arrow', false, Arrow(wb, s, sBtn)); _initGroupHandle(c); } function _initCliparts(sBtn) { const c = OmUtil.tmpl('#wb-area-cliparts'); t.find('.drawings').after(c); c.find('.om-icon.clipart').each(function() { const cur = $(this); cur.css('background-image', 'url(' + cur.data('image') + ')') .click(function() { _setCurrent(c, cur); }); _initToolBtn(cur.data('mode'), false, Clipart(wb, cur, s, sBtn)); }); _initGroupHandle(c); } function _updateZoomPanel() { const ccount = canvases.length; if (ccount > 1 && role === PRESENTER) { z.find('.doc-group').show(); const ns = 1 * slide; z.find('.doc-group .curr-slide').val(ns + 1).attr('max', ccount); z.find('.doc-group .up').prop('disabled', ns < 1); z.find('.doc-group .down').prop('disabled', ns > ccount - 2); z.find('.doc-group .last-page').text(ccount); } else { z.find('.doc-group').hide(); } } function _setSlide(_sld) { slide = _sld; wbAction('setSlide', JSON.stringify({ wbId: wb.id , slide: _sld })); _updateZoomPanel(); } function _initSettings() { function setStyle(canvas, styleName, value) { const o = canvas.getActiveObject(); if (o.setSelectionStyles && o.isEditing) { let style = {}; style[styleName] = value; o.setSelectionStyles(style); } else { o[styleName] = value; } canvas.requestRenderAll(); } s.find('.wb-prop-b, .wb-prop-i') .button() .click(function() { $(this).toggleClass('ui-state-active selected'); const btn = _getBtn() , isB = $(this).hasClass('wb-prop-b') , style = isB ? 'bold' : 'italic' , v = $(this).hasClass('selected') , val = v ? style : ''; btn.data().obj.style[style] = v; wb.eachCanvas(function(canvas) { setStyle(canvas, isB ? 'fontWeight' : 'fontStyle', val) }); }); s.find('.wb-prop-lock-color, .wb-prop-lock-fill') .button({icon: 'ui-icon-locked', showLabel: false}) .click(function() { const btn = _getBtn() , isColor = $(this).hasClass('wb-prop-lock-color') , c = s.find(isColor ? '.wb-prop-color' : '.wb-prop-fill') , enabled = $(this).button('option', 'icon') === 'ui-icon-locked'; $(this).button('option', 'icon', enabled ? 'ui-icon-unlocked' : 'ui-icon-locked'); c.prop('disabled', !enabled); btn.data().obj[isColor ? 'stroke' : 'fill'].enabled = enabled; }); s.find('.wb-prop-color').change(function() { const btn = _getBtn(); if (btn.length === 1) { const v = $(this).val(); btn.data().obj.stroke.color = v; wb.eachCanvas(function(canvas) { if ('paint' === mode) { canvas.freeDrawingBrush.color = v; } else { setStyle(canvas, 'stroke', v) } }); } }); s.find('.wb-prop-width').change(function() { const btn = _getBtn(); if (btn.length === 1) { const v = 1 * $(this).val(); btn.data().obj.stroke.width = v; wb.eachCanvas(function(canvas) { if ('paint' === mode) { canvas.freeDrawingBrush.width = v; } else { setStyle(canvas, 'strokeWidth', v) } }); } }); s.find('.wb-prop-fill').change(function() { const btn = _getBtn(); if (btn.length === 1) { const v = $(this).val(); btn.data().obj.fill.color = v; wb.eachCanvas(function(canvas) { setStyle(canvas, 'fill', v) }); } }); s.find('.wb-prop-opacity').change(function() { const btn = _getBtn(); if (btn.length === 1) { const v = (1 * $(this).val()) / 100; btn.data().obj.opacity = v; wb.eachCanvas(function(canvas) { if ('paint' === mode) { canvas.freeDrawingBrush.opacity = v; } else { setStyle(canvas, 'opacity', v) } }); } }); s.find('.ui-dialog-titlebar-close').click(function() { s.hide(); }); s.draggable({ scroll: false , containment: 'body' , start: function() { if (!!s.css('bottom')) { s.css('bottom', '').css(Settings.isRtl ? 'left' : 'right', ''); } } , drag: function() { if (s.position().x + s.width() >= s.parent().width()) { return false; } } }); } function internalInit() { t.draggable({ snap: 'parent' , containment: 'parent' , scroll: false , stop: function(event, ui) { const pos = ui.helper.position(); if (pos.left === 0 || pos.left + ui.helper.width() === ui.helper.parent().width()) { ui.helper.removeClass('horisontal').addClass('vertical'); } else if (pos.top === 0 || pos.top + ui.helper.height() === ui.helper.parent().height()) { ui.helper.removeClass('vertical').addClass('horisontal'); } } }); z.draggable({ snap: 'parent' , containment: 'parent' , scroll: false }); const clearAll = t.find('.om-icon.clear-all') , sBtn = t.find('.om-icon.settings'); let _firstToolItem = true; switch (role) { case PRESENTER: clearAll.click(function() { OmUtil.confirmDlg('clear-all-confirm', function() { wbAction('clearAll', JSON.stringify({wbId: wb.id})); }); }).removeClass('disabled'); z.find('.curr-slide').change(function() { _setSlide($(this).val() - 1); showCurrentSlide(); }); z.find('.doc-group .up').click(function () { _setSlide(1 * slide - 1); showCurrentSlide(); }); z.find('.doc-group .down').click(function () { _setSlide(1 * slide + 1); showCurrentSlide(); }); z.find('.settings-group').show().find('.settings').click(function () { const wbs = $('#wb-settings') , wbsw = wbs.find('.wbs-width').val(width) , wbsh = wbs.find('.wbs-height').val(height); function isNumeric(n) { return !isNaN(parseInt(n)) && isFinite(n); } wbs.dialog({ buttons: [ { text: wbs.data('btn-ok') , click: function() { const __w = wbsw.val(), __h = wbsh.val(); if (isNumeric(__w) && isNumeric(__h)) { width = parseInt(__w); height = parseInt(__h); _sendSetSize(); } $(this).dialog("close"); } } , { text: wbs.data('btn-cancel') , click: function() { $(this).dialog("close"); } } ] }); }); case WHITEBOARD: if (role === WHITEBOARD) { clearAll.addClass('disabled'); } _initToolBtn('pointer', _firstToolItem, Pointer(wb, s, sBtn)); _firstToolItem = false; _initTexts(sBtn); _initDrawings(sBtn); _initToolBtn('math', _firstToolItem, TMath(wb, s, sBtn)); _initCliparts(sBtn); t.find('.om-icon.settings').click(function() { s.show(); }); t.find('.om-icon.math').click(function() { f.show(); }); t.find('.om-icon.clear-slide').click(function() { OmUtil.confirmDlg('clear-slide-confirm', function() { wbAction('clearSlide', JSON.stringify({wbId: wb.id, slide: slide})); }); }); t.find('.om-icon.save').click(function() { wbAction('save', JSON.stringify({wbId: wb.id})); }); t.find('.om-icon.undo').click(function() { wbAction('undo', JSON.stringify({wbId: wb.id})); }); f.find('.ui-dialog-titlebar-close').click(function() { f.hide(); }); _initSettings(); f.find('.update-btn').button().click(function() { const o = _findObject({ uid: $(this).data('uid') , slide: $(this).data('slide') }); const json = toOmJson(o); json.formula = f.find('textarea').val(); const cnvs = canvases[o.slide]; StaticTMath.create(json, cnvs , function(obj) { _removeHandler(o); cnvs.trigger('object:modified', {target: obj}); } , function(msg) { const err = f.find('.status'); err.text(msg); StaticTMath.highlight(err); }); }).parent().css('text-align', Settings.isRtl ? 'left' : 'right'); f.draggable({ scroll: false , containment: 'body' , start: function() { if (!!f.css('bottom')) { f.css('bottom', '').css(Settings.isRtl ? 'left' : 'right', ''); } } , drag: function() { if (f.position().x + f.width() >= f.parent().width()) { return false; } } }).resizable({ alsoResize: f.find('.text-container') }); case NONE: _updateZoomPanel(); z.find('.zoom-out').click(function() { zoom -= .2; if (zoom < .1) { zoom = .1; } zoomMode = 'zoom'; _sendSetSize(); }); z.find('.zoom-in').click(function() { zoom += .2; zoomMode = 'zoom'; _sendSetSize(); }); z.find('.zoom').change(function() { const zzz = $(this).val(); zoomMode = 'zoom'; if (isNaN(zzz)) { switch (zzz) { case 'fullFit': case 'pageWidth': zoomMode = zzz; break; case 'custom': zoom = 1. * $(this).data('custom-val'); break; } } else { zoom = 1. * zzz; } _sendSetSize(); }); _setSize(); _initToolBtn('apointer', _firstToolItem, APointer(wb, s, sBtn)); default: //no-op } } function _sendSetSize() { _setSize(); wbAction('setSize', JSON.stringify({ wbId: wb.id , zoom: zoom , zoomMode: zoomMode , width: width , height: height })); } function _findObject(o) { let _o = null; const cnvs = canvases[o.slide]; if (!!cnvs) { cnvs.forEachObject(function(__o) { if (!!__o && o.uid === __o.uid) { _o = __o; return false; } }); } return _o; } function _removeHandler(o) { const __o = _findObject(o); if (!!__o) { const cnvs = canvases[o.slide]; if (!!cnvs) { if ('Video' === __o.omType) { $('#wb-video-' + __o.uid).remove(); } cnvs.remove(__o); } } } function _modifyHandler(_o) { _removeHandler(_o); _createHandler(_o); } function _createHandler(_o) { switch (_o.fileType) { case 'Video': case 'Recording': //no-op break; case 'Presentation': { const ccount = canvases.length; for (let i = 0; i < _o.count; ++i) { if (canvases.length < i + 1) { addCanvas(); } const canvas = canvases[i]; if (_o.deleted) { ToolUtil.addDeletedItem(canvas, _o); } else { let scale = width / _o.width; scale = scale < 1 ? 1 : scale; canvas.setBackgroundImage(_o._src + '&slide=' + i, canvas.renderAll.bind(canvas) , {scaleX: scale, scaleY: scale}); } } _updateZoomPanel(); if (ccount !== canvases.length) { const b = _getBtn(); if (b.length && b.hasClass(ACTIVE)) { b.data().deactivate(); b.data().activate(); } showCurrentSlide(); } } break; default: { const canvas = canvases[_o.slide]; if (!!canvas) { _o.selectable = canvas.selection; _o.editable = ('text' === mode || 'textbox' === mode); canvas.add(_o); } } break; } } function _createObject(arr, handler) { fabric.util.enlivenObjects(arr, function(objects) { wb.eachCanvas(function(canvas) { canvas.renderOnAddRemove = false; }); for (let i = 0; i < objects.length; ++i) { const _o = objects[i]; _o.loaded = true; handler(_o); } wb.eachCanvas(function(canvas) { canvas.renderOnAddRemove = true; canvas.requestRenderAll(); }); }); }; function toOmJson(o) { const r = o.toJSON(extraProps); switch (o.omType) { case 'Video': r.type = 'video'; delete r.objects; break; case 'Math': r.type = 'math'; delete r.objects; break; } return r; } //events function objCreatedHandler(o) { if (role === NONE && o.type !== 'pointer') return; let json; switch(o.type) { case 'pointer': json = o; break; default: o.includeDefaultValues = false; json = toOmJson(o); break; } wbAction('createObj', JSON.stringify({ wbId: wb.id , obj: json })); }; function objAddedHandler(e) { const o = e.target; if (!!o.loaded) return; switch(o.type) { case 'textbox': case 'i-text': o.uid = UUID.generate(); o.slide = this.slide; objCreatedHandler(o); break; default: o.selectable = this.selection; break; } }; function objModifiedHandler(e) { const o = e.target, items = []; if (role === NONE && o.type !== 'pointer') return; o.includeDefaultValues = false; if ('activeSelection' === o.type) { o.clone(function(_o) { // ungrouping _o.includeDefaultValues = false; const _items = _o.destroy().getObjects(); for (let i = 0; i < _items.length; ++i) { items.push(toOmJson(_items[i])); } }, extraProps); } else { items.push(toOmJson(o)); } wbAction('modifyObj', JSON.stringify({ wbId: wb.id , obj: items })); }; function objSelectedHandler(e) { const o = e.target; s.find('.wb-dim-x').val(o.left); s.find('.wb-dim-y').val(o.top); s.find('.wb-dim-w').val(o.width); s.find('.wb-dim-h').val(o.height); } function selectionCleared(e) { const o = e.target; if (!o || '' !== o.text) { return; } if ('textbox' === o.type || 'i-text' === o.type) { wbAction('deleteObj', JSON.stringify({ wbId: wb.id , obj: [{ uid: o.uid , slide: o.slide }] })); } } function pathCreatedHandler(o) { o.path.uid = UUID.generate(); o.path.slide = this.slide; objCreatedHandler(o.path); }; function scrollHandler() { if (scrollTimeout !== null) { clearTimeout(scrollTimeout); } scrollTimeout = setTimeout(function() { const sc = a.find('.scroll-container') , canvases = sc.find('.canvas-container'); if (Math.round(sc.height() + sc[0].scrollTop) === sc[0].scrollHeight) { if (slide !== canvases.length - 1) { _setSlide(canvases.length - 1); } return false; } canvases.each(function(idx) { const h = $(this).height(), pos = $(this).position(); if (slide !== idx && pos.top > BUMPER - h && pos.top < BUMPER) { _setSlide(idx); return false; } }); }, 100); } function showCurrentSlide() { a.find('.scroll-container .canvas-container').each(function(idx) { if (role === PRESENTER) { $(this).show(); const cclist = a.find('.scroll-container .canvas-container'); if (cclist.length > slide) { cclist[slide].scrollIntoView(); } } else { if (idx === slide) { $(this).show(); } else { $(this).hide(); } } }); } /*TODO interactive text change var textEditedHandler = function (e) { var obj = e.target; console.log('Text Edit Exit', obj); }; var textChangedHandler = function (e) { var obj = e.target; console.log('Text Changed', obj); };*/ function setHandlers(canvas) { // off everything first to prevent duplicates canvas.off({ 'wb:object:created': objCreatedHandler , 'object:modified': objModifiedHandler , 'object:added': objAddedHandler , 'object:selected': objSelectedHandler , 'path:created': pathCreatedHandler //, 'text:editing:exited': textEditedHandler //, 'text:changed': textChangedHandler , 'before:selection:cleared': selectionCleared }); canvas.on({ 'wb:object:created': objCreatedHandler , 'object:modified': objModifiedHandler }); if (role !== NONE) { canvas.on({ 'object:added': objAddedHandler , 'object:selected': objSelectedHandler , 'path:created': pathCreatedHandler , 'before:selection:cleared': selectionCleared //, 'text:editing:exited': textEditedHandler //, 'text:changed': textChangedHandler }); } } function addCanvas() { const sl = canvases.length , cid = 'can-' + a.attr('id') + '-slide-' + sl , c = $('<canvas></canvas>').attr('id', cid); a.find('.canvases').append(c); const canvas = new fabric.Canvas(c.attr('id'), { preserveObjectStacking: true }); canvas.wbId = wb.id; canvas.slide = sl; canvases.push(canvas); const cc = $('#' + cid).closest('.canvas-container'); if (role === NONE) { if (sl === slide) { cc.show(); } else { cc.hide(); } } __setSize(canvas); setHandlers(canvas); } function __setSize(_cnv) { _cnv.setWidth(zoom * width).setHeight(zoom * height).setZoom(zoom); } function _setSize() { switch (zoomMode) { case 'fullFit': zoom = Math.min((area.width() - 10) / width, (area.height() - bar.height() - 10) / height); z.find('.zoom').val(zoomMode); break; case 'pageWidth': zoom = (area.width() - 10) / width; z.find('.zoom').val(zoomMode); break; default: { const oo = z.find('.zoom').find('option[value="' + zoom.toFixed(2) + '"]'); if (oo.length === 1) { oo.prop('selected', true); } else { z.find('.zoom').data('custom-val', zoom).find('option[value=custom]') .text((100. * zoom).toFixed(0) + '%') .prop('selected', true); } } break; } wb.eachCanvas(function(canvas) { __setSize(canvas); }); _setSlide(slide); } function _videoStatus(json) { const g = _findObject(json); if (!!g) { g.videoStatus(json.status); } } function __safeRemove(e) { if (typeof(e) === 'object') { e.remove(); } } function __destroySettings() { const wbs = $('#wb-settings'); if (wbs.dialog('instance')) { wbs.dialog('destroy'); } } wb.setRole = function(_role) { if (role !== _role) { const btn = _getBtn(); if (!!btn && btn.length === 1) { btn.data().deactivate(); } a.find('.tools').remove(); a.find('.wb-tool-settings').remove(); a.find('.wb-zoom').remove(); role = _role; const sc = a.find('.scroll-container'); z = OmUtil.tmpl('#wb-zoom') .attr('style', 'position: absolute; top: 0px; ' + (Settings.isRtl ? 'right' : 'left') + ': 80px;'); __safeRemove(t); __safeRemove(s); __safeRemove(f); if (role === NONE) { __destroySettings(); t = !!Room.getOptions().questions ? OmUtil.tmpl('#wb-tools-readonly') : a.find('invalid-wb-element'); sc.off('scroll', scrollHandler); } else { t = OmUtil.tmpl('#wb-tools'); s = OmUtil.tmpl('#wb-tool-settings') .attr('style', 'display: none; bottom: 100px; ' + (Settings.isRtl ? 'left' : 'right') + ': 100px;'); f = OmUtil.tmpl('#wb-formula') .attr('style', 'display: none; bottom: 100px; ' + (Settings.isRtl ? 'left' : 'right') + ': 100px;'); a.append(s, f); sc.on('scroll', scrollHandler); } t.attr('style', 'position: absolute; top: 20px; ' + (Settings.isRtl ? 'left' : 'right') + ': 20px;'); a.append(t).append(z); showCurrentSlide(); t = a.find('.tools'), s = a.find('.wb-tool-settings'); wb.eachCanvas(function(canvas) { setHandlers(canvas); canvas.forEachObject(function(__o) { //TODO reduce iterations if (!!__o && __o.omType === 'Video') { __o.setPlayable(role); } }); }); internalInit(); } }; wb.init = function(wbo, tid, _role) { wb.id = wbo.wbId; wb.name = wbo.name; width = wbo.width; height = wbo.height; zoom = wbo.zoom; zoomMode = wbo.zoomMode; a = $('#' + tid); addCanvas(); wb.setRole(_role); }; wb.setSize = function(wbo) { width = wbo.width; height = wbo.height; zoom = wbo.zoom; zoomMode = wbo.zoomMode; _setSize(); } wb.resize = function() { if (t.length === 1 && t.position().left + t.width() > a.width()) { t.position({ my: (Settings.isRtl ? 'left' : 'right') , at: (Settings.isRtl ? 'left' : 'right') + '-20' , of: '#' + a[0].id , collision: 'fit' }); } if (z.position().left + z.width() > a.width()) { z.position({ my: (Settings.isRtl ? 'right' : 'left') + ' top' , at: 'center top' , of: '#' + a[0].id , collision: 'fit' }); } if (zoomMode !== 'zoom') { _setSize(); } }; wb.setSlide = function(_sl) { slide = _sl; showCurrentSlide(); }; wb.createObj = function(obj) { const arr = [], del = [], _arr = Array.isArray(obj) ? obj : [obj]; for (let i = 0; i < _arr.length; ++i) { const o = _arr[i]; if (!!o.deleted && 'Presentation' !== o.fileType) { del.push(o); continue; } switch(o.type) { case 'pointer': APointer(wb).create(canvases[o.slide], o); break; case 'video': Player.create(canvases[o.slide], o, wb); break; case 'math': StaticTMath.create(o, canvases[o.slide]); break; default: { const __o = _findObject(o); if (!__o) { arr.push(o); } } break; } } if (arr.length > 0) { _createObject(arr, _createHandler); } for (let i = 0; i < del.length; ++i) { const o = del[i]; ToolUtil.addDeletedItem(canvases[o.slide], o); } }; wb.load = wb.createObj; wb.modifyObj = function(obj) { //TODO need to be unified const arr = [], _arr = Array.isArray(obj) ? obj : [obj]; for (let i = 0; i < _arr.length; ++i) { const o = _arr[i]; switch(o.type) { case 'pointer': _modifyHandler(APointer(wb).create(canvases[o.slide], o)) break; case 'video': { const g = _findObject(o); if (!!g) { Player.modify(g, o); } } break; case 'math': { _removeHandler(o); StaticTMath.create(o, canvases[o.slide]); } break; default: arr.push(o); break; } } if (arr.length > 0) { _createObject(arr, _modifyHandler); } }; wb.removeObj = function(arr) { for (let i = 0; i < arr.length; ++i) { _removeHandler(arr[i]); } }; wb.clearAll = function() { for (let i = 1; i < canvases.length; ++i) { const cc = $('#can-wb-tab-' + wb.id + '-slide-' + i).closest('.canvas-container'); cc.remove(); canvases[i].dispose(); } $('.room.wb.area .wb-video').remove(); canvases.splice(1); canvases[0].clear(); _updateZoomPanel(); }; wb.clearSlide = function(_sl) { if (canvases.length > _sl) { const canvas = canvases[_sl]; canvas.renderOnAddRemove = false; let arr = canvas.getObjects(); while (arr.length > 0) { canvas.remove(arr[arr.length - 1]); arr = canvas.getObjects(); } $('.room.wb.area .wb-video.slide-' + _sl).remove(); canvas.renderOnAddRemove = true; canvas.requestRenderAll(); } }; wb.getCanvas = function() { return canvases[slide]; }; wb.eachCanvas = function(func) { for (let i = 0; i < canvases.length; ++i) { func(canvases[i]); } }; wb.videoStatus = _videoStatus; wb.getRole = function() { return role; }; wb.getFormula = function() { return f; }; wb.getZoom = function() { return zoom; } wb.destroy = function() { __destroySettings(); } return wb; }; /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var PRESENTER = 'presenter'; var WHITEBOARD = 'whiteBoard'; var DrawWbArea = function() { const self = {} , arrowImg = new Image(), delImg = new Image(); arrowImg.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAICAYAAADqSp8ZAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAygAAAMoBawMUsgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAFsSURBVCiRrdI/SEJRFMfx37lPGxqKoGwxKJoaImhpCf8NEUFL9WgLUrPnIyEIa6reVEPQn0GeWDS4NDQETQ2JT4waojUoHBqCoJKWINB3720yIhGl+q7ncj5nuIQ6jWiaq1xmU4IwBACQ5GCAU5D8IECRAkUQzt8V++wmlSrX20e1BoFIrFdwHidIIQhH5O68sgzD/vnOF4m0QyijJGgMQIHZtJdJJ4oNg6qqNr20dKwBaOWKvZFPpZ7qXV3JH4wNSMbjJHGZ7XIlYRiiFkiBsL4CphwLwbck5E7uwMw3ClXD2iRImYYUq9lD886nLXZbyd2HL9AbXpglySOQeFVstpRJJ+5/i1UajkbbHCXahMS1ZAiS2+W1DMNmqqoqBLFMYIME1uxkvPRXDAAuTPMNhCwIGiT62eOzAQDkD+nbAjQDxudy+8mT/8C+FwjNjwuwdQnqY7b0kCesT7DC7allWVU/8D/zh3SdC/R8Aq9QhRc3h8LfAAAAAElFTkSuQmCC'; delImg.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAAGgrv1cAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADNQTFRFAAAA4j094j094j094j094j094j094j094j094j094j094j094j094j094j094j094j09hIdAxgAAABB0Uk5TABAgMEBQYHCAj5+vv8/f7yMagooAAADXSURBVBgZBcEBYoQgDACw1DJETmz//9olwGn6AAAbBxoiSACTpCTtJd02smg+MPoef7UgnpPQeVM42Vg02kl+qAPeE2B19wYAgO83xi6ggRMoBfuvsUSxp+vPjag98VqwC8oI9ozC5rMnUVbw5ITID94Fo4D4umsAwN/+urvfOwDg6d8FiFUnALPnkwCs6zvg+UKcSmD3ZBWyL4hTye4J3s16AXG6J+D+uD/A7vtUAutFT9g9EacSURNX33ZPQJzKqAW8lQCIXyWAVfUM5Hz7vQAAMcZIAP9DvgiOL2K6DwAAAABJRU5ErkJggg=='; let container, area, tabs, scroll, role = NONE, _inited = false; // Fabric overrides (should be kept up-to-date on fabric.js updates) if ('function' !== typeof(window.originalDrawControl)) { window.originalDrawControl = fabric.Object.prototype._drawControl; window.originalGetRotatedCornerCursor = fabric.Canvas.prototype._getRotatedCornerCursor; window.originalGetActionFromCorner = fabric.Canvas.prototype._getActionFromCorner; window.originalGetCornerCursor = fabric.Canvas.prototype.getCornerCursor; fabric.Object.prototype._drawControl = function(control, ctx, methodName, left, top, styleOverride) { switch (control) { case 'mtr': { const x = left + (this.cornerSize - arrowImg.width) / 2 , y = top + (this.cornerSize - arrowImg.height) / 2; ctx.drawImage(arrowImg, x, y); } break; case 'tr': { const x = left + (this.cornerSize - delImg.width) / 2 , y = top + (this.cornerSize - delImg.height) / 2; ctx.drawImage(delImg, x, y); } break; default: window.originalDrawControl.call(this, control, ctx, methodName, left, top, styleOverride); break; } }; fabric.Canvas.prototype._getRotatedCornerCursor = function(corner, target, e) { if (role === PRESENTER && 'tr' === corner) { return 'pointer'; } return window.originalGetRotatedCornerCursor.call(this, corner, target, e); }; fabric.Canvas.prototype._getActionFromCorner = function(alreadySelected, corner, e) { if (role === PRESENTER && 'tr' === corner) { _performDelete(); return 'none'; } return window.originalGetActionFromCorner.call(this, alreadySelected, corner, e); }; fabric.Canvas.prototype.getCornerCursor = function(corner, target, e) { if (role === PRESENTER && 'tr' === corner) { return 'pointer'; } return window.originalGetCornerCursor.call(this, corner, target, e); } } function refreshTabs() { tabs.tabs('refresh').find('ul').removeClass('ui-corner-all').removeClass('ui-widget-header'); } function getActive() { const idx = tabs.tabs('option', 'active'); if (idx > -1) { const href = tabs.find('a')[idx]; if (!!href) { return $($(href).attr('href')); } } return null; } function _performDelete() { const wb = getActive().data() , canvas = wb.getCanvas(); if (role !== PRESENTER || !canvas) { return true; } const arr = [], objs = canvas.getActiveObjects(); for (let i = 0; i < objs.length; ++i) { arr.push({ uid: objs[i].uid , slide: objs[i].slide }); } wbAction('deleteObj', JSON.stringify({ wbId: wb.id , obj: arr })); return false; } function _deleteHandler(e) { if ('BODY' === e.target.tagName) { switch (e.which) { case 8: // backspace case 46: // delete e.preventDefault(); e.stopImmediatePropagation(); return _performDelete(); } } } function _getWbTab(wbId) { return tabs.find('li[data-wb-id="' + wbId + '"]'); } function _activateTab(wbId) { container.find('.wb-tabbar li').each(function(idx) { if (wbId === 1 * $(this).data('wb-id')) { tabs.tabs('option', 'active', idx); $(this)[0].scrollIntoView(); return false; } }); } function _setTabName(li, name) { return li.find('a').attr('title', name) .find('span').text(name) } function _renameTab(obj) { _setTabName(_getWbTab(obj.wbId), obj.name); } function _resizeWbs() { const w = area.width(), hh = area.height() , wbTabs = area.find('.tabs.ui-tabs') , tabPanels = wbTabs.find('.ui-tabs-panel') , wbah = hh - 5 - wbTabs.find('ul.ui-tabs-nav').height(); tabPanels.height(wbah); tabPanels.each(function() { $(this).data().resize(w - 25, wbah - 20); }); wbTabs.find('.ui-tabs-panel .scroll-container').height(wbah); } function _addCloseBtn(li) { if (role !== PRESENTER) { return; } li.append(OmUtil.tmpl('#wb-tab-close')); li.find('button').click(function() { OmUtil.confirmDlg('wb-confirm-remove', function() { wbAction('removeWb', JSON.stringify({wbId: li.data().wbId})); }); }); } function _getImage(cnv) { return cnv.toDataURL({ format: 'image/png' , width: cnv.width , height: cnv.height , multiplier: 1. / cnv.getZoom() , left: 0 , top: 0 }); } function _videoStatus(json) { self.getWb(json.wbId).videoStatus(json); } function _initVideos(arr) { for (let i = 0; i < arr.length; ++i) { _videoStatus(arr[i]); } } self.getWbTabId = function(id) { return 'wb-tab-' + id; }; self.getWb = function(id) { return $('#' + self.getWbTabId(id)).data(); }; self.getCanvas = function(id) { return self.getWb(id).getCanvas(); }; self.setRole = function(_role) { if (!_inited) return; role = _role; const tabsNav = tabs.find('.ui-tabs-nav'); tabsNav.sortable(role === PRESENTER ? 'enable' : 'disable'); const prev = tabs.find('.prev.om-icon'), next = tabs.find('.next.om-icon'); if (role === PRESENTER) { if (prev.length === 0) { const cc = tabs.find('.wb-tabbar .scroll-container') , left = OmUtil.tmpl('#wb-tabbar-ctrls-left') , right = OmUtil.tmpl('#wb-tabbar-ctrls-right'); cc.before(left).after(right); tabs.find('.add.om-icon').click(function() { wbAction('createWb'); }); tabs.find('.prev.om-icon').click(function() { scroll.scrollLeft(scroll.scrollLeft() - 30); }); tabs.find('.next.om-icon').click(function() { scroll.scrollLeft(scroll.scrollLeft() + 30); }); tabsNav.find('li').each(function() { const li = $(this); _addCloseBtn(li); }); self.addDeleteHandler(); } } else { if (prev.length > 0) { prev.parent().remove(); next.parent().remove(); tabsNav.find('li button').remove(); } self.removeDeleteHandler(); } tabs.find('.ui-tabs-panel').each(function() { $(this).data().setRole(role); }); } function _actionActivateWb(_wbId) { wbAction('activateWb', JSON.stringify({wbId: _wbId})); } self.init = function() { container = $('.room.wb.area'); tabs = container.find('.tabs'); if (tabs.length === 0) return; tabs.tabs({ beforeActivate: function(e) { let res = true; if (e.originalEvent && e.originalEvent.type === 'click') { res = role === PRESENTER; } return res; } , activate: function(e, ui) { //only send `activateWb` event if activation was initiated by user if (e.originalEvent && e.originalEvent.type === 'click') { _actionActivateWb(ui.newTab.data('wb-id')); } } }); scroll = tabs.find('.scroll-container'); area = container.find('.wb-area'); tabs.find('.ui-tabs-nav').sortable({ axis: 'x' , stop: function() { refreshTabs(); } }); _inited = true; self.setRole(role); $('#wb-rename-menu').menu().find('.wb-rename').click(function() { _getWbTab($(this).parent().data('wb-id')).find('a span').trigger('dblclick'); }); }; self.destroy = function() { self.removeDeleteHandler(); }; self.create = function(obj) { if (!_inited) return; const tid = self.getWbTabId(obj.wbId) , wb = OmUtil.tmpl('#wb-area', tid) , li = OmUtil.tmpl('#wb-area-tab').data('wb-id', obj.wbId).attr('data-wb-id', obj.wbId) .contextmenu(function(e) { if (role !== PRESENTER) { return; } e.preventDefault(); $('#wb-rename-menu').show().data('wb-id', obj.wbId) .position({my: 'left top', collision: 'none', of: _getWbTab(obj.wbId)}); }); li.find('a').attr('href', '#' + tid); _setTabName(li, obj.name) .dblclick(function() { if (role !== PRESENTER) { return; } const editor = $('<input name="newName" type="text" style="color: black;"/>') , name = $(this).hide().after(editor.val(obj.name)) , renameWbTab = function() { const newName = editor.val(); if (newName !== '') { wbAction('renameWb', JSON.stringify({wbId: obj.wbId, name: newName})); } editor.remove(); name.show(); }; editor.focus() .blur(renameWbTab) .keyup(function(evt) { if (evt.which === 13) { renameWbTab(); } }); }); tabs.find('.ui-tabs-nav').append(li); tabs.append(wb); refreshTabs(); _addCloseBtn(li); const wbo = Wb(); wbo.init(obj, tid, role); wb.on('remove', wbo.destroy); wb.data(wbo); _resizeWbs(); } self.createWb = function(obj) { if (!_inited) return; self.create(obj); _activateTab(obj.wbId); _actionActivateWb(obj.wbId); }; self.activateWb = function(obj) { if (!_inited) return; _activateTab(obj.wbId); } self.renameWb = function(obj) { if (!_inited) return; _renameTab(obj); } self.removeWb = function(obj) { if (!_inited) return; const tabId = self.getWbTabId(obj.wbId); _getWbTab(obj.wbId).remove(); $('#' + tabId).remove(); refreshTabs(); _actionActivateWb(getActive().data().id); }; self.load = function(json) { if (!_inited) return; self.getWb(json.wbId).load(json.obj); }; self.setSlide = function(json) { if (!_inited) return; self.getWb(json.wbId).setSlide(json.slide); }; self.createObj = function(json) { if (!_inited) return; self.getWb(json.wbId).createObj(json.obj); }; self.modifyObj = function(json) { if (!_inited) return; self.getWb(json.wbId).modifyObj(json.obj); }; self.deleteObj = function(json) { if (!_inited) return; self.getWb(json.wbId).removeObj(json.obj); }; self.clearAll = function(json) { if (!_inited) return; self.getWb(json.wbId).clearAll(); Room.setSize(); }; self.clearSlide = function(json) { if (!_inited) return; self.getWb(json.wbId).clearSlide(json.slide); }; self.resize = function(sbW, chW, w, h) { const hh = h - 5; container.width(w).height(h).css('left', (Settings.isRtl ? chW : sbW) + 'px'); if (!container || !_inited) return; area.width(w).height(hh); const wbTabs = area.find('.tabs.ui-tabs'); wbTabs.height(hh); _resizeWbs(); } self.setSize = function(json) { if (!_inited) return; self.getWb(json.wbId).setSize(json); } self.download = function(fmt) { if (!_inited) return; const wb = getActive().data(); if ('pdf' === fmt) { const arr = []; wb.eachCanvas(function(cnv) { arr.push(_getImage(cnv)); }); wbAction('downloadPdf', JSON.stringify({ slides: arr })); } else { const cnv = wb.getCanvas() , dataUri = _getImage(cnv); try { const dlg = $('#download-png'); dlg.find('img').attr('src', dataUri); dlg.dialog({ width: 350 , appendTo: '.room.wb.area' }); } catch (e) { console.error(e); } } } self.videoStatus = _videoStatus; self.loadVideos = function() { if (!_inited) return; wbAction('loadVideos'); }; self.initVideos = _initVideos; self.addDeleteHandler = function() { if (role === PRESENTER) { $(window).keyup(_deleteHandler); } }; self.removeDeleteHandler = function() { $(window).off('keyup', _deleteHandler); }; self.updateAreaClass = function() {}; self.doCleanAll = function() { if (!_inited) { return; } tabs.find('li').each(function() { const wbId = $(this).data('wb-id') , tabId = self.getWbTabId(wbId); $(this).remove(); $('#' + tabId).remove(); }); refreshTabs(); }; return self; }; $(function() { Wicket.Event.subscribe('/websocket/message', function(jqEvent, msg) { try { if (msg instanceof Blob) { return; //ping } const m = jQuery.parseJSON(msg); if (m && 'wb' === m.type && typeof(WbArea) !== 'undefined' && !!m.func) { WbArea[m.func](m.param); } } catch (err) { //no-op } }); });