/editor/svg-editor.js
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
- /*globals saveAs:true, svgEditor:true, globalStorage, widget, svgedit, canvg, jsPDF, svgElementToPdf, jQuery, $, DOMParser, FileReader, URL */
- /*jslint vars: true, eqeq: true, todo: true, forin: true, continue: true, regexp: true */
- /*
- * svg-editor.js
- *
- * Licensed under the MIT License
- *
- * Copyright(c) 2010 Alexis Deveria
- * Copyright(c) 2010 Pavol Rusnak
- * Copyright(c) 2010 Jeff Schiller
- * Copyright(c) 2010 Narendra Sisodiya
- * Copyright(c) 2014 Brett Zamir
- *
- */
- // Dependencies:
- // 1) units.js
- // 2) browser.js
- // 3) svgcanvas.js
- /*
- TODOS
- 1. JSDoc
- */
- (function() {
- if (window.svgEditor) {
- return;
- }
- window.svgEditor = (function($) {
- var editor = {};
- // EDITOR PROPERTIES: (defined below)
- // curPrefs, curConfig, canvas, storage, uiStrings
- //
- // STATE MAINTENANCE PROPERTIES
- editor.tool_scale = 1; // Dependent on icon size, so any use to making configurable instead? Used by JQuerySpinBtn.js
- editor.langChanged = false;
- editor.showSaveWarning = false;
- editor.storagePromptClosed = false; // For use with ext-storage.js
- var svgCanvas, urldata,
- Utils = svgedit.utilities,
- isReady = false,
- callbacks = [],
- customHandlers = {},
- /**
- * PREFS AND CONFIG
- */
- // The iteration algorithm for defaultPrefs does not currently support array/objects
- defaultPrefs = {
- // EDITOR OPTIONS (DIALOG)
- lang: '', // Default to "en" if locale.js detection does not detect another language
- iconsize: '', // Will default to 's' if the window height is smaller than the minimum height and 'm' otherwise
- bkgd_color: '#FFF',
- bkgd_url: '',
- // DOCUMENT PROPERTIES (DIALOG)
- img_save: 'embed',
- // ALERT NOTICES
- // Only shows in UI as far as alert notices, but useful to remember, so keeping as pref
- save_notice_done: false,
- export_notice_done: false
- },
- curPrefs = {},
- // Note: The difference between Prefs and Config is that Prefs
- // can be changed in the UI and are stored in the browser,
- // while config cannot
- curConfig = {
- // We do not put on defaultConfig to simplify object copying
- // procedures (we obtain instead from defaultExtensions)
- extensions: [],
- /**
- * Can use window.location.origin to indicate the current
- * origin. Can contain a '*' to allow all domains or 'null' (as
- * a string) to support all file:// URLs. Cannot be set by
- * URL for security reasons (not safe, at least for
- * privacy or data integrity of SVG content).
- * Might have been fairly safe to allow
- * `new URL(window.location.href).origin` by default but
- * avoiding it ensures some more security that even third
- * party apps on the same domain also cannot communicate
- * with this app by default.
- * For use with ext-xdomain-messaging.js
- * @todo We might instead make as a user-facing preference.
- */
- allowedOrigins: []
- },
- defaultExtensions = [
- 'ext-overview_window.js',
- 'ext-markers.js',
- 'ext-connector.js',
- 'ext-eyedropper.js',
- 'ext-shapes.js',
- 'ext-imagelib.js',
- 'ext-grid.js',
- 'ext-polygon.js',
- 'ext-star.js',
- 'ext-panning.js',
- 'ext-storage.js'
- ],
- defaultConfig = {
- // Todo: svgcanvas.js also sets and checks: show_outside_canvas, selectNew; add here?
- // Change the following to preferences and add pref controls to the UI (e.g., initTool, wireframe, showlayers)?
- canvasName: 'default',
- canvas_expansion: 3,
- initFill: {
- color: 'FF0000', // solid red
- opacity: 1
- },
- initStroke: {
- width: 5,
- color: '000000', // solid black
- opacity: 1
- },
- initOpacity: 1,
- 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
- initTool: 'select',
- wireframe: false,
- showlayers: false,
- no_save_warning: false,
- // PATH CONFIGURATION
- // The following path configuration items are disallowed in the URL (as should any future path configurations)
- imgPath: 'images/',
- langPath: 'locale/',
- extPath: 'extensions/',
- jGraduatePath: 'jgraduate/images/',
- // DOCUMENT PROPERTIES
- // Change the following to a preference (already in the Document Properties dialog)?
- dimensions: [640, 480],
- // EDITOR OPTIONS
- // Change the following to preferences (already in the Editor Options dialog)?
- gridSnapping: false,
- gridColor: '#000',
- baseUnit: 'px',
- snappingStep: 10,
- showRulers: true,
- // URL BEHAVIOR CONFIGURATION
- preventAllURLConfig: false,
- preventURLContentLoading: false,
- // EXTENSION CONFIGURATION (see also preventAllURLConfig)
- lockExtensions: false, // Disallowed in URL setting
- noDefaultExtensions: false, // noDefaultExtensions can only be meaningfully used in config.js or in the URL
- // EXTENSION-RELATED (GRID)
- showGrid: false, // Set by ext-grid.js
- // EXTENSION-RELATED (STORAGE)
- noStorageOnLoad: false, // Some interaction with ext-storage.js; prevent even the loading of previously saved local storage
- 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
- emptyStorageOnDecline: false // Used by ext-storage.js; empty any prior storage if the user declines to store
- },
- /**
- * LOCALE
- * @todo Can we remove now that we are always loading even English? (unless locale is set to null)
- */
- uiStrings = editor.uiStrings = {
- common: {
- ok: 'OK',
- cancel: 'Cancel',
- key_up: 'Up',
- key_down: 'Down',
- key_backspace: 'Backspace',
- key_del: 'Del'
- },
- // This is needed if the locale is English, since the locale strings are not read in that instance.
- layers: {
- layer: 'Layer'
- },
- notification: {
- invalidAttrValGiven: 'Invalid value given',
- noContentToFitTo: 'No content to fit to',
- dupeLayerName: 'There is already a layer named that!',
- enterUniqueLayerName: 'Please enter a unique layer name',
- enterNewLayerName: 'Please enter the new layer name',
- layerHasThatName: 'Layer already has that name',
- QmoveElemsToLayer: 'Move selected elements to layer \'%s\'?',
- QwantToClear: 'Do you want to clear the drawing?\nThis will also erase your undo history!',
- QwantToOpen: 'Do you want to open a new file?\nThis will also erase your undo history!',
- QerrorsRevertToSource: 'There were parsing errors in your SVG source.\nRevert back to original SVG source?',
- QignoreSourceChanges: 'Ignore changes made to SVG source?',
- featNotSupported: 'Feature not supported',
- enterNewImgURL: 'Enter the new image URL',
- 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.',
- loadingImage: 'Loading image, please wait...',
- saveFromBrowser: 'Select \'Save As...\' in your browser to save this image as a %s file.',
- noteTheseIssues: 'Also note the following issues: ',
- unsavedChanges: 'There are unsaved changes.',
- enterNewLinkURL: 'Enter the new hyperlink URL',
- errorLoadingSVG: 'Error: Unable to load SVG data',
- URLloadFail: 'Unable to load from URL',
- retrieving: 'Retrieving \'%s\' ...'
- }
- };
- function loadSvgString (str, callback) {
- var success = svgCanvas.setSvgString(str) !== false;
- callback = callback || $.noop;
- if (success) {
- callback(true);
- } else {
- $.alert(uiStrings.notification.errorLoadingSVG, function() {
- callback(false);
- });
- }
- }
-
- function checkCanvg (callCanvg) {
- return function (win, data) {
- if (window.canvg) {
- callCanvg(win, data);
- } else { // Might not be set up yet
- $.getScript('canvg/rgbcolor.js', function() {
- $.getScript('canvg/canvg.js', function() {
- callCanvg(win, data);
- });
- });
- }
- };
- }
- /**
- * EXPORTS
- */
-
- /**
- * Store and retrieve preferences
- * @param {string} key The preference name to be retrieved or set
- * @param {string} [val] The value. If the value supplied is missing or falsey, no change to the preference will be made.
- * @returns {string} If val is missing or falsey, the value of the previously stored preference will be returned.
- * @todo Can we change setting on the jQuery namespace (onto editor) to avoid conflicts?
- * @todo Review whether any remaining existing direct references to
- * getting curPrefs can be changed to use $.pref() getting to ensure
- * defaultPrefs fallback (also for sake of allowInitialUserOverride); specifically, bkgd_color could be changed so that
- * the pref dialog has a button to auto-calculate background, but otherwise uses $.pref() to be able to get default prefs
- * or overridable settings
- */
- $.pref = function (key, val) {
- if (val) {
- curPrefs[key] = val;
- editor.curPrefs = curPrefs; // Update exported value
- return;
- }
- return (key in curPrefs) ? curPrefs[key] : defaultPrefs[key];
- };
-
- /**
- * EDITOR PUBLIC METHODS
- * locale.js also adds "putLang" and "readLang" as editor methods
- * @todo Sort these methods per invocation order, ideally with init at the end
- * @todo Prevent execution until init executes if dependent on it?
- */
- /**
- * Where permitted, sets canvas and/or defaultPrefs based on previous
- * storage. This will override URL settings (for security reasons) but
- * not config.js configuration (unless initial user overriding is explicitly
- * permitted there via allowInitialUserOverride).
- * @todo Split allowInitialUserOverride into allowOverrideByURL and
- * allowOverrideByUserStorage so config.js can disallow some
- * individual items for URL setting but allow for user storage AND/OR
- * change URL setting so that it always uses a different namespace,
- * so it won't affect pre-existing user storage (but then if users saves
- * that, it will then be subject to tampering
- */
- editor.loadContentAndPrefs = function () {
- if (!curConfig.forceStorage && (curConfig.noStorageOnLoad || !document.cookie.match(/(?:^|;\s*)store=(?:prefsAndContent|prefsOnly)/))) {
- return;
- }
- // LOAD CONTENT
- if (editor.storage && // Cookies do not have enough available memory to hold large documents
- (curConfig.forceStorage || (!curConfig.noStorageOnLoad && document.cookie.match(/(?:^|;\s*)store=prefsAndContent/)))
- ) {
- var name = 'svgedit-' + curConfig.canvasName;
- var cached = editor.storage.getItem(name);
- if (cached) {
- editor.loadFromString(cached);
- }
- }
-
- // LOAD PREFS
- var key;
- for (key in defaultPrefs) {
- if (defaultPrefs.hasOwnProperty(key)) { // It's our own config, so we don't need to iterate up the prototype chain
- var storeKey = 'svg-edit-' + key;
- if (editor.storage) {
- var val = editor.storage.getItem(storeKey);
- if (val) {
- defaultPrefs[key] = String(val); // Convert to string for FF (.value fails in Webkit)
- }
- }
- else if (window.widget) {
- defaultPrefs[key] = widget.preferenceForKey(storeKey);
- }
- else {
- var result = document.cookie.match(new RegExp('(?:^|;\\s*)' + Utils.preg_quote(encodeURIComponent(storeKey)) + '=([^;]+)'));
- defaultPrefs[key] = result ? decodeURIComponent(result[1]) : '';
- }
- }
- }
- };
- /**
- * Allows setting of preferences or configuration (including extensions).
- * @param {object} opts The preferences or configuration (including extensions)
- * @param {object} [cfgCfg] Describes configuration which applies to the particular batch of supplied options
- * @param {boolean} [cfgCfg.allowInitialUserOverride=false] Set to true if you wish
- * to allow initial overriding of settings by the user via the URL
- * (if permitted) or previously stored preferences (if permitted);
- * note that it will be too late if you make such calls in extension
- * code because the URL or preference storage settings will
- * have already taken place.
- * @param {boolean} [cfgCfg.overwrite=true] Set to false if you wish to
- * prevent the overwriting of prior-set preferences or configuration
- * (URL settings will always follow this requirement for security
- * reasons, so config.js settings cannot be overridden unless it
- * explicitly permits via "allowInitialUserOverride" but extension config
- * can be overridden as they will run after URL settings). Should
- * not be needed in config.js.
- */
- editor.setConfig = function (opts, cfgCfg) {
- cfgCfg = cfgCfg || {};
- function extendOrAdd (cfgObj, key, val) {
- if (cfgObj[key] && typeof cfgObj[key] === 'object') {
- $.extend(true, cfgObj[key], val);
- }
- else {
- cfgObj[key] = val;
- }
- return;
- }
- $.each(opts, function(key, val) {
- if (opts.hasOwnProperty(key)) {
- // Only allow prefs defined in defaultPrefs
- if (defaultPrefs.hasOwnProperty(key)) {
- if (cfgCfg.overwrite === false && (
- curConfig.preventAllURLConfig ||
- curPrefs.hasOwnProperty(key)
- )) {
- return;
- }
- if (cfgCfg.allowInitialUserOverride === true) {
- defaultPrefs[key] = val;
- }
- else {
- $.pref(key, val);
- }
- }
- else if (['extensions', 'allowedOrigins'].indexOf(key) > -1) {
- if (cfgCfg.overwrite === false &&
- (
- curConfig.preventAllURLConfig ||
- key === 'allowedOrigins' ||
- (key === 'extensions' && curConfig.lockExtensions)
- )
- ) {
- return;
- }
- curConfig[key] = curConfig[key].concat(val); // We will handle any dupes later
- }
- // Only allow other curConfig if defined in defaultConfig
- else if (defaultConfig.hasOwnProperty(key)) {
- if (cfgCfg.overwrite === false && (
- curConfig.preventAllURLConfig ||
- curConfig.hasOwnProperty(key)
- )) {
- return;
- }
- // Potentially overwriting of previously set config
- if (curConfig.hasOwnProperty(key)) {
- if (cfgCfg.overwrite === false) {
- return;
- }
- extendOrAdd(curConfig, key, val);
- }
- else {
- if (cfgCfg.allowInitialUserOverride === true) {
- extendOrAdd(defaultConfig, key, val);
- }
- else {
- if (defaultConfig[key] && typeof defaultConfig[key] === 'object') {
- curConfig[key] = {};
- $.extend(true, curConfig[key], val); // Merge properties recursively, e.g., on initFill, initStroke objects
- }
- else {
- curConfig[key] = val;
- }
- }
- }
- }
- }
- });
- editor.curConfig = curConfig; // Update exported value
- };
- /**
- * @param {object} opts Extension mechanisms may call setCustomHandlers with three functions: opts.open, opts.save, and opts.exportImage
- * opts.open's responsibilities are:
- * - invoke a file chooser dialog in 'open' mode
- * - let user pick a SVG file
- * - calls setCanvas.setSvgString() with the string contents of that file
- * opts.save's responsibilities are:
- * - accept the string contents of the current document
- * - invoke a file chooser dialog in 'save' mode
- * - save the file to location chosen by the user
- * opts.exportImage's responsibilities (with regard to the object it is supplied in its 2nd argument) are:
- * - inform user of any issues supplied via the "issues" property
- * - convert the "svg" property SVG string into an image for export;
- * utilize the properties "type" (currently 'PNG', 'JPEG', 'BMP',
- * 'WEBP', 'PDF'), "mimeType", and "quality" (for 'JPEG' and 'WEBP'
- * types) to determine the proper output.
- */
- editor.setCustomHandlers = function (opts) {
- editor.ready(function() {
- if (opts.open) {
- $('#tool_open > input[type="file"]').remove();
- $('#tool_open').show();
- svgCanvas.open = opts.open;
- }
- if (opts.save) {
- editor.showSaveWarning = false;
- svgCanvas.bind('saved', opts.save);
- }
- if (opts.exportImage) {
- svgCanvas.bind('exported', checkCanvg(opts.exportImage));
- }
- customHandlers = opts;
- });
- };
- editor.randomizeIds = function () {
- svgCanvas.randomizeIds(arguments);
- };
- editor.init = function () {
- // var host = location.hostname,
- // onWeb = host && host.indexOf('.') >= 0;
- // Some FF versions throw security errors here when directly accessing
- try {
- if ('localStorage' in window) { // && onWeb removed so Webkit works locally
- editor.storage = localStorage;
- }
- } catch(err) {}
- // Todo: Avoid var-defined functions and group functions together, etc. where possible
- var good_langs = [];
- $('#lang_select option').each(function() {
- good_langs.push(this.value);
- });
- function setupCurPrefs () {
- curPrefs = $.extend(true, {}, defaultPrefs, curPrefs); // Now safe to merge with priority for curPrefs in the event any are already set
- // Export updated prefs
- editor.curPrefs = curPrefs;
- }
- function setupCurConfig () {
- curConfig = $.extend(true, {}, defaultConfig, curConfig); // Now safe to merge with priority for curConfig in the event any are already set
-
- // Now deal with extensions and other array config
- if (!curConfig.noDefaultExtensions) {
- curConfig.extensions = curConfig.extensions.concat(defaultExtensions);
- }
- // ...and remove any dupes
- $.each(['extensions', 'allowedOrigins'], function (i, cfg) {
- curConfig[cfg] = $.grep(curConfig[cfg], function (n, i) {
- return i === curConfig[cfg].indexOf(n);
- });
- });
- // Export updated config
- editor.curConfig = curConfig;
- }
- (function() {
- // Load config/data from URL if given
- var src, qstr;
- urldata = $.deparam.querystring(true);
- if (!$.isEmptyObject(urldata)) {
- if (urldata.dimensions) {
- urldata.dimensions = urldata.dimensions.split(',');
- }
- if (urldata.bkgd_color) {
- urldata.bkgd_color = '#' + urldata.bkgd_color;
- }
-
- if (urldata.extensions) {
- // For security reasons, disallow cross-domain or cross-folder extensions via URL
- urldata.extensions = urldata.extensions.match(/[:\/\\]/) ? '' : urldata.extensions.split(',');
- }
- // Disallowing extension paths via URL for
- // security reasons, even for same-domain
- // ones given potential to interact in undesirable
- // ways with other script resources
- $.each(
- [
- 'extPath', 'imgPath',
- 'langPath', 'jGraduatePath'
- ],
- function (pathConfig) {
- if (urldata[pathConfig]) {
- delete urldata[pathConfig];
- }
- }
- );
- editor.setConfig(urldata, {overwrite: false}); // Note: source and url (as with storagePrompt later) are not set on config but are used below
-
- setupCurConfig();
- if (!curConfig.preventURLContentLoading) {
- src = urldata.source;
- qstr = $.param.querystring();
- if (!src) { // urldata.source may have been null if it ended with '='
- if (qstr.indexOf('source=data:') >= 0) {
- src = qstr.match(/source=(data:[^&]*)/)[1];
- }
- }
- if (src) {
- if (src.indexOf('data:') === 0) {
- editor.loadFromDataURI(src);
- } else {
- editor.loadFromString(src);
- }
- return;
- }
- if (urldata.url) {
- editor.loadFromURL(urldata.url);
- return;
- }
- }
- if (!urldata.noStorageOnLoad || curConfig.forceStorage) {
- editor.loadContentAndPrefs();
- }
- setupCurPrefs();
- }
- else {
- setupCurConfig();
- editor.loadContentAndPrefs();
- setupCurPrefs();
- }
- }());
- // For external openers
- (function() {
- // let the opener know SVG Edit is ready (now that config is set up)
- var svgEditorReadyEvent,
- w = window.opener;
- if (w) {
- try {
- svgEditorReadyEvent = w.document.createEvent('Event');
- svgEditorReadyEvent.initEvent('svgEditorReady', true, true);
- w.document.documentElement.dispatchEvent(svgEditorReadyEvent);
- }
- catch(e) {}
- }
- }());
-
- var setIcon = editor.setIcon = function(elem, icon_id, forcedSize) {
- var icon = (typeof icon_id === 'string') ? $.getSvgIcon(icon_id, true) : icon_id.clone();
- if (!icon) {
- console.log('NOTE: Icon image missing: ' + icon_id);
- return;
- }
- $(elem).empty().append(icon);
- };
- var extFunc = function() {
- $.each(curConfig.extensions, function() {
- var extname = this;
- if (!extname.match(/^ext-.*\.js/)) { // Ensure URL cannot specify some other unintended file in the extPath
- return;
- }
- $.getScript(curConfig.extPath + extname, function(d) {
- // Fails locally in Chrome 5
- if (!d) {
- var s = document.createElement('script');
- s.src = curConfig.extPath + extname;
- document.querySelector('head').appendChild(s);
- }
- });
- });
- // var lang = ('lang' in curPrefs) ? curPrefs.lang : null;
- editor.putLocale(null, good_langs);
- };
- // Load extensions
- // Bit of a hack to run extensions in local Opera/IE9
- if (document.location.protocol === 'file:') {
- setTimeout(extFunc, 100);
- } else {
- extFunc();
- }
- $.svgIcons(curConfig.imgPath + 'svg_edit_icons.svg', {
- w:24, h:24,
- id_match: false,
- no_img: !svgedit.browser.isWebkit(), // Opera & Firefox 4 gives odd behavior w/images
- fallback_path: curConfig.imgPath,
- fallback: {
- 'new_image': 'clear.png',
- 'save': 'save.png',
- 'open': 'open.png',
- 'source': 'source.png',
- 'docprops': 'document-properties.png',
- 'wireframe': 'wireframe.png',
- 'undo': 'undo.png',
- 'redo': 'redo.png',
- 'select': 'select.png',
- 'select_node': 'select_node.png',
- 'pencil': 'fhpath.png',
- 'pen': 'line.png',
- 'square': 'square.png',
- 'rect': 'rect.png',
- 'fh_rect': 'freehand-square.png',
- 'circle': 'circle.png',
- 'ellipse': 'ellipse.png',
- 'fh_ellipse': 'freehand-circle.png',
- 'path': 'path.png',
- 'text': 'text.png',
- 'image': 'image.png',
- 'zoom': 'zoom.png',
- 'clone': 'clone.png',
- 'node_clone': 'node_clone.png',
- 'delete': 'delete.png',
- 'node_delete': 'node_delete.png',
- 'group': 'shape_group_elements.png',
- 'ungroup': 'shape_ungroup.png',
- 'move_top': 'move_top.png',
- 'move_bottom': 'move_bottom.png',
- 'to_path': 'to_path.png',
- 'link_controls': 'link_controls.png',
- 'reorient': 'reorient.png',
- 'align_left': 'align-left.png',
- 'align_center': 'align-center.png',
- 'align_right': 'align-right.png',
- 'align_top': 'align-top.png',
- 'align_middle': 'align-middle.png',
- 'align_bottom': 'align-bottom.png',
- 'go_up': 'go-up.png',
- 'go_down': 'go-down.png',
- 'ok': 'save.png',
- 'cancel': 'cancel.png',
- 'arrow_right': 'flyouth.png',
- 'arrow_down': 'dropdown.gif'
- },
- placement: {
- '#logo': 'logo',
- '#tool_clear div,#layer_new': 'new_image',
- '#tool_save div': 'save',
- '#tool_export div': 'export',
- '#tool_open div div': 'open',
- '#tool_import div div': 'import',
- '#tool_source': 'source',
- '#tool_docprops > div': 'docprops',
- '#tool_wireframe': 'wireframe',
- '#tool_undo': 'undo',
- '#tool_redo': 'redo',
- '#tool_select': 'select',
- '#tool_fhpath': 'pencil',
- '#tool_line': 'pen',
- '#tool_rect,#tools_rect_show': 'rect',
- '#tool_square': 'square',
- '#tool_fhrect': 'fh_rect',
- '#tool_ellipse,#tools_ellipse_show': 'ellipse',
- '#tool_circle': 'circle',
- '#tool_fhellipse': 'fh_ellipse',
- '#tool_path': 'path',
- '#tool_text,#layer_rename': 'text',
- '#tool_image': 'image',
- '#tool_zoom': 'zoom',
- '#tool_clone,#tool_clone_multi': 'clone',
- '#tool_node_clone': 'node_clone',
- '#layer_delete,#tool_delete,#tool_delete_multi': 'delete',
- '#tool_node_delete': 'node_delete',
- '#tool_add_subpath': 'add_subpath',
- '#tool_openclose_path': 'open_path',
- '#tool_move_top': 'move_top',
- '#tool_move_bottom': 'move_bottom',
- '#tool_topath': 'to_path',
- '#tool_node_link': 'link_controls',
- '#tool_reorient': 'reorient',
- '#tool_group_elements': 'group_elements',
- '#tool_ungroup': 'ungroup',
- '#tool_unlink_use': 'unlink_use',
- '#tool_alignleft, #tool_posleft': 'align_left',
- '#tool_aligncenter, #tool_poscenter': 'align_center',
- '#tool_alignright, #tool_posright': 'align_right',
- '#tool_aligntop, #tool_postop': 'align_top',
- '#tool_alignmiddle, #tool_posmiddle': 'align_middle',
- '#tool_alignbottom, #tool_posbottom': 'align_bottom',
- '#cur_position': 'align',
- '#linecap_butt,#cur_linecap': 'linecap_butt',
- '#linecap_round': 'linecap_round',
- '#linecap_square': 'linecap_square',
- '#linejoin_miter,#cur_linejoin': 'linejoin_miter',
- '#linejoin_round': 'linejoin_round',
- '#linejoin_bevel': 'linejoin_bevel',
- '#url_notice': 'warning',
- '#layer_up': 'go_up',
- '#layer_down': 'go_down',
- '#layer_moreopts': 'context_menu',
- '#layerlist td.layervis': 'eye',
- '#tool_source_save,#tool_docprops_save,#tool_prefs_save': 'ok',
- '#tool_source_cancel,#tool_docprops_cancel,#tool_prefs_cancel': 'cancel',
- '#rwidthLabel, #iwidthLabel': 'width',
- '#rheightLabel, #iheightLabel': 'height',
- '#cornerRadiusLabel span': 'c_radius',
- '#angleLabel': 'angle',
- '#linkLabel,#tool_make_link,#tool_make_link_multi': 'globe_link',
- '#zoomLabel': 'zoom',
- '#tool_fill label': 'fill',
- '#tool_stroke .icon_label': 'stroke',
- '#group_opacityLabel': 'opacity',
- '#blurLabel': 'blur',
- '#font_sizeLabel': 'fontsize',
- '.flyout_arrow_horiz': 'arrow_right',
- '.dropdown button, #main_button .dropdown': 'arrow_down',
- '#palette .palette_item:first, #fill_bg, #stroke_bg': 'no_color'
- },
- resize: {
- '#logo .svg_icon': 28,
- '.flyout_arrow_horiz .svg_icon': 5,
- '.layer_button .svg_icon, #layerlist td.layervis .svg_icon': 14,
- '.dropdown button .svg_icon': 7,
- '#main_button .dropdown .svg_icon': 9,
- '.palette_item:first .svg_icon' : 15,
- '#fill_bg .svg_icon, #stroke_bg .svg_icon': 16,
- '.toolbar_button button .svg_icon': 16,
- '.stroke_tool div div .svg_icon': 20,
- '#tools_bottom label .svg_icon': 18
- },
- callback: function(icons) {
- $('.toolbar_button button > svg, .toolbar_button button > img').each(function() {
- $(this).parent().prepend(this);
- });
- var min_height,
- tleft = $('#tools_left');
- if (tleft.length !== 0) {
- min_height = tleft.offset().top + tleft.outerHeight();
- }
-
- var size = $.pref('iconsize');
- editor.setIconSize(size || ($(window).height() < min_height ? 's': 'm'));
- // Look for any missing flyout icons from plugins
- $('.tools_flyout').each(function() {
- var shower = $('#' + this.id + '_show');
- var sel = shower.attr('data-curopt');
- // Check if there's an icon here
- if (!shower.children('svg, img').length) {
- var clone = $(sel).children().clone();
- if (clone.length) {
- clone[0].removeAttribute('style'); //Needed for Opera
- shower.append(clone);
- }
- }
- });
- editor.runCallbacks();
- setTimeout(function() {
- $('.flyout_arrow_horiz:empty').each(function() {
- $(this).append($.getSvgIcon('arrow_right').width(5).height(5));
- });
- }, 1);
- }
- });
- editor.canvas = svgCanvas = new $.SvgCanvas(document.getElementById('svgcanvas'), curConfig);
- var supportsNonSS, resize_timer, changeZoom, Actions, curScrollPos,
- palette = [ // Todo: Make into configuration item?
- '#000000', '#3f3f3f', '#7f7f7f', '#bfbfbf', '#ffffff',
- '#ff0000', '#ff7f00', '#ffff00', '#7fff00',
- '#00ff00', '#00ff7f', '#00ffff', '#007fff',
- '#0000ff', '#7f00ff', '#ff00ff', '#ff007f',
- '#7f0000', '#7f3f00', '#7f7f00', '#3f7f00',
- '#007f00', '#007f3f', '#007f7f', '#003f7f',
- '#00007f', '#3f007f', '#7f007f', '#7f003f',
- '#ffaaaa', '#ffd4aa', '#ffffaa', '#d4ffaa',
- '#aaffaa', '#aaffd4', '#aaffff', '#aad4ff',
- '#aaaaff', '#d4aaff', '#ffaaff', '#ffaad4'
- ],
- modKey = (svgedit.browser.isMac() ? 'meta+' : 'ctrl+'), // ⌘
- path = svgCanvas.pathActions,
- undoMgr = svgCanvas.undoMgr,
- defaultImageURL = curConfig.imgPath + 'logo.png',
- workarea = $('#workarea'),
- canv_menu = $('#cmenu_canvas'),
- // layer_menu = $('#cmenu_layers'), // Unused
- exportWindow = null,
- zoomInIcon = 'crosshair',
- zoomOutIcon = 'crosshair',
- ui_context = 'toolbars',
- origSource = '',
- paintBox = {fill: null, stroke:null};
-
- // This sets up alternative dialog boxes. They mostly work the same way as
- // their UI counterparts, expect instead of returning the result, a callback
- // needs to be included that returns the result as its first parameter.
- // In the future we may want to add additional types of dialog boxes, since
- // they should be easy to handle this way.
- (function() {
- $('#dialog_container').draggable({cancel: '#dialog_content, #dialog_buttons *', containment: 'window'});
- var box = $('#dialog_box'),
- btn_holder = $('#dialog_buttons'),
- dialog_content = $('#dialog_content'),
- dbox = function(type, msg, callback, defaultVal, opts, changeCb, checkbox) {
- var ok, ctrl, chkbx;
- dialog_content.html('<p>'+msg.replace(/\n/g, '</p><p>')+'</p>')
- .toggleClass('prompt', (type == 'prompt'));
- btn_holder.empty();
- ok = $('<input type="button" value="' + uiStrings.common.ok + '">').appendTo(btn_holder);
- if (type !== 'alert') {
- $('<input type="button" value="' + uiStrings.common.cancel + '">')
- .appendTo(btn_holder)
- .click(function() { box.hide(); callback(false);});
- }
- if (type === 'prompt') {
- ctrl = $('<input type="text">').prependTo(btn_holder);
- ctrl.val(defaultVal || '');
- ctrl.bind('keydown', 'return', function() {ok.click();});
- }
- else if (type === 'select') {
- var div = $('<div style="text-align:center;">');
- ctrl = $('<select>').appendTo(div);
- if (checkbox) {
- var label = $('<label>').text(checkbox.label);
- chkbx = $('<input type="checkbox">').appendTo(label);
- chkbx.val(checkbox.value);
- if (checkbox.tooltip) {
- label.attr('title', checkbox.tooltip);
- }
- chkbx.prop('checked', !!checkbox.checked);
- div.append($('<div>').append(label));
- }
- $.each(opts || [], function (opt, val) {
- if (typeof val === 'object') {
- ctrl.append($('<option>').val(val.value).html(val.text));
- }
- else {
- ctrl.append($('<option>').html(val));
- }
- });
- dialog_content.append(div);
- if (defaultVal) {
- ctrl.val(defaultVal);
- }
- if (changeCb) {
- ctrl.bind('change', 'return', changeCb);
- }
- ctrl.bind('keydown', 'return', function() {ok.click();});
- }
- if (type === 'process') {
- ok.hide();
- }
- box.show();
- ok.click(function() {
- box.hide();
- var resp = (type === 'prompt' || type === 'select') ? ctrl.val() : true;
- if (callback) {
- if (chkbx) {
- callback(resp, chkbx.prop('checked'));
- }
- else {
- callback(resp);
- }
- }
- }).focus();
- if (type === 'prompt' || type === 'select') {
- ctrl.focus();
- }
- };
- $.alert = function(msg, cb) { dbox('alert', msg, cb);};
- $.confirm = function(msg, cb) { dbox('confirm', msg, cb);};
- $.process_cancel = function(msg, cb) { dbox('process', msg, cb);};
- $.prompt = function(msg, txt, cb) { dbox('prompt', msg, cb, txt);};
- $.select = function(msg, opts, cb, changeCb, txt, checkbox) { dbox('select', msg, cb, txt, opts, changeCb, checkbox);};
- }());
- var setSelectMode = function() {
- var curr = $('.tool_button_current');
- if (curr.length && curr[0].id !== 'tool_select') {
- curr.removeClass('tool_button_current').addClass('tool_button');
- $('#tool_select').addClass('tool_button_current').removeClass('tool_button');
- $('#styleoverrides').text('#svgcanvas svg *{cursor:move;pointer-events:all} #svgcanvas svg{cursor:default}');
- }
- svgCanvas.setMode('select');
- workarea.css('cursor', 'auto');
- };
- // used to make the flyouts stay on the screen longer the very first time
- // var flyoutspeed = 1250; // Currently unused
- var textBeingEntered = false;
- var selectedElement = null;
- var multiselected = false;
- var editingsource = false;
- var docprops = false;
- var preferences = false;
- var cur_context = '';
- var origTitle = $('title:first').text();
- // Make [1,2,5] array
- var r_intervals = [];
- var i;
- for (i = 0.1; i < 1E5; i *= 10) {
- r_intervals.push(i);
- r_intervals.push(2 * i);
- r_intervals.push(5 * i);
- }
- // This function highlights the layer passed in (by fading out the other layers)
- // if no layer is passed in, this function restores the other layers
- var toggleHighlightLayer = function(layerNameToHighlight) {
- var i, curNames = [], numLayers = svgCanvas.getCurrentDrawing().getNumLayers();
- for (i = 0; i < numLayers; i++) {
- curNames[i] = svgCanvas.getCurrentDrawing().getLayerName(i);
- }
- if (layerNameToHighlight) {
- for (i = 0; i < numLayers; ++i) {
- if (curNames[i] != layerNameToHighlight) {
- svgCanvas.getCurrentDrawing().setLayerOpacity(curNames[i], 0.5);
- }
- }
- } else {
- for (i = 0; i < numLayers; ++i) {
- svgCanvas.getCurrentDrawing().setLayerOpacity(curNames[i], 1.0);
- }
- }
- };
- var populateLayers = function() {
- svgCanvas.clearSelection();
- var layerlist = $('#layerlist tbody').empty();
- var selLayerNames = $('#selLayerNames').empty();
- var drawing = svgCanvas.getCurrentDrawing();
- var currentLayerName = drawing.getCurrentLayerName();
- var layer = svgCanvas.getCurrentDrawing().getNumLayers();
- var icon = $.getSvgIcon('eye');
- // we get the layers in the reverse z-order (the layer rendered on top is listed first)
- while (layer--) {
- var name = drawing.getLayerName(layer);
- var layerTr = $('<tr class="layer">').toggleClass('layersel', name === currentLayerName);
- var layerVis = $('<td class="layervis">').toggleClass('layerinvis', !drawing.getLayerVisibility(name));
- var layerName = $('<td class="layername">' + name + '</td>');
- layerlist.append(layerTr.append(layerVis, layerName));
- selLayerNames.append('<option value="' + name + '">' + name + '</option>');
- }
- if (icon !== undefined) {
- var copy = icon.clone();
- $('td.layervis', layerlist).append(copy);
- $.resizeSvgIcons({'td.layervis .svg_icon': 14});
- }
- // handle selection of layer
- $('#layerlist td.layername')
- .mouseup(function(evt) {
- $('#layerlist tr.layer').removeClass('layersel');
- $(this.parentNode).addClass('layersel');
- svgCanvas.setCurrentLayer(this.textContent);
- evt.preventDefault();
- })
- .mouseover(function() {
- toggleHighlightLayer(this.textContent);
- })
- .mouseout(function() {
- toggleHighlightLayer();
- });
- $('#layerlist td.layervis').click(function() {
- var row = $(this.parentNode).prevAll().length;
- var name = $('#layerlist tr.layer:eq(' + row + ') td.layername').text();
- var vis = $(this).hasClass('layerinvis');
- svgCanvas.setLayerVisibility(name, vis);
- $(this).toggleClass('layerinvis');
- });
- // if there were too few rows, let's add a few to make it not so lonely
- var num = 5 - $('#layerlist tr.layer').size();
- while (num-- > 0) {
- // FIXME: there must a better way to do this
- layerlist.append('<tr><td style="color:white">_</td><td/></tr>');
- }
- };
- var showSourceEditor = function(e, forSaving) {
- if (editingsource) {return;}
- editingsource = true;
- origSource = svgCanvas.getSvgString();
- $('#save_output_btns').toggle(!!forSaving);
- $('#tool_source_back').toggle(!forSaving);
- $('#svg_source_textarea').val(origSource);
- $('#svg_source_editor').fadeIn();
- $('#svg_source_textarea').focus();
- };
- var togglePathEditMode = function(editmode, elems) {
- $('#path_node_panel').toggle(editmode);
- $('#tools_bottom_2,#tools_bottom_3').toggle(!editmode);
- if (editmode) {
- // Change select icon
- $('.tool_button_current').removeClass('tool_button_current').addClass('tool_button');
- $('#tool_select').addClass('tool_button_current').removeClass('tool_button');
- setIcon('#tool_select', 'select_node');
- multiselected = false;
- if (elems.length) {
- selectedElement = elems[0];
- }
- } else {
- setTimeout(function () {
- setIcon('#tool_select', 'select');
- }, 1000);
- }
- };
- var saveHandler = function(wind, svg) {
- editor.showSaveWarning = false;
- // by default, we add the XML prolog back, systems integrating SVG-edit (wikis, CMSs)
- // can just provide their own custom save handler and might not want the XML prolog
- svg = '<?xml version="1.0"?>\n' + svg;
- // IE9 doesn't allow standalone Data URLs
- // https://connect.microsoft.com/IE/feedback/details/542600/data-uri-images-fail-when-loaded-by-themselves
- if (svgedit.browser.isIE()) {
- showSourceEditor(0, true);
- return;
- }
- // Opens the SVG in new window
- var win = wind.open('data:image/svg+xml;base64,' + Utils.encode64(svg));
- // Alert will only appear the first time saved OR the first time the bug is encountered
- var done = $.pref('save_notice_done');
- if (done !== 'all') {
- var note = uiStrings.notification.saveFromBrowser.replace('%s', 'SVG');
- // Check if FF and has <defs/>
- if (svgedit.browser.isGecko()) {
- if (svg.indexOf('<defs') !== -1) {
- // warning about Mozilla bug #308590 when applicable (seems to be fixed now in Feb 2013)
- note += '\n\n' + uiStrings.notification.defsFailOnSave;
- $.pref('save_notice_done', 'all');
- done = 'all';
- } else {
- $.pref('save_notice_done', 'part');
- }
- } else {
- $.pref('save_notice_done', 'all');
- }
- if (done !== 'part') {
- win.alert(note);
- }
- }
- };
- // Export global for use by jsPDF
- saveAs = function (blob, options) {
- var blobUrl = URL.createObjectURL(blob);
- try {
- // This creates a bookmarkable data URL,
- // but it doesn't currently work in
- // Firefox, and although it works in Chrome,
- // Chrome doesn't make the full "data:" URL
- // visible unless you right-click to "Inspect
- // element" and then right-click on the element
- // to "Copy link address".
- var xhr = new XMLHttpRequest();
- xhr.responseType = 'blob';
- xhr.onload = function() {
- var recoveredBlob = xhr.response;
- var reader = new FileReader();
- reader.onload = function() {
- var blobAsDataUrl = reader.result;
- exportWindow.location.href = blobAsDataUrl;
- };
- reader.readAsDataURL(recoveredBlob);
- };
- xhr.open('GET', blobUrl);
- xhr.send();
- }
- catch (e) {
- exportWindow.location.href = blobUrl;
- }
- };
- var exportHandler = function(win, data) {
- var issues = data.issues,
- type = data.type || 'PNG',
- dataURLType = (type === 'ICO' ? 'BMP' : type).toLowerCase();
- exportWindow = window.open('', data.exportWindowName); // A hack to get the window via JSON-able name without opening a new one
- if (!$('#export_canvas').length) {
- $('<canvas>', {id: 'export_canvas'}).hide().appendTo('body');
- }
- var c = $('#export_canvas')[0];
- if (type === 'PDF') {
- var res = svgCanvas.getResolution();
- var orientation = res.w > res.h ? 'landscape' : 'portrait';
- var units = 'pt'; // curConfig.baseUnit; // We could use baseUnit, but that is presumably not intended for export purposes
- 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)
- var docTitle = svgCanvas.getDocumentTitle();
- doc.setProperties({
- title: docTitle/*,
- subject: '',
- author: '',
- keywords: '',
- creator: ''*/
- });
- svgElementToPdf(data.svg, doc, {});
- doc.save(docTitle + '.pdf');
- return;
- }
- c.width = svgCanvas.contentW;
- c.height = svgCanvas.contentH;
-
- canvg(c, data.svg, {renderCallback: function() {
- var datauri = data.quality ? c.toDataURL('image/' + dataURLType, data.quality) : c.toDataURL('image/' + dataURLType);
- exportWindow.location.href = datauri;
- var done = $.pref('export_notice_done');
- if (done !== 'all') {
- var note = uiStrings.notification.saveFromBrowser.replace('%s', type);
- // Check if there's issues
- if (issues.length) {
- var pre = '\n \u2022 ';
- note += ('\n\n' + uiStrings.notification.noteTheseIssues + pre + issues.join(pre));
- }
- // Note that this will also prevent the notice even though new issues may appear later.
- // May want to find a way to deal with that without annoying the user
- $.pref('export_notice_done', 'all');
- exportWindow.alert(note);
- }
- }});
- };
- var operaRepaint = function() {
- // Repaints canvas in Opera. Needed for stroke-dasharray change as well as fill change
- if (!window.opera) {
- return;
- }
- $('<p/>').hide().appendTo('body').remove();
- };
- function setStrokeOpt(opt, changeElem) {
- var id = opt.id;
- var bits = id.split('_');
- var pre = bits[0];
- var val = bits[1];
- if (changeElem) {
- svgCanvas.setStrokeAttr('stroke-' + pre, val);
- }
- operaRepaint();
- setIcon('#cur_' + pre, id, 20);
- $(opt).addClass('current').siblings().removeClass('current');
- }
- // This is a common function used when a tool has been clicked (chosen)
- // It does several common things:
- // - removes the tool_button_current class from whatever tool currently has it
- // - hides any flyouts
- // - adds the tool_button_current class to the button passed in
- var toolButtonClick = editor.toolButtonClick = function(button, noHiding) {
- if ($(button).hasClass('disabled')) {return false;}
- if ($(button).parent().hasClass('tools_flyout')) {return true;}
- var fadeFlyouts = 'normal';
- if (!noHiding) {
- $('.tools_flyout').fadeOut(fadeFlyouts);
- }
- $('#styleoverrides').text('');
- workarea.css('cursor', 'auto');
- $('.tool_button_current').removeClass('tool_button_current').addClass('tool_button');
- $(button).addClass('tool_button_current').removeClass('tool_button');
- return true;
- };
- var clickSelect = editor.clickSelect = function() {
- if (toolButtonClick('#tool_select')) {
- svgCanvas.setMode('select');
- $('#styleoverrides').text('#svgcanvas svg *{cursor:move;pointer-events:all}, #svgcanvas svg{cursor:default}');
- }
- };
- var setImageURL = editor.setImageURL = function(url) {
- if (!url) {
- url = defaultImageURL;
- }
- svgCanvas.setImageURL(url);
- $('#image_url').val(url);
- if (url.indexOf('data:') === 0) {
- // data URI found
- $('#image_url').hide();
- $('#change_image_url').show();
- } else {
- // regular URL
- svgCanvas.embedImage(url, function(dataURI) {
- // Couldn't embed, so show warning
- $('#url_notice').toggle(!dataURI);
- defaultImageURL = url;
- });
- $('#image_url').show();
- $('#change_image_url').hide();
- }
- };
- function setBackground (color, url) {
- // if (color == $.pref('bkgd_color') && url == $.pref('bkgd_url')) {return;}
- $.pref('bkgd_color', color);
- $.pref('bkgd_url', url);
- // This should be done in svgcanvas.js for the borderRect fill
- svgCanvas.setBackground(color, url);
- }
- function promptImgURL() {
- var curhref = svgCanvas.getHref(selectedElement);
- curhref = curhref.indexOf('data:') === 0 ? '' : curhref;
- $.prompt(uiStrings.notification.enterNewImgURL, curhref, function(url) {
- if (url) {setImageURL(url);}
- });
- }
- var setInputWidth = function(elem) {
- var w = Math.min(Math.max(12 + elem.value.length * 6, 50), 300);
- $(elem).width(w);
- };
- function updateRulers(scanvas, zoom) {
- if (!zoom) {zoom = svgCanvas.getZoom();}
- if (!scanvas) {scanvas = $('#svgcanvas');}
- var d, i;
- var limit = 30000;
- var contentElem = svgCanvas.getContentElem();
- var units = svgedit.units.getTypeMap();
- var unit = units[curConfig.baseUnit]; // 1 = 1px
- // draw x ruler then y ruler
- for (d = 0; d < 2; d++) {
- var isX = (d === 0);
- var dim = isX ? 'x' : 'y';
- var lentype = isX ? 'width' : 'height';
- var contentDim = Number(contentElem.getAttribute(dim));
- var $hcanv_orig = $('#ruler_' + dim + ' canvas:first');
- // Bit of a hack to fully clear the canvas in Safari & IE9
- var $hcanv = $hcanv_orig.clone();
- $hcanv_orig.replaceWith($hcanv);
- var hcanv = $hcanv[0];
- // Set the canvas size to the width of the container
- var ruler_len = scanvas[lentype]();
- var total_len = ruler_len;
- hcanv.parentNode.style[lentype] = total_len + 'px';
- var ctx_num = 0;
- var ctx = hcanv.getContext('2d');
- var ctx_arr, num, ctx_arr_num;
- ctx.fillStyle = 'rgb(200,0,0)';
- ctx.fillRect(0, 0, hcanv.width, hcanv.height);
- // Remove any existing canvasses
- $hcanv.siblings().remove();
-
- // Create multiple canvases when necessary (due to browser limits)
- if (ruler_len >= limit) {
- ctx_arr_num = parseInt(ruler_len / limit, 10) + 1;
- ctx_arr = [];
- ctx_arr[0] = ctx;
- var copy;
- for (i = 1; i < ctx_arr_num; i++) {
- hcanv[lentype] = limit;
- copy = hcanv.cloneNode(true);
- hcanv.parentNode.appendChild(copy);
- ctx_arr[i] = copy.getContext('2d');
- }
- copy[lentype] = ruler_len % limit;
- // set copy width to last
- ruler_len = limit;
- }
- hcanv[lentype] = ruler_len;
- var u_multi = unit * zoom;
- // Calculate the main number interval
- var raw_m = 50 / u_multi;
- var multi = 1;
- for (i = 0; i < r_intervals.length; i++) {
- num = r_intervals[i];
- multi = num;
- if (raw_m <= num) {
- break;
- }
- }
- var big_int = multi * u_multi;
- ctx.font = '9px sans-serif';
- var ruler_d = ((contentDim / u_multi) % multi) * u_multi;
- var label_pos = ruler_d - big_int;
- // draw big intervals
- while (ruler_d < total_len) {
- label_pos += big_int;
- // var real_d = ruler_d - contentDim; // Currently unused
- var cur_d = Math.round(ruler_d) + 0.5;
- if (isX) {
- ctx.moveTo(cur_d, 15);
- ctx.lineTo(cur_d, 0);
- }
- else {
- ctx.moveTo(15, cur_d);
- ctx.lineTo(0, cur_d);
- }
- num = (label_pos - contentDim) / u_multi;
- var label;
- if (multi >= 1) {
- label = Math.round(num);
- }
- else {
- var decs = String(multi).split('.')[1].length;
- label = num.toFixed(decs);
- }
- // Change 1000s to Ks
- if (label !== 0 && label !== 1000 && label % 1000 === 0) {
- label = (label / 1000) + 'K';
- }
- if (isX) {
- ctx.fillText(label, ruler_d+2, 8);
- } else {
- // draw label vertically
- var str = String(label).split('');
- for (i = 0; i < str.length; i++) {
- ctx.fillText(str[i], 1, (ruler_d+9) + i*9);
- }
- }
- var part = big_int / 10;
- // draw the small intervals
- for (i = 1; i < 10; i++) {
- var sub_d = Math.round(ruler_d + part * i) + 0.5;
- if (ctx_arr && sub_d > ruler_len) {
- ctx_num++;
- ctx.stroke();
- if (ctx_num >= ctx_arr_num) {
- i = 10;
- ruler_d = total_len;
- continue;
- }
- ctx = ctx_arr[ctx_num];
- ruler_d -= limit;
- sub_d = Math.round(ruler_d + part * i) + 0.5;
- }
- // odd lines are slighly longer
- var line_num = (i % 2) ? 12 : 10;
- if (isX) {
- ctx.moveTo(sub_d, 15);
- ctx.lineTo(sub_d, line_num);
- } else {
- ctx.moveTo(15, sub_d);
- ctx.lineTo(line_num, sub_d);
- }
- }
- ruler_d += big_int;
- }
- ctx.strokeStyle = '#000';
- ctx.stroke();
- }
- }
- var updateCanvas = editor.updateCanvas = function(center, new_ctr) {
- var w = workarea.width(), h = workarea.height();
- var w_orig = w, h_orig = h;
- var zoom = svgCanvas.getZoom();
- var w_area = workarea;
- var cnvs = $('#svgcanvas');
- var old_ctr = {
- x: w_area[0].scrollLeft + w_orig/2,
- y: w_area[0].scrollTop + h_orig/2
- };
- var multi = curConfig.canvas_expansion;
- w = Math.max(w_orig, svgCanvas.contentW * zoom * multi);
- h = Math.max(h_orig, svgCanvas.contentH * zoom * multi);
- if (w == w_orig && h == h_orig) {
- workarea.css('overflow', 'hidden');
- } else {
- workarea.css('overflow', 'scroll');
- }
- var old_can_y = cnvs.height()/2;
- var old_can_x = cnvs.width()/2;
- cnvs.width(w).height(h);
- var new_can_y = h/2;
- var new_can_x = w/2;
- var offset = svgCanvas.updateCanvas(w, h);
- var ratio = new_can_x / old_can_x;
- var scroll_x = w/2 - w_orig/2;
- var scroll_y = h/2 - h_orig/2;
- if (!new_ctr) {
- var old_dist_x = old_ctr.x - old_can_x;
- var new_x = new_can_x + old_dist_x * ratio;
- var old_dist_y = old_ctr.y - old_can_y;
- var new_y = new_can_y + old_dist_y * ratio;
- new_ctr = {
- x: new_x,
- y: new_y
- };
- } else {
- new_ctr.x += offset.x;
- new_ctr.y += offset.y;
- }
- if (center) {
- // Go to top-left for larger documents
- if (svgCanvas.contentW > w_area.width()) {
- // Top-left
- workarea[0].scrollLeft = offset.x - 10;
- workarea[0].scrollTop = offset.y - 10;
- } else {
- // Center
- w_area[0].scro…
Large files files are truncated, but you can click here to view the full file