PageRenderTime 6277ms CodeModel.GetById 30ms RepoModel.GetById 10ms app.codeStats 1ms

/editor/svg-editor.js

http://svg-edit.googlecode.com/
JavaScript | 5180 lines | 3927 code | 588 blank | 665 comment | 744 complexity | b945fbd6e1fac0e3a488181d21e0bd89 MD5 | raw file
Possible License(s): Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. /*globals saveAs:true, svgEditor:true, globalStorage, widget, svgedit, canvg, jsPDF, svgElementToPdf, jQuery, $, DOMParser, FileReader, URL */
  2. /*jslint vars: true, eqeq: true, todo: true, forin: true, continue: true, regexp: true */
  3. /*
  4. * svg-editor.js
  5. *
  6. * Licensed under the MIT License
  7. *
  8. * Copyright(c) 2010 Alexis Deveria
  9. * Copyright(c) 2010 Pavol Rusnak
  10. * Copyright(c) 2010 Jeff Schiller
  11. * Copyright(c) 2010 Narendra Sisodiya
  12. * Copyright(c) 2014 Brett Zamir
  13. *
  14. */
  15. // Dependencies:
  16. // 1) units.js
  17. // 2) browser.js
  18. // 3) svgcanvas.js
  19. /*
  20. TODOS
  21. 1. JSDoc
  22. */
  23. (function() {
  24. if (window.svgEditor) {
  25. return;
  26. }
  27. window.svgEditor = (function($) {
  28. var editor = {};
  29. // EDITOR PROPERTIES: (defined below)
  30. // curPrefs, curConfig, canvas, storage, uiStrings
  31. //
  32. // STATE MAINTENANCE PROPERTIES
  33. editor.tool_scale = 1; // Dependent on icon size, so any use to making configurable instead? Used by JQuerySpinBtn.js
  34. editor.langChanged = false;
  35. editor.showSaveWarning = false;
  36. editor.storagePromptClosed = false; // For use with ext-storage.js
  37. var svgCanvas, urldata,
  38. Utils = svgedit.utilities,
  39. isReady = false,
  40. callbacks = [],
  41. customHandlers = {},
  42. /**
  43. * PREFS AND CONFIG
  44. */
  45. // The iteration algorithm for defaultPrefs does not currently support array/objects
  46. defaultPrefs = {
  47. // EDITOR OPTIONS (DIALOG)
  48. lang: '', // Default to "en" if locale.js detection does not detect another language
  49. iconsize: '', // Will default to 's' if the window height is smaller than the minimum height and 'm' otherwise
  50. bkgd_color: '#FFF',
  51. bkgd_url: '',
  52. // DOCUMENT PROPERTIES (DIALOG)
  53. img_save: 'embed',
  54. // ALERT NOTICES
  55. // Only shows in UI as far as alert notices, but useful to remember, so keeping as pref
  56. save_notice_done: false,
  57. export_notice_done: false
  58. },
  59. curPrefs = {},
  60. // Note: The difference between Prefs and Config is that Prefs
  61. // can be changed in the UI and are stored in the browser,
  62. // while config cannot
  63. curConfig = {
  64. // We do not put on defaultConfig to simplify object copying
  65. // procedures (we obtain instead from defaultExtensions)
  66. extensions: [],
  67. /**
  68. * Can use window.location.origin to indicate the current
  69. * origin. Can contain a '*' to allow all domains or 'null' (as
  70. * a string) to support all file:// URLs. Cannot be set by
  71. * URL for security reasons (not safe, at least for
  72. * privacy or data integrity of SVG content).
  73. * Might have been fairly safe to allow
  74. * `new URL(window.location.href).origin` by default but
  75. * avoiding it ensures some more security that even third
  76. * party apps on the same domain also cannot communicate
  77. * with this app by default.
  78. * For use with ext-xdomain-messaging.js
  79. * @todo We might instead make as a user-facing preference.
  80. */
  81. allowedOrigins: []
  82. },
  83. defaultExtensions = [
  84. 'ext-overview_window.js',
  85. 'ext-markers.js',
  86. 'ext-connector.js',
  87. 'ext-eyedropper.js',
  88. 'ext-shapes.js',
  89. 'ext-imagelib.js',
  90. 'ext-grid.js',
  91. 'ext-polygon.js',
  92. 'ext-star.js',
  93. 'ext-panning.js',
  94. 'ext-storage.js'
  95. ],
  96. defaultConfig = {
  97. // Todo: svgcanvas.js also sets and checks: show_outside_canvas, selectNew; add here?
  98. // Change the following to preferences and add pref controls to the UI (e.g., initTool, wireframe, showlayers)?
  99. canvasName: 'default',
  100. canvas_expansion: 3,
  101. initFill: {
  102. color: 'FF0000', // solid red
  103. opacity: 1
  104. },
  105. initStroke: {
  106. width: 5,
  107. color: '000000', // solid black
  108. opacity: 1
  109. },
  110. initOpacity: 1,
  111. colorPickerCSS: null, // Defaults to 'left' with a position equal to that of the fill_color or stroke_color element minus 140, and a 'bottom' equal to 40
  112. initTool: 'select',
  113. wireframe: false,
  114. showlayers: false,
  115. no_save_warning: false,
  116. // PATH CONFIGURATION
  117. // The following path configuration items are disallowed in the URL (as should any future path configurations)
  118. imgPath: 'images/',
  119. langPath: 'locale/',
  120. extPath: 'extensions/',
  121. jGraduatePath: 'jgraduate/images/',
  122. // DOCUMENT PROPERTIES
  123. // Change the following to a preference (already in the Document Properties dialog)?
  124. dimensions: [640, 480],
  125. // EDITOR OPTIONS
  126. // Change the following to preferences (already in the Editor Options dialog)?
  127. gridSnapping: false,
  128. gridColor: '#000',
  129. baseUnit: 'px',
  130. snappingStep: 10,
  131. showRulers: true,
  132. // URL BEHAVIOR CONFIGURATION
  133. preventAllURLConfig: false,
  134. preventURLContentLoading: false,
  135. // EXTENSION CONFIGURATION (see also preventAllURLConfig)
  136. lockExtensions: false, // Disallowed in URL setting
  137. noDefaultExtensions: false, // noDefaultExtensions can only be meaningfully used in config.js or in the URL
  138. // EXTENSION-RELATED (GRID)
  139. showGrid: false, // Set by ext-grid.js
  140. // EXTENSION-RELATED (STORAGE)
  141. noStorageOnLoad: false, // Some interaction with ext-storage.js; prevent even the loading of previously saved local storage
  142. forceStorage: false, // Some interaction with ext-storage.js; strongly discouraged from modification as it bypasses user privacy by preventing them from choosing whether to keep local storage or not
  143. emptyStorageOnDecline: false // Used by ext-storage.js; empty any prior storage if the user declines to store
  144. },
  145. /**
  146. * LOCALE
  147. * @todo Can we remove now that we are always loading even English? (unless locale is set to null)
  148. */
  149. uiStrings = editor.uiStrings = {
  150. common: {
  151. ok: 'OK',
  152. cancel: 'Cancel',
  153. key_up: 'Up',
  154. key_down: 'Down',
  155. key_backspace: 'Backspace',
  156. key_del: 'Del'
  157. },
  158. // This is needed if the locale is English, since the locale strings are not read in that instance.
  159. layers: {
  160. layer: 'Layer'
  161. },
  162. notification: {
  163. invalidAttrValGiven: 'Invalid value given',
  164. noContentToFitTo: 'No content to fit to',
  165. dupeLayerName: 'There is already a layer named that!',
  166. enterUniqueLayerName: 'Please enter a unique layer name',
  167. enterNewLayerName: 'Please enter the new layer name',
  168. layerHasThatName: 'Layer already has that name',
  169. QmoveElemsToLayer: 'Move selected elements to layer \'%s\'?',
  170. QwantToClear: 'Do you want to clear the drawing?\nThis will also erase your undo history!',
  171. QwantToOpen: 'Do you want to open a new file?\nThis will also erase your undo history!',
  172. QerrorsRevertToSource: 'There were parsing errors in your SVG source.\nRevert back to original SVG source?',
  173. QignoreSourceChanges: 'Ignore changes made to SVG source?',
  174. featNotSupported: 'Feature not supported',
  175. enterNewImgURL: 'Enter the new image URL',
  176. defsFailOnSave: 'NOTE: Due to a bug in your browser, this image may appear wrong (missing gradients or elements). It will however appear correct once actually saved.',
  177. loadingImage: 'Loading image, please wait...',
  178. saveFromBrowser: 'Select \'Save As...\' in your browser to save this image as a %s file.',
  179. noteTheseIssues: 'Also note the following issues: ',
  180. unsavedChanges: 'There are unsaved changes.',
  181. enterNewLinkURL: 'Enter the new hyperlink URL',
  182. errorLoadingSVG: 'Error: Unable to load SVG data',
  183. URLloadFail: 'Unable to load from URL',
  184. retrieving: 'Retrieving \'%s\' ...'
  185. }
  186. };
  187. function loadSvgString (str, callback) {
  188. var success = svgCanvas.setSvgString(str) !== false;
  189. callback = callback || $.noop;
  190. if (success) {
  191. callback(true);
  192. } else {
  193. $.alert(uiStrings.notification.errorLoadingSVG, function() {
  194. callback(false);
  195. });
  196. }
  197. }
  198. function checkCanvg (callCanvg) {
  199. return function (win, data) {
  200. if (window.canvg) {
  201. callCanvg(win, data);
  202. } else { // Might not be set up yet
  203. $.getScript('canvg/rgbcolor.js', function() {
  204. $.getScript('canvg/canvg.js', function() {
  205. callCanvg(win, data);
  206. });
  207. });
  208. }
  209. };
  210. }
  211. /**
  212. * EXPORTS
  213. */
  214. /**
  215. * Store and retrieve preferences
  216. * @param {string} key The preference name to be retrieved or set
  217. * @param {string} [val] The value. If the value supplied is missing or falsey, no change to the preference will be made.
  218. * @returns {string} If val is missing or falsey, the value of the previously stored preference will be returned.
  219. * @todo Can we change setting on the jQuery namespace (onto editor) to avoid conflicts?
  220. * @todo Review whether any remaining existing direct references to
  221. * getting curPrefs can be changed to use $.pref() getting to ensure
  222. * defaultPrefs fallback (also for sake of allowInitialUserOverride); specifically, bkgd_color could be changed so that
  223. * the pref dialog has a button to auto-calculate background, but otherwise uses $.pref() to be able to get default prefs
  224. * or overridable settings
  225. */
  226. $.pref = function (key, val) {
  227. if (val) {
  228. curPrefs[key] = val;
  229. editor.curPrefs = curPrefs; // Update exported value
  230. return;
  231. }
  232. return (key in curPrefs) ? curPrefs[key] : defaultPrefs[key];
  233. };
  234. /**
  235. * EDITOR PUBLIC METHODS
  236. * locale.js also adds "putLang" and "readLang" as editor methods
  237. * @todo Sort these methods per invocation order, ideally with init at the end
  238. * @todo Prevent execution until init executes if dependent on it?
  239. */
  240. /**
  241. * Where permitted, sets canvas and/or defaultPrefs based on previous
  242. * storage. This will override URL settings (for security reasons) but
  243. * not config.js configuration (unless initial user overriding is explicitly
  244. * permitted there via allowInitialUserOverride).
  245. * @todo Split allowInitialUserOverride into allowOverrideByURL and
  246. * allowOverrideByUserStorage so config.js can disallow some
  247. * individual items for URL setting but allow for user storage AND/OR
  248. * change URL setting so that it always uses a different namespace,
  249. * so it won't affect pre-existing user storage (but then if users saves
  250. * that, it will then be subject to tampering
  251. */
  252. editor.loadContentAndPrefs = function () {
  253. if (!curConfig.forceStorage && (curConfig.noStorageOnLoad || !document.cookie.match(/(?:^|;\s*)store=(?:prefsAndContent|prefsOnly)/))) {
  254. return;
  255. }
  256. // LOAD CONTENT
  257. if (editor.storage && // Cookies do not have enough available memory to hold large documents
  258. (curConfig.forceStorage || (!curConfig.noStorageOnLoad && document.cookie.match(/(?:^|;\s*)store=prefsAndContent/)))
  259. ) {
  260. var name = 'svgedit-' + curConfig.canvasName;
  261. var cached = editor.storage.getItem(name);
  262. if (cached) {
  263. editor.loadFromString(cached);
  264. }
  265. }
  266. // LOAD PREFS
  267. var key;
  268. for (key in defaultPrefs) {
  269. if (defaultPrefs.hasOwnProperty(key)) { // It's our own config, so we don't need to iterate up the prototype chain
  270. var storeKey = 'svg-edit-' + key;
  271. if (editor.storage) {
  272. var val = editor.storage.getItem(storeKey);
  273. if (val) {
  274. defaultPrefs[key] = String(val); // Convert to string for FF (.value fails in Webkit)
  275. }
  276. }
  277. else if (window.widget) {
  278. defaultPrefs[key] = widget.preferenceForKey(storeKey);
  279. }
  280. else {
  281. var result = document.cookie.match(new RegExp('(?:^|;\\s*)' + Utils.preg_quote(encodeURIComponent(storeKey)) + '=([^;]+)'));
  282. defaultPrefs[key] = result ? decodeURIComponent(result[1]) : '';
  283. }
  284. }
  285. }
  286. };
  287. /**
  288. * Allows setting of preferences or configuration (including extensions).
  289. * @param {object} opts The preferences or configuration (including extensions)
  290. * @param {object} [cfgCfg] Describes configuration which applies to the particular batch of supplied options
  291. * @param {boolean} [cfgCfg.allowInitialUserOverride=false] Set to true if you wish
  292. * to allow initial overriding of settings by the user via the URL
  293. * (if permitted) or previously stored preferences (if permitted);
  294. * note that it will be too late if you make such calls in extension
  295. * code because the URL or preference storage settings will
  296. * have already taken place.
  297. * @param {boolean} [cfgCfg.overwrite=true] Set to false if you wish to
  298. * prevent the overwriting of prior-set preferences or configuration
  299. * (URL settings will always follow this requirement for security
  300. * reasons, so config.js settings cannot be overridden unless it
  301. * explicitly permits via "allowInitialUserOverride" but extension config
  302. * can be overridden as they will run after URL settings). Should
  303. * not be needed in config.js.
  304. */
  305. editor.setConfig = function (opts, cfgCfg) {
  306. cfgCfg = cfgCfg || {};
  307. function extendOrAdd (cfgObj, key, val) {
  308. if (cfgObj[key] && typeof cfgObj[key] === 'object') {
  309. $.extend(true, cfgObj[key], val);
  310. }
  311. else {
  312. cfgObj[key] = val;
  313. }
  314. return;
  315. }
  316. $.each(opts, function(key, val) {
  317. if (opts.hasOwnProperty(key)) {
  318. // Only allow prefs defined in defaultPrefs
  319. if (defaultPrefs.hasOwnProperty(key)) {
  320. if (cfgCfg.overwrite === false && (
  321. curConfig.preventAllURLConfig ||
  322. curPrefs.hasOwnProperty(key)
  323. )) {
  324. return;
  325. }
  326. if (cfgCfg.allowInitialUserOverride === true) {
  327. defaultPrefs[key] = val;
  328. }
  329. else {
  330. $.pref(key, val);
  331. }
  332. }
  333. else if (['extensions', 'allowedOrigins'].indexOf(key) > -1) {
  334. if (cfgCfg.overwrite === false &&
  335. (
  336. curConfig.preventAllURLConfig ||
  337. key === 'allowedOrigins' ||
  338. (key === 'extensions' && curConfig.lockExtensions)
  339. )
  340. ) {
  341. return;
  342. }
  343. curConfig[key] = curConfig[key].concat(val); // We will handle any dupes later
  344. }
  345. // Only allow other curConfig if defined in defaultConfig
  346. else if (defaultConfig.hasOwnProperty(key)) {
  347. if (cfgCfg.overwrite === false && (
  348. curConfig.preventAllURLConfig ||
  349. curConfig.hasOwnProperty(key)
  350. )) {
  351. return;
  352. }
  353. // Potentially overwriting of previously set config
  354. if (curConfig.hasOwnProperty(key)) {
  355. if (cfgCfg.overwrite === false) {
  356. return;
  357. }
  358. extendOrAdd(curConfig, key, val);
  359. }
  360. else {
  361. if (cfgCfg.allowInitialUserOverride === true) {
  362. extendOrAdd(defaultConfig, key, val);
  363. }
  364. else {
  365. if (defaultConfig[key] && typeof defaultConfig[key] === 'object') {
  366. curConfig[key] = {};
  367. $.extend(true, curConfig[key], val); // Merge properties recursively, e.g., on initFill, initStroke objects
  368. }
  369. else {
  370. curConfig[key] = val;
  371. }
  372. }
  373. }
  374. }
  375. }
  376. });
  377. editor.curConfig = curConfig; // Update exported value
  378. };
  379. /**
  380. * @param {object} opts Extension mechanisms may call setCustomHandlers with three functions: opts.open, opts.save, and opts.exportImage
  381. * opts.open's responsibilities are:
  382. * - invoke a file chooser dialog in 'open' mode
  383. * - let user pick a SVG file
  384. * - calls setCanvas.setSvgString() with the string contents of that file
  385. * opts.save's responsibilities are:
  386. * - accept the string contents of the current document
  387. * - invoke a file chooser dialog in 'save' mode
  388. * - save the file to location chosen by the user
  389. * opts.exportImage's responsibilities (with regard to the object it is supplied in its 2nd argument) are:
  390. * - inform user of any issues supplied via the "issues" property
  391. * - convert the "svg" property SVG string into an image for export;
  392. * utilize the properties "type" (currently 'PNG', 'JPEG', 'BMP',
  393. * 'WEBP', 'PDF'), "mimeType", and "quality" (for 'JPEG' and 'WEBP'
  394. * types) to determine the proper output.
  395. */
  396. editor.setCustomHandlers = function (opts) {
  397. editor.ready(function() {
  398. if (opts.open) {
  399. $('#tool_open > input[type="file"]').remove();
  400. $('#tool_open').show();
  401. svgCanvas.open = opts.open;
  402. }
  403. if (opts.save) {
  404. editor.showSaveWarning = false;
  405. svgCanvas.bind('saved', opts.save);
  406. }
  407. if (opts.exportImage) {
  408. svgCanvas.bind('exported', checkCanvg(opts.exportImage));
  409. }
  410. customHandlers = opts;
  411. });
  412. };
  413. editor.randomizeIds = function () {
  414. svgCanvas.randomizeIds(arguments);
  415. };
  416. editor.init = function () {
  417. // var host = location.hostname,
  418. // onWeb = host && host.indexOf('.') >= 0;
  419. // Some FF versions throw security errors here when directly accessing
  420. try {
  421. if ('localStorage' in window) { // && onWeb removed so Webkit works locally
  422. editor.storage = localStorage;
  423. }
  424. } catch(err) {}
  425. // Todo: Avoid var-defined functions and group functions together, etc. where possible
  426. var good_langs = [];
  427. $('#lang_select option').each(function() {
  428. good_langs.push(this.value);
  429. });
  430. function setupCurPrefs () {
  431. curPrefs = $.extend(true, {}, defaultPrefs, curPrefs); // Now safe to merge with priority for curPrefs in the event any are already set
  432. // Export updated prefs
  433. editor.curPrefs = curPrefs;
  434. }
  435. function setupCurConfig () {
  436. curConfig = $.extend(true, {}, defaultConfig, curConfig); // Now safe to merge with priority for curConfig in the event any are already set
  437. // Now deal with extensions and other array config
  438. if (!curConfig.noDefaultExtensions) {
  439. curConfig.extensions = curConfig.extensions.concat(defaultExtensions);
  440. }
  441. // ...and remove any dupes
  442. $.each(['extensions', 'allowedOrigins'], function (i, cfg) {
  443. curConfig[cfg] = $.grep(curConfig[cfg], function (n, i) {
  444. return i === curConfig[cfg].indexOf(n);
  445. });
  446. });
  447. // Export updated config
  448. editor.curConfig = curConfig;
  449. }
  450. (function() {
  451. // Load config/data from URL if given
  452. var src, qstr;
  453. urldata = $.deparam.querystring(true);
  454. if (!$.isEmptyObject(urldata)) {
  455. if (urldata.dimensions) {
  456. urldata.dimensions = urldata.dimensions.split(',');
  457. }
  458. if (urldata.bkgd_color) {
  459. urldata.bkgd_color = '#' + urldata.bkgd_color;
  460. }
  461. if (urldata.extensions) {
  462. // For security reasons, disallow cross-domain or cross-folder extensions via URL
  463. urldata.extensions = urldata.extensions.match(/[:\/\\]/) ? '' : urldata.extensions.split(',');
  464. }
  465. // Disallowing extension paths via URL for
  466. // security reasons, even for same-domain
  467. // ones given potential to interact in undesirable
  468. // ways with other script resources
  469. $.each(
  470. [
  471. 'extPath', 'imgPath',
  472. 'langPath', 'jGraduatePath'
  473. ],
  474. function (pathConfig) {
  475. if (urldata[pathConfig]) {
  476. delete urldata[pathConfig];
  477. }
  478. }
  479. );
  480. editor.setConfig(urldata, {overwrite: false}); // Note: source and url (as with storagePrompt later) are not set on config but are used below
  481. setupCurConfig();
  482. if (!curConfig.preventURLContentLoading) {
  483. src = urldata.source;
  484. qstr = $.param.querystring();
  485. if (!src) { // urldata.source may have been null if it ended with '='
  486. if (qstr.indexOf('source=data:') >= 0) {
  487. src = qstr.match(/source=(data:[^&]*)/)[1];
  488. }
  489. }
  490. if (src) {
  491. if (src.indexOf('data:') === 0) {
  492. editor.loadFromDataURI(src);
  493. } else {
  494. editor.loadFromString(src);
  495. }
  496. return;
  497. }
  498. if (urldata.url) {
  499. editor.loadFromURL(urldata.url);
  500. return;
  501. }
  502. }
  503. if (!urldata.noStorageOnLoad || curConfig.forceStorage) {
  504. editor.loadContentAndPrefs();
  505. }
  506. setupCurPrefs();
  507. }
  508. else {
  509. setupCurConfig();
  510. editor.loadContentAndPrefs();
  511. setupCurPrefs();
  512. }
  513. }());
  514. // For external openers
  515. (function() {
  516. // let the opener know SVG Edit is ready (now that config is set up)
  517. var svgEditorReadyEvent,
  518. w = window.opener;
  519. if (w) {
  520. try {
  521. svgEditorReadyEvent = w.document.createEvent('Event');
  522. svgEditorReadyEvent.initEvent('svgEditorReady', true, true);
  523. w.document.documentElement.dispatchEvent(svgEditorReadyEvent);
  524. }
  525. catch(e) {}
  526. }
  527. }());
  528. var setIcon = editor.setIcon = function(elem, icon_id, forcedSize) {
  529. var icon = (typeof icon_id === 'string') ? $.getSvgIcon(icon_id, true) : icon_id.clone();
  530. if (!icon) {
  531. console.log('NOTE: Icon image missing: ' + icon_id);
  532. return;
  533. }
  534. $(elem).empty().append(icon);
  535. };
  536. var extFunc = function() {
  537. $.each(curConfig.extensions, function() {
  538. var extname = this;
  539. if (!extname.match(/^ext-.*\.js/)) { // Ensure URL cannot specify some other unintended file in the extPath
  540. return;
  541. }
  542. $.getScript(curConfig.extPath + extname, function(d) {
  543. // Fails locally in Chrome 5
  544. if (!d) {
  545. var s = document.createElement('script');
  546. s.src = curConfig.extPath + extname;
  547. document.querySelector('head').appendChild(s);
  548. }
  549. });
  550. });
  551. // var lang = ('lang' in curPrefs) ? curPrefs.lang : null;
  552. editor.putLocale(null, good_langs);
  553. };
  554. // Load extensions
  555. // Bit of a hack to run extensions in local Opera/IE9
  556. if (document.location.protocol === 'file:') {
  557. setTimeout(extFunc, 100);
  558. } else {
  559. extFunc();
  560. }
  561. $.svgIcons(curConfig.imgPath + 'svg_edit_icons.svg', {
  562. w:24, h:24,
  563. id_match: false,
  564. no_img: !svgedit.browser.isWebkit(), // Opera & Firefox 4 gives odd behavior w/images
  565. fallback_path: curConfig.imgPath,
  566. fallback: {
  567. 'new_image': 'clear.png',
  568. 'save': 'save.png',
  569. 'open': 'open.png',
  570. 'source': 'source.png',
  571. 'docprops': 'document-properties.png',
  572. 'wireframe': 'wireframe.png',
  573. 'undo': 'undo.png',
  574. 'redo': 'redo.png',
  575. 'select': 'select.png',
  576. 'select_node': 'select_node.png',
  577. 'pencil': 'fhpath.png',
  578. 'pen': 'line.png',
  579. 'square': 'square.png',
  580. 'rect': 'rect.png',
  581. 'fh_rect': 'freehand-square.png',
  582. 'circle': 'circle.png',
  583. 'ellipse': 'ellipse.png',
  584. 'fh_ellipse': 'freehand-circle.png',
  585. 'path': 'path.png',
  586. 'text': 'text.png',
  587. 'image': 'image.png',
  588. 'zoom': 'zoom.png',
  589. 'clone': 'clone.png',
  590. 'node_clone': 'node_clone.png',
  591. 'delete': 'delete.png',
  592. 'node_delete': 'node_delete.png',
  593. 'group': 'shape_group_elements.png',
  594. 'ungroup': 'shape_ungroup.png',
  595. 'move_top': 'move_top.png',
  596. 'move_bottom': 'move_bottom.png',
  597. 'to_path': 'to_path.png',
  598. 'link_controls': 'link_controls.png',
  599. 'reorient': 'reorient.png',
  600. 'align_left': 'align-left.png',
  601. 'align_center': 'align-center.png',
  602. 'align_right': 'align-right.png',
  603. 'align_top': 'align-top.png',
  604. 'align_middle': 'align-middle.png',
  605. 'align_bottom': 'align-bottom.png',
  606. 'go_up': 'go-up.png',
  607. 'go_down': 'go-down.png',
  608. 'ok': 'save.png',
  609. 'cancel': 'cancel.png',
  610. 'arrow_right': 'flyouth.png',
  611. 'arrow_down': 'dropdown.gif'
  612. },
  613. placement: {
  614. '#logo': 'logo',
  615. '#tool_clear div,#layer_new': 'new_image',
  616. '#tool_save div': 'save',
  617. '#tool_export div': 'export',
  618. '#tool_open div div': 'open',
  619. '#tool_import div div': 'import',
  620. '#tool_source': 'source',
  621. '#tool_docprops > div': 'docprops',
  622. '#tool_wireframe': 'wireframe',
  623. '#tool_undo': 'undo',
  624. '#tool_redo': 'redo',
  625. '#tool_select': 'select',
  626. '#tool_fhpath': 'pencil',
  627. '#tool_line': 'pen',
  628. '#tool_rect,#tools_rect_show': 'rect',
  629. '#tool_square': 'square',
  630. '#tool_fhrect': 'fh_rect',
  631. '#tool_ellipse,#tools_ellipse_show': 'ellipse',
  632. '#tool_circle': 'circle',
  633. '#tool_fhellipse': 'fh_ellipse',
  634. '#tool_path': 'path',
  635. '#tool_text,#layer_rename': 'text',
  636. '#tool_image': 'image',
  637. '#tool_zoom': 'zoom',
  638. '#tool_clone,#tool_clone_multi': 'clone',
  639. '#tool_node_clone': 'node_clone',
  640. '#layer_delete,#tool_delete,#tool_delete_multi': 'delete',
  641. '#tool_node_delete': 'node_delete',
  642. '#tool_add_subpath': 'add_subpath',
  643. '#tool_openclose_path': 'open_path',
  644. '#tool_move_top': 'move_top',
  645. '#tool_move_bottom': 'move_bottom',
  646. '#tool_topath': 'to_path',
  647. '#tool_node_link': 'link_controls',
  648. '#tool_reorient': 'reorient',
  649. '#tool_group_elements': 'group_elements',
  650. '#tool_ungroup': 'ungroup',
  651. '#tool_unlink_use': 'unlink_use',
  652. '#tool_alignleft, #tool_posleft': 'align_left',
  653. '#tool_aligncenter, #tool_poscenter': 'align_center',
  654. '#tool_alignright, #tool_posright': 'align_right',
  655. '#tool_aligntop, #tool_postop': 'align_top',
  656. '#tool_alignmiddle, #tool_posmiddle': 'align_middle',
  657. '#tool_alignbottom, #tool_posbottom': 'align_bottom',
  658. '#cur_position': 'align',
  659. '#linecap_butt,#cur_linecap': 'linecap_butt',
  660. '#linecap_round': 'linecap_round',
  661. '#linecap_square': 'linecap_square',
  662. '#linejoin_miter,#cur_linejoin': 'linejoin_miter',
  663. '#linejoin_round': 'linejoin_round',
  664. '#linejoin_bevel': 'linejoin_bevel',
  665. '#url_notice': 'warning',
  666. '#layer_up': 'go_up',
  667. '#layer_down': 'go_down',
  668. '#layer_moreopts': 'context_menu',
  669. '#layerlist td.layervis': 'eye',
  670. '#tool_source_save,#tool_docprops_save,#tool_prefs_save': 'ok',
  671. '#tool_source_cancel,#tool_docprops_cancel,#tool_prefs_cancel': 'cancel',
  672. '#rwidthLabel, #iwidthLabel': 'width',
  673. '#rheightLabel, #iheightLabel': 'height',
  674. '#cornerRadiusLabel span': 'c_radius',
  675. '#angleLabel': 'angle',
  676. '#linkLabel,#tool_make_link,#tool_make_link_multi': 'globe_link',
  677. '#zoomLabel': 'zoom',
  678. '#tool_fill label': 'fill',
  679. '#tool_stroke .icon_label': 'stroke',
  680. '#group_opacityLabel': 'opacity',
  681. '#blurLabel': 'blur',
  682. '#font_sizeLabel': 'fontsize',
  683. '.flyout_arrow_horiz': 'arrow_right',
  684. '.dropdown button, #main_button .dropdown': 'arrow_down',
  685. '#palette .palette_item:first, #fill_bg, #stroke_bg': 'no_color'
  686. },
  687. resize: {
  688. '#logo .svg_icon': 28,
  689. '.flyout_arrow_horiz .svg_icon': 5,
  690. '.layer_button .svg_icon, #layerlist td.layervis .svg_icon': 14,
  691. '.dropdown button .svg_icon': 7,
  692. '#main_button .dropdown .svg_icon': 9,
  693. '.palette_item:first .svg_icon' : 15,
  694. '#fill_bg .svg_icon, #stroke_bg .svg_icon': 16,
  695. '.toolbar_button button .svg_icon': 16,
  696. '.stroke_tool div div .svg_icon': 20,
  697. '#tools_bottom label .svg_icon': 18
  698. },
  699. callback: function(icons) {
  700. $('.toolbar_button button > svg, .toolbar_button button > img').each(function() {
  701. $(this).parent().prepend(this);
  702. });
  703. var min_height,
  704. tleft = $('#tools_left');
  705. if (tleft.length !== 0) {
  706. min_height = tleft.offset().top + tleft.outerHeight();
  707. }
  708. var size = $.pref('iconsize');
  709. editor.setIconSize(size || ($(window).height() < min_height ? 's': 'm'));
  710. // Look for any missing flyout icons from plugins
  711. $('.tools_flyout').each(function() {
  712. var shower = $('#' + this.id + '_show');
  713. var sel = shower.attr('data-curopt');
  714. // Check if there's an icon here
  715. if (!shower.children('svg, img').length) {
  716. var clone = $(sel).children().clone();
  717. if (clone.length) {
  718. clone[0].removeAttribute('style'); //Needed for Opera
  719. shower.append(clone);
  720. }
  721. }
  722. });
  723. editor.runCallbacks();
  724. setTimeout(function() {
  725. $('.flyout_arrow_horiz:empty').each(function() {
  726. $(this).append($.getSvgIcon('arrow_right').width(5).height(5));
  727. });
  728. }, 1);
  729. }
  730. });
  731. editor.canvas = svgCanvas = new $.SvgCanvas(document.getElementById('svgcanvas'), curConfig);
  732. var supportsNonSS, resize_timer, changeZoom, Actions, curScrollPos,
  733. palette = [ // Todo: Make into configuration item?
  734. '#000000', '#3f3f3f', '#7f7f7f', '#bfbfbf', '#ffffff',
  735. '#ff0000', '#ff7f00', '#ffff00', '#7fff00',
  736. '#00ff00', '#00ff7f', '#00ffff', '#007fff',
  737. '#0000ff', '#7f00ff', '#ff00ff', '#ff007f',
  738. '#7f0000', '#7f3f00', '#7f7f00', '#3f7f00',
  739. '#007f00', '#007f3f', '#007f7f', '#003f7f',
  740. '#00007f', '#3f007f', '#7f007f', '#7f003f',
  741. '#ffaaaa', '#ffd4aa', '#ffffaa', '#d4ffaa',
  742. '#aaffaa', '#aaffd4', '#aaffff', '#aad4ff',
  743. '#aaaaff', '#d4aaff', '#ffaaff', '#ffaad4'
  744. ],
  745. modKey = (svgedit.browser.isMac() ? 'meta+' : 'ctrl+'), // â&#x152;&#x2DC;
  746. path = svgCanvas.pathActions,
  747. undoMgr = svgCanvas.undoMgr,
  748. defaultImageURL = curConfig.imgPath + 'logo.png',
  749. workarea = $('#workarea'),
  750. canv_menu = $('#cmenu_canvas'),
  751. // layer_menu = $('#cmenu_layers'), // Unused
  752. exportWindow = null,
  753. zoomInIcon = 'crosshair',
  754. zoomOutIcon = 'crosshair',
  755. ui_context = 'toolbars',
  756. origSource = '',
  757. paintBox = {fill: null, stroke:null};
  758. // This sets up alternative dialog boxes. They mostly work the same way as
  759. // their UI counterparts, expect instead of returning the result, a callback
  760. // needs to be included that returns the result as its first parameter.
  761. // In the future we may want to add additional types of dialog boxes, since
  762. // they should be easy to handle this way.
  763. (function() {
  764. $('#dialog_container').draggable({cancel: '#dialog_content, #dialog_buttons *', containment: 'window'});
  765. var box = $('#dialog_box'),
  766. btn_holder = $('#dialog_buttons'),
  767. dialog_content = $('#dialog_content'),
  768. dbox = function(type, msg, callback, defaultVal, opts, changeCb, checkbox) {
  769. var ok, ctrl, chkbx;
  770. dialog_content.html('<p>'+msg.replace(/\n/g, '</p><p>')+'</p>')
  771. .toggleClass('prompt', (type == 'prompt'));
  772. btn_holder.empty();
  773. ok = $('<input type="button" value="' + uiStrings.common.ok + '">').appendTo(btn_holder);
  774. if (type !== 'alert') {
  775. $('<input type="button" value="' + uiStrings.common.cancel + '">')
  776. .appendTo(btn_holder)
  777. .click(function() { box.hide(); callback(false);});
  778. }
  779. if (type === 'prompt') {
  780. ctrl = $('<input type="text">').prependTo(btn_holder);
  781. ctrl.val(defaultVal || '');
  782. ctrl.bind('keydown', 'return', function() {ok.click();});
  783. }
  784. else if (type === 'select') {
  785. var div = $('<div style="text-align:center;">');
  786. ctrl = $('<select>').appendTo(div);
  787. if (checkbox) {
  788. var label = $('<label>').text(checkbox.label);
  789. chkbx = $('<input type="checkbox">').appendTo(label);
  790. chkbx.val(checkbox.value);
  791. if (checkbox.tooltip) {
  792. label.attr('title', checkbox.tooltip);
  793. }
  794. chkbx.prop('checked', !!checkbox.checked);
  795. div.append($('<div>').append(label));
  796. }
  797. $.each(opts || [], function (opt, val) {
  798. if (typeof val === 'object') {
  799. ctrl.append($('<option>').val(val.value).html(val.text));
  800. }
  801. else {
  802. ctrl.append($('<option>').html(val));
  803. }
  804. });
  805. dialog_content.append(div);
  806. if (defaultVal) {
  807. ctrl.val(defaultVal);
  808. }
  809. if (changeCb) {
  810. ctrl.bind('change', 'return', changeCb);
  811. }
  812. ctrl.bind('keydown', 'return', function() {ok.click();});
  813. }
  814. if (type === 'process') {
  815. ok.hide();
  816. }
  817. box.show();
  818. ok.click(function() {
  819. box.hide();
  820. var resp = (type === 'prompt' || type === 'select') ? ctrl.val() : true;
  821. if (callback) {
  822. if (chkbx) {
  823. callback(resp, chkbx.prop('checked'));
  824. }
  825. else {
  826. callback(resp);
  827. }
  828. }
  829. }).focus();
  830. if (type === 'prompt' || type === 'select') {
  831. ctrl.focus();
  832. }
  833. };
  834. $.alert = function(msg, cb) { dbox('alert', msg, cb);};
  835. $.confirm = function(msg, cb) { dbox('confirm', msg, cb);};
  836. $.process_cancel = function(msg, cb) { dbox('process', msg, cb);};
  837. $.prompt = function(msg, txt, cb) { dbox('prompt', msg, cb, txt);};
  838. $.select = function(msg, opts, cb, changeCb, txt, checkbox) { dbox('select', msg, cb, txt, opts, changeCb, checkbox);};
  839. }());
  840. var setSelectMode = function() {
  841. var curr = $('.tool_button_current');
  842. if (curr.length && curr[0].id !== 'tool_select') {
  843. curr.removeClass('tool_button_current').addClass('tool_button');
  844. $('#tool_select').addClass('tool_button_current').removeClass('tool_button');
  845. $('#styleoverrides').text('#svgcanvas svg *{cursor:move;pointer-events:all} #svgcanvas svg{cursor:default}');
  846. }
  847. svgCanvas.setMode('select');
  848. workarea.css('cursor', 'auto');
  849. };
  850. // used to make the flyouts stay on the screen longer the very first time
  851. // var flyoutspeed = 1250; // Currently unused
  852. var textBeingEntered = false;
  853. var selectedElement = null;
  854. var multiselected = false;
  855. var editingsource = false;
  856. var docprops = false;
  857. var preferences = false;
  858. var cur_context = '';
  859. var origTitle = $('title:first').text();
  860. // Make [1,2,5] array
  861. var r_intervals = [];
  862. var i;
  863. for (i = 0.1; i < 1E5; i *= 10) {
  864. r_intervals.push(i);
  865. r_intervals.push(2 * i);
  866. r_intervals.push(5 * i);
  867. }
  868. // This function highlights the layer passed in (by fading out the other layers)
  869. // if no layer is passed in, this function restores the other layers
  870. var toggleHighlightLayer = function(layerNameToHighlight) {
  871. var i, curNames = [], numLayers = svgCanvas.getCurrentDrawing().getNumLayers();
  872. for (i = 0; i < numLayers; i++) {
  873. curNames[i] = svgCanvas.getCurrentDrawing().getLayerName(i);
  874. }
  875. if (layerNameToHighlight) {
  876. for (i = 0; i < numLayers; ++i) {
  877. if (curNames[i] != layerNameToHighlight) {
  878. svgCanvas.getCurrentDrawing().setLayerOpacity(curNames[i], 0.5);
  879. }
  880. }
  881. } else {
  882. for (i = 0; i < numLayers; ++i) {
  883. svgCanvas.getCurrentDrawing().setLayerOpacity(curNames[i], 1.0);
  884. }
  885. }
  886. };
  887. var populateLayers = function() {
  888. svgCanvas.clearSelection();
  889. var layerlist = $('#layerlist tbody').empty();
  890. var selLayerNames = $('#selLayerNames').empty();
  891. var drawing = svgCanvas.getCurrentDrawing();
  892. var currentLayerName = drawing.getCurrentLayerName();
  893. var layer = svgCanvas.getCurrentDrawing().getNumLayers();
  894. var icon = $.getSvgIcon('eye');
  895. // we get the layers in the reverse z-order (the layer rendered on top is listed first)
  896. while (layer--) {
  897. var name = drawing.getLayerName(layer);
  898. var layerTr = $('<tr class="layer">').toggleClass('layersel', name === currentLayerName);
  899. var layerVis = $('<td class="layervis">').toggleClass('layerinvis', !drawing.getLayerVisibility(name));
  900. var layerName = $('<td class="layername">' + name + '</td>');
  901. layerlist.append(layerTr.append(layerVis, layerName));
  902. selLayerNames.append('<option value="' + name + '">' + name + '</option>');
  903. }
  904. if (icon !== undefined) {
  905. var copy = icon.clone();
  906. $('td.layervis', layerlist).append(copy);
  907. $.resizeSvgIcons({'td.layervis .svg_icon': 14});
  908. }
  909. // handle selection of layer
  910. $('#layerlist td.layername')
  911. .mouseup(function(evt) {
  912. $('#layerlist tr.layer').removeClass('layersel');
  913. $(this.parentNode).addClass('layersel');
  914. svgCanvas.setCurrentLayer(this.textContent);
  915. evt.preventDefault();
  916. })
  917. .mouseover(function() {
  918. toggleHighlightLayer(this.textContent);
  919. })
  920. .mouseout(function() {
  921. toggleHighlightLayer();
  922. });
  923. $('#layerlist td.layervis').click(function() {
  924. var row = $(this.parentNode).prevAll().length;
  925. var name = $('#layerlist tr.layer:eq(' + row + ') td.layername').text();
  926. var vis = $(this).hasClass('layerinvis');
  927. svgCanvas.setLayerVisibility(name, vis);
  928. $(this).toggleClass('layerinvis');
  929. });
  930. // if there were too few rows, let's add a few to make it not so lonely
  931. var num = 5 - $('#layerlist tr.layer').size();
  932. while (num-- > 0) {
  933. // FIXME: there must a better way to do this
  934. layerlist.append('<tr><td style="color:white">_</td><td/></tr>');
  935. }
  936. };
  937. var showSourceEditor = function(e, forSaving) {
  938. if (editingsource) {return;}
  939. editingsource = true;
  940. origSource = svgCanvas.getSvgString();
  941. $('#save_output_btns').toggle(!!forSaving);
  942. $('#tool_source_back').toggle(!forSaving);
  943. $('#svg_source_textarea').val(origSource);
  944. $('#svg_source_editor').fadeIn();
  945. $('#svg_source_textarea').focus();
  946. };
  947. var togglePathEditMode = function(editmode, elems) {
  948. $('#path_node_panel').toggle(editmode);
  949. $('#tools_bottom_2,#tools_bottom_3').toggle(!editmode);
  950. if (editmode) {
  951. // Change select icon
  952. $('.tool_button_current').removeClass('tool_button_current').addClass('tool_button');
  953. $('#tool_select').addClass('tool_button_current').removeClass('tool_button');
  954. setIcon('#tool_select', 'select_node');
  955. multiselected = false;
  956. if (elems.length) {
  957. selectedElement = elems[0];
  958. }
  959. } else {
  960. setTimeout(function () {
  961. setIcon('#tool_select', 'select');
  962. }, 1000);
  963. }
  964. };
  965. var saveHandler = function(wind, svg) {
  966. editor.showSaveWarning = false;
  967. // by default, we add the XML prolog back, systems integrating SVG-edit (wikis, CMSs)
  968. // can just provide their own custom save handler and might not want the XML prolog
  969. svg = '<?xml version="1.0"?>\n' + svg;
  970. // IE9 doesn't allow standalone Data URLs
  971. // https://connect.microsoft.com/IE/feedback/details/542600/data-uri-images-fail-when-loaded-by-themselves
  972. if (svgedit.browser.isIE()) {
  973. showSourceEditor(0, true);
  974. return;
  975. }
  976. // Opens the SVG in new window
  977. var win = wind.open('data:image/svg+xml;base64,' + Utils.encode64(svg));
  978. // Alert will only appear the first time saved OR the first time the bug is encountered
  979. var done = $.pref('save_notice_done');
  980. if (done !== 'all') {
  981. var note = uiStrings.notification.saveFromBrowser.replace('%s', 'SVG');
  982. // Check if FF and has <defs/>
  983. if (svgedit.browser.isGecko()) {
  984. if (svg.indexOf('<defs') !== -1) {
  985. // warning about Mozilla bug #308590 when applicable (seems to be fixed now in Feb 2013)
  986. note += '\n\n' + uiStrings.notification.defsFailOnSave;
  987. $.pref('save_notice_done', 'all');
  988. done = 'all';
  989. } else {
  990. $.pref('save_notice_done', 'part');
  991. }
  992. } else {
  993. $.pref('save_notice_done', 'all');
  994. }
  995. if (done !== 'part') {
  996. win.alert(note);
  997. }
  998. }
  999. };
  1000. // Export global for use by jsPDF
  1001. saveAs = function (blob, options) {
  1002. var blobUrl = URL.createObjectURL(blob);
  1003. try {
  1004. // This creates a bookmarkable data URL,
  1005. // but it doesn't currently work in
  1006. // Firefox, and although it works in Chrome,
  1007. // Chrome doesn't make the full "data:" URL
  1008. // visible unless you right-click to "Inspect
  1009. // element" and then right-click on the element
  1010. // to "Copy link address".
  1011. var xhr = new XMLHttpRequest();
  1012. xhr.responseType = 'blob';
  1013. xhr.onload = function() {
  1014. var recoveredBlob = xhr.response;
  1015. var reader = new FileReader();
  1016. reader.onload = function() {
  1017. var blobAsDataUrl = reader.result;
  1018. exportWindow.location.href = blobAsDataUrl;
  1019. };
  1020. reader.readAsDataURL(recoveredBlob);
  1021. };
  1022. xhr.open('GET', blobUrl);
  1023. xhr.send();
  1024. }
  1025. catch (e) {
  1026. exportWindow.location.href = blobUrl;
  1027. }
  1028. };
  1029. var exportHandler = function(win, data) {
  1030. var issues = data.issues,
  1031. type = data.type || 'PNG',
  1032. dataURLType = (type === 'ICO' ? 'BMP' : type).toLowerCase();
  1033. exportWindow = window.open('', data.exportWindowName); // A hack to get the window via JSON-able name without opening a new one
  1034. if (!$('#export_canvas').length) {
  1035. $('<canvas>', {id: 'export_canvas'}).hide().appendTo('body');
  1036. }
  1037. var c = $('#export_canvas')[0];
  1038. if (type === 'PDF') {
  1039. var res = svgCanvas.getResolution();
  1040. var orientation = res.w > res.h ? 'landscape' : 'portrait';
  1041. var units = 'pt'; // curConfig.baseUnit; // We could use baseUnit, but that is presumably not intended for export purposes
  1042. var doc = new jsPDF(orientation, units, [res.w, res.h]); // Todo: Give options to use predefined jsPDF formats like "a4", etc. from pull-down (with option to keep customizable)
  1043. var docTitle = svgCanvas.getDocumentTitle();
  1044. doc.setProperties({
  1045. title: docTitle/*,
  1046. subject: '',
  1047. author: '',
  1048. keywords: '',
  1049. creator: ''*/
  1050. });
  1051. svgElementToPdf(data.svg, doc, {});
  1052. doc.save(docTitle + '.pdf');
  1053. return;
  1054. }
  1055. c.width = svgCanvas.contentW;
  1056. c.height = svgCanvas.contentH;
  1057. canvg(c, data.svg, {renderCallback: function() {
  1058. var datauri = data.quality ? c.toDataURL('image/' + dataURLType, data.quality) : c.toDataURL('image/' + dataURLType);
  1059. exportWindow.location.href = datauri;
  1060. var done = $.pref('export_notice_done');
  1061. if (done !== 'all') {
  1062. var note = uiStrings.notification.saveFromBrowser.replace('%s', type);
  1063. // Check if there's issues
  1064. if (issues.length) {
  1065. var pre = '\n \u2022 ';
  1066. note += ('\n\n' + uiStrings.notification.noteTheseIssues + pre + issues.join(pre));
  1067. }
  1068. // Note that this will also prevent the notice even though new issues may appear later.
  1069. // May want to find a way to deal with that without annoying the user
  1070. $.pref('export_notice_done', 'all');
  1071. exportWindow.alert(note);
  1072. }
  1073. }});
  1074. };
  1075. var operaRepaint = function() {
  1076. // Repaints canvas in Opera. Needed for stroke-dasharray change as well as fill change
  1077. if (!window.opera) {
  1078. return;
  1079. }
  1080. $('<p/>').hide().appendTo('body').remove();
  1081. };
  1082. function setStrokeOpt(opt, changeElem) {
  1083. var id = opt.id;
  1084. var bits = id.split('_');
  1085. var pre = bits[0];
  1086. var val = bits[1];
  1087. if (changeElem) {
  1088. svgCanvas.setStrokeAttr('stroke-' + pre, val);
  1089. }
  1090. operaRepaint();
  1091. setIcon('#cur_' + pre, id, 20);
  1092. $(opt).addClass('current').siblings().removeClass('current');
  1093. }
  1094. // This is a common function used when a tool has been clicked (chosen)
  1095. // It does several common things:
  1096. // - removes the tool_button_current class from whatever tool currently has it
  1097. // - hides any flyouts
  1098. // - adds the tool_button_current class to the button passed in
  1099. var toolButtonClick = editor.toolButtonClick = function(button, noHiding) {
  1100. if ($(button).hasClass('disabled')) {return false;}
  1101. if ($(button).parent().hasClass('tools_flyout')) {return true;}
  1102. var fadeFlyouts = 'normal';
  1103. if (!noHiding) {
  1104. $('.tools_flyout').fadeOut(fadeFlyouts);
  1105. }
  1106. $('#styleoverrides').text('');
  1107. workarea.css('cursor', 'auto');
  1108. $('.tool_button_current').removeClass('tool_button_current').addClass('tool_button');
  1109. $(button).addClass('tool_button_current').removeClass('tool_button');
  1110. return true;
  1111. };
  1112. var clickSelect = editor.clickSelect = function() {
  1113. if (toolButtonClick('#tool_select')) {
  1114. svgCanvas.setMode('select');
  1115. $('#styleoverrides').text('#svgcanvas svg *{cursor:move;pointer-events:all}, #svgcanvas svg{cursor:default}');
  1116. }
  1117. };
  1118. var setImageURL = editor.setImageURL = function(url) {
  1119. if (!url) {
  1120. url = defaultImageURL;
  1121. }
  1122. svgCanvas.setImageURL(url);
  1123. $('#image_url').val(url);
  1124. if (url.indexOf('data:') === 0) {
  1125. // data URI found
  1126. $('#image_url').hide();
  1127. $('#change_image_url').show();
  1128. } else {
  1129. // regular URL
  1130. svgCanvas.embedImage(url, function(dataURI) {
  1131. // Couldn't embed, so show warning
  1132. $('#url_notice').toggle(!dataURI);
  1133. defaultImageURL = url;
  1134. });
  1135. $('#image_url').show();
  1136. $('#change_image_url').hide();
  1137. }
  1138. };
  1139. function setBackground (color, url) {
  1140. // if (color == $.pref('bkgd_color') && url == $.pref('bkgd_url')) {return;}
  1141. $.pref('bkgd_color', color);
  1142. $.pref('bkgd_url', url);
  1143. // This should be done in svgcanvas.js for the borderRect fill
  1144. svgCanvas.setBackground(color, url);
  1145. }
  1146. function promptImgURL() {
  1147. var curhref = svgCanvas.getHref(selectedElement);
  1148. curhref = curhref.indexOf('data:') === 0 ? '' : curhref;
  1149. $.prompt(uiStrings.notification.enterNewImgURL, curhref, function(url) {
  1150. if (url) {setImageURL(url);}
  1151. });
  1152. }
  1153. var setInputWidth = function(elem) {
  1154. var w = Math.min(Math.max(12 + elem.value.length * 6, 50), 300);
  1155. $(elem).width(w);
  1156. };
  1157. function updateRulers(scanvas, zoom) {
  1158. if (!zoom) {zoom = svgCanvas.getZoom();}
  1159. if (!scanvas) {scanvas = $('#svgcanvas');}
  1160. var d, i;
  1161. var limit = 30000;
  1162. var contentElem = svgCanvas.getContentElem();
  1163. var units = svgedit.units.getTypeMap();
  1164. var unit = units[curConfig.baseUnit]; // 1 = 1px
  1165. // draw x ruler then y ruler
  1166. for (d = 0; d < 2; d++) {
  1167. var isX = (d === 0);
  1168. var dim = isX ? 'x' : 'y';
  1169. var lentype = isX ? 'width' : 'height';
  1170. var contentDim = Number(contentElem.getAttribute(dim));
  1171. var $hcanv_orig = $('#ruler_' + dim + ' canvas:first');
  1172. // Bit of a hack to fully clear the canvas in Safari & IE9
  1173. var $hcanv = $hcanv_orig.clone();
  1174. $hcanv_orig.replaceWith($hcanv);
  1175. var hcanv = $hcanv[0];
  1176. // Set the canvas size to the width of the container
  1177. var ruler_len = scanvas[lentype]();
  1178. var total_len = ruler_len;
  1179. hcanv.parentNode.style[lentype] = total_len + 'px';
  1180. var ctx_num = 0;
  1181. var ctx = hcanv.getContext('2d');
  1182. var ctx_arr, num, ctx_arr_num;
  1183. ctx.fillStyle = 'rgb(200,0,0)';
  1184. ctx.fillRect(0, 0, hcanv.width, hcanv.height);
  1185. // Remove any existing canvasses
  1186. $hcanv.siblings().remove();
  1187. // Create multiple canvases when necessary (due to browser limits)
  1188. if (ruler_len >= limit) {
  1189. ctx_arr_num = parseInt(ruler_len / limit, 10) + 1;
  1190. ctx_arr = [];
  1191. ctx_arr[0] = ctx;
  1192. var copy;
  1193. for (i = 1; i < ctx_arr_num; i++) {
  1194. hcanv[lentype] = limit;
  1195. copy = hcanv.cloneNode(true);
  1196. hcanv.parentNode.appendChild(copy);
  1197. ctx_arr[i] = copy.getContext('2d');
  1198. }
  1199. copy[lentype] = ruler_len % limit;
  1200. // set copy width to last
  1201. ruler_len = limit;
  1202. }
  1203. hcanv[lentype] = ruler_len;
  1204. var u_multi = unit * zoom;
  1205. // Calculate the main number interval
  1206. var raw_m = 50 / u_multi;
  1207. var multi = 1;
  1208. for (i = 0; i < r_intervals.length; i++) {
  1209. num = r_intervals[i];
  1210. multi = num;
  1211. if (raw_m <= num) {
  1212. break;
  1213. }
  1214. }
  1215. var big_int = multi * u_multi;
  1216. ctx.font = '9px sans-serif';
  1217. var ruler_d = ((contentDim / u_multi) % multi) * u_multi;
  1218. var label_pos = ruler_d - big_int;
  1219. // draw big intervals
  1220. while (ruler_d < total_len) {
  1221. label_pos += big_int;
  1222. // var real_d = ruler_d - contentDim; // Currently unused
  1223. var cur_d = Math.round(ruler_d) + 0.5;
  1224. if (isX) {
  1225. ctx.moveTo(cur_d, 15);
  1226. ctx.lineTo(cur_d, 0);
  1227. }
  1228. else {
  1229. ctx.moveTo(15, cur_d);
  1230. ctx.lineTo(0, cur_d);
  1231. }
  1232. num = (label_pos - contentDim) / u_multi;
  1233. var label;
  1234. if (multi >= 1) {
  1235. label = Math.round(num);
  1236. }
  1237. else {
  1238. var decs = String(multi).split('.')[1].length;
  1239. label = num.toFixed(decs);
  1240. }
  1241. // Change 1000s to Ks
  1242. if (label !== 0 && label !== 1000 && label % 1000 === 0) {
  1243. label = (label / 1000) + 'K';
  1244. }
  1245. if (isX) {
  1246. ctx.fillText(label, ruler_d+2, 8);
  1247. } else {
  1248. // draw label vertically
  1249. var str = String(label).split('');
  1250. for (i = 0; i < str.length; i++) {
  1251. ctx.fillText(str[i], 1, (ruler_d+9) + i*9);
  1252. }
  1253. }
  1254. var part = big_int / 10;
  1255. // draw the small intervals
  1256. for (i = 1; i < 10; i++) {
  1257. var sub_d = Math.round(ruler_d + part * i) + 0.5;
  1258. if (ctx_arr && sub_d > ruler_len) {
  1259. ctx_num++;
  1260. ctx.stroke();
  1261. if (ctx_num >= ctx_arr_num) {
  1262. i = 10;
  1263. ruler_d = total_len;
  1264. continue;
  1265. }
  1266. ctx = ctx_arr[ctx_num];
  1267. ruler_d -= limit;
  1268. sub_d = Math.round(ruler_d + part * i) + 0.5;
  1269. }
  1270. // odd lines are slighly longer
  1271. var line_num = (i % 2) ? 12 : 10;
  1272. if (isX) {
  1273. ctx.moveTo(sub_d, 15);
  1274. ctx.lineTo(sub_d, line_num);
  1275. } else {
  1276. ctx.moveTo(15, sub_d);
  1277. ctx.lineTo(line_num, sub_d);
  1278. }
  1279. }
  1280. ruler_d += big_int;
  1281. }
  1282. ctx.strokeStyle = '#000';
  1283. ctx.stroke();
  1284. }
  1285. }
  1286. var updateCanvas = editor.updateCanvas = function(center, new_ctr) {
  1287. var w = workarea.width(), h = workarea.height();
  1288. var w_orig = w, h_orig = h;
  1289. var zoom = svgCanvas.getZoom();
  1290. var w_area = workarea;
  1291. var cnvs = $('#svgcanvas');
  1292. var old_ctr = {
  1293. x: w_area[0].scrollLeft + w_orig/2,
  1294. y: w_area[0].scrollTop + h_orig/2
  1295. };
  1296. var multi = curConfig.canvas_expansion;
  1297. w = Math.max(w_orig, svgCanvas.contentW * zoom * multi);
  1298. h = Math.max(h_orig, svgCanvas.contentH * zoom * multi);
  1299. if (w == w_orig && h == h_orig) {
  1300. workarea.css('overflow', 'hidden');
  1301. } else {
  1302. workarea.css('overflow', 'scroll');
  1303. }
  1304. var old_can_y = cnvs.height()/2;
  1305. var old_can_x = cnvs.width()/2;
  1306. cnvs.width(w).height(h);
  1307. var new_can_y = h/2;
  1308. var new_can_x = w/2;
  1309. var offset = svgCanvas.updateCanvas(w, h);
  1310. var ratio = new_can_x / old_can_x;
  1311. var scroll_x = w/2 - w_orig/2;
  1312. var scroll_y = h/2 - h_orig/2;
  1313. if (!new_ctr) {
  1314. var old_dist_x = old_ctr.x - old_can_x;
  1315. var new_x = new_can_x + old_dist_x * ratio;
  1316. var old_dist_y = old_ctr.y - old_can_y;
  1317. var new_y = new_can_y + old_dist_y * ratio;
  1318. new_ctr = {
  1319. x: new_x,
  1320. y: new_y
  1321. };
  1322. } else {
  1323. new_ctr.x += offset.x;
  1324. new_ctr.y += offset.y;
  1325. }
  1326. if (center) {
  1327. // Go to top-left for larger documents
  1328. if (svgCanvas.contentW > w_area.width()) {
  1329. // Top-left
  1330. workarea[0].scrollLeft = offset.x - 10;
  1331. workarea[0].scrollTop = offset.y - 10;
  1332. } else {
  1333. // Center
  1334. w_area[0].scro

Large files files are truncated, but you can click here to view the full file