1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543 |
- /*!!
- * Canvas 2 Svg v1.0.19
- * A low level canvas to SVG converter. Uses a mock canvas context to build an SVG document.
- *
- * Licensed under the MIT license:
- * http://www.opensource.org/licenses/mit-license.php
- *
- * Author:
- * Kerry Liu
- *
- * Copyright (c) 2014 Gliffy Inc.
- */
- (function() {
- 'use strict';
- var STYLES, ctx, CanvasGradient, CanvasPattern, namedEntities;
- //helper function to format a string
- function format(str, args) {
- var keys = Object.keys(args),
- i;
- for (i = 0; i < keys.length; i++) {
- str = str.replace(
- new RegExp('\\{' + keys[i] + '\\}', 'gi'),
- args[keys[i]]
- );
- }
- return str;
- }
- //helper function that generates a random string
- function randomString(holder) {
- var chars, randomstring, i;
- if (!holder) {
- throw new Error(
- 'cannot create a random attribute name for an undefined object'
- );
- }
- chars = 'ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz';
- randomstring = '';
- do {
- randomstring = '';
- for (i = 0; i < 12; i++) {
- randomstring += chars[Math.floor(Math.random() * chars.length)];
- }
- } while (holder[randomstring]);
- return randomstring;
- }
- //helper function to map named to numbered entities
- function createNamedToNumberedLookup(items, radix) {
- var i,
- entity,
- lookup = {},
- base10,
- base16;
- items = items.split(',');
- radix = radix || 10;
- // Map from named to numbered entities.
- for (i = 0; i < items.length; i += 2) {
- entity = '&' + items[i + 1] + ';';
- base10 = parseInt(items[i], radix);
- lookup[entity] = '&#' + base10 + ';';
- }
- //FF and IE need to create a regex from hex values ie == \xa0
- lookup['\\xa0'] = ' ';
- return lookup;
- }
- //helper function to map canvas-textAlign to svg-textAnchor
- function getTextAnchor(textAlign) {
- //TODO: support rtl languages
- var mapping = {
- left: 'start',
- right: 'end',
- center: 'middle',
- start: 'start',
- end: 'end'
- };
- return mapping[textAlign] || mapping.start;
- }
- //helper function to map canvas-textBaseline to svg-dominantBaseline
- function getDominantBaseline(textBaseline) {
- //INFO: not supported in all browsers
- var mapping = {
- alphabetic: 'alphabetic',
- hanging: 'hanging',
- top: 'text-before-edge',
- bottom: 'text-after-edge',
- middle: 'central'
- };
- return mapping[textBaseline] || mapping.alphabetic;
- }
- // Unpack entities lookup where the numbers are in radix 32 to reduce the size
- // entity mapping courtesy of tinymce
- namedEntities = createNamedToNumberedLookup(
- '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
- '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
- '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
- '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
- '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
- '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
- '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
- '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
- '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
- '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
- 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
- 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
- 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
- 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
- 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
- '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
- '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
- '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
- '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
- '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
- 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
- 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
- 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
- '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
- '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro',
- 32
- );
- //Some basic mappings for attributes and default values.
- STYLES = {
- strokeStyle: {
- svgAttr: 'stroke', //corresponding svg attribute
- canvas: '#000000', //canvas default
- svg: 'none', //svg default
- apply: 'stroke' //apply on stroke() or fill()
- },
- fillStyle: {
- svgAttr: 'fill',
- canvas: '#000000',
- svg: null, //svg default is black, but we need to special case this to handle canvas stroke without fill
- apply: 'fill'
- },
- lineCap: {
- svgAttr: 'stroke-linecap',
- canvas: 'butt',
- svg: 'butt',
- apply: 'stroke'
- },
- lineJoin: {
- svgAttr: 'stroke-linejoin',
- canvas: 'miter',
- svg: 'miter',
- apply: 'stroke'
- },
- miterLimit: {
- svgAttr: 'stroke-miterlimit',
- canvas: 10,
- svg: 4,
- apply: 'stroke'
- },
- lineWidth: {
- svgAttr: 'stroke-width',
- canvas: 1,
- svg: 1,
- apply: 'stroke'
- },
- globalAlpha: {
- svgAttr: 'opacity',
- canvas: 1,
- svg: 1,
- apply: 'fill stroke'
- },
- font: {
- //font converts to multiple svg attributes, there is custom logic for this
- canvas: '12px Arial'
- },
- shadowColor: {
- canvas: '#000000'
- },
- shadowOffsetX: {
- canvas: 0
- },
- shadowOffsetY: {
- canvas: 0
- },
- shadowBlur: {
- canvas: 0
- },
- textAlign: {
- canvas: 'start'
- },
- textBaseline: {
- canvas: 'alphabetic'
- },
- lineDash: {
- svgAttr: 'stroke-dasharray',
- canvas: [],
- svg: null,
- apply: 'stroke'
- }
- };
- /**
- *
- * @param gradientNode - reference to the gradient
- * @constructor
- */
- CanvasGradient = function(gradientNode, ctx) {
- this.__root = gradientNode;
- this.__ctx = ctx;
- };
- /**
- * Adds a color stop to the gradient root
- */
- CanvasGradient.prototype.addColorStop = function(offset, color) {
- var stop = this.__ctx.__createElement('stop'),
- regex,
- matches;
- stop.setAttribute('offset', offset);
- if (color.indexOf('rgba') !== -1) {
- //separate alpha value, since webkit can't handle it
- regex = /rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d?\.?\d*)\s*\)/gi;
- matches = regex.exec(color);
- stop.setAttribute(
- 'stop-color',
- format('rgb({r},{g},{b})', {
- r: matches[1],
- g: matches[2],
- b: matches[3]
- })
- );
- stop.setAttribute('stop-opacity', matches[4]);
- } else {
- stop.setAttribute('stop-color', color);
- }
- this.__root.appendChild(stop);
- };
- CanvasPattern = function(pattern, ctx) {
- this.__root = pattern;
- this.__ctx = ctx;
- };
- /**
- * The mock canvas context
- * @param o - options include:
- * ctx - existing Context2D to wrap around
- * width - width of your canvas (defaults to 500)
- * height - height of your canvas (defaults to 500)
- * enableMirroring - enables canvas mirroring (get image data) (defaults to false)
- * document - the document object (defaults to the current document)
- */
- ctx = function(o) {
- var defaultOptions = { width: 500, height: 500, enableMirroring: false },
- options;
- //keep support for this way of calling C2S: new C2S(width,height)
- if (arguments.length > 1) {
- options = defaultOptions;
- options.width = arguments[0];
- options.height = arguments[1];
- } else if (!o) {
- options = defaultOptions;
- } else {
- options = o;
- }
- if (!(this instanceof ctx)) {
- //did someone call this without new?
- return new ctx(options);
- }
- //setup options
- this.width = options.width || defaultOptions.width;
- this.height = options.height || defaultOptions.height;
- this.enableMirroring =
- options.enableMirroring !== undefined
- ? options.enableMirroring
- : defaultOptions.enableMirroring;
- this.canvas = this; ///point back to this instance!
- this.__document = options.document || document;
- // allow passing in an existing context to wrap around
- // if a context is passed in, we know a canvas already exist
- if (options.ctx) {
- this.__ctx = options.ctx;
- } else {
- this.__canvas = this.__document.createElement('canvas');
- this.__ctx = this.__canvas.getContext('2d');
- }
- this.__setDefaultStyles();
- this.__stack = [this.__getStyleState()];
- this.__groupStack = [];
- //the root svg element
- this.__root = this.__document.createElementNS(
- 'http://www.w3.org/2000/svg',
- 'svg'
- );
- this.__root.setAttribute('version', 1.1);
- this.__root.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
- this.__root.setAttributeNS(
- 'http://www.w3.org/2000/xmlns/',
- 'xmlns:xlink',
- 'http://www.w3.org/1999/xlink'
- );
- this.__root.setAttribute('width', this.width);
- this.__root.setAttribute('height', this.height);
- //make sure we don't generate the same ids in defs
- this.__ids = {};
- //defs tag
- this.__defs = this.__document.createElementNS(
- 'http://www.w3.org/2000/svg',
- 'defs'
- );
- this.__root.appendChild(this.__defs);
- //also add a group child. the svg element can't use the transform attribute
- this.__currentElement = this.__document.createElementNS(
- 'http://www.w3.org/2000/svg',
- 'g'
- );
- this.__root.appendChild(this.__currentElement);
- };
- /**
- * Creates the specified svg element
- * @private
- */
- ctx.prototype.__createElement = function(elementName, properties, resetFill) {
- if (typeof properties === 'undefined') {
- properties = {};
- }
- var element = this.__document.createElementNS(
- 'http://www.w3.org/2000/svg',
- elementName
- ),
- keys = Object.keys(properties),
- i,
- key;
- if (resetFill) {
- //if fill or stroke is not specified, the svg element should not display. By default SVG's fill is black.
- element.setAttribute('fill', 'none');
- element.setAttribute('stroke', 'none');
- }
- for (i = 0; i < keys.length; i++) {
- key = keys[i];
- element.setAttribute(key, properties[key]);
- }
- return element;
- };
- /**
- * Applies default canvas styles to the context
- * @private
- */
- ctx.prototype.__setDefaultStyles = function() {
- //default 2d canvas context properties see:http://www.w3.org/TR/2dcontext/
- var keys = Object.keys(STYLES),
- i,
- key;
- for (i = 0; i < keys.length; i++) {
- key = keys[i];
- this[key] = STYLES[key].canvas;
- }
- };
- ctx.prototype.setAttrs = function(pen) {
- if (!pen) {
- return;
- }
- var currentElement = this.__currentElement;
- currentElement.setAttribute('id', pen.id);
- currentElement.setAttribute('name', pen.name);
- pen.text && currentElement.setAttribute('text', pen.text);
- pen.lineName && currentElement.setAttribute('line-name', pen.lineName);
- // 连线起终点
- if (pen.type == 1) {
- const from = pen.anchors[0];
- from.connectTo && currentElement.setAttribute('from-id', from.connectTo);
- const to = pen.anchors[pen.anchors.length - 1];
- to.connectTo && currentElement.setAttribute('to-id', to.connectTo);
- }
- // 业务数据
- if (!Array.isArray(pen.form)) {
- return;
- }
- pen.form.forEach(({key}) => {
- // TODO: pen[key] 若是一个对象,待考虑
- currentElement.setAttribute('data-' + key, pen[key]);
- });
- };
- /**
- * Applies styles on restore
- * @param styleState
- * @private
- */
- ctx.prototype.__applyStyleState = function(styleState) {
- var keys = Object.keys(styleState),
- i,
- key;
- for (i = 0; i < keys.length; i++) {
- key = keys[i];
- this[key] = styleState[key];
- }
- };
- /**
- * Gets the current style state
- * @return {Object}
- * @private
- */
- ctx.prototype.__getStyleState = function() {
- var i,
- styleState = {},
- keys = Object.keys(STYLES),
- key;
- for (i = 0; i < keys.length; i++) {
- key = keys[i];
- styleState[key] = this[key];
- }
- return styleState;
- };
- /**
- * Apples the current styles to the current SVG element. On "ctx.fill" or "ctx.stroke"
- * @param type
- * @private
- */
- ctx.prototype.__applyStyleToCurrentElement = function(type) {
- var currentElement = this.__currentElement;
- var currentStyleGroup = this.__currentElementsToStyle;
- if (currentStyleGroup) {
- currentElement.setAttribute(type, '');
- currentElement = currentStyleGroup.element;
- currentStyleGroup.children.forEach(function(node) {
- node.setAttribute(type, '');
- });
- }
- var keys = Object.keys(STYLES),
- i,
- style,
- value,
- id,
- regex,
- matches;
- for (i = 0; i < keys.length; i++) {
- style = STYLES[keys[i]];
- value = this[keys[i]];
- if (style.apply) {
- //is this a gradient or pattern?
- if (value instanceof CanvasPattern) {
- //pattern
- if (value.__ctx) {
- //copy over defs
- while (value.__ctx.__defs.childNodes.length) {
- id = value.__ctx.__defs.childNodes[0].getAttribute('id');
- this.__ids[id] = id;
- this.__defs.appendChild(value.__ctx.__defs.childNodes[0]);
- }
- }
- currentElement.setAttribute(
- style.apply,
- format('url(#{id})', { id: value.__root.getAttribute('id') })
- );
- } else if (value instanceof CanvasGradient) {
- //gradient
- currentElement.setAttribute(
- style.apply,
- format('url(#{id})', { id: value.__root.getAttribute('id') })
- );
- } else if (style.apply.indexOf(type) !== -1 && style.svg !== value) {
- if (
- (style.svgAttr === 'stroke' || style.svgAttr === 'fill') &&
- value &&
- value.indexOf('rgba') !== -1
- ) {
- //separate alpha value, since illustrator can't handle it
- regex = /rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d?\.?\d*)\s*\)/gi;
- matches = regex.exec(value);
- currentElement.setAttribute(
- style.svgAttr,
- format('rgb({r},{g},{b})', {
- r: matches[1],
- g: matches[2],
- b: matches[3]
- })
- );
- //should take globalAlpha here
- var opacity = matches[4];
- var globalAlpha = this.globalAlpha;
- if (globalAlpha != null) {
- opacity *= globalAlpha;
- }
- currentElement.setAttribute(style.svgAttr + '-opacity', opacity);
- } else {
- var attr = style.svgAttr;
- if (keys[i] === 'globalAlpha') {
- attr = type + '-' + style.svgAttr;
- if (currentElement.getAttribute(attr)) {
- //fill-opacity or stroke-opacity has already been set by stroke or fill.
- continue;
- }
- }
- //otherwise only update attribute if right type, and not svg default
- currentElement.setAttribute(attr, value);
- }
- }
- }
- }
- };
- /**
- * Will return the closest group or svg node. May return the current element.
- * @private
- */
- ctx.prototype.__closestGroupOrSvg = function(node) {
- node = node || this.__currentElement;
- if (node.nodeName === 'g' || node.nodeName === 'svg') {
- return node;
- } else {
- return this.__closestGroupOrSvg(node.parentNode);
- }
- };
- /**
- * Returns the serialized value of the svg so far
- * @param fixNamedEntities - Standalone SVG doesn't support named entities, which document.createTextNode encodes.
- * If true, we attempt to find all named entities and encode it as a numeric entity.
- * @return serialized svg
- */
- ctx.prototype.getSerializedSvg = function(fixNamedEntities) {
- var serialized = new XMLSerializer().serializeToString(this.__root),
- keys,
- i,
- key,
- value,
- regexp,
- xmlns;
- //IE search for a duplicate xmnls because they didn't implement setAttributeNS correctly
- xmlns = /xmlns="http:\/\/www\.w3\.org\/2000\/svg".+xmlns="http:\/\/www\.w3\.org\/2000\/svg/gi;
- if (xmlns.test(serialized)) {
- serialized = serialized.replace(
- 'xmlns="http://www.w3.org/2000/svg',
- 'xmlns:xlink="http://www.w3.org/1999/xlink'
- );
- }
- if (fixNamedEntities) {
- keys = Object.keys(namedEntities);
- //loop over each named entity and replace with the proper equivalent.
- for (i = 0; i < keys.length; i++) {
- key = keys[i];
- value = namedEntities[key];
- regexp = new RegExp(key, 'gi');
- if (regexp.test(serialized)) {
- serialized = serialized.replace(regexp, value);
- }
- }
- }
- return serialized;
- };
- /**
- * Returns the root svg
- * @return
- */
- ctx.prototype.getSvg = function() {
- return this.__root;
- };
- /**
- * Will generate a group tag.
- */
- ctx.prototype.save = function() {
- var group = this.__createElement('g');
- var parent = this.__closestGroupOrSvg();
- this.__groupStack.push(parent);
- parent.appendChild(group);
- this.__currentElement = group;
- this.__stack.push(this.__getStyleState());
- };
- /**
- * Sets current element to parent, or just root if already root
- */
- ctx.prototype.restore = function() {
- this.__currentElement = this.__groupStack.pop();
- this.__currentElementsToStyle = null;
- //Clearing canvas will make the poped group invalid, currentElement is set to the root group node.
- if (!this.__currentElement) {
- this.__currentElement = this.__root.childNodes[1];
- }
- var state = this.__stack.pop();
- this.__applyStyleState(state);
- };
- /**
- * Helper method to add transform
- * @private
- */
- ctx.prototype.__addTransform = function(t) {
- //if the current element has siblings, add another group
- var parent = this.__closestGroupOrSvg();
- if (parent.childNodes.length > 0) {
- if (this.__currentElement.nodeName === 'path') {
- if (!this.__currentElementsToStyle)
- this.__currentElementsToStyle = { element: parent, children: [] };
- this.__currentElementsToStyle.children.push(this.__currentElement);
- this.__applyCurrentDefaultPath();
- }
- var group = this.__createElement('g');
- parent.appendChild(group);
- this.__currentElement = group;
- }
- var transform = this.__currentElement.getAttribute('transform');
- if (transform) {
- transform += ' ';
- } else {
- transform = '';
- }
- transform += t;
- this.__currentElement.setAttribute('transform', transform);
- };
- /**
- * scales the current element
- */
- ctx.prototype.scale = function(x, y) {
- if (y === undefined) {
- y = x;
- }
- this.__addTransform(format('scale({x},{y})', { x: x, y: y }));
- };
- /**
- * rotates the current element
- */
- ctx.prototype.rotate = function(angle) {
- var degrees = (angle * 180) / Math.PI;
- this.__addTransform(
- format('rotate({angle},{cx},{cy})', { angle: degrees, cx: 0, cy: 0 })
- );
- };
- /**
- * translates the current element
- */
- ctx.prototype.translate = function(x, y) {
- this.__addTransform(format('translate({x},{y})', { x: x, y: y }));
- };
- /**
- * applies a transform to the current element
- */
- ctx.prototype.transform = function(a, b, c, d, e, f) {
- this.__addTransform(
- format('matrix({a},{b},{c},{d},{e},{f})', {
- a: a,
- b: b,
- c: c,
- d: d,
- e: e,
- f: f
- })
- );
- };
- /**
- * Create a new Path Element
- */
- ctx.prototype.beginPath = function() {
- var path, parent;
- // Note that there is only one current default path, it is not part of the drawing state.
- // See also: https://html.spec.whatwg.org/multipage/scripting.html#current-default-path
- this.__currentDefaultPath = '';
- this.__currentPosition = {};
- path = this.__createElement('path', {}, true);
- parent = this.__closestGroupOrSvg();
- parent.appendChild(path);
- this.__currentElement = path;
- };
- /**
- * Helper function to apply currentDefaultPath to current path element
- * @private
- */
- ctx.prototype.__applyCurrentDefaultPath = function() {
- var currentElement = this.__currentElement;
- if (currentElement.nodeName === 'path') {
- currentElement.setAttribute('d', this.__currentDefaultPath);
- } else {
- console.error(
- 'Attempted to apply path command to node',
- currentElement.nodeName
- );
- }
- };
- ctx.prototype.svgPath = function(text) {
- this.__addPathCommand(text);
- };
- /**
- * Helper function to add path command
- * @private
- */
- ctx.prototype.__addPathCommand = function(command) {
- this.__currentDefaultPath += ' ';
- this.__currentDefaultPath += command;
- };
- /**
- * Adds the move command to the current path element,
- * if the currentPathElement is not empty create a new path element
- */
- ctx.prototype.moveTo = function(x, y) {
- if (this.__currentElement.nodeName !== 'path') {
- this.beginPath();
- }
- // creates a new subpath with the given point
- this.__currentPosition = { x: x, y: y };
- this.__addPathCommand(format('M {x} {y}', { x: x, y: y }));
- };
- /**
- * Closes the current path
- */
- ctx.prototype.closePath = function() {
- if (this.__currentDefaultPath) {
- this.__addPathCommand('Z');
- }
- };
- /**
- * Adds a line to command
- */
- ctx.prototype.lineTo = function(x, y) {
- this.__currentPosition = { x: x, y: y };
- if (this.__currentDefaultPath.indexOf('M') > -1) {
- this.__addPathCommand(format('L {x} {y}', { x: x, y: y }));
- } else {
- this.__addPathCommand(format('M {x} {y}', { x: x, y: y }));
- }
- };
- /**
- * Add a bezier command
- */
- ctx.prototype.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
- this.__currentPosition = { x: x, y: y };
- this.__addPathCommand(
- format('C {cp1x} {cp1y} {cp2x} {cp2y} {x} {y}', {
- cp1x: cp1x,
- cp1y: cp1y,
- cp2x: cp2x,
- cp2y: cp2y,
- x: x,
- y: y
- })
- );
- };
- /**
- * Adds a quadratic curve to command
- */
- ctx.prototype.quadraticCurveTo = function(cpx, cpy, x, y) {
- this.__currentPosition = { x: x, y: y };
- this.__addPathCommand(
- format('Q {cpx} {cpy} {x} {y}', { cpx: cpx, cpy: cpy, x: x, y: y })
- );
- };
- /**
- * Return a new normalized vector of given vector
- */
- var normalize = function(vector) {
- var len = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1]);
- return [vector[0] / len, vector[1] / len];
- };
- /**
- * Adds the arcTo to the current path
- *
- * @see http://www.w3.org/TR/2015/WD-2dcontext-20150514/#dom-context-2d-arcto
- */
- ctx.prototype.arcTo = function(x1, y1, x2, y2, radius) {
- // Let the point (x0, y0) be the last point in the subpath.
- var x0 = this.__currentPosition && this.__currentPosition.x;
- var y0 = this.__currentPosition && this.__currentPosition.y;
- // First ensure there is a subpath for (x1, y1).
- if (typeof x0 == 'undefined' || typeof y0 == 'undefined') {
- return;
- }
- // Negative values for radius must cause the implementation to throw an IndexSizeError exception.
- if (radius < 0) {
- throw new Error(
- 'IndexSizeError: The radius provided (' + radius + ') is negative.'
- );
- }
- // If the point (x0, y0) is equal to the point (x1, y1),
- // or if the point (x1, y1) is equal to the point (x2, y2),
- // or if the radius radius is zero,
- // then the method must add the point (x1, y1) to the subpath,
- // and connect that point to the previous point (x0, y0) by a straight line.
- if ((x0 === x1 && y0 === y1) || (x1 === x2 && y1 === y2) || radius === 0) {
- this.lineTo(x1, y1);
- return;
- }
- // Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2) all lie on a single straight line,
- // then the method must add the point (x1, y1) to the subpath,
- // and connect that point to the previous point (x0, y0) by a straight line.
- var unit_vec_p1_p0 = normalize([x0 - x1, y0 - y1]);
- var unit_vec_p1_p2 = normalize([x2 - x1, y2 - y1]);
- if (
- unit_vec_p1_p0[0] * unit_vec_p1_p2[1] ===
- unit_vec_p1_p0[1] * unit_vec_p1_p2[0]
- ) {
- this.lineTo(x1, y1);
- return;
- }
- // Otherwise, let The Arc be the shortest arc given by circumference of the circle that has radius radius,
- // and that has one point tangent to the half-infinite line that crosses the point (x0, y0) and ends at the point (x1, y1),
- // and that has a different point tangent to the half-infinite line that ends at the point (x1, y1), and crosses the point (x2, y2).
- // The points at which this circle touches these two lines are called the start and end tangent points respectively.
- // note that both vectors are unit vectors, so the length is 1
- var cos =
- unit_vec_p1_p0[0] * unit_vec_p1_p2[0] +
- unit_vec_p1_p0[1] * unit_vec_p1_p2[1];
- var theta = Math.acos(Math.abs(cos));
- // Calculate origin
- var unit_vec_p1_origin = normalize([
- unit_vec_p1_p0[0] + unit_vec_p1_p2[0],
- unit_vec_p1_p0[1] + unit_vec_p1_p2[1]
- ]);
- var len_p1_origin = radius / Math.sin(theta / 2);
- var x = x1 + len_p1_origin * unit_vec_p1_origin[0];
- var y = y1 + len_p1_origin * unit_vec_p1_origin[1];
- // Calculate start angle and end angle
- // rotate 90deg clockwise (note that y axis points to its down)
- var unit_vec_origin_start_tangent = [-unit_vec_p1_p0[1], unit_vec_p1_p0[0]];
- // rotate 90deg counter clockwise (note that y axis points to its down)
- var unit_vec_origin_end_tangent = [unit_vec_p1_p2[1], -unit_vec_p1_p2[0]];
- var getAngle = function(vector) {
- // get angle (clockwise) between vector and (1, 0)
- var x = vector[0];
- var y = vector[1];
- if (y >= 0) {
- // note that y axis points to its down
- return Math.acos(x);
- } else {
- return -Math.acos(x);
- }
- };
- var startAngle = getAngle(unit_vec_origin_start_tangent);
- var endAngle = getAngle(unit_vec_origin_end_tangent);
- // Connect the point (x0, y0) to the start tangent point by a straight line
- this.lineTo(
- x + unit_vec_origin_start_tangent[0] * radius,
- y + unit_vec_origin_start_tangent[1] * radius
- );
- // Connect the start tangent point to the end tangent point by arc
- // and adding the end tangent point to the subpath.
- this.arc(x, y, radius, startAngle, endAngle);
- };
- /**
- * Sets the stroke property on the current element
- */
- ctx.prototype.stroke = function() {
- if (this.__currentElement.nodeName === 'path') {
- this.__currentElement.setAttribute('paint-order', 'fill stroke markers');
- }
- this.__applyCurrentDefaultPath();
- this.__applyStyleToCurrentElement('stroke');
- };
- /**
- * Sets fill properties on the current element
- */
- ctx.prototype.fill = function() {
- if (this.__currentElement.nodeName === 'path') {
- this.__currentElement.setAttribute('paint-order', 'stroke fill markers');
- }
- this.__applyCurrentDefaultPath();
- this.__applyStyleToCurrentElement('fill');
- };
- /**
- * Adds a rectangle to the path.
- */
- ctx.prototype.rect = function(x, y, width, height) {
- if (this.__currentElement.nodeName !== 'path') {
- this.beginPath();
- }
- this.moveTo(x, y);
- this.lineTo(x + width, y);
- this.lineTo(x + width, y + height);
- this.lineTo(x, y + height);
- this.lineTo(x, y);
- this.closePath();
- };
- /**
- * adds a rectangle element
- */
- ctx.prototype.fillRect = function(x, y, width, height) {
- var rect, parent;
- rect = this.__createElement(
- 'rect',
- {
- x: x,
- y: y,
- width: width,
- height: height
- },
- true
- );
- parent = this.__closestGroupOrSvg();
- parent.appendChild(rect);
- this.__currentElement = rect;
- this.__applyStyleToCurrentElement('fill');
- };
- /**
- * Draws a rectangle with no fill
- * @param x
- * @param y
- * @param width
- * @param height
- */
- ctx.prototype.strokeRect = function(x, y, width, height) {
- var rect, parent;
- rect = this.__createElement(
- 'rect',
- {
- x: x,
- y: y,
- width: width,
- height: height
- },
- true
- );
- parent = this.__closestGroupOrSvg();
- parent.appendChild(rect);
- this.__currentElement = rect;
- this.__applyStyleToCurrentElement('stroke');
- };
- /**
- * Clear entire canvas:
- * 1. save current transforms
- * 2. remove all the childNodes of the root g element
- */
- ctx.prototype.__clearCanvas = function() {
- var current = this.__closestGroupOrSvg(),
- transform = current.getAttribute('transform');
- var rootGroup = this.__root.childNodes[1];
- var childNodes = rootGroup.childNodes;
- for (var i = childNodes.length - 1; i >= 0; i--) {
- if (childNodes[i]) {
- rootGroup.removeChild(childNodes[i]);
- }
- }
- this.__currentElement = rootGroup;
- //reset __groupStack as all the child group nodes are all removed.
- this.__groupStack = [];
- if (transform) {
- this.__addTransform(transform);
- }
- };
- /**
- * "Clears" a canvas by just drawing a white rectangle in the current group.
- */
- ctx.prototype.clearRect = function(x, y, width, height) {
- //clear entire canvas
- if (x === 0 && y === 0 && width === this.width && height === this.height) {
- this.__clearCanvas();
- return;
- }
- var rect,
- parent = this.__closestGroupOrSvg();
- rect = this.__createElement(
- 'rect',
- {
- x: x,
- y: y,
- width: width,
- height: height,
- fill: '#FFFFFF'
- },
- true
- );
- parent.appendChild(rect);
- };
- /**
- * Adds a linear gradient to a defs tag.
- * Returns a canvas gradient object that has a reference to it's parent def
- */
- ctx.prototype.createLinearGradient = function(x1, y1, x2, y2) {
- var grad = this.__createElement(
- 'linearGradient',
- {
- id: randomString(this.__ids),
- x1: x1 + 'px',
- x2: x2 + 'px',
- y1: y1 + 'px',
- y2: y2 + 'px',
- gradientUnits: 'userSpaceOnUse'
- },
- false
- );
- this.__defs.appendChild(grad);
- return new CanvasGradient(grad, this);
- };
- /**
- * Adds a radial gradient to a defs tag.
- * Returns a canvas gradient object that has a reference to it's parent def
- */
- ctx.prototype.createRadialGradient = function(x0, y0, r0, x1, y1, r1) {
- var grad = this.__createElement(
- 'radialGradient',
- {
- id: randomString(this.__ids),
- cx: x1 + 'px',
- cy: y1 + 'px',
- r: r1 + 'px',
- fx: x0 + 'px',
- fy: y0 + 'px',
- gradientUnits: 'userSpaceOnUse'
- },
- false
- );
- this.__defs.appendChild(grad);
- return new CanvasGradient(grad, this);
- };
- /**
- * Parses the font string and returns svg mapping
- * @private
- */
- ctx.prototype.__parseFont = function() {
- var fontPart = this.font.split(' ') || [];
- if (fontPart[3] && fontPart[3].indexOf('/') > -1) {
- fontPart[3] = fontPart[3].split('/')[0];
- }
- var family = '';
- for (var i = 4; i < fontPart.length; ++i) {
- family += fontPart[i] + ' ';
- }
- if (fontPart.length === 2) {
- family = fontPart[1];
- fontPart = ['normal', 'normal', 'normal', fontPart[0]];
- }
- var data = {
- style: fontPart[0] || 'normal',
- size: fontPart[3] || '12px',
- family: family || 'Arial',
- weight: fontPart[2] || 'normal',
- decoration: fontPart[1] || 'normal',
- href: null
- };
- //canvas doesn't support underline natively, but we can pass this attribute
- if (this.__fontUnderline === 'underline') {
- data.decoration = 'underline';
- }
- //canvas also doesn't support linking, but we can pass this as well
- if (this.__fontHref) {
- data.href = this.__fontHref;
- }
- return data;
- };
- /**
- * Helper to link text fragments
- * @param font
- * @param element
- * @return {*}
- * @private
- */
- ctx.prototype.__wrapTextLink = function(font, element) {
- if (font.href) {
- var a = this.__createElement('a');
- a.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', font.href);
- a.appendChild(element);
- return a;
- }
- return element;
- };
- /**
- * Fills or strokes text
- * @param text
- * @param x
- * @param y
- * @param action - stroke or fill
- * @private
- */
- ctx.prototype.__applyText = function(text, x, y, action) {
- var font = this.__parseFont(),
- parent = this.__closestGroupOrSvg(),
- textElement = this.__createElement(
- 'text',
- {
- 'font-family': font.family,
- 'font-size': font.size,
- 'font-style': font.style,
- 'font-weight': font.weight,
- 'text-decoration': font.decoration,
- x: x,
- y: y,
- 'text-anchor': getTextAnchor(this.textAlign),
- 'dominant-baseline': getDominantBaseline(this.textBaseline)
- },
- true
- );
- if (font.family === 'topology') {
- text = '--le5le--' + (+text.charCodeAt()).toString(16) + ';';
- }
- textElement.appendChild(this.__document.createTextNode(text));
- this.__currentElement = textElement;
- this.__applyStyleToCurrentElement(action);
- parent.appendChild(this.__wrapTextLink(font, textElement));
- };
- /**
- * Creates a text element
- * @param text
- * @param x
- * @param y
- */
- ctx.prototype.fillText = function(text, x, y) {
- this.__applyText(text, x, y, 'fill');
- };
- /**
- * Strokes text
- * @param text
- * @param x
- * @param y
- */
- ctx.prototype.strokeText = function(text, x, y) {
- this.__applyText(text, x, y, 'stroke');
- };
- /**
- * No need to implement this for svg.
- * @param text
- * @return {TextMetrics}
- */
- ctx.prototype.measureText = function(text) {
- this.__ctx.font = this.font;
- return this.__ctx.measureText(text);
- };
- /**
- * Arc command!
- */
- ctx.prototype.arc = function(
- x,
- y,
- radius,
- startAngle,
- endAngle,
- counterClockwise
- ) {
- // in canvas no circle is drawn if no angle is provided.
- if (startAngle === endAngle) {
- return;
- }
- if (this.__currentElement.nodeName !== 'path') {
- this.beginPath();
- }
- startAngle = startAngle % (2 * Math.PI);
- endAngle = endAngle % (2 * Math.PI);
- if (startAngle === endAngle) {
- //circle time! subtract some of the angle so svg is happy (svg elliptical arc can't draw a full circle)
- endAngle =
- (endAngle + 2 * Math.PI - 0.001 * (counterClockwise ? -1 : 1)) %
- (2 * Math.PI);
- }
- var endX = x + radius * Math.cos(endAngle),
- endY = y + radius * Math.sin(endAngle),
- startX = x + radius * Math.cos(startAngle),
- startY = y + radius * Math.sin(startAngle),
- sweepFlag = counterClockwise ? 0 : 1,
- largeArcFlag = 0,
- diff = endAngle - startAngle;
- // https://github.com/gliffy/canvas2svg/issues/4
- if (diff < 0) {
- diff += 2 * Math.PI;
- }
- if (counterClockwise) {
- largeArcFlag = diff > Math.PI ? 0 : 1;
- } else {
- largeArcFlag = diff > Math.PI ? 1 : 0;
- }
- this.lineTo(startX, startY);
- this.__addPathCommand(
- format(
- 'A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}',
- {
- rx: radius,
- ry: radius,
- xAxisRotation: 0,
- largeArcFlag: largeArcFlag,
- sweepFlag: sweepFlag,
- endX: endX,
- endY: endY
- }
- )
- );
- this.__currentPosition = { x: endX, y: endY };
- };
- /**
- * Generates a ClipPath from the clip command.
- */
- ctx.prototype.clip = function() {
- var group = this.__closestGroupOrSvg(),
- clipPath = this.__createElement('clipPath'),
- id = randomString(this.__ids),
- newGroup = this.__createElement('g');
- this.__applyCurrentDefaultPath();
- group.removeChild(this.__currentElement);
- clipPath.setAttribute('id', id);
- clipPath.appendChild(this.__currentElement);
- this.__defs.appendChild(clipPath);
- //set the clip path to this group
- group.setAttribute('clip-path', format('url(#{id})', { id: id }));
- //clip paths can be scaled and transformed, we need to add another wrapper group to avoid later transformations
- // to this path
- group.appendChild(newGroup);
- this.__currentElement = newGroup;
- };
- /**
- * Draws a canvas, image or mock context to this canvas.
- * Note that all svg dom manipulation uses node.childNodes rather than node.children for IE support.
- * http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-drawimage
- */
- ctx.prototype.drawImage = function() {
- //convert arguments to a real array
- var args = Array.prototype.slice.call(arguments),
- image = args[0],
- dx,
- dy,
- dw,
- dh,
- sx = 0,
- sy = 0,
- sw,
- sh,
- parent,
- svg,
- defs,
- group,
- currentElement,
- svgImage,
- canvas,
- context,
- id;
- if (args.length === 3) {
- dx = args[1];
- dy = args[2];
- sw = image.width;
- sh = image.height;
- dw = sw;
- dh = sh;
- } else if (args.length === 5) {
- dx = args[1];
- dy = args[2];
- dw = args[3];
- dh = args[4];
- sw = image.width;
- sh = image.height;
- } else if (args.length === 9) {
- sx = args[1];
- sy = args[2];
- sw = args[3];
- sh = args[4];
- dx = args[5];
- dy = args[6];
- dw = args[7];
- dh = args[8];
- } else {
- throw new Error(
- 'Invalid number of arguments passed to drawImage: ' + arguments.length
- );
- }
- parent = this.__closestGroupOrSvg();
- currentElement = this.__currentElement;
- var translateDirective = 'translate(' + dx + ', ' + dy + ')';
- if (image instanceof ctx) {
- //canvas2svg mock canvas context. In the future we may want to clone nodes instead.
- //also I'm currently ignoring dw, dh, sw, sh, sx, sy for a mock context.
- svg = image.getSvg().cloneNode(true);
- if (svg.childNodes && svg.childNodes.length > 1) {
- defs = svg.childNodes[0];
- while (defs.childNodes.length) {
- id = defs.childNodes[0].getAttribute('id');
- this.__ids[id] = id;
- this.__defs.appendChild(defs.childNodes[0]);
- }
- group = svg.childNodes[1];
- if (group) {
- //save original transform
- var originTransform = group.getAttribute('transform');
- var transformDirective;
- if (originTransform) {
- transformDirective = originTransform + ' ' + translateDirective;
- } else {
- transformDirective = translateDirective;
- }
- group.setAttribute('transform', transformDirective);
- parent.appendChild(group);
- }
- }
- } else if (image.nodeName === 'CANVAS' || image.nodeName === 'IMG') {
- //canvas or image
- svgImage = this.__createElement('image');
- svgImage.setAttribute('width', dw);
- svgImage.setAttribute('height', dh);
- svgImage.setAttribute('preserveAspectRatio', 'none');
- if (sx || sy || sw !== image.width || sh !== image.height) {
- //crop the image using a temporary canvas
- canvas = this.__document.createElement('canvas');
- canvas.width = dw;
- canvas.height = dh;
- context = canvas.getContext('2d');
- context.drawImage(image, sx, sy, sw, sh, 0, 0, dw, dh);
- image = canvas;
- }
- svgImage.setAttribute('transform', translateDirective);
- var imgSrc = image.getAttribute('src');
- if (imgSrc[0] === '/') {
- imgSrc = location.protocol + '//' + location.host + imgSrc;
- }
- svgImage.setAttributeNS(
- 'http://www.w3.org/1999/xlink',
- 'xlink:href',
- image.nodeName === 'CANVAS' ? image.toDataURL() : imgSrc
- );
- parent.appendChild(svgImage);
- }
- };
- /**
- * Generates a pattern tag
- */
- ctx.prototype.createPattern = function(image, repetition) {
- var pattern = this.__document.createElementNS(
- 'http://www.w3.org/2000/svg',
- 'pattern'
- ),
- id = randomString(this.__ids),
- img;
- pattern.setAttribute('id', id);
- pattern.setAttribute('width', image.width);
- pattern.setAttribute('height', image.height);
- if (image.nodeName === 'CANVAS' || image.nodeName === 'IMG') {
- img = this.__document.createElementNS(
- 'http://www.w3.org/2000/svg',
- 'image'
- );
- img.setAttribute('width', image.width);
- img.setAttribute('height', image.height);
- img.setAttributeNS(
- 'http://www.w3.org/1999/xlink',
- 'xlink:href',
- image.nodeName === 'CANVAS'
- ? image.toDataURL()
- : image.getAttribute('src')
- );
- pattern.appendChild(img);
- this.__defs.appendChild(pattern);
- } else if (image instanceof ctx) {
- pattern.appendChild(image.__root.childNodes[1]);
- this.__defs.appendChild(pattern);
- }
- return new CanvasPattern(pattern, this);
- };
- ctx.prototype.setLineDash = function(dashArray) {
- if (dashArray && dashArray.length > 0) {
- this.lineDash = dashArray.join(',');
- } else {
- this.lineDash = null;
- }
- };
- /*
- * Ellipse command
- * @param x
- * @param y
- * @param radiusX
- * @param radiusY
- * @param startAngle
- * @param endAngle
- * @counterClockwise
- */
- ctx.prototype.ellipse = function(
- x,
- y,
- radiusX,
- radiusY,
- rotation,
- startAngle,
- endAngle,
- counterClockwise
- ) {
- //ellipse is the same svg command as arc, but with a radiusX and radiusY instead of just radius
- if (startAngle === endAngle) {
- return;
- }
- if (this.__currentElement.nodeName !== 'path') {
- this.beginPath();
- }
- startAngle = startAngle % (2 * Math.PI);
- endAngle = endAngle % (2 * Math.PI);
- if (startAngle === endAngle) {
- endAngle =
- (endAngle + 2 * Math.PI - 0.001 * (counterClockwise ? -1 : 1)) %
- (2 * Math.PI);
- }
- var endX =
- x +
- Math.cos(-rotation) * radiusX * Math.cos(endAngle) +
- Math.sin(-rotation) * radiusY * Math.sin(endAngle),
- endY =
- y -
- Math.sin(-rotation) * radiusX * Math.cos(endAngle) +
- Math.cos(-rotation) * radiusY * Math.sin(endAngle),
- startX =
- x +
- Math.cos(-rotation) * radiusX * Math.cos(startAngle) +
- Math.sin(-rotation) * radiusY * Math.sin(startAngle),
- startY =
- y -
- Math.sin(-rotation) * radiusX * Math.cos(startAngle) +
- Math.cos(-rotation) * radiusY * Math.sin(startAngle),
- sweepFlag = counterClockwise ? 0 : 1,
- largeArcFlag = 0,
- diff = endAngle - startAngle;
- if (diff < 0) {
- diff += 2 * Math.PI;
- }
- if (counterClockwise) {
- largeArcFlag = diff > Math.PI ? 0 : 1;
- } else {
- largeArcFlag = diff > Math.PI ? 1 : 0;
- }
- this.lineTo(startX, startY);
- this.__addPathCommand(
- format(
- 'A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}',
- {
- rx: radiusX,
- ry: radiusY,
- xAxisRotation: rotation * (180 / Math.PI),
- largeArcFlag: largeArcFlag,
- sweepFlag: sweepFlag,
- endX: endX,
- endY: endY
- }
- )
- );
- this.__currentPosition = { x: endX, y: endY };
- };
- /**
- * Not yet implemented
- */
- ctx.prototype.drawFocusRing = function() {};
- ctx.prototype.createImageData = function() {};
- ctx.prototype.getImageData = function() {};
- ctx.prototype.putImageData = function() {};
- ctx.prototype.globalCompositeOperation = function() {};
- ctx.prototype.setTransform = function() {};
- //add options for alternative namespace
- if (typeof window === 'object') {
- window.C2S = ctx;
- }
- // CommonJS/Browserify
- if (typeof module === 'object' && typeof module.exports === 'object') {
- module.exports = ctx;
- }
- })();
|