Refactor2 (#712)

* review svg-exec

* review selection.js elemgetset and selectedelems

* Update svgcanvas.js
This commit is contained in:
JFH
2022-01-22 21:25:49 +01:00
committed by GitHub
parent 18f14755b3
commit 77646cd14e
5 changed files with 1680 additions and 927 deletions

View File

@@ -24,13 +24,53 @@ let svgCanvas = null
*/ */
export const init = (canvas) => { export const init = (canvas) => {
svgCanvas = canvas svgCanvas = canvas
svgCanvas.getBold = getBoldMethod // Check whether selected element is bold or not.
svgCanvas.setBold = setBoldMethod // Make the selected element bold or normal.
svgCanvas.getItalic = getItalicMethod // Check whether selected element is in italics or not.
svgCanvas.setItalic = setItalicMethod // Make the selected element italic or normal.
svgCanvas.hasTextDecoration = hasTextDecorationMethod // Check whether the selected element has the given text decoration or not.
svgCanvas.addTextDecoration = addTextDecorationMethod // Adds the given value to the text decoration
svgCanvas.removeTextDecoration = removeTextDecorationMethod // Removes the given value from the text decoration
svgCanvas.setTextAnchor = setTextAnchorMethod // Set the new text anchor.
svgCanvas.setLetterSpacing = setLetterSpacingMethod // Set the new letter spacing.
svgCanvas.setWordSpacing = setWordSpacingMethod // Set the new word spacing.
svgCanvas.setTextLength = setTextLengthMethod // Set the new text length.
svgCanvas.setLengthAdjust = setLengthAdjustMethod // Set the new length adjust.
svgCanvas.getFontFamily = getFontFamilyMethod // The current font family
svgCanvas.setFontFamily = setFontFamilyMethod // Set the new font family.
svgCanvas.setFontColor = setFontColorMethod // Set the new font color.
svgCanvas.getFontColor = getFontColorMethod // The current font color
svgCanvas.getFontSize = getFontSizeMethod // The current font size
svgCanvas.setFontSize = setFontSizeMethod // Applies the given font size to the selected element.
svgCanvas.getText = getTextMethod // current text (`textContent`) of the selected element
svgCanvas.setTextContent = setTextContentMethod // Updates the text element with the given string.
svgCanvas.setImageURL = setImageURLMethod // Sets the new image URL for the selected image element
svgCanvas.setLinkURL = setLinkURLMethod // Sets the new link URL for the selected anchor element.
svgCanvas.setRectRadius = setRectRadiusMethod // Sets the `rx` and `ry` values to the selected `rect` element
svgCanvas.makeHyperlink = makeHyperlinkMethod // Wraps the selected element(s) in an anchor element or converts group to one.
svgCanvas.removeHyperlink = removeHyperlinkMethod
svgCanvas.setSegType = setSegTypeMethod // Sets the new segment type to the selected segment(s).
svgCanvas.setStrokeWidth = setStrokeWidthMethod // Sets the stroke width for the current selected elements.
svgCanvas.getResolution = getResolutionMethod // The current dimensions and zoom level in an object
svgCanvas.getTitle = getTitleMethod // the current group/SVG's title contents or `undefined` if no element
svgCanvas.setGroupTitle = setGroupTitleMethod // Sets the group/SVG's title content.
svgCanvas.setStrokeAttr = setStrokeAttrMethod // Set the given stroke-related attribute the given value for selected elements.
svgCanvas.setBackground = setBackgroundMethod // Set the background of the editor (NOT the actual document).
svgCanvas.setDocumentTitle = setDocumentTitleMethod // Adds/updates a title element for the document with the given name.
svgCanvas.getEditorNS = getEditorNSMethod // Returns the editor's namespace URL, optionally adding it to the root element.
svgCanvas.setResolution = setResolutionMethod // Changes the document's dimensions to the given size.
svgCanvas.setBBoxZoom = setBBoxZoomMethod // Sets the zoom level on the canvas-side based on the given value.
svgCanvas.setCurrentZoom = setZoomMethod // Sets the zoom to the given level.
svgCanvas.setColor = setColorMethod // Change the current stroke/fill color/gradien
svgCanvas.setGradient = setGradientMethod // Apply the current gradient to selected element's fill or stroke.
svgCanvas.setPaint = setPaintMethod // Set a color/gradient to a fill/stroke.
} }
/** /**
* @function module:elem-get-set.SvgCanvas#getResolution * @function module:elem-get-set.SvgCanvas#getResolution
* @returns {DimensionsAndZoom} The current dimensions and zoom level in an object * @returns {DimensionsAndZoom} The current dimensions and zoom level in an object
*/ */
export const getResolutionMethod = () => { const getResolutionMethod = () => {
const zoom = svgCanvas.getZoom() const zoom = svgCanvas.getZoom()
const w = svgCanvas.getSvgContent().getAttribute('width') / zoom const w = svgCanvas.getSvgContent().getAttribute('width') / zoom
const h = svgCanvas.getSvgContent().getAttribute('height') / zoom const h = svgCanvas.getSvgContent().getAttribute('height') / zoom
@@ -48,7 +88,7 @@ export const getResolutionMethod = () => {
* @returns {string|void} the current group/SVG's title contents or * @returns {string|void} the current group/SVG's title contents or
* `undefined` if no element is passed nd there are no selected elements. * `undefined` if no element is passed nd there are no selected elements.
*/ */
export const getTitleMethod = (elem) => { const getTitleMethod = (elem) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const dataStorage = svgCanvas.getDataStorage() const dataStorage = svgCanvas.getDataStorage()
elem = elem || selectedElements[0] elem = elem || selectedElements[0]
@@ -74,7 +114,7 @@ export const getTitleMethod = (elem) => {
* @todo Combine this with `setDocumentTitle` * @todo Combine this with `setDocumentTitle`
* @returns {void} * @returns {void}
*/ */
export const setGroupTitleMethod = (val) => { const setGroupTitleMethod = (val) => {
const { const {
InsertElementCommand, RemoveElementCommand, InsertElementCommand, RemoveElementCommand,
ChangeElementCommand, BatchCommand ChangeElementCommand, BatchCommand
@@ -119,7 +159,7 @@ export const setGroupTitleMethod = (val) => {
* @param {string} newTitle - String with the new title * @param {string} newTitle - String with the new title
* @returns {void} * @returns {void}
*/ */
export const setDocumentTitleMethod = (newTitle) => { const setDocumentTitleMethod = (newTitle) => {
const { ChangeElementCommand, BatchCommand } = svgCanvas.history const { ChangeElementCommand, BatchCommand } = svgCanvas.history
const childs = svgCanvas.getSvgContent().childNodes const childs = svgCanvas.getSvgContent().childNodes
let docTitle = false; let oldTitle = '' let docTitle = false; let oldTitle = ''
@@ -159,7 +199,7 @@ export const setDocumentTitleMethod = (newTitle) => {
* @returns {boolean} Indicates if resolution change was successful. * @returns {boolean} Indicates if resolution change was successful.
* It will fail on "fit to content" option with no content to fit to. * It will fail on "fit to content" option with no content to fit to.
*/ */
export const setResolutionMethod = (x, y) => { const setResolutionMethod = (x, y) => {
const { ChangeElementCommand, BatchCommand } = svgCanvas.history const { ChangeElementCommand, BatchCommand } = svgCanvas.history
const zoom = svgCanvas.getZoom() const zoom = svgCanvas.getZoom()
const res = svgCanvas.getResolution() const res = svgCanvas.getResolution()
@@ -220,7 +260,7 @@ export const setResolutionMethod = (x, y) => {
* @param {boolean} [add] - Indicates whether or not to add the namespace value * @param {boolean} [add] - Indicates whether or not to add the namespace value
* @returns {string} The editor's namespace URL * @returns {string} The editor's namespace URL
*/ */
export const getEditorNSMethod = (add) => { const getEditorNSMethod = (add) => {
if (add) { if (add) {
svgCanvas.getSvgContent().setAttribute('xmlns:se', NS.SE) svgCanvas.getSvgContent().setAttribute('xmlns:se', NS.SE)
} }
@@ -240,7 +280,7 @@ export const getEditorNSMethod = (add) => {
* @param {Integer} editorH - The editor's workarea box's height * @param {Integer} editorH - The editor's workarea box's height
* @returns {module:elem-get-set.ZoomAndBBox|void} * @returns {module:elem-get-set.ZoomAndBBox|void}
*/ */
export const setBBoxZoomMethod = (val, editorW, editorH) => { const setBBoxZoomMethod = (val, editorW, editorH) => {
const zoom = svgCanvas.getZoom() const zoom = svgCanvas.getZoom()
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
let spacer = 0.85 let spacer = 0.85
@@ -299,7 +339,7 @@ export const setBBoxZoomMethod = (val, editorW, editorH) => {
* @fires module:elem-get-set.SvgCanvas#event:ext_zoomChanged * @fires module:elem-get-set.SvgCanvas#event:ext_zoomChanged
* @returns {void} * @returns {void}
*/ */
export const setZoomMethod = (zoomLevel) => { const setZoomMethod = (zoomLevel) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const res = svgCanvas.getResolution() const res = svgCanvas.getResolution()
svgCanvas.getSvgContent().setAttribute('viewBox', '0 0 ' + res.w / zoomLevel + ' ' + res.h / zoomLevel) svgCanvas.getSvgContent().setAttribute('viewBox', '0 0 ' + res.w / zoomLevel + ' ' + res.h / zoomLevel)
@@ -317,11 +357,11 @@ export const setZoomMethod = (zoomLevel) => {
* @function module:elem-get-set.SvgCanvas#setColor * @function module:elem-get-set.SvgCanvas#setColor
* @param {string} type - String indicating fill or stroke * @param {string} type - String indicating fill or stroke
* @param {string} val - The value to set the stroke attribute to * @param {string} val - The value to set the stroke attribute to
* @param {boolean} preventUndo - Boolean indicating whether or not this should be an undoable option * @param {boolean} preventUndo - Boolean indicating whether or not svgCanvas should be an undoable option
* @fires module:elem-get-set.SvgCanvas#event:changed * @fires module:elem-get-set.SvgCanvas#event:changed
* @returns {void} * @returns {void}
*/ */
export const setColorMethod = (type, val, preventUndo) => { const setColorMethod = (type, val, preventUndo) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
svgCanvas.setCurShape(type, val) svgCanvas.setCurShape(type, val)
svgCanvas.setCurProperties(type + '_paint', { type: 'solidColor' }) svgCanvas.setCurProperties(type + '_paint', { type: 'solidColor' })
@@ -367,7 +407,7 @@ export const setColorMethod = (type, val, preventUndo) => {
* @param {"fill"|"stroke"} type - String indicating "fill" or "stroke" to apply to an element * @param {"fill"|"stroke"} type - String indicating "fill" or "stroke" to apply to an element
* @returns {void} * @returns {void}
*/ */
export const setGradientMethod = (type) => { const setGradientMethod = (type) => {
if (!svgCanvas.getCurProperties(type + '_paint') || if (!svgCanvas.getCurProperties(type + '_paint') ||
svgCanvas.getCurProperties(type + '_paint').type === 'solidColor') { return } svgCanvas.getCurProperties(type + '_paint').type === 'solidColor') { return }
const canvas = svgCanvas const canvas = svgCanvas
@@ -394,7 +434,7 @@ export const setGradientMethod = (type) => {
* @param {SVGGradientElement} grad - The gradient DOM element to compare to others * @param {SVGGradientElement} grad - The gradient DOM element to compare to others
* @returns {SVGGradientElement} The existing gradient if found, `null` if not * @returns {SVGGradientElement} The existing gradient if found, `null` if not
*/ */
export const findDuplicateGradient = (grad) => { const findDuplicateGradient = (grad) => {
const defs = findDefs() const defs = findDefs()
const existingGrads = defs.querySelectorAll('linearGradient, radialGradient') const existingGrads = defs.querySelectorAll('linearGradient, radialGradient')
let i = existingGrads.length let i = existingGrads.length
@@ -468,7 +508,7 @@ export const findDuplicateGradient = (grad) => {
* @param {module:jGraduate.jGraduatePaintOptions} paint - The jGraduate paint object to apply * @param {module:jGraduate.jGraduatePaintOptions} paint - The jGraduate paint object to apply
* @returns {void} * @returns {void}
*/ */
export const setPaintMethod = (type, paint) => { const setPaintMethod = (type, paint) => {
// make a copy // make a copy
const p = new jGraduate.Paint(paint) const p = new jGraduate.Paint(paint)
svgCanvas.setPaintOpacity(type, p.alpha / 100, true) svgCanvas.setPaintOpacity(type, p.alpha / 100, true)
@@ -494,7 +534,7 @@ export const setPaintMethod = (type, paint) => {
* @fires module:elem-get-set.SvgCanvas#event:changed * @fires module:elem-get-set.SvgCanvas#event:changed
* @returns {void} * @returns {void}
*/ */
export const setStrokeWidthMethod = (val) => { const setStrokeWidthMethod = (val) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
if (val === 0 && ['line', 'path'].includes(svgCanvas.getMode())) { if (val === 0 && ['line', 'path'].includes(svgCanvas.getMode())) {
svgCanvas.setStrokeWidth(1) svgCanvas.setStrokeWidth(1)
@@ -538,7 +578,7 @@ export const setStrokeWidthMethod = (val) => {
* @fires module:elem-get-set.SvgCanvas#event:changed * @fires module:elem-get-set.SvgCanvas#event:changed
* @returns {void} * @returns {void}
*/ */
export const setStrokeAttrMethod = (attr, val) => { const setStrokeAttrMethod = (attr, val) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
svgCanvas.setCurShape(attr.replace('-', '_'), val) svgCanvas.setCurShape(attr.replace('-', '_'), val)
const elems = [] const elems = []
@@ -564,7 +604,7 @@ export const setStrokeAttrMethod = (attr, val) => {
* @function module:svgcanvas.SvgCanvas#getBold * @function module:svgcanvas.SvgCanvas#getBold
* @returns {boolean} Indicates whether or not element is bold * @returns {boolean} Indicates whether or not element is bold
*/ */
export const getBoldMethod = () => { const getBoldMethod = () => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
// should only have one element selected // should only have one element selected
const selected = selectedElements[0] const selected = selectedElements[0]
@@ -581,7 +621,7 @@ export const getBoldMethod = () => {
* @param {boolean} b - Indicates bold (`true`) or normal (`false`) * @param {boolean} b - Indicates bold (`true`) or normal (`false`)
* @returns {void} * @returns {void}
*/ */
export const setBoldMethod = (b) => { const setBoldMethod = (b) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const selected = selectedElements[0] const selected = selectedElements[0]
if (selected?.tagName === 'text' && if (selected?.tagName === 'text' &&
@@ -597,7 +637,7 @@ export const setBoldMethod = (b) => {
* Check whether selected element has the given text decoration value or not. * Check whether selected element has the given text decoration value or not.
* @returns {boolean} Indicates whether or not element has the text decoration value * @returns {boolean} Indicates whether or not element has the text decoration value
*/ */
export const hasTextDecorationMethod = (value) => { const hasTextDecorationMethod = (value) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const selected = selectedElements[0] const selected = selectedElements[0]
@@ -614,7 +654,7 @@ export const hasTextDecorationMethod = (value) => {
* @param value The text decoration value * @param value The text decoration value
* @returns {void} * @returns {void}
*/ */
export const addTextDecorationMethod = (value) => { const addTextDecorationMethod = (value) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const selected = selectedElements[0] const selected = selectedElements[0]
if (selected?.tagName === 'text' && !selectedElements[1]) { if (selected?.tagName === 'text' && !selectedElements[1]) {
@@ -631,7 +671,7 @@ export const addTextDecorationMethod = (value) => {
* @param value The text decoration value * @param value The text decoration value
* @returns {void} * @returns {void}
*/ */
export const removeTextDecorationMethod = (value) => { const removeTextDecorationMethod = (value) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const selected = selectedElements[0] const selected = selectedElements[0]
if (selected?.tagName === 'text' && !selectedElements[1]) { if (selected?.tagName === 'text' && !selectedElements[1]) {
@@ -648,7 +688,7 @@ export const removeTextDecorationMethod = (value) => {
* @function module:svgcanvas.SvgCanvas#getItalic * @function module:svgcanvas.SvgCanvas#getItalic
* @returns {boolean} Indicates whether or not element is italic * @returns {boolean} Indicates whether or not element is italic
*/ */
export const getItalicMethod = () => { const getItalicMethod = () => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const selected = selectedElements[0] const selected = selectedElements[0]
if (selected?.tagName === 'text' && !selectedElements[1]) { if (selected?.tagName === 'text' && !selectedElements[1]) {
@@ -663,7 +703,7 @@ export const getItalicMethod = () => {
* @param {boolean} i - Indicates italic (`true`) or normal (`false`) * @param {boolean} i - Indicates italic (`true`) or normal (`false`)
* @returns {void} * @returns {void}
*/ */
export const setItalicMethod = (i) => { const setItalicMethod = (i) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const selected = selectedElements[0] const selected = selectedElements[0]
if (selected?.tagName === 'text' && !selectedElements[1]) { if (selected?.tagName === 'text' && !selectedElements[1]) {
@@ -679,7 +719,7 @@ export const setItalicMethod = (i) => {
* @param {string} value - The text anchor value (start, middle or end) * @param {string} value - The text anchor value (start, middle or end)
* @returns {void} * @returns {void}
*/ */
export const setTextAnchorMethod = (value) => { const setTextAnchorMethod = (value) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const selected = selectedElements[0] const selected = selectedElements[0]
if (selected?.tagName === 'text' && !selectedElements[1]) { if (selected?.tagName === 'text' && !selectedElements[1]) {
@@ -695,7 +735,7 @@ export const setTextAnchorMethod = (value) => {
* @param {string} value - The letter spacing value * @param {string} value - The letter spacing value
* @returns {void} * @returns {void}
*/ */
export const setLetterSpacingMethod = (value) => { const setLetterSpacingMethod = (value) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const selected = selectedElements[0] const selected = selectedElements[0]
if (selected?.tagName === 'text' && !selectedElements[1]) { if (selected?.tagName === 'text' && !selectedElements[1]) {
@@ -711,7 +751,7 @@ export const setLetterSpacingMethod = (value) => {
* @param {string} value - The word spacing value * @param {string} value - The word spacing value
* @returns {void} * @returns {void}
*/ */
export const setWordSpacingMethod = (value) => { const setWordSpacingMethod = (value) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const selected = selectedElements[0] const selected = selectedElements[0]
if (selected?.tagName === 'text' && !selectedElements[1]) { if (selected?.tagName === 'text' && !selectedElements[1]) {
@@ -727,7 +767,7 @@ export const setWordSpacingMethod = (value) => {
* @param {string} value - The text length value * @param {string} value - The text length value
* @returns {void} * @returns {void}
*/ */
export const setTextLengthMethod = (value) => { const setTextLengthMethod = (value) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const selected = selectedElements[0] const selected = selectedElements[0]
if (selected?.tagName === 'text' && !selectedElements[1]) { if (selected?.tagName === 'text' && !selectedElements[1]) {
@@ -743,7 +783,7 @@ export const setTextLengthMethod = (value) => {
* @param {string} value - The length adjust value * @param {string} value - The length adjust value
* @returns {void} * @returns {void}
*/ */
export const setLengthAdjustMethod = (value) => { const setLengthAdjustMethod = (value) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const selected = selectedElements[0] const selected = selectedElements[0]
if (selected?.tagName === 'text' && !selectedElements[1]) { if (selected?.tagName === 'text' && !selectedElements[1]) {
@@ -758,7 +798,7 @@ export const setLengthAdjustMethod = (value) => {
* @function module:svgcanvas.SvgCanvas#getFontFamily * @function module:svgcanvas.SvgCanvas#getFontFamily
* @returns {string} The current font family * @returns {string} The current font family
*/ */
export const getFontFamilyMethod = () => { const getFontFamilyMethod = () => {
return svgCanvas.getCurText('font_family') return svgCanvas.getCurText('font_family')
} }
@@ -768,7 +808,7 @@ export const getFontFamilyMethod = () => {
* @param {string} val - String with the new font family * @param {string} val - String with the new font family
* @returns {void} * @returns {void}
*/ */
export const setFontFamilyMethod = (val) => { const setFontFamilyMethod = (val) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
svgCanvas.setCurText('font_family', val) svgCanvas.setCurText('font_family', val)
svgCanvas.changeSelectedAttribute('font-family', val) svgCanvas.changeSelectedAttribute('font-family', val)
@@ -783,7 +823,7 @@ export const setFontFamilyMethod = (val) => {
* @param {string} val - String with the new font color * @param {string} val - String with the new font color
* @returns {void} * @returns {void}
*/ */
export const setFontColorMethod = (val) => { const setFontColorMethod = (val) => {
svgCanvas.setCurText('fill', val) svgCanvas.setCurText('fill', val)
svgCanvas.changeSelectedAttribute('fill', val) svgCanvas.changeSelectedAttribute('fill', val)
} }
@@ -792,7 +832,7 @@ export const setFontColorMethod = (val) => {
* @function module:svgcanvas.SvgCanvas#getFontColor * @function module:svgcanvas.SvgCanvas#getFontColor
* @returns {string} The current font color * @returns {string} The current font color
*/ */
export const getFontColorMethod = () => { const getFontColorMethod = () => {
return svgCanvas.getCurText('fill') return svgCanvas.getCurText('fill')
} }
@@ -800,7 +840,7 @@ export const getFontColorMethod = () => {
* @function module:svgcanvas.SvgCanvas#getFontSize * @function module:svgcanvas.SvgCanvas#getFontSize
* @returns {Float} The current font size * @returns {Float} The current font size
*/ */
export const getFontSizeMethod = () => { const getFontSizeMethod = () => {
return svgCanvas.getCurText('font_size') return svgCanvas.getCurText('font_size')
} }
@@ -810,7 +850,7 @@ export const getFontSizeMethod = () => {
* @param {Float} val - Float with the new font size * @param {Float} val - Float with the new font size
* @returns {void} * @returns {void}
*/ */
export const setFontSizeMethod = (val) => { const setFontSizeMethod = (val) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
svgCanvas.setCurText('font_size', val) svgCanvas.setCurText('font_size', val)
svgCanvas.changeSelectedAttribute('font-size', val) svgCanvas.changeSelectedAttribute('font-size', val)
@@ -823,7 +863,7 @@ export const setFontSizeMethod = (val) => {
* @function module:svgcanvas.SvgCanvas#getText * @function module:svgcanvas.SvgCanvas#getText
* @returns {string} The current text (`textContent`) of the selected element * @returns {string} The current text (`textContent`) of the selected element
*/ */
export const getTextMethod = () => { const getTextMethod = () => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const selected = selectedElements[0] const selected = selectedElements[0]
return (selected) ? selected.textContent : '' return (selected) ? selected.textContent : ''
@@ -835,7 +875,7 @@ export const getTextMethod = () => {
* @param {string} val - String with the new text * @param {string} val - String with the new text
* @returns {void} * @returns {void}
*/ */
export const setTextContentMethod = (val) => { const setTextContentMethod = (val) => {
svgCanvas.changeSelectedAttribute('#text', val) svgCanvas.changeSelectedAttribute('#text', val)
svgCanvas.textActions.init(val) svgCanvas.textActions.init(val)
svgCanvas.textActions.setCursor() svgCanvas.textActions.setCursor()
@@ -849,7 +889,7 @@ export const setTextContentMethod = (val) => {
* @fires module:svgcanvas.SvgCanvas#event:changed * @fires module:svgcanvas.SvgCanvas#event:changed
* @returns {void} * @returns {void}
*/ */
export const setImageURLMethod = (val) => { const setImageURLMethod = (val) => {
const { ChangeElementCommand, BatchCommand } = svgCanvas.history const { ChangeElementCommand, BatchCommand } = svgCanvas.history
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const elem = selectedElements[0] const elem = selectedElements[0]
@@ -898,7 +938,7 @@ export const setImageURLMethod = (val) => {
* @param {string} val - String with the link URL/path * @param {string} val - String with the link URL/path
* @returns {void} * @returns {void}
*/ */
export const setLinkURLMethod = (val) => { const setLinkURLMethod = (val) => {
const { ChangeElementCommand, BatchCommand } = svgCanvas.history const { ChangeElementCommand, BatchCommand } = svgCanvas.history
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
let elem = selectedElements[0] let elem = selectedElements[0]
@@ -935,7 +975,7 @@ export const setLinkURLMethod = (val) => {
* @fires module:svgcanvas.SvgCanvas#event:changed * @fires module:svgcanvas.SvgCanvas#event:changed
* @returns {void} * @returns {void}
*/ */
export const setRectRadiusMethod = (val) => { const setRectRadiusMethod = (val) => {
const { ChangeElementCommand } = svgCanvas.history const { ChangeElementCommand } = svgCanvas.history
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const selected = selectedElements[0] const selected = selectedElements[0]
@@ -956,7 +996,7 @@ export const setRectRadiusMethod = (val) => {
* @param {string} url * @param {string} url
* @returns {void} * @returns {void}
*/ */
export const makeHyperlinkMethod = (url) => { const makeHyperlinkMethod = (url) => {
svgCanvas.groupSelectedElements('a', url) svgCanvas.groupSelectedElements('a', url)
} }
@@ -964,7 +1004,7 @@ export const makeHyperlinkMethod = (url) => {
* @function module:svgcanvas.SvgCanvas#removeHyperlink * @function module:svgcanvas.SvgCanvas#removeHyperlink
* @returns {void} * @returns {void}
*/ */
export const removeHyperlinkMethod = () => { const removeHyperlinkMethod = () => {
svgCanvas.ungroupSelectedElement() svgCanvas.ungroupSelectedElement()
} }
@@ -978,7 +1018,7 @@ export const removeHyperlinkMethod = () => {
* @param {Integer} newType - New segment type. See {@link https://www.w3.org/TR/SVG/paths.html#InterfaceSVGPathSeg} for list * @param {Integer} newType - New segment type. See {@link https://www.w3.org/TR/SVG/paths.html#InterfaceSVGPathSeg} for list
* @returns {void} * @returns {void}
*/ */
export const setSegTypeMethod = (newType) => { const setSegTypeMethod = (newType) => {
svgCanvas.pathActions.setSegType(newType) svgCanvas.pathActions.setSegType(newType)
} }
@@ -989,7 +1029,7 @@ export const setSegTypeMethod = (newType) => {
* @param {string} url - URL or path to image to use * @param {string} url - URL or path to image to use
* @returns {void} * @returns {void}
*/ */
export const setBackgroundMethod = (color, url) => { const setBackgroundMethod = (color, url) => {
const bg = getElement('canvasBackground') const bg = getElement('canvasBackground')
const border = bg.querySelector('rect') const border = bg.querySelector('rect')
let bgImg = getElement('background_image') let bgImg = getElement('background_image')

View File

@@ -10,22 +10,34 @@ import { NS } from './namespaces.js'
import * as hstry from './history.js' import * as hstry from './history.js'
import * as pathModule from './path.js' import * as pathModule from './path.js'
import { import {
getStrokedBBoxDefaultVisible, setHref, getElement, getHref, getVisibleElements, getStrokedBBoxDefaultVisible,
findDefs, getRotationAngle, getRefElem, getBBox as utilsGetBBox, walkTreePost, assignAttributes, getFeGaussianBlur setHref,
getElement,
getHref,
getVisibleElements,
findDefs,
getRotationAngle,
getRefElem,
getBBox as utilsGetBBox,
walkTreePost,
assignAttributes,
getFeGaussianBlur
} from './utilities.js' } from './utilities.js'
import { import {
transformPoint, matrixMultiply, transformListToTransform transformPoint,
matrixMultiply,
transformListToTransform
} from './math.js' } from './math.js'
import { import { recalculateDimensions } from './recalculate.js'
recalculateDimensions import { isGecko } from '../common/browser.js' // , supportsEditableText
} from './recalculate.js'
import {
isGecko
} from '../common/browser.js' // , supportsEditableText
import { getParents } from '../editor/components/jgraduate/Util.js' import { getParents } from '../editor/components/jgraduate/Util.js'
const { const {
MoveElementCommand, BatchCommand, InsertElementCommand, RemoveElementCommand, ChangeElementCommand MoveElementCommand,
BatchCommand,
InsertElementCommand,
RemoveElementCommand,
ChangeElementCommand
} = hstry } = hstry
let svgCanvas = null let svgCanvas = null
@@ -35,7 +47,7 @@ let svgCanvas = null
* @param {module:selected-elem.elementContext} elementContext * @param {module:selected-elem.elementContext} elementContext
* @returns {void} * @returns {void}
*/ */
export const init = (canvas) => { export const init = canvas => {
svgCanvas = canvas svgCanvas = canvas
svgCanvas.copySelectedElements = copySelectedElements svgCanvas.copySelectedElements = copySelectedElements
svgCanvas.groupSelectedElements = groupSelectedElements // Wraps all the selected elements in a group (`g`) element. svgCanvas.groupSelectedElements = groupSelectedElements // Wraps all the selected elements in a group (`g`) element.
@@ -69,7 +81,9 @@ const moveToTopSelectedElem = () => {
// If the element actually moved position, add the command and fire the changed // If the element actually moved position, add the command and fire the changed
// event handler. // event handler.
if (oldNextSibling !== t.nextSibling) { if (oldNextSibling !== t.nextSibling) {
svgCanvas.addCommandToHistory(new MoveElementCommand(t, oldNextSibling, oldParent, 'top')) svgCanvas.addCommandToHistory(
new MoveElementCommand(t, oldNextSibling, oldParent, 'top')
)
svgCanvas.call('changed', [t]) svgCanvas.call('changed', [t])
} }
} }
@@ -101,7 +115,9 @@ const moveToBottomSelectedElem = () => {
// If the element actually moved position, add the command and fire the changed // If the element actually moved position, add the command and fire the changed
// event handler. // event handler.
if (oldNextSibling !== t.nextSibling) { if (oldNextSibling !== t.nextSibling) {
svgCanvas.addCommandToHistory(new MoveElementCommand(t, oldNextSibling, oldParent, 'bottom')) svgCanvas.addCommandToHistory(
new MoveElementCommand(t, oldNextSibling, oldParent, 'bottom')
)
svgCanvas.call('changed', [t]) svgCanvas.call('changed', [t])
} }
} }
@@ -115,18 +131,25 @@ const moveToBottomSelectedElem = () => {
* @fires module:selected-elem.SvgCanvas#event:changed * @fires module:selected-elem.SvgCanvas#event:changed
* @returns {void} * @returns {void}
*/ */
const moveUpDownSelected = (dir) => { const moveUpDownSelected = dir => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const selected = selectedElements[0] const selected = selectedElements[0]
if (!selected) { return } if (!selected) {
return
}
svgCanvas.setCurBBoxes([]) svgCanvas.setCurBBoxes([])
let closest; let foundCur let closest
let foundCur
// jQuery sorts this list // jQuery sorts this list
const list = svgCanvas.getIntersectionList(getStrokedBBoxDefaultVisible([selected])) const list = svgCanvas.getIntersectionList(
if (dir === 'Down') { list.reverse() } getStrokedBBoxDefaultVisible([selected])
)
if (dir === 'Down') {
list.reverse()
}
Array.prototype.forEach.call(list, (el) => { Array.prototype.forEach.call(list, el => {
if (!foundCur) { if (!foundCur) {
if (el === selected) { if (el === selected) {
foundCur = true foundCur = true
@@ -136,7 +159,9 @@ const moveUpDownSelected = (dir) => {
closest = el closest = el
return false return false
}) })
if (!closest) { return } if (!closest) {
return
}
const t = selected const t = selected
const oldParent = t.parentNode const oldParent = t.parentNode
@@ -149,7 +174,9 @@ const moveUpDownSelected = (dir) => {
// If the element actually moved position, add the command and fire the changed // If the element actually moved position, add the command and fire the changed
// event handler. // event handler.
if (oldNextSibling !== t.nextSibling) { if (oldNextSibling !== t.nextSibling) {
svgCanvas.addCommandToHistory(new MoveElementCommand(t, oldNextSibling, oldParent, 'Move ' + dir)) svgCanvas.addCommandToHistory(
new MoveElementCommand(t, oldNextSibling, oldParent, 'Move ' + dir)
)
svgCanvas.call('changed', [t]) svgCanvas.call('changed', [t])
} }
} }
@@ -198,7 +225,10 @@ const moveSelectedElements = (dx, dy, undoable = true) => {
batchCmd.addSubCommand(cmd) batchCmd.addSubCommand(cmd)
} }
svgCanvas.gettingSelectorManager().requestSelector(selected).resize() svgCanvas
.gettingSelectorManager()
.requestSelector(selected)
.resize()
} }
}) })
if (!batchCmd.isEmpty()) { if (!batchCmd.isEmpty()) {
@@ -222,12 +252,13 @@ const moveSelectedElements = (dx, dy, undoable = true) => {
const cloneSelectedElements = (x, y) => { const cloneSelectedElements = (x, y) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const currentGroup = svgCanvas.getCurrentGroup() const currentGroup = svgCanvas.getCurrentGroup()
let i; let elem let i
let elem
const batchCmd = new BatchCommand('Clone Elements') const batchCmd = new BatchCommand('Clone Elements')
// find all the elements selected (stop at first null) // find all the elements selected (stop at first null)
const len = selectedElements.length const len = selectedElements.length
const index = (el) => { const index = el => {
if (!el) return -1 if (!el) return -1
let i = 0 let i = 0
do { do {
@@ -243,12 +274,14 @@ const cloneSelectedElements = (x, y) => {
* @returns {Integer} * @returns {Integer}
*/ */
const sortfunction = (a, b) => { const sortfunction = (a, b) => {
return (index(b) - index(a)) return index(b) - index(a)
} }
selectedElements.sort(sortfunction) selectedElements.sort(sortfunction)
for (i = 0; i < len; ++i) { for (i = 0; i < len; ++i) {
elem = selectedElements[i] elem = selectedElements[i]
if (!elem) { break } if (!elem) {
break
}
} }
// use slice to quickly get the subset of elements we need // use slice to quickly get the subset of elements we need
const copiedElements = selectedElements.slice(0, i) const copiedElements = selectedElements.slice(0, i)
@@ -259,8 +292,8 @@ const cloneSelectedElements = (x, y) => {
i = copiedElements.length i = copiedElements.length
while (i--) { while (i--) {
// clone each element and replace it within copiedElements // clone each element and replace it within copiedElements
elem = copiedElements[i] = drawing.copyElem(copiedElements[i]); elem = copiedElements[i] = drawing.copyElem(copiedElements[i])
(currentGroup || drawing.getCurrentLayer()).append(elem) ;(currentGroup || drawing.getCurrentLayer()).append(elem)
batchCmd.addSubCommand(new InsertElementCommand(elem)) batchCmd.addSubCommand(new InsertElementCommand(elem))
} }
@@ -281,21 +314,39 @@ const alignSelectedElements = (type, relativeTo) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const bboxes = [] // angles = []; const bboxes = [] // angles = [];
const len = selectedElements.length const len = selectedElements.length
if (!len) { return } if (!len) {
let minx = Number.MAX_VALUE; let maxx = Number.MIN_VALUE return
let miny = Number.MAX_VALUE; let maxy = Number.MIN_VALUE }
let curwidth = Number.MIN_VALUE; let curheight = Number.MIN_VALUE let minx = Number.MAX_VALUE
let maxx = Number.MIN_VALUE
let miny = Number.MAX_VALUE
let maxy = Number.MIN_VALUE
let curwidth = Number.MIN_VALUE
let curheight = Number.MIN_VALUE
for (let i = 0; i < len; ++i) { for (let i = 0; i < len; ++i) {
if (!selectedElements[i]) { break } if (!selectedElements[i]) {
break
}
const elem = selectedElements[i] const elem = selectedElements[i]
bboxes[i] = getStrokedBBoxDefaultVisible([elem]) bboxes[i] = getStrokedBBoxDefaultVisible([elem])
// now bbox is axis-aligned and handles rotation // now bbox is axis-aligned and handles rotation
switch (relativeTo) { switch (relativeTo) {
case 'smallest': case 'smallest':
if (((type === 'l' || type === 'c' || type === 'r' || type === 'left' || type === 'center' || type === 'right') && if (
((type === 'l' ||
type === 'c' ||
type === 'r' ||
type === 'left' ||
type === 'center' ||
type === 'right') &&
(curwidth === Number.MIN_VALUE || curwidth > bboxes[i].width)) || (curwidth === Number.MIN_VALUE || curwidth > bboxes[i].width)) ||
((type === 't' || type === 'm' || type === 'b' || type === 'top' || type === 'middle' || type === 'bottom') && ((type === 't' ||
type === 'm' ||
type === 'b' ||
type === 'top' ||
type === 'middle' ||
type === 'bottom') &&
(curheight === Number.MIN_VALUE || curheight > bboxes[i].height)) (curheight === Number.MIN_VALUE || curheight > bboxes[i].height))
) { ) {
minx = bboxes[i].x minx = bboxes[i].x
@@ -307,9 +358,20 @@ const alignSelectedElements = (type, relativeTo) => {
} }
break break
case 'largest': case 'largest':
if (((type === 'l' || type === 'c' || type === 'r' || type === 'left' || type === 'center' || type === 'right') && if (
((type === 'l' ||
type === 'c' ||
type === 'r' ||
type === 'left' ||
type === 'center' ||
type === 'right') &&
(curwidth === Number.MIN_VALUE || curwidth < bboxes[i].width)) || (curwidth === Number.MIN_VALUE || curwidth < bboxes[i].width)) ||
((type === 't' || type === 'm' || type === 'b' || type === 'top' || type === 'middle' || type === 'bottom') && ((type === 't' ||
type === 'm' ||
type === 'b' ||
type === 'top' ||
type === 'middle' ||
type === 'bottom') &&
(curheight === Number.MIN_VALUE || curheight < bboxes[i].height)) (curheight === Number.MIN_VALUE || curheight < bboxes[i].height))
) { ) {
minx = bboxes[i].x minx = bboxes[i].x
@@ -320,11 +382,20 @@ const alignSelectedElements = (type, relativeTo) => {
curheight = bboxes[i].height curheight = bboxes[i].height
} }
break break
default: // 'selected' default:
if (bboxes[i].x < minx) { minx = bboxes[i].x } // 'selected'
if (bboxes[i].y < miny) { miny = bboxes[i].y } if (bboxes[i].x < minx) {
if (bboxes[i].x + bboxes[i].width > maxx) { maxx = bboxes[i].x + bboxes[i].width } minx = bboxes[i].x
if (bboxes[i].y + bboxes[i].height > maxy) { maxy = bboxes[i].y + bboxes[i].height } }
if (bboxes[i].y < miny) {
miny = bboxes[i].y
}
if (bboxes[i].x + bboxes[i].width > maxx) {
maxx = bboxes[i].x + bboxes[i].width
}
if (bboxes[i].y + bboxes[i].height > maxy) {
maxy = bboxes[i].y + bboxes[i].height
}
break break
} }
} // loop for each element to find the bbox and adjust min/max } // loop for each element to find the bbox and adjust min/max
@@ -339,7 +410,9 @@ const alignSelectedElements = (type, relativeTo) => {
const dx = new Array(len) const dx = new Array(len)
const dy = new Array(len) const dy = new Array(len)
for (let i = 0; i < len; ++i) { for (let i = 0; i < len; ++i) {
if (!selectedElements[i]) { break } if (!selectedElements[i]) {
break
}
// const elem = selectedElements[i]; // const elem = selectedElements[i];
const bbox = bboxes[i] const bbox = bboxes[i]
dx[i] = 0 dx[i] = 0
@@ -389,7 +462,9 @@ const deleteSelectedElements = () => {
for (let i = 0; i < len; ++i) { for (let i = 0; i < len; ++i) {
const selected = selectedElements[i] const selected = selectedElements[i]
if (!selected) { break } if (!selected) {
break
}
let parent = selected.parentNode let parent = selected.parentNode
let t = selected let t = selected
@@ -414,7 +489,9 @@ const deleteSelectedElements = () => {
} }
svgCanvas.setEmptySelectedElements() svgCanvas.setEmptySelectedElements()
if (!batchCmd.isEmpty()) { svgCanvas.addCommandToHistory(batchCmd) } if (!batchCmd.isEmpty()) {
svgCanvas.addCommandToHistory(batchCmd)
}
svgCanvas.call('changed', selectedCopy) svgCanvas.call('changed', selectedCopy)
svgCanvas.clearSelection() svgCanvas.clearSelection()
} }
@@ -426,8 +503,9 @@ const deleteSelectedElements = () => {
*/ */
const copySelectedElements = () => { const copySelectedElements = () => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const data = const data = JSON.stringify(
JSON.stringify(selectedElements.map((x) => svgCanvas.getJsonFromSvgElements(x))) selectedElements.map(x => svgCanvas.getJsonFromSvgElements(x))
)
// Use sessionStorage for the clipboard data. // Use sessionStorage for the clipboard data.
sessionStorage.setItem(svgCanvas.getClipboardID(), data) sessionStorage.setItem(svgCanvas.getClipboardID(), data)
svgCanvas.flashStorage() svgCanvas.flashStorage()
@@ -446,7 +524,9 @@ const copySelectedElements = () => {
*/ */
const groupSelectedElements = (type, urlArg) => { const groupSelectedElements = (type, urlArg) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
if (!type) { type = 'g' } if (!type) {
type = 'g'
}
let cmdStr = '' let cmdStr = ''
let url let url
@@ -455,7 +535,8 @@ const groupSelectedElements = (type, urlArg) => {
cmdStr = 'Make hyperlink' cmdStr = 'Make hyperlink'
url = urlArg || '' url = urlArg || ''
break break
} default: { }
default: {
type = 'g' type = 'g'
cmdStr = 'Group Elements' cmdStr = 'Group Elements'
break break
@@ -480,18 +561,27 @@ const groupSelectedElements = (type, urlArg) => {
let i = selectedElements.length let i = selectedElements.length
while (i--) { while (i--) {
let elem = selectedElements[i] let elem = selectedElements[i]
if (!elem) { continue } if (!elem) {
continue
}
if (elem.parentNode.tagName === 'a' && elem.parentNode.childNodes.length === 1) { if (
elem.parentNode.tagName === 'a' &&
elem.parentNode.childNodes.length === 1
) {
elem = elem.parentNode elem = elem.parentNode
} }
const oldNextSibling = elem.nextSibling const oldNextSibling = elem.nextSibling
const oldParent = elem.parentNode const oldParent = elem.parentNode
g.append(elem) g.append(elem)
batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldParent)) batchCmd.addSubCommand(
new MoveElementCommand(elem, oldNextSibling, oldParent)
)
}
if (!batchCmd.isEmpty()) {
svgCanvas.addCommandToHistory(batchCmd)
} }
if (!batchCmd.isEmpty()) { svgCanvas.addCommandToHistory(batchCmd) }
// update selection // update selection
svgCanvas.selectOnly([g], true) svgCanvas.selectOnly([g], true)
@@ -528,24 +618,32 @@ const pushGroupProperty = (g, undoable) => {
filter: g.getAttribute('filter'), filter: g.getAttribute('filter'),
opacity: g.getAttribute('opacity') opacity: g.getAttribute('opacity')
} }
let gfilter; let gblur; let changes let gfilter
let gblur
let changes
const drawing = svgCanvas.getDrawing() const drawing = svgCanvas.getDrawing()
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
const elem = children[i] const elem = children[i]
if (elem.nodeType !== 1) { continue } if (elem.nodeType !== 1) {
continue
}
if (gattrs.opacity !== null && gattrs.opacity !== 1) { if (gattrs.opacity !== null && gattrs.opacity !== 1) {
// const c_opac = elem.getAttribute('opacity') || 1; // const c_opac = elem.getAttribute('opacity') || 1;
const newOpac = Math.round((elem.getAttribute('opacity') || 1) * gattrs.opacity * 100) / 100 const newOpac =
Math.round((elem.getAttribute('opacity') || 1) * gattrs.opacity * 100) /
100
svgCanvas.changeSelectedAttribute('opacity', newOpac, [elem]) svgCanvas.changeSelectedAttribute('opacity', newOpac, [elem])
} }
if (gattrs.filter) { if (gattrs.filter) {
let cblur = svgCanvas.getBlur(elem) let cblur = svgCanvas.getBlur(elem)
const origCblur = cblur const origCblur = cblur
if (!gblur) { gblur = svgCanvas.getBlur(g) } if (!gblur) {
gblur = svgCanvas.getBlur(g)
}
if (cblur) { if (cblur) {
// Is this formula correct? // Is this formula correct?
cblur = Number(gblur) + Number(cblur) cblur = Number(gblur) + Number(cblur)
@@ -566,9 +664,14 @@ const pushGroupProperty = (g, undoable) => {
// const filterElem = getRefElem(gfilter); // const filterElem = getRefElem(gfilter);
const blurElem = getFeGaussianBlur(gfilter) const blurElem = getFeGaussianBlur(gfilter)
// Change this in future for different filters // Change this in future for different filters
const suffix = (blurElem?.tagName === 'feGaussianBlur') ? 'blur' : 'filter' const suffix =
blurElem?.tagName === 'feGaussianBlur' ? 'blur' : 'filter'
gfilter.id = elem.id + '_' + suffix gfilter.id = elem.id + '_' + suffix
svgCanvas.changeSelectedAttribute('filter', 'url(#' + gfilter.id + ')', [elem]) svgCanvas.changeSelectedAttribute(
'filter',
'url(#' + gfilter.id + ')',
[elem]
)
} }
} else { } else {
gfilter = getRefElem(elem.getAttribute('filter')) gfilter = getRefElem(elem.getAttribute('filter'))
@@ -586,13 +689,19 @@ const pushGroupProperty = (g, undoable) => {
let chtlist = elem.transform?.baseVal let chtlist = elem.transform?.baseVal
// Don't process gradient transforms // Don't process gradient transforms
if (elem.tagName.includes('Gradient')) { chtlist = null } if (elem.tagName.includes('Gradient')) {
chtlist = null
}
// Hopefully not a problem to add this. Necessary for elements like <desc/> // Hopefully not a problem to add this. Necessary for elements like <desc/>
if (!chtlist) { continue } if (!chtlist) {
continue
}
// Apparently <defs> can get get a transformlist, but we don't want it to have one! // Apparently <defs> can get get a transformlist, but we don't want it to have one!
if (elem.tagName === 'defs') { continue } if (elem.tagName === 'defs') {
continue
}
if (glist.numberOfItems) { if (glist.numberOfItems) {
// TODO: if the group's transform is just a rotate, we can always transfer the // TODO: if the group's transform is just a rotate, we can always transfer the
@@ -621,7 +730,11 @@ const pushGroupProperty = (g, undoable) => {
// get child's old center of rotation // get child's old center of rotation
const cbox = utilsGetBBox(elem) const cbox = utilsGetBBox(elem)
const ceqm = transformListToTransform(chtlist).matrix const ceqm = transformListToTransform(chtlist).matrix
const coldc = transformPoint(cbox.x + cbox.width / 2, cbox.y + cbox.height / 2, ceqm) const coldc = transformPoint(
cbox.x + cbox.width / 2,
cbox.y + cbox.height / 2,
ceqm
)
// sum group and child's angles // sum group and child's angles
const sangle = gangle + cangle const sangle = gangle + cangle
@@ -655,7 +768,8 @@ const pushGroupProperty = (g, undoable) => {
chtlist.appendItem(tr) chtlist.appendItem(tr)
} }
} }
} else { // more complicated than just a rotate } else {
// more complicated than just a rotate
// transfer the group's transform down to each child and then // transfer the group's transform down to each child and then
// call recalculateDimensions() // call recalculateDimensions()
const oldxform = elem.getAttribute('transform') const oldxform = elem.getAttribute('transform')
@@ -673,7 +787,9 @@ const pushGroupProperty = (g, undoable) => {
chtlist.appendItem(newxform) chtlist.appendItem(newxform)
} }
const cmd = recalculateDimensions(elem) const cmd = recalculateDimensions(elem)
if (cmd) { batchCmd.addSubCommand(cmd) } if (cmd) {
batchCmd.addSubCommand(cmd)
}
} }
} }
@@ -699,7 +815,7 @@ const pushGroupProperty = (g, undoable) => {
* @fires module:selected-elem.SvgCanvas#event:selected * @fires module:selected-elem.SvgCanvas#event:selected
* @returns {void} * @returns {void}
*/ */
const convertToGroup = (elem) => { const convertToGroup = elem => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
if (!elem) { if (!elem) {
elem = selectedElements[0] elem = selectedElements[0]
@@ -752,7 +868,13 @@ const convertToGroup = (elem) => {
const prev = $elem.previousElementSibling const prev = $elem.previousElementSibling
// Remove <use> element // Remove <use> element
batchCmd.addSubCommand(new RemoveElementCommand($elem, $elem.nextElementSibling, $elem.parentNode)) batchCmd.addSubCommand(
new RemoveElementCommand(
$elem,
$elem.nextElementSibling,
$elem.parentNode
)
)
$elem.remove() $elem.remove()
// See if other elements reference this symbol // See if other elements reference this symbol
@@ -772,7 +894,9 @@ const convertToGroup = (elem) => {
// Duplicate the gradients for Gecko, since they weren't included in the <symbol> // Duplicate the gradients for Gecko, since they weren't included in the <symbol>
if (isGecko()) { if (isGecko()) {
const svgElement = findDefs() const svgElement = findDefs()
const gradients = svgElement.querySelectorAll('linearGradient,radialGradient,pattern') const gradients = svgElement.querySelectorAll(
'linearGradient,radialGradient,pattern'
)
for (let i = 0, im = gradients.length; im > i; i++) { for (let i = 0, im = gradients.length; im > i; i++) {
g.appendChild(gradients[i].cloneNode(true)) g.appendChild(gradients[i].cloneNode(true))
} }
@@ -789,7 +913,9 @@ const convertToGroup = (elem) => {
// Put the dupe gradients back into <defs> (after uniquifying them) // Put the dupe gradients back into <defs> (after uniquifying them)
if (isGecko()) { if (isGecko()) {
const svgElement = findDefs() const svgElement = findDefs()
const elements = g.querySelectorAll('linearGradient,radialGradient,pattern') const elements = g.querySelectorAll(
'linearGradient,radialGradient,pattern'
)
for (let i = 0, im = elements.length; im > i; i++) { for (let i = 0, im = elements.length; im > i; i++) {
svgElement.appendChild(elements[i]) svgElement.appendChild(elements[i])
} }
@@ -805,7 +931,9 @@ const convertToGroup = (elem) => {
// remove symbol/svg element // remove symbol/svg element
const { nextSibling } = elem const { nextSibling } = elem
elem.remove() elem.remove()
batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent)) batchCmd.addSubCommand(
new RemoveElementCommand(elem, nextSibling, parent)
)
} }
batchCmd.addSubCommand(new InsertElementCommand(g)) batchCmd.addSubCommand(new InsertElementCommand(g))
} }
@@ -820,7 +948,7 @@ const convertToGroup = (elem) => {
// recalculate dimensions on the top-level children so that unnecessary transforms // recalculate dimensions on the top-level children so that unnecessary transforms
// are removed // are removed
walkTreePost(g, (n) => { walkTreePost(g, n => {
try { try {
recalculateDimensions(n) recalculateDimensions(n)
} catch (e) { } catch (e) {
@@ -830,8 +958,10 @@ const convertToGroup = (elem) => {
// Give ID for any visible element missing one // Give ID for any visible element missing one
const visElems = g.querySelectorAll(svgCanvas.getVisElems()) const visElems = g.querySelectorAll(svgCanvas.getVisElems())
Array.prototype.forEach.call(visElems, (el) => { Array.prototype.forEach.call(visElems, el => {
if (!el.id) { el.id = svgCanvas.getNextId() } if (!el.id) {
el.id = svgCanvas.getNextId()
}
}) })
svgCanvas.selectOnly([g]) svgCanvas.selectOnly([g])
@@ -882,7 +1012,9 @@ const ungroupSelectedElement = () => {
if (g.tagName === 'g' || g.tagName === 'a') { if (g.tagName === 'g' || g.tagName === 'a') {
const batchCmd = new BatchCommand('Ungroup Elements') const batchCmd = new BatchCommand('Ungroup Elements')
const cmd = pushGroupProperty(g, true) const cmd = pushGroupProperty(g, true)
if (cmd) { batchCmd.addSubCommand(cmd) } if (cmd) {
batchCmd.addSubCommand(cmd)
}
const parent = g.parentNode const parent = g.parentNode
const anchor = g.nextSibling const anchor = g.nextSibling
@@ -897,13 +1029,17 @@ const ungroupSelectedElement = () => {
// Remove child title elements // Remove child title elements
if (elem.tagName === 'title') { if (elem.tagName === 'title') {
const { nextSibling } = elem const { nextSibling } = elem
batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, oldParent)) batchCmd.addSubCommand(
new RemoveElementCommand(elem, nextSibling, oldParent)
)
elem.remove() elem.remove()
continue continue
} }
children[i++] = parent.insertBefore(elem, anchor) children[i++] = parent.insertBefore(elem, anchor)
batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldParent)) batchCmd.addSubCommand(
new MoveElementCommand(elem, oldNextSibling, oldParent)
)
} }
// remove the group from the selection // remove the group from the selection
@@ -914,7 +1050,9 @@ const ungroupSelectedElement = () => {
g.remove() g.remove()
batchCmd.addSubCommand(new RemoveElementCommand(g, gNextSibling, parent)) batchCmd.addSubCommand(new RemoveElementCommand(g, gNextSibling, parent))
if (!batchCmd.isEmpty()) { svgCanvas.addCommandToHistory(batchCmd) } if (!batchCmd.isEmpty()) {
svgCanvas.addCommandToHistory(batchCmd)
}
// update selection // update selection
svgCanvas.addToSelection(children) svgCanvas.addToSelection(children)
@@ -935,8 +1073,8 @@ const updateCanvas = (w, h) => {
const bg = document.getElementById('canvasBackground') const bg = document.getElementById('canvasBackground')
const oldX = Number(svgCanvas.getSvgContent().getAttribute('x')) const oldX = Number(svgCanvas.getSvgContent().getAttribute('x'))
const oldY = Number(svgCanvas.getSvgContent().getAttribute('y')) const oldY = Number(svgCanvas.getSvgContent().getAttribute('y'))
const x = ((w - svgCanvas.contentW * zoom) / 2) const x = (w - svgCanvas.contentW * zoom) / 2
const y = ((h - svgCanvas.contentH * zoom) / 2) const y = (h - svgCanvas.contentH * zoom) / 2
assignAttributes(svgCanvas.getSvgContent(), { assignAttributes(svgCanvas.getSvgContent(), {
width: svgCanvas.contentW * zoom, width: svgCanvas.contentW * zoom,
@@ -961,7 +1099,10 @@ const updateCanvas = (w, h) => {
}) })
} }
svgCanvas.selectorManager.selectorParentGroup.setAttribute('transform', 'translate(' + x + ',' + y + ')') svgCanvas.selectorManager.selectorParentGroup.setAttribute(
'transform',
'translate(' + x + ',' + y + ')'
)
/** /**
* Invoked upon updates to the canvas. * Invoked upon updates to the canvas.
@@ -979,7 +1120,14 @@ const updateCanvas = (w, h) => {
/** /**
* @type {module:svgcanvas.SvgCanvas#event:ext_canvasUpdated} * @type {module:svgcanvas.SvgCanvas#event:ext_canvasUpdated}
*/ */
{ new_x: x, new_y: y, old_x: oldX, old_y: oldY, d_x: x - oldX, d_y: y - oldY } {
new_x: x,
new_y: y,
old_x: oldX,
old_y: oldY,
d_x: x - oldX,
d_y: y - oldY
}
) )
return { x, y, old_x: oldX, old_y: oldY, d_x: x - oldX, d_y: y - oldY } return { x, y, old_x: oldX, old_y: oldY, d_x: x - oldX, d_y: y - oldY }
} }
@@ -990,14 +1138,18 @@ const updateCanvas = (w, h) => {
* @fires module:svgcanvas.SvgCanvas#event:selected * @fires module:svgcanvas.SvgCanvas#event:selected
* @returns {void} * @returns {void}
*/ */
const cycleElement = (next) => { const cycleElement = next => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
const currentGroup = svgCanvas.getCurrentGroup() const currentGroup = svgCanvas.getCurrentGroup()
let num let num
const curElem = selectedElements[0] const curElem = selectedElements[0]
let elem = false let elem = false
const allElems = getVisibleElements(currentGroup || svgCanvas.getCurrentDrawing().getCurrentLayer()) const allElems = getVisibleElements(
if (!allElems.length) { return } currentGroup || svgCanvas.getCurrentDrawing().getCurrentLayer()
)
if (!allElems.length) {
return
}
if (!curElem) { if (!curElem) {
num = next ? allElems.length - 1 : 0 num = next ? allElems.length - 1 : 0
elem = allElems[num] elem = allElems[num]

View File

@@ -28,6 +28,15 @@ let svgCanvas = null
*/ */
export const init = (canvas) => { export const init = (canvas) => {
svgCanvas = canvas svgCanvas = canvas
svgCanvas.getMouseTarget = getMouseTargetMethod
svgCanvas.clearSelection = clearSelectionMethod
svgCanvas.addToSelection = addToSelectionMethod
svgCanvas.getIntersectionList = getIntersectionListMethod
svgCanvas.runExtensions = runExtensionsMethod
svgCanvas.groupSvgElem = groupSvgElem
svgCanvas.prepareSvg = prepareSvg
svgCanvas.recalculateAllSelectedDimensions = recalculateAllSelectedDimensions
svgCanvas.setRotationAngle = setRotationAngle
} }
/** /**
@@ -37,7 +46,7 @@ export const init = (canvas) => {
* @type {module:draw.DrawCanvasInit#clearSelection|module:path.EditorContext#clearSelection} * @type {module:draw.DrawCanvasInit#clearSelection|module:path.EditorContext#clearSelection}
* @fires module:selection.SvgCanvas#event:selected * @fires module:selection.SvgCanvas#event:selected
*/ */
export const clearSelectionMethod = (noCall) => { const clearSelectionMethod = (noCall) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
selectedElements.forEach((elem) => { selectedElements.forEach((elem) => {
if (!elem) { if (!elem) {
@@ -59,7 +68,7 @@ export const clearSelectionMethod = (noCall) => {
* @type {module:path.EditorContext#addToSelection} * @type {module:path.EditorContext#addToSelection}
* @fires module:selection.SvgCanvas#event:selected * @fires module:selection.SvgCanvas#event:selected
*/ */
export const addToSelectionMethod = (elemsToAdd, showGrips) => { const addToSelectionMethod = (elemsToAdd, showGrips) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
if (!elemsToAdd.length) { if (!elemsToAdd.length) {
return return
@@ -134,7 +143,7 @@ export const addToSelectionMethod = (elemsToAdd, showGrips) => {
* @name module:svgcanvas.SvgCanvas#getMouseTarget * @name module:svgcanvas.SvgCanvas#getMouseTarget
* @type {module:path.EditorContext#getMouseTarget} * @type {module:path.EditorContext#getMouseTarget}
*/ */
export const getMouseTargetMethod = (evt) => { const getMouseTargetMethod = (evt) => {
if (!evt) { if (!evt) {
return null return null
} }
@@ -211,7 +220,7 @@ export const getMouseTargetMethod = (evt) => {
* @returns {GenericArray<module:svgcanvas.ExtensionStatus>|module:svgcanvas.ExtensionStatus|false} See {@tutorial ExtensionDocs} on the ExtensionStatus. * @returns {GenericArray<module:svgcanvas.ExtensionStatus>|module:svgcanvas.ExtensionStatus|false} See {@tutorial ExtensionDocs} on the ExtensionStatus.
*/ */
/* eslint-enable max-len */ /* eslint-enable max-len */
export const runExtensionsMethod = ( const runExtensionsMethod = (
action, action,
vars, vars,
returnArray returnArray
@@ -248,7 +257,7 @@ export const runExtensionsMethod = (
* @param {Element} parent - The parent DOM element to search within * @param {Element} parent - The parent DOM element to search within
* @returns {ElementAndBBox[]} An array with objects that include: * @returns {ElementAndBBox[]} An array with objects that include:
*/ */
export const getVisibleElementsAndBBoxes = (parent) => { const getVisibleElementsAndBBoxes = (parent) => {
if (!parent) { if (!parent) {
const svgContent = svgCanvas.getSvgContent() const svgContent = svgCanvas.getSvgContent()
parent = svgContent.children // Prevent layers from being included parent = svgContent.children // Prevent layers from being included
@@ -275,7 +284,7 @@ export const getVisibleElementsAndBBoxes = (parent) => {
* @param {SVGRect} rect * @param {SVGRect} rect
* @returns {Element[]|NodeList} Bbox elements * @returns {Element[]|NodeList} Bbox elements
*/ */
export const getIntersectionListMethod = (rect) => { const getIntersectionListMethod = (rect) => {
const zoom = svgCanvas.getZoom() const zoom = svgCanvas.getZoom()
if (!svgCanvas.getRubberBox()) { if (!svgCanvas.getRubberBox()) {
return null return null
@@ -338,7 +347,7 @@ export const getIntersectionListMethod = (rect) => {
* @param {Element} elem - SVG element to wrap * @param {Element} elem - SVG element to wrap
* @returns {void} * @returns {void}
*/ */
export const groupSvgElem = (elem) => { const groupSvgElem = (elem) => {
const dataStorage = svgCanvas.getDataStorage() const dataStorage = svgCanvas.getDataStorage()
const g = document.createElementNS(NS.SVG, 'g') const g = document.createElementNS(NS.SVG, 'g')
elem.replaceWith(g) elem.replaceWith(g)
@@ -353,7 +362,7 @@ export const groupSvgElem = (elem) => {
* @param {XMLDocument} newDoc - The SVG DOM document * @param {XMLDocument} newDoc - The SVG DOM document
* @returns {void} * @returns {void}
*/ */
export const prepareSvg = (newDoc) => { const prepareSvg = (newDoc) => {
svgCanvas.sanitizeSvg(newDoc.documentElement) svgCanvas.sanitizeSvg(newDoc.documentElement)
// convert paths into absolute commands // convert paths into absolute commands
@@ -374,7 +383,7 @@ export const prepareSvg = (newDoc) => {
* @fires module:svgcanvas.SvgCanvas#event:changed * @fires module:svgcanvas.SvgCanvas#event:changed
* @returns {void} * @returns {void}
*/ */
export const setRotationAngle = (val, preventUndo) => { const setRotationAngle = (val, preventUndo) => {
const selectedElements = svgCanvas.getSelectedElements() const selectedElements = svgCanvas.getSelectedElements()
// ensure val is the proper type // ensure val is the proper type
val = Number.parseFloat(val) val = Number.parseFloat(val)
@@ -444,7 +453,7 @@ export const setRotationAngle = (val, preventUndo) => {
* @fires module:svgcanvas.SvgCanvas#event:changed * @fires module:svgcanvas.SvgCanvas#event:changed
* @returns {void} * @returns {void}
*/ */
export const recalculateAllSelectedDimensions = () => { const recalculateAllSelectedDimensions = () => {
const text = const text =
svgCanvas.getCurrentResizeMode() === 'none' ? 'position' : 'size' svgCanvas.getCurrentResizeMode() === 'none' ? 'position' : 'size'
const batchCmd = new BatchCommand(text) const batchCmd = new BatchCommand(text)

View File

@@ -10,28 +10,33 @@ import 'svg2pdf.js/dist/svg2pdf.es.js'
import html2canvas from 'html2canvas' import html2canvas from 'html2canvas'
import * as hstry from './history.js' import * as hstry from './history.js'
import { import {
text2xml, cleanupElement, findDefs, getHref, preventClickDefault, text2xml,
toXml, getStrokedBBoxDefaultVisible, encode64, createObjectURL, cleanupElement,
dataURLToObjectURL, walkTree, getBBox as utilsGetBBox findDefs,
getHref,
preventClickDefault,
toXml,
getStrokedBBoxDefaultVisible,
encode64,
createObjectURL,
dataURLToObjectURL,
walkTree,
getBBox as utilsGetBBox
} from './utilities.js' } from './utilities.js'
import { import { transformPoint, transformListToTransform } from './math.js'
transformPoint, transformListToTransform import { convertUnit, shortFloat, convertToNum } from '../common/units.js'
} from './math.js'
import {
convertUnit, shortFloat, convertToNum
} from '../common/units.js'
import { isGecko, isChrome, isWebkit } from '../common/browser.js' import { isGecko, isChrome, isWebkit } from '../common/browser.js'
import * as pathModule from './path.js' import * as pathModule from './path.js'
import { NS } from './namespaces.js' import { NS } from './namespaces.js'
import * as draw from './draw.js' import * as draw from './draw.js'
import { import { recalculateDimensions } from './recalculate.js'
recalculateDimensions
} from './recalculate.js'
import { getParents, getClosest } from '../editor/components/jgraduate/Util.js' import { getParents, getClosest } from '../editor/components/jgraduate/Util.js'
const { const {
InsertElementCommand, RemoveElementCommand, InsertElementCommand,
ChangeElementCommand, BatchCommand RemoveElementCommand,
ChangeElementCommand,
BatchCommand
} = hstry } = hstry
let svgCanvas = null let svgCanvas = null
@@ -41,8 +46,19 @@ let svgCanvas = null
* @param {module:svg-exec.SvgCanvas#init} svgContext * @param {module:svg-exec.SvgCanvas#init} svgContext
* @returns {void} * @returns {void}
*/ */
export const init = (canvas) => { export const init = canvas => {
svgCanvas = canvas svgCanvas = canvas
svgCanvas.setSvgString = setSvgString
svgCanvas.importSvgString = importSvgString
svgCanvas.uniquifyElems = uniquifyElemsMethod
svgCanvas.setUseData = setUseDataMethod
svgCanvas.convertGradients = convertGradientsMethod
svgCanvas.removeUnusedDefElems = removeUnusedDefElemsMethod // remove DOM elements inside the `<defs>` if they are notreferred to,
svgCanvas.svgCanvasToString = svgCanvasToString // Main function to set up the SVG content for output.
svgCanvas.svgToString = svgToString // Sub function ran on each SVG element to convert it to a string as desired.
svgCanvas.embedImage = embedImage // Converts a given image file to a data URL when possibl
svgCanvas.rasterExport = rasterExport // Generates a PNG (or JPG, BMP, WEBP) Data URL based on the current image
svgCanvas.exportPDF = exportPDF // Generates a PDF based on the current image, then calls "exportedPDF"
} }
/** /**
@@ -50,7 +66,7 @@ export const init = (canvas) => {
* @function module:svgcanvas.SvgCanvas#svgCanvasToString * @function module:svgcanvas.SvgCanvas#svgCanvasToString
* @returns {string} The SVG image for output * @returns {string} The SVG image for output
*/ */
export const svgCanvasToString = () => { const svgCanvasToString = () => {
// keep calling it until there are none to remove // keep calling it until there are none to remove
while (svgCanvas.removeUnusedDefElems() > 0) {} // eslint-disable-line no-empty while (svgCanvas.removeUnusedDefElems() > 0) {} // eslint-disable-line no-empty
@@ -58,7 +74,7 @@ export const svgCanvasToString = () => {
// Keep SVG-Edit comment on top // Keep SVG-Edit comment on top
const childNodesElems = svgCanvas.getSvgContent().childNodes const childNodesElems = svgCanvas.getSvgContent().childNodes
childNodesElems.forEach(function (node, i) { childNodesElems.forEach((node, i) => {
if (i && node.nodeType === 8 && node.data.includes('Created with')) { if (i && node.nodeType === 8 && node.data.includes('Created with')) {
svgCanvas.getSvgContent().firstChild.before(node) svgCanvas.getSvgContent().firstChild.before(node)
} }
@@ -74,7 +90,7 @@ export const svgCanvasToString = () => {
// Unwrap gsvg if it has no special attributes (only id and style) // Unwrap gsvg if it has no special attributes (only id and style)
const gsvgElems = svgCanvas.getSvgContent().querySelectorAll('g[data-gsvg]') const gsvgElems = svgCanvas.getSvgContent().querySelectorAll('g[data-gsvg]')
Array.prototype.forEach.call(gsvgElems, function (element) { Array.prototype.forEach.call(gsvgElems, (element) => {
const attrs = element.attributes const attrs = element.attributes
let len = attrs.length let len = attrs.length
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
@@ -93,7 +109,7 @@ export const svgCanvasToString = () => {
// Rewrap gsvg // Rewrap gsvg
if (nakedSvgs.length) { if (nakedSvgs.length) {
Array.prototype.forEach.call(nakedSvgs, function (el) { Array.prototype.forEach.call(nakedSvgs, (el) => {
svgCanvas.groupSvgElem(el) svgCanvas.groupSvgElem(el)
}) })
} }
@@ -108,7 +124,7 @@ export const svgCanvasToString = () => {
* @param {Integer} indent - Number of spaces to indent this tag * @param {Integer} indent - Number of spaces to indent this tag
* @returns {string} The given element as an SVG tag * @returns {string} The given element as an SVG tag
*/ */
export const svgToString = function (elem, indent) { const svgToString = (elem, indent) => {
const curConfig = svgCanvas.getCurConfig() const curConfig = svgCanvas.getCurConfig()
const nsMap = svgCanvas.getNsMap() const nsMap = svgCanvas.getNsMap()
const out = [] const out = []
@@ -123,8 +139,11 @@ export const svgToString = function (elem, indent) {
return a.name > b.name ? -1 : 1 return a.name > b.name ? -1 : 1
}) })
for (let i = 0; i < indent; i++) { out.push(' ') } for (let i = 0; i < indent; i++) {
out.push('<'); out.push(elem.nodeName) out.push(' ')
}
out.push('<')
out.push(elem.nodeName)
if (elem.id === 'svgcontent') { if (elem.id === 'svgcontent') {
// Process root element separately // Process root element separately
const res = svgCanvas.getResolution() const res = svgCanvas.getResolution()
@@ -147,7 +166,17 @@ export const svgToString = function (elem, indent) {
res.h = convertUnit(res.h, unit) + unit res.h = convertUnit(res.h, unit) + unit
} }
out.push(' width="' + res.w + '" height="' + res.h + '"' + vb + ' xmlns="' + NS.SVG + '"') out.push(
' width="' +
res.w +
'" height="' +
res.h +
'"' +
vb +
' xmlns="' +
NS.SVG +
'"'
)
const nsuris = {} const nsuris = {}
@@ -155,11 +184,17 @@ export const svgToString = function (elem, indent) {
const csElements = elem.querySelectorAll('*') const csElements = elem.querySelectorAll('*')
const cElements = Array.prototype.slice.call(csElements) const cElements = Array.prototype.slice.call(csElements)
cElements.push(elem) cElements.push(elem)
Array.prototype.forEach.call(cElements, function (el) { Array.prototype.forEach.call(cElements, (el) => {
// const el = this; // const el = this;
// for some elements have no attribute // for some elements have no attribute
const uri = el.namespaceURI const uri = el.namespaceURI
if (uri && !nsuris[uri] && nsMap[uri] && nsMap[uri] !== 'xmlns' && nsMap[uri] !== 'xml') { if (
uri &&
!nsuris[uri] &&
nsMap[uri] &&
nsMap[uri] !== 'xmlns' &&
nsMap[uri] !== 'xml'
) {
nsuris[uri] = true nsuris[uri] = true
out.push(' xmlns:' + nsMap[uri] + '="' + uri + '"') out.push(' xmlns:' + nsMap[uri] + '="' + uri + '"')
} }
@@ -175,40 +210,71 @@ export const svgToString = function (elem, indent) {
}) })
let i = attrs.length let i = attrs.length
const attrNames = ['width', 'height', 'xmlns', 'x', 'y', 'viewBox', 'id', 'overflow'] const attrNames = [
'width',
'height',
'xmlns',
'x',
'y',
'viewBox',
'id',
'overflow'
]
while (i--) { while (i--) {
const attr = attrs[i] const attr = attrs[i]
const attrVal = toXml(attr.value) const attrVal = toXml(attr.value)
// Namespaces have already been dealt with, so skip // Namespaces have already been dealt with, so skip
if (attr.nodeName.startsWith('xmlns:')) { continue } if (attr.nodeName.startsWith('xmlns:')) {
continue
}
// only serialize attributes we don't use internally // only serialize attributes we don't use internally
if (attrVal !== '' && !attrNames.includes(attr.localName) && (!attr.namespaceURI || nsMap[attr.namespaceURI])) { if (
attrVal !== '' &&
!attrNames.includes(attr.localName) &&
(!attr.namespaceURI || nsMap[attr.namespaceURI])
) {
out.push(' ') out.push(' ')
out.push(attr.nodeName); out.push('="') out.push(attr.nodeName)
out.push(attrVal); out.push('"') out.push('="')
out.push(attrVal)
out.push('"')
} }
} }
} else { } else {
// Skip empty defs // Skip empty defs
if (elem.nodeName === 'defs' && !elem.firstChild) { return '' } if (elem.nodeName === 'defs' && !elem.firstChild) {
return ''
}
const mozAttrs = ['-moz-math-font-style', '_moz-math-font-style'] const mozAttrs = ['-moz-math-font-style', '_moz-math-font-style']
for (let i = attrs.length - 1; i >= 0; i--) { for (let i = attrs.length - 1; i >= 0; i--) {
const attr = attrs[i] const attr = attrs[i]
let attrVal = toXml(attr.value) let attrVal = toXml(attr.value)
// remove bogus attributes added by Gecko // remove bogus attributes added by Gecko
if (mozAttrs.includes(attr.localName)) { continue } if (mozAttrs.includes(attr.localName)) {
continue
}
if (attrVal === 'null') { if (attrVal === 'null') {
const styleName = attr.localName.replace(/-[a-z]/g, (s) => s[1].toUpperCase()) const styleName = attr.localName.replace(/-[a-z]/g, s =>
if (Object.prototype.hasOwnProperty.call(elem.style, styleName)) { continue } s[1].toUpperCase()
)
if (Object.prototype.hasOwnProperty.call(elem.style, styleName)) {
continue
}
} }
if (attrVal !== '') { if (attrVal !== '') {
if (attrVal.startsWith('pointer-events')) { continue } if (attrVal.startsWith('pointer-events')) {
if (attr.localName === 'class' && attrVal.startsWith('se_')) { continue } continue
}
if (attr.localName === 'class' && attrVal.startsWith('se_')) {
continue
}
out.push(' ') out.push(' ')
if (attr.localName === 'd') { attrVal = svgCanvas.pathActions.convertPath(elem, true) } if (attr.localName === 'd') {
attrVal = svgCanvas.pathActions.convertPath(elem, true)
}
if (!isNaN(attrVal)) { if (!isNaN(attrVal)) {
attrVal = shortFloat(attrVal) attrVal = shortFloat(attrVal)
} else if (unitRe.test(attrVal)) { } else if (unitRe.test(attrVal)) {
@@ -216,21 +282,30 @@ export const svgToString = function (elem, indent) {
} }
// Embed images when saving // Embed images when saving
if (svgCanvas.getSvgOptionApply() && if (
svgCanvas.getSvgOptionApply() &&
elem.nodeName === 'image' && elem.nodeName === 'image' &&
attr.localName === 'href' && attr.localName === 'href' &&
svgCanvas.getSvgOptionImages() && svgCanvas.getSvgOptionImages() &&
svgCanvas.getSvgOptionImages() === 'embed' svgCanvas.getSvgOptionImages() === 'embed'
) { ) {
const img = svgCanvas.getEncodableImages(attrVal) const img = svgCanvas.getEncodableImages(attrVal)
if (img) { attrVal = img } if (img) {
attrVal = img
}
} }
// map various namespaces to our fixed namespace prefixes // map various namespaces to our fixed namespace prefixes
// (the default xmlns attribute itself does not get a prefix) // (the default xmlns attribute itself does not get a prefix)
if (!attr.namespaceURI || attr.namespaceURI === NS.SVG || nsMap[attr.namespaceURI]) { if (
out.push(attr.nodeName); out.push('="') !attr.namespaceURI ||
out.push(attrVal); out.push('"') attr.namespaceURI === NS.SVG ||
nsMap[attr.namespaceURI]
) {
out.push(attr.nodeName)
out.push('="')
out.push(attrVal)
out.push('"')
} }
} }
} }
@@ -248,14 +323,16 @@ export const svgToString = function (elem, indent) {
out.push('\n') out.push('\n')
out.push(svgCanvas.svgToString(child, indent)) out.push(svgCanvas.svgToString(child, indent))
break break
case 3: { // text node case 3: {
// text node
const str = child.nodeValue.replace(/^\s+|\s+$/g, '') const str = child.nodeValue.replace(/^\s+|\s+$/g, '')
if (str !== '') { if (str !== '') {
bOneLine = true bOneLine = true
out.push(String(toXml(str))) out.push(String(toXml(str)))
} }
break break
} case 4: // cdata node }
case 4: // cdata node
out.push('\n') out.push('\n')
out.push(new Array(indent + 1).join(' ')) out.push(new Array(indent + 1).join(' '))
out.push('<![CDATA[') out.push('<![CDATA[')
@@ -274,9 +351,13 @@ export const svgToString = function (elem, indent) {
indent-- indent--
if (!bOneLine) { if (!bOneLine) {
out.push('\n') out.push('\n')
for (let i = 0; i < indent; i++) { out.push(' ') } for (let i = 0; i < indent; i++) {
out.push(' ')
} }
out.push('</'); out.push(elem.nodeName); out.push('>') }
out.push('</')
out.push(elem.nodeName)
out.push('>')
} else { } else {
out.push('/>') out.push('/>')
} }
@@ -297,14 +378,16 @@ export const svgToString = function (elem, indent) {
* @returns {boolean} This function returns `false` if the set was * @returns {boolean} This function returns `false` if the set was
* unsuccessful, `true` otherwise. * unsuccessful, `true` otherwise.
*/ */
export const setSvgString = function (xmlString, preventUndo) { const setSvgString = (xmlString, preventUndo) => {
const curConfig = svgCanvas.getCurConfig() const curConfig = svgCanvas.getCurConfig()
const dataStorage = svgCanvas.getDataStorage() const dataStorage = svgCanvas.getDataStorage()
try { try {
// convert string into XML document // convert string into XML document
const newDoc = text2xml(xmlString) const newDoc = text2xml(xmlString)
if (newDoc.firstElementChild && if (
newDoc.firstElementChild.namespaceURI !== NS.SVG) { newDoc.firstElementChild &&
newDoc.firstElementChild.namespaceURI !== NS.SVG
) {
return false return false
} }
@@ -317,20 +400,29 @@ export const setSvgString = function (xmlString, preventUndo) {
svgCanvas.getSvgContent().remove() svgCanvas.getSvgContent().remove()
const oldzoom = svgCanvas.getSvgContent() const oldzoom = svgCanvas.getSvgContent()
batchCmd.addSubCommand(new RemoveElementCommand(oldzoom, nextSibling, svgCanvas.getSvgRoot())) batchCmd.addSubCommand(
new RemoveElementCommand(oldzoom, nextSibling, svgCanvas.getSvgRoot())
)
// set new svg document // set new svg document
// If DOM3 adoptNode() available, use it. Otherwise fall back to DOM2 importNode() // If DOM3 adoptNode() available, use it. Otherwise fall back to DOM2 importNode()
if (svgCanvas.getDOMDocument().adoptNode) { if (svgCanvas.getDOMDocument().adoptNode) {
svgCanvas.setSvgContent(svgCanvas.getDOMDocument().adoptNode(newDoc.documentElement)) svgCanvas.setSvgContent(
svgCanvas.getDOMDocument().adoptNode(newDoc.documentElement)
)
} else { } else {
svgCanvas.setSvgContent(svgCanvas.getDOMDocument().importNode(newDoc.documentElement, true)) svgCanvas.setSvgContent(
svgCanvas.getDOMDocument().importNode(newDoc.documentElement, true)
)
} }
svgCanvas.getSvgRoot().append(svgCanvas.getSvgContent()) svgCanvas.getSvgRoot().append(svgCanvas.getSvgContent())
const content = svgCanvas.getSvgContent() const content = svgCanvas.getSvgContent()
svgCanvas.current_drawing_ = new draw.Drawing(svgCanvas.getSvgContent(), svgCanvas.getIdPrefix()) svgCanvas.current_drawing_ = new draw.Drawing(
svgCanvas.getSvgContent(),
svgCanvas.getIdPrefix()
)
// retrieve or set the nonce // retrieve or set the nonce
const nonce = svgCanvas.getCurrentDrawing().getNonce() const nonce = svgCanvas.getCurrentDrawing().getNonce()
@@ -342,7 +434,7 @@ export const setSvgString = function (xmlString, preventUndo) {
// change image href vals if possible // change image href vals if possible
const elements = content.querySelectorAll('image') const elements = content.querySelectorAll('image')
Array.prototype.forEach.call(elements, function (image) { Array.prototype.forEach.call(elements, (image) => {
preventClickDefault(image) preventClickDefault(image)
const val = svgCanvas.getHref(image) const val = svgCanvas.getHref(image)
if (val) { if (val) {
@@ -388,9 +480,11 @@ export const setSvgString = function (xmlString, preventUndo) {
// Wrap child SVGs in group elements // Wrap child SVGs in group elements
const svgElements = content.querySelectorAll('svg') const svgElements = content.querySelectorAll('svg')
Array.prototype.forEach.call(svgElements, function (element) { Array.prototype.forEach.call(svgElements, (element) => {
// Skip if it's in a <defs> // Skip if it's in a <defs>
if (getClosest(element.parentNode, 'defs')) { return } if (getClosest(element.parentNode, 'defs')) {
return
}
svgCanvas.uniquifyElems(element) svgCanvas.uniquifyElems(element)
@@ -407,8 +501,10 @@ export const setSvgString = function (xmlString, preventUndo) {
// For Firefox: Put all paint elems in defs // For Firefox: Put all paint elems in defs
if (isGecko()) { if (isGecko()) {
const svgDefs = findDefs() const svgDefs = findDefs()
const findElems = content.querySelectorAll('linearGradient, radialGradient, pattern') const findElems = content.querySelectorAll(
Array.prototype.forEach.call(findElems, function (ele) { 'linearGradient, radialGradient, pattern'
)
Array.prototype.forEach.call(findElems, (ele) => {
svgDefs.appendChild(ele) svgDefs.appendChild(ele)
}) })
} }
@@ -435,7 +531,7 @@ export const setSvgString = function (xmlString, preventUndo) {
attrs.height = vb[3] attrs.height = vb[3]
// handle content that doesn't have a viewBox // handle content that doesn't have a viewBox
} else { } else {
['width', 'height'].forEach(function (dim) { ;['width', 'height'].forEach((dim) => {
// Set to 100 if not given // Set to 100 if not given
const val = content.getAttribute(dim) || '100%' const val = content.getAttribute(dim) || '100%'
if (String(val).substr(-1) === '%') { if (String(val).substr(-1) === '%') {
@@ -452,10 +548,12 @@ export const setSvgString = function (xmlString, preventUndo) {
// Give ID for any visible layer children missing one // Give ID for any visible layer children missing one
const chiElems = content.children const chiElems = content.children
Array.prototype.forEach.call(chiElems, function (chiElem) { Array.prototype.forEach.call(chiElems, (chiElem) => {
const visElems = chiElem.querySelectorAll(svgCanvas.getVisElems()) const visElems = chiElem.querySelectorAll(svgCanvas.getVisElems())
Array.prototype.forEach.call(visElems, function (elem) { Array.prototype.forEach.call(visElems, (elem) => {
if (!elem.id) { elem.id = svgCanvas.getNextId() } if (!elem.id) {
elem.id = svgCanvas.getNextId()
}
}) })
}) })
@@ -468,8 +566,12 @@ export const setSvgString = function (xmlString, preventUndo) {
// Just in case negative numbers are given or // Just in case negative numbers are given or
// result from the percs calculation // result from the percs calculation
if (attrs.width <= 0) { attrs.width = 100 } if (attrs.width <= 0) {
if (attrs.height <= 0) { attrs.height = 100 } attrs.width = 100
}
if (attrs.height <= 0) {
attrs.height = 100
}
for (const [key, value] of Object.entries(attrs)) { for (const [key, value] of Object.entries(attrs)) {
content.setAttribute(key, value) content.setAttribute(key, value)
@@ -482,7 +584,9 @@ export const setSvgString = function (xmlString, preventUndo) {
const width = content.getAttribute('width') const width = content.getAttribute('width')
const height = content.getAttribute('height') const height = content.getAttribute('height')
const changes = { width: width, height: height } const changes = { width: width, height: height }
batchCmd.addSubCommand(new ChangeElementCommand(svgCanvas.getSvgRoot(), changes)) batchCmd.addSubCommand(
new ChangeElementCommand(svgCanvas.getSvgRoot(), changes)
)
// reset zoom // reset zoom
svgCanvas.setZoom(1) svgCanvas.setZoom(1)
@@ -515,9 +619,11 @@ export const setSvgString = function (xmlString, preventUndo) {
* arbitrary transform lists, but makes some assumptions about how the transform list * arbitrary transform lists, but makes some assumptions about how the transform list
* was obtained * was obtained
*/ */
export const importSvgString = function (xmlString) { const importSvgString = (xmlString) => {
const dataStorage = svgCanvas.getDataStorage() const dataStorage = svgCanvas.getDataStorage()
let j; let ts; let useEl let j
let ts
let useEl
try { try {
// Get unique ID // Get unique ID
const uid = encode64(xmlString.length + xmlString).substr(0, 32) const uid = encode64(xmlString.length + xmlString).substr(0, 32)
@@ -564,7 +670,10 @@ export const importSvgString = function (xmlString) {
canvash = Number(svgCanvas.getSvgContent().getAttribute('height')) canvash = Number(svgCanvas.getSvgContent().getAttribute('height'))
// imported content should be 1/3 of the canvas on its largest dimension // imported content should be 1/3 of the canvas on its largest dimension
ts = innerh > innerw ? 'scale(' + (canvash / 3) / vb[3] + ')' : 'scale(' + (canvash / 3) / vb[2] + ')' ts =
innerh > innerw
? 'scale(' + canvash / 3 / vb[3] + ')'
: 'scale(' + canvash / 3 / vb[2] + ')'
// Hack to make recalculateDimensions understand how to scale // Hack to make recalculateDimensions understand how to scale
ts = 'translate(0) ' + ts + ' translate(0)' ts = 'translate(0) ' + ts + ' translate(0)'
@@ -576,8 +685,10 @@ export const importSvgString = function (xmlString) {
// Move all gradients into root for Firefox, workaround for this bug: // Move all gradients into root for Firefox, workaround for this bug:
// https://bugzilla.mozilla.org/show_bug.cgi?id=353575 // https://bugzilla.mozilla.org/show_bug.cgi?id=353575
// TODO: Make this properly undo-able. // TODO: Make this properly undo-able.
const elements = svg.querySelectorAll('linearGradient, radialGradient, pattern') const elements = svg.querySelectorAll(
Array.prototype.forEach.call(elements, function (el) { 'linearGradient, radialGradient, pattern'
)
Array.prototype.forEach.call(elements, (el) => {
defs.appendChild(el) defs.appendChild(el)
}) })
} }
@@ -587,7 +698,8 @@ export const importSvgString = function (xmlString) {
symbol.append(first) symbol.append(first)
} }
const attrs = svg.attributes const attrs = svg.attributes
for (const attr of attrs) { // Ok for `NamedNodeMap` for (const attr of attrs) {
// Ok for `NamedNodeMap`
symbol.setAttribute(attr.nodeName, attr.value) symbol.setAttribute(attr.nodeName, attr.value)
} }
symbol.id = svgCanvas.getNextId() symbol.id = svgCanvas.getNextId()
@@ -604,9 +716,11 @@ export const importSvgString = function (xmlString) {
useEl = svgCanvas.getDOMDocument().createElementNS(NS.SVG, 'use') useEl = svgCanvas.getDOMDocument().createElementNS(NS.SVG, 'use')
useEl.id = svgCanvas.getNextId() useEl.id = svgCanvas.getNextId()
svgCanvas.setHref(useEl, '#' + symbol.id); svgCanvas.setHref(useEl, '#' + symbol.id)
;(
(svgCanvas.getCurrentGroup() || svgCanvas.getCurrentDrawing().getCurrentLayer()).append(useEl) svgCanvas.getCurrentGroup() ||
svgCanvas.getCurrentDrawing().getCurrentLayer()
).append(useEl)
batchCmd.addSubCommand(new InsertElementCommand(useEl)) batchCmd.addSubCommand(new InsertElementCommand(useEl))
svgCanvas.clearSelection() svgCanvas.clearSelection()
@@ -642,12 +756,12 @@ export const importSvgString = function (xmlString) {
* @param {string} src - The path/URL of the image * @param {string} src - The path/URL of the image
* @returns {Promise<string|false>} Resolves to a Data URL (string|false) * @returns {Promise<string|false>} Resolves to a Data URL (string|false)
*/ */
export const embedImage = function (src) { const embedImage = (src) => {
// Todo: Remove this Promise in favor of making an async/await `Image.load` utility // Todo: Remove this Promise in favor of making an async/await `Image.load` utility
return new Promise(function (resolve, reject) { return new Promise((resolve, reject) => {
// load in the image and once it's loaded, get the dimensions // load in the image and once it's loaded, get the dimensions
const imgI = new Image() const imgI = new Image()
imgI.addEventListener('load', (e) => { imgI.addEventListener('load', e => {
// create a canvas the same size as the raster image // create a canvas the same size as the raster image
const cvs = document.createElement('canvas') const cvs = document.createElement('canvas')
cvs.width = e.currentTarget.width cvs.width = e.currentTarget.width
@@ -665,8 +779,12 @@ export const embedImage = function (src) {
svgCanvas.setGoodImage(src) svgCanvas.setGoodImage(src)
resolve(svgCanvas.getEncodableImages(src)) resolve(svgCanvas.getEncodableImages(src))
}) })
imgI.addEventListener('error', (e) => { imgI.addEventListener('error', e => {
reject(new Error(`error loading image: ${e.currentTarget.attributes.src.value}`)) reject(
new Error(
`error loading image: ${e.currentTarget.attributes.src.value}`
)
)
}) })
imgI.setAttribute('src', src) imgI.setAttribute('src', src)
}) })
@@ -682,7 +800,7 @@ export const embedImage = function (src) {
* Codes only is useful for locale-independent detection. * Codes only is useful for locale-independent detection.
* @returns {module:svgcanvas.IssuesAndCodes} * @returns {module:svgcanvas.IssuesAndCodes}
*/ */
function getIssues () { const getIssues = () => {
const uiStrings = svgCanvas.getUIStrings() const uiStrings = svgCanvas.getUIStrings()
// remove the selected outline before serializing // remove the selected outline before serializing
svgCanvas.clearSelection() svgCanvas.clearSelection()
@@ -693,15 +811,15 @@ function getIssues () {
// Selector and notice // Selector and notice
const issueList = { const issueList = {
feGaussianBlur: uiStrings.exportNoBlur, feGaussianBlur: uiStrings.NoBlur,
foreignObject: uiStrings.exportNoforeignObject, foreignObject: uiStrings.NoforeignObject,
'[stroke-dasharray]': uiStrings.exportNoDashArray '[stroke-dasharray]': uiStrings.NoDashArray
} }
const content = svgCanvas.getSvgContent() const content = svgCanvas.getSvgContent()
// Add font/text check if Canvas Text API is not implemented // Add font/text check if Canvas Text API is not implemented
if (!('font' in document.querySelector('CANVAS').getContext('2d'))) { if (!('font' in document.querySelector('CANVAS').getContext('2d'))) {
issueList.text = uiStrings.exportNoText issueList.text = uiStrings.NoText
} }
for (const [sel, descr] of Object.entries(issueList)) { for (const [sel, descr] of Object.entries(issueList)) {
@@ -713,7 +831,7 @@ function getIssues () {
return { issues, issueCodes } return { issues, issueCodes }
} }
/** /**
* @typedef {PlainObject} module:svgcanvas.ImageExportedResults * @typedef {PlainObject} module:svgcanvas.ImageedResults
* @property {string} datauri Contents as a Data URL * @property {string} datauri Contents as a Data URL
* @property {string} bloburl May be the empty string * @property {string} bloburl May be the empty string
* @property {string} svg The SVG contents as a string * @property {string} svg The SVG contents as a string
@@ -722,39 +840,40 @@ function getIssues () {
* @property {"PNG"|"JPEG"|"BMP"|"WEBP"|"ICO"} type The chosen image type * @property {"PNG"|"JPEG"|"BMP"|"WEBP"|"ICO"} type The chosen image type
* @property {"image/png"|"image/jpeg"|"image/bmp"|"image/webp"} mimeType The image MIME type * @property {"image/png"|"image/jpeg"|"image/bmp"|"image/webp"} mimeType The image MIME type
* @property {Float} quality A decimal between 0 and 1 (for use with JPEG or WEBP) * @property {Float} quality A decimal between 0 and 1 (for use with JPEG or WEBP)
* @property {string} exportWindowName A convenience for passing along a `window.name` to target a window on which the export could be added * @property {string} WindowName A convenience for passing along a `window.name` to target a window on which the could be added
*/ */
/** /**
* Generates a PNG (or JPG, BMP, WEBP) Data URL based on the current image, * Generates a PNG (or JPG, BMP, WEBP) Data URL based on the current image,
* then calls "exported" with an object including the string, image * then calls "ed" with an object including the string, image
* information, and any issues found. * information, and any issues found.
* @function module:svgcanvas.SvgCanvas#rasterExport * @function module:svgcanvas.SvgCanvas#raster
* @param {"PNG"|"JPEG"|"BMP"|"WEBP"|"ICO"} [imgType="PNG"] * @param {"PNG"|"JPEG"|"BMP"|"WEBP"|"ICO"} [imgType="PNG"]
* @param {Float} [quality] Between 0 and 1 * @param {Float} [quality] Between 0 and 1
* @param {string} [exportWindowName] * @param {string} [WindowName]
* @param {PlainObject} [opts] * @param {PlainObject} [opts]
* @param {boolean} [opts.avoidEvent] * @param {boolean} [opts.avoidEvent]
* @fires module:svgcanvas.SvgCanvas#event:exported * @fires module:svgcanvas.SvgCanvas#event:ed
* @todo Confirm/fix ICO type * @todo Confirm/fix ICO type
* @returns {Promise<module:svgcanvas.ImageExportedResults>} Resolves to {@link module:svgcanvas.ImageExportedResults} * @returns {Promise<module:svgcanvas.ImageedResults>} Resolves to {@link module:svgcanvas.ImageedResults}
*/ */
export const rasterExport = async function (imgType, quality, exportWindowName, opts = {}) { const rasterExport = async (imgType, quality, WindowName, opts = {}) => {
const type = imgType === 'ICO' ? 'BMP' : (imgType || 'PNG') const type = imgType === 'ICO' ? 'BMP' : imgType || 'PNG'
const mimeType = 'image/' + type.toLowerCase() const mimeType = 'image/' + type.toLowerCase()
const { issues, issueCodes } = getIssues() const { issues, issueCodes } = getIssues()
const svg = svgCanvas.svgCanvasToString() const svg = svgCanvas.svgCanvasToString()
const iframe = document.createElement('iframe') const iframe = document.createElement('iframe')
iframe.onload = function () { iframe.onload = () => {
const iframedoc = iframe.contentDocument || iframe.contentWindow.document const iframedoc = iframe.contentDocument || iframe.contentWindow.document
const ele = svgCanvas.getSvgContent() const ele = svgCanvas.getSvgContent()
const cln = ele.cloneNode(true) const cln = ele.cloneNode(true)
iframedoc.body.appendChild(cln) iframedoc.body.appendChild(cln)
setTimeout(function () { setTimeout(() => {
// eslint-disable-next-line promise/catch-or-return // eslint-disable-next-line promise/catch-or-return
html2canvas(iframedoc.body, { useCORS: true, allowTaint: true }).then((canvas) => { html2canvas(iframedoc.body, { useCORS: true, allowTaint: true }).then(
return new Promise((resolve) => { canvas => {
return new Promise(resolve => {
const dataURLType = type.toLowerCase() const dataURLType = type.toLowerCase()
const datauri = quality const datauri = quality
? canvas.toDataURL('image/' + dataURLType, quality) ? canvas.toDataURL('image/' + dataURLType, quality)
@@ -762,7 +881,7 @@ export const rasterExport = async function (imgType, quality, exportWindowName,
iframe.parentNode.removeChild(iframe) iframe.parentNode.removeChild(iframe)
let bloburl let bloburl
function done () { const done = () => {
const obj = { const obj = {
datauri, datauri,
bloburl, bloburl,
@@ -772,24 +891,29 @@ export const rasterExport = async function (imgType, quality, exportWindowName,
type: imgType, type: imgType,
mimeType, mimeType,
quality, quality,
exportWindowName WindowName
} }
if (!opts.avoidEvent) { if (!opts.avoidEvent) {
svgCanvas.call('exported', obj) svgCanvas.call('ed', obj)
} }
resolve(obj) resolve(obj)
} }
if (canvas.toBlob) { if (canvas.toBlob) {
canvas.toBlob((blob) => { canvas.toBlob(
blob => {
bloburl = createObjectURL(blob) bloburl = createObjectURL(blob)
done() done()
}, mimeType, quality) },
mimeType,
quality
)
return return
} }
bloburl = dataURLToObjectURL(datauri) bloburl = dataURLToObjectURL(datauri)
done() done()
}) })
}) }
)
}, 1000) }, 1000)
} }
document.body.appendChild(iframe) document.body.appendChild(iframe)
@@ -797,10 +921,10 @@ export const rasterExport = async function (imgType, quality, exportWindowName,
/** /**
* @typedef {void|"save"|"arraybuffer"|"blob"|"datauristring"|"dataurlstring"|"dataurlnewwindow"|"datauri"|"dataurl"} external:jsPDF.OutputType * @typedef {void|"save"|"arraybuffer"|"blob"|"datauristring"|"dataurlstring"|"dataurlnewwindow"|"datauri"|"dataurl"} external:jsPDF.OutputType
* @todo Newer version to add also allows these `outputType` values "bloburi"|"bloburl" which return strings, so document here and for `outputType` of `module:svgcanvas.PDFExportedResults` below if added * @todo Newer version to add also allows these `outputType` values "bloburi"|"bloburl" which return strings, so document here and for `outputType` of `module:svgcanvas.PDFedResults` below if added
*/ */
/** /**
* @typedef {PlainObject} module:svgcanvas.PDFExportedResults * @typedef {PlainObject} module:svgcanvas.PDFedResults
* @property {string} svg The SVG PDF output * @property {string} svg The SVG PDF output
* @property {string|ArrayBuffer|Blob|window} output The output based on the `outputType`; * @property {string|ArrayBuffer|Blob|window} output The output based on the `outputType`;
* if `undefined`, "datauristring", "dataurlstring", "datauri", * if `undefined`, "datauristring", "dataurlstring", "datauri",
@@ -815,34 +939,35 @@ export const rasterExport = async function (imgType, quality, exportWindowName,
* @property {external:jsPDF.OutputType} outputType * @property {external:jsPDF.OutputType} outputType
* @property {string[]} issues The human-readable localization messages of corresponding `issueCodes` * @property {string[]} issues The human-readable localization messages of corresponding `issueCodes`
* @property {module:svgcanvas.IssueCode[]} issueCodes * @property {module:svgcanvas.IssueCode[]} issueCodes
* @property {string} exportWindowName * @property {string} WindowName
*/ */
/** /**
* Generates a PDF based on the current image, then calls "exportedPDF" with * Generates a PDF based on the current image, then calls "edPDF" with
* an object including the string, the data URL, and any issues found. * an object including the string, the data URL, and any issues found.
* @function module:svgcanvas.SvgCanvas#exportPDF * @function module:svgcanvas.SvgCanvas#PDF
* @param {string} [exportWindowName] Will also be used for the download file name here * @param {string} [WindowName] Will also be used for the download file name here
* @param {external:jsPDF.OutputType} [outputType="dataurlstring"] * @param {external:jsPDF.OutputType} [outputType="dataurlstring"]
* @fires module:svgcanvas.SvgCanvas#event:exportedPDF * @fires module:svgcanvas.SvgCanvas#event:edPDF
* @returns {Promise<module:svgcanvas.PDFExportedResults>} Resolves to {@link module:svgcanvas.PDFExportedResults} * @returns {Promise<module:svgcanvas.PDFedResults>} Resolves to {@link module:svgcanvas.PDFedResults}
*/ */
export const exportPDF = async ( const exportPDF = async (
exportWindowName, WindowName,
outputType = isChrome() ? 'save' : undefined outputType = isChrome() ? 'save' : undefined
) => { ) => {
const res = svgCanvas.getResolution() const res = svgCanvas.getResolution()
const orientation = res.w > res.h ? 'landscape' : 'portrait' const orientation = res.w > res.h ? 'landscape' : 'portrait'
const unit = 'pt' // curConfig.baseUnit; // We could use baseUnit, but that is presumably not intended for export purposes const unit = 'pt' // curConfig.baseUnit; // We could use baseUnit, but that is presumably not intended for purposes
const iframe = document.createElement('iframe') const iframe = document.createElement('iframe')
iframe.onload = function () { iframe.onload = () => {
const iframedoc = iframe.contentDocument || iframe.contentWindow.document const iframedoc = iframe.contentDocument || iframe.contentWindow.document
const ele = svgCanvas.getSvgContent() const ele = svgCanvas.getSvgContent()
const cln = ele.cloneNode(true) const cln = ele.cloneNode(true)
iframedoc.body.appendChild(cln) iframedoc.body.appendChild(cln)
setTimeout(function () { setTimeout(() => {
// eslint-disable-next-line promise/catch-or-return // eslint-disable-next-line promise/catch-or-return
html2canvas(iframedoc.body, { useCORS: true, allowTaint: true }).then((canvas) => { html2canvas(iframedoc.body, { useCORS: true, allowTaint: true }).then(
canvas => {
const imgData = canvas.toDataURL('image/png') const imgData = canvas.toDataURL('image/png')
const doc = new JsPDF({ const doc = new JsPDF({
orientation: orientation, orientation: orientation,
@@ -857,11 +982,15 @@ export const exportPDF = async (
iframe.parentNode.removeChild(iframe) iframe.parentNode.removeChild(iframe)
const { issues, issueCodes } = getIssues() const { issues, issueCodes } = getIssues()
outputType = outputType || 'dataurlstring' outputType = outputType || 'dataurlstring'
const obj = { issues, issueCodes, exportWindowName, outputType } const obj = { issues, issueCodes, WindowName, outputType }
obj.output = doc.output(outputType, outputType === 'save' ? (exportWindowName || 'svg.pdf') : undefined) obj.output = doc.output(
svgCanvas.call('exportedPDF', obj) outputType,
outputType === 'save' ? WindowName || 'svg.pdf' : undefined
)
svgCanvas.call('edPDF', obj)
return obj return obj
}) }
)
}, 1000) }, 1000)
} }
document.body.appendChild(iframe) document.body.appendChild(iframe)
@@ -872,7 +1001,7 @@ export const exportPDF = async (
* @param {Element} g - The parent element of the tree to give unique IDs * @param {Element} g - The parent element of the tree to give unique IDs
* @returns {void} * @returns {void}
*/ */
export const uniquifyElemsMethod = function (g) { const uniquifyElemsMethod = (g) => {
const ids = {} const ids = {}
// TODO: Handle markers and connectors. These are not yet re-identified properly // TODO: Handle markers and connectors. These are not yet re-identified properly
// as their referring elements do not get remapped. // as their referring elements do not get remapped.
@@ -882,9 +1011,17 @@ export const uniquifyElemsMethod = function (g) {
// //
// Problem #1: if svg_1 gets renamed, we do not update the polyline's se:connector attribute // Problem #1: if svg_1 gets renamed, we do not update the polyline's se:connector attribute
// Problem #2: if the polyline svg_7 gets renamed, we do not update the marker id nor the polyline's marker-end attribute // Problem #2: if the polyline svg_7 gets renamed, we do not update the marker id nor the polyline's marker-end attribute
const refElems = ['filter', 'linearGradient', 'pattern', 'radialGradient', 'symbol', 'textPath', 'use'] const refElems = [
'filter',
'linearGradient',
'pattern',
'radialGradient',
'symbol',
'textPath',
'use'
]
walkTree(g, function (n) { walkTree(g, (n) => {
// if it's an element node // if it's an element node
if (n.nodeType === 1) { if (n.nodeType === 1) {
// and the element has an ID // and the element has an ID
@@ -899,7 +1036,7 @@ export const uniquifyElemsMethod = function (g) {
// now search for all attributes on this element that might refer // now search for all attributes on this element that might refer
// to other elements // to other elements
svgCanvas.getrefAttrs().forEach(function (attr) { svgCanvas.getrefAttrs().forEach((attr) => {
const attrnode = n.getAttributeNode(attr) const attrnode = n.getAttributeNode(attr)
if (attrnode) { if (attrnode) {
// the incoming file has been sanitized, so we should be able to safely just strip off the leading # // the incoming file has been sanitized, so we should be able to safely just strip off the leading #
@@ -933,7 +1070,9 @@ export const uniquifyElemsMethod = function (g) {
// in ids, we now have a map of ids, elements and attributes, let's re-identify // in ids, we now have a map of ids, elements and attributes, let's re-identify
for (const oldid in ids) { for (const oldid in ids) {
if (!oldid) { continue } if (!oldid) {
continue
}
const { elem } = ids[oldid] const { elem } = ids[oldid]
if (elem) { if (elem) {
const newid = svgCanvas.getNextId() const newid = svgCanvas.getNextId()
@@ -966,7 +1105,7 @@ export const uniquifyElemsMethod = function (g) {
* @param {Element} parent * @param {Element} parent
* @returns {void} * @returns {void}
*/ */
export const setUseDataMethod = function (parent) { const setUseDataMethod = (parent) => {
let elems = parent let elems = parent
if (parent.tagName !== 'use') { if (parent.tagName !== 'use') {
@@ -974,11 +1113,13 @@ export const setUseDataMethod = function (parent) {
elems = elems.querySelectorAll('use') elems = elems.querySelectorAll('use')
} }
Array.prototype.forEach.call(elems, function (el, _) { Array.prototype.forEach.call(elems, (el, _) => {
const dataStorage = svgCanvas.getDataStorage() const dataStorage = svgCanvas.getDataStorage()
const id = svgCanvas.getHref(el).substr(1) const id = svgCanvas.getHref(el).substr(1)
const refElem = svgCanvas.getElement(id) const refElem = svgCanvas.getElement(id)
if (!refElem) { return } if (!refElem) {
return
}
dataStorage.put(el, 'ref', refElem) dataStorage.put(el, 'ref', refElem)
if (refElem.tagName === 'symbol' || refElem.tagName === 'svg') { if (refElem.tagName === 'symbol' || refElem.tagName === 'svg') {
dataStorage.put(el, 'symbol', refElem) dataStorage.put(el, 'symbol', refElem)
@@ -993,21 +1134,31 @@ export const setUseDataMethod = function (parent) {
* @function module:svgcanvas.SvgCanvas#removeUnusedDefElems * @function module:svgcanvas.SvgCanvas#removeUnusedDefElems
* @returns {Integer} The number of elements that were removed * @returns {Integer} The number of elements that were removed
*/ */
export const removeUnusedDefElemsMethod = function () { const removeUnusedDefElemsMethod = () => {
const defs = svgCanvas.getSvgContent().getElementsByTagNameNS(NS.SVG, 'defs') const defs = svgCanvas.getSvgContent().getElementsByTagNameNS(NS.SVG, 'defs')
if (!defs || !defs.length) { return 0 } if (!defs || !defs.length) {
return 0
}
// if (!defs.firstChild) { return; } // if (!defs.firstChild) { return; }
const defelemUses = [] const defelemUses = []
let numRemoved = 0 let numRemoved = 0
const attrs = ['fill', 'stroke', 'filter', 'marker-start', 'marker-mid', 'marker-end'] const attrs = [
'fill',
'stroke',
'filter',
'marker-start',
'marker-mid',
'marker-end'
]
const alen = attrs.length const alen = attrs.length
const allEls = svgCanvas.getSvgContent().getElementsByTagNameNS(NS.SVG, '*') const allEls = svgCanvas.getSvgContent().getElementsByTagNameNS(NS.SVG, '*')
const allLen = allEls.length const allLen = allEls.length
let i; let j let i
let j
for (i = 0; i < allLen; i++) { for (i = 0; i < allLen; i++) {
const el = allEls[i] const el = allEls[i]
for (j = 0; j < alen; j++) { for (j = 0; j < alen; j++) {
@@ -1024,8 +1175,10 @@ export const removeUnusedDefElemsMethod = function () {
} }
} }
Array.prototype.forEach.call(defs, function (def, i) { Array.prototype.forEach.call(defs, (def, i) => {
const defelems = def.querySelectorAll('linearGradient, radialGradient, filter, marker, svg, symbol') const defelems = def.querySelectorAll(
'linearGradient, radialGradient, filter, marker, svg, symbol'
)
i = defelems.length i = defelems.length
while (i--) { while (i--) {
const defelem = defelems[i] const defelem = defelems[i]
@@ -1047,26 +1200,43 @@ export const removeUnusedDefElemsMethod = function () {
* @param {Element} elem * @param {Element} elem
* @returns {void} * @returns {void}
*/ */
export const convertGradientsMethod = function (elem) { const convertGradientsMethod = (elem) => {
let elems = elem.querySelectorAll('linearGradient, radialGradient') let elems = elem.querySelectorAll('linearGradient, radialGradient')
if (!elems.length && isWebkit()) { if (!elems.length && isWebkit()) {
// Bug in webkit prevents regular *Gradient selector search // Bug in webkit prevents regular *Gradient selector search
elems = Array.prototype.filter.call(elem.querySelectorAll('*'), function (curThis) { elems = Array.prototype.filter.call(elem.querySelectorAll('*'), (
return (curThis.tagName.includes('Gradient')) curThis
) => {
return curThis.tagName.includes('Gradient')
}) })
} }
Array.prototype.forEach.call(elems, function (grad) { Array.prototype.forEach.call(elems, (grad) => {
if (grad.getAttribute('gradientUnits') === 'userSpaceOnUse') { if (grad.getAttribute('gradientUnits') === 'userSpaceOnUse') {
const svgContent = svgCanvas.getSvgContent() const svgContent = svgCanvas.getSvgContent()
// TODO: Support more than one element with this ref by duplicating parent grad // TODO: Support more than one element with this ref by duplicating parent grad
let fillStrokeElems = svgContent.querySelectorAll('[fill="url(#' + grad.id + ')"],[stroke="url(#' + grad.id + ')"]') let fillStrokeElems = svgContent.querySelectorAll(
'[fill="url(#' + grad.id + ')"],[stroke="url(#' + grad.id + ')"]'
)
if (!fillStrokeElems.length) { if (!fillStrokeElems.length) {
const tmpFillStrokeElems = svgContent.querySelectorAll('[*|href="#' + grad.id + '"]') const tmpFillStrokeElems = svgContent.querySelectorAll(
'[*|href="#' + grad.id + '"]'
)
if (!tmpFillStrokeElems.length) { if (!tmpFillStrokeElems.length) {
return return
} else { } else {
if ((tmpFillStrokeElems[0].tagName === 'linearGradient' || tmpFillStrokeElems[0].tagName === 'radialGradient') && tmpFillStrokeElems[0].getAttribute('gradientUnits') === 'userSpaceOnUse') { if (
fillStrokeElems = svgContent.querySelectorAll('[fill="url(#' + tmpFillStrokeElems[0].id + ')"],[stroke="url(#' + tmpFillStrokeElems[0].id + ')"]') (tmpFillStrokeElems[0].tagName === 'linearGradient' ||
tmpFillStrokeElems[0].tagName === 'radialGradient') &&
tmpFillStrokeElems[0].getAttribute('gradientUnits') ===
'userSpaceOnUse'
) {
fillStrokeElems = svgContent.querySelectorAll(
'[fill="url(#' +
tmpFillStrokeElems[0].id +
')"],[stroke="url(#' +
tmpFillStrokeElems[0].id +
')"]'
)
} else { } else {
return return
} }
@@ -1077,7 +1247,9 @@ export const convertGradientsMethod = function (elem) {
// This will occur if the element is inside a <defs> or a <symbol>, // This will occur if the element is inside a <defs> or a <symbol>,
// in which we shouldn't need to convert anyway. // in which we shouldn't need to convert anyway.
if (!bb) { return } if (!bb) {
return
}
if (grad.tagName === 'linearGradient') { if (grad.tagName === 'linearGradient') {
const gCoords = { const gCoords = {
x1: grad.getAttribute('x1'), x1: grad.getAttribute('x1'),

File diff suppressed because it is too large Load Diff