Skip to content

Instantly share code, notes, and snippets.

@vieron
Last active June 4, 2021 20:26
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save vieron/4998587 to your computer and use it in GitHub Desktop.
Save vieron/4998587 to your computer and use it in GitHub Desktop.
Export each as SVG. Modified some lines from the original by Aaron Beall (http://fireworks.abeall.com/extensions/commands/Export/Export%20SVG.jsf) to export each selected object as a separate SVG file.
// Fireworks JavaScript Command
// Exports current document state as SVG graphics format
// Install by copying to Fireworks/Configuration/Commands/
// Run via the Commands menu in Fireworks
// Aaron Beall 2010-2011
// Version
var VERSION = "0.6.1";
// Params
var INDENT = "\t";
var NEWLINE = "\n";
var NUMBER_PRECISION = 4;
var SKIP_HIDDEN_LAYERS = true;
var SKIP_HIDDEN_OBJECTS = true;
var IMAGES_FOLDER = "{filename}.images";
/* TODO
SVG Features:
- [DONE-v0.5] rectangle primitives
- [DONE-v0.5.2] symbols to def/use tags
- filters:
- - [DONE] gaussian blur, blur, blur more
- - [DONE] dropshadow
- - inner shaddow
- - [DONE] glow
- - inner glow
- - bevel/emboss
- - adjust color filters
- - noise
- - convert to alpha
- - PS effects to not be supported
- [DONE-v0.5] text
- - support wrapping (force line-breaks? svg extension?)
- - kerning and line-spacing
- [DONE-v0.6] pattern fills
- textured fills
- recognize identical definitions and re-use rather than re-define
- [DONE-v0.5] export images
- embed images (base64 encode, requires SWF)
- when rendering hidden layer (as group) use visibility setting
- blend modes via feBlend
- [Done-v0.6] masking/clipping paths
- intelligent filter sizing
- [Done-v0.6] render hotspots as <a> tags
- flatten unsupported effects
- - preserve vectors
- - cases to flatten: unsupported filters, transform uvw other than 001, any gradient other than linear or radial/ellipse, pattern/texture fills
- support feather by applying blur
- render default names for all elements
- wrap path data
- use userSpaceOnUse for gradient positioning
- use polygon/polyline for paths with no curves
JSF Code:
- use a string buffering method or more intermediate strings to avoid gigantic string concatenations -- benchmark to see if its an issue anyway
- investigate the use of html export extensions
- [DONE-v0.4] export selected elements
- copy to clipboard
- [VOID-found workaround for file URL location] browseForFolder instead of file so it can remember last location
Dialog UI:
- selected elements | entire canvas
- include hidden objects | exclude hidden objects
- maintain appearance | preserve vectors
- fixed doc size | scaleable doc size
- export svg doc | export svg elements only
- number precision
- embed images | export images
- export entire library | export used symbols only
- convert text to paths
Bugs:
- [FIXED-v0.5.8] text positioning is totally wrong
- [FIXED-v0.5.7] glow fails when glow size is 0
- [FIXED-v0.5.6] gradient doesn't work right when first color is beyond first opacity node
- [FIXED-v0.4] glow color doesn't show in FF -- renders as black
*/
var dom = fw.getDocumentDOM(); // document object
var sel = fw.selection; // saved selection
var docType = '<?xml version="1.0" standalone="no"?>' + NEWLINE
+ '<!-- Generator: Adobe ' + fw.appName + ', Export SVG Extension by Aaron Beall (http://fireworks.abeall.com) . Version: ' + VERSION + ' -->' + NEWLINE
+ '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'
function ExportSVG() {
if (!dom)
return false;
// export selected elements or all elements
var elements = null;
if(sel.length && fw.yesNoDialog("Export selected elements only?")){
SKIP_HIDDEN_OBJECTS = false;
elements = sel;
}
// pages and states
var exportPages = false, exportStates = false;
if(!elements && dom.pagesCount > 1)
exportPages = fw.yesNoDialog("Export all pages? Press No to export current page only.");
// prompt user for save location
var fileURL, dirURL;
if(exportPages){
var lastDir = dom.lastExportDirectory || fw.getPref("LastExportSVGLocation");
dirURL = fw.browseForFolderURL("Export SVG location", Files.exists(lastDir) ? lastDir : null);
fileURL = Files.makePathFromDirAndFile(dirURL, dom.docTitleWithoutExtension || "Untitled");
}else{
// last save location
var lastDir = dom.lastExportDirectory || Files.makePathFromDirAndFile(fw.getPref("LastExportSVGLocation"), "");
var filePathForSave = dom.filePathForSave;
if(Files.exists(lastDir))
dom.filePathForSave = lastDir;
else
filePathForSave = null;
// save file with validation
do{
fileURL = fw.browseForFileURL("save");
}while(fileURL && fileURL.length && !validateFile(fileURL));
dirURL = Files.getDirectory(fileURL);
}
function validateFile(url){
if(fileURL.toLowerCase().substr(-4) != ".svg"){
fileURL = fileURL + ".svg";
if(Files.exists(fileURL))
return confirm(unescape(fileURL) + " already exists. Do you want to replace it?");
}
return true;
}
// restore file path for save
if(filePathForSave)
dom.filePathForSave = filePathForSave;
// exit if dialog canceled
if(!fileURL || !dirURL)
return false;
// store location
dom.lastExportFile = Files.getFilename(fileURL);
dom.lastExportDirectory = dirURL;
fw.setPref("LastExportSVGLocation", dom.lastExportDirectory)
// parse image paths
var imagesFolder = IMAGES_FOLDER.replace(/{filename}/g, Files.getFilename(fileURL));
var imagesDir = dirURL + "/" + imagesFolder + "/";
// render
var currentIndent, defs, symbols, patterns = {};
var svgOutput;
elements.each_with_index(function(el, i) {
var orig_left = el.left;
var orig_top = el.top;
el.set_position(0, 0);
svgOutput = parseDocument(dom, [el]);
writeSVG(svgOutput, fileURL.replace('.svg', '.' + i + '.svg'));
el.set_position(orig_left, orig_top);
}, false);
// write svg output to file
function writeSVG(svgOutput, fileURL){
Files.deleteFileIfExisting(fileURL);
Files.createFile(fileURL, ".svg", fw.appMacCreator);
var file = Files.open(fileURL, true, "utf8");
if(file){
file.writeUTF8(svgOutput);
file.close();
}else{
return alert("ERROR: Unable to write file to disk.");
}
}
// parse a document
function parseDocument(dom, elements){
// reset doc globals
currentIndent = 1;
defs = [];
symbols = [];
// build svg drawing from fireworks objects
if(!elements){
// layers have to be reversed for correct svg stacking order
elements = [];
var layers = (dom.topLayers || dom.layers);
var i = layers.length;
while(i--)
elements.push(layers[i]);
}
svgDrawing = parseObjects(elements);
// begin svg output
var svgOutput = docType + NEWLINE;
var viewBox = dom.left + " " + dom.top + " " + elements[0].width + " " + elements[0].height;
svgOutput += '<svg id="' + escape(dom.docTitleWithoutExtension || "Untitled") + "-" + escape(dom.pageName) + '" viewBox="' + viewBox + '" style="background-color:' + dom.backgroundColor + '" version="1.1"' + NEWLINE;
svgOutput += INDENT + 'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve"' + NEWLINE;
svgOutput += INDENT + 'x="' + dom.left + 'px" y="' + dom.top + 'px" width="' + elements[0].width + 'px" height="' + elements[0].height + 'px"' + NEWLINE;
svgOutput += ">" + NEWLINE;
// render defs output
var svgDefs = "";
if(defs.length || symbols.length){
svgDefs = INDENT + '<defs>';
if(symbols.length){
svgDefs += NEWLINE + INDENT + INDENT + "<!-- Library -->";
for(var i = 0; i < symbols.length; i++)
svgDefs += NEWLINE + symbols[i].svg;
svgDefs += NEWLINE + INDENT + INDENT + "<!-- End Library -->";
}
svgDefs += NEWLINE + defs.join(NEWLINE)
svgDefs += NEWLINE + INDENT + '</defs>' + NEWLINE;
}
// build final svg output string
svgOutput += svgDefs + svgDrawing + "</svg>";
return svgOutput;
}
// parse out a list of objects
function parseObjects(objects){
currentIndent++;
var svgStr = "";
//for(var i = 0; i < objects.length; i++){
var i = objects.length;
while(i--){
var elem = objects[i];
if(elem.hasOwnProperty("visible") && !elem.visible && SKIP_HIDDEN_OBJECTS)
continue;
if(elem.mask && elem.mask.showAttrs)
svgStr += parseObjects([elem.mask.element]);
switch(elem.toString()){
case "[object Layer]":
if(elem.layerType == "normal" && (elem.frames[dom.currentFrameNum].visible || !SKIP_HIDDEN_LAYERS))
svgStr += renderGroup(elem, elem.frames[dom.currentFrameNum].elemsandsublayers);
else if(elem.layerType == "web" && elem.elems.length)
svgStr += renderGroup(elem, elem.elems);
break;
case "[object Group]":
svgStr += renderGroup(elem, elem.elements);
break;
case "[object Path]":
svgStr += renderPath(elem);
break;
case "[object CompoundShape]":
svgStr += renderPath(elem.resultantPath);
break;
case "[object RectanglePrimitive]":
svgStr += renderRectanglePrimitive(elem);
break;
case "[object Text]":
svgStr += renderText(elem);
break;
case "[object Image]":
svgStr += renderImage(elem);
break;
case "[object Instance]":
svgStr += renderInstance(elem);
break;
case "[object Hotspot]":
case "[object SliceHotspot]":
svgStr += renderHotspot(elem);
break;
}
}
currentIndent--;
return svgStr;
}
// parse out a list of objects as paths
function parsePathObjects(objects){
currentIndent++;
var svgStr = "";
var i = objects.length;
while(i--){
var elem = objects[i];
if(elem.hasOwnProperty("visible") && !elem.visible && SKIP_HIDDEN_OBJECTS)
continue;
if(elem.mask && elem.mask.showAttrs)
svgStr += parseObjects([elem.mask.element]);
switch(elem.toString()){
case "[object Group]":
currentIndent--;
svgStr += parsePathObjects(elem.elements);
currentIndent++;
break;
case "[object Path]":
svgStr += renderPath(elem);
break;
case "[object CompoundShape]":
svgStr += renderPath(elem.resultantPath);
break;
case "[object RectanglePrimitive]":
svgStr += renderRectanglePrimitive(elem);
break;
case "[object Text]":
svgStr += renderText(elem);
break;
}
}
currentIndent--;
return svgStr;
}
// render a group
function renderGroup(elem, elements){
var svgStr = renderIndent() + "<g" + renderElementProperties(elem) + ">" + NEWLINE;
svgStr += parseObjects(elements);
svgStr += renderIndent() + "</g>" + NEWLINE;
return svgStr;
}
// render a path
function renderPath(elem){
var svgStr = renderIndent() + "<path" + renderElementProperties(elem);
// path data
var data = "";
var contours = elem.contours || [elem.contour];
for(var c = 0; c < contours.length; c++){
var contour = contours[c];
var nodes = contour.nodes;
// move to new contour
data += "M " + round(nodes[0].x) + " " + round(nodes[0].y) + " ";
for(var n = 1; n <= nodes.length; n++){
// non closed contours
if(!contour.isClosed && n == nodes.length)
break;
// determine two bordering nodes
var node = nodes[n < nodes.length ? n : 0];
var prevNode = nodes[n > 0 ? n - 1 : nodes.length - 1]
// draw between the nodes
if(node.predX == node.x && node.predY == node.y && prevNode.succX == prevNode.x && prevNode.succY == prevNode.y){
// line to
data += "L " + round(node.x) + " " + round(node.y) + " ";
}else{
// curve to
data += "C " + round(prevNode.succX) + " " + round(prevNode.succY)
+ " " + round(node.predX) + " " + round(node.predY)
+ " " + round(node.x) + " " + round(node.y) + " ";
}
}
// closed contour
if(contour.isClosed)
data += "Z";
}
svgStr += ' d="' + data + '"';
// path attributes
svgStr += renderPathAttributes(elem);
// finish path
svgStr += "/>" + NEWLINE;
return svgStr;
}
// render rectangle primitive
function renderRectanglePrimitive(elem){
var svgStr = renderIndent() + "<rect" + renderElementProperties(elem);
// size, position, path attributes
svgStr += renderPosition(elem);
svgStr += renderSize(elem);
svgStr += renderPathAttributes(elem);
// rounded corners
if(elem.roundness){
var r = round(elem.roundness);
// convert percentage radius to pixel
if(elem.mode != "exact")
r = round((Math.min(elem.width, elem.height) * .5) * elem.roundness);
svgStr += ' rx="' + r + '" ry="' + r + '"';
}
// finish rect
svgStr += "/>" + NEWLINE;
return svgStr;
}
// render text
function renderText(elem){
var svgStr = renderIndent() + "<text" + renderElementProperties(elem);
// position
//svgStr += renderPosition(elem);
svgStr += ' x="' + round(elem.left) + '" y="' + round(elem.top + elem.height) + '"';
// explicit size
if(!elem.autoSize)
svgStr += ' width="' + round(elem.width) + '"';
// path attributes
svgStr += renderPathAttributes(elem);
// initial text attributes
svgStr += renderTextAttributes(elem.textRuns.initialAttrs);
// open text
svgStr += ">";
// text content
//svgStr += elem.textChars;
currentIndent++;
var runs = elem.textRuns.textRuns, prevAttrs = elem.textRuns.initialAttrs;
for(var i = 0; i < runs.length; i++){
svgStr += /*NEWLINE + renderIndent() +*/ "<tspan" + renderTextAttributes(runs[i].changedAttrs, prevAttrs) + "><![CDATA[" + runs[i].characters + "]]></tspan>";
prevAttrs = runs[i].changedAttrs;
}
currentIndent--;
// close text
svgStr += /*NEWLINE + renderIndent() +*/ "</text>" + NEWLINE;
return svgStr;
}
// render text attributes
function renderTextAttributes(textAttrs, prevAttrs){
var svgStr = "";
for(var i in textAttrs){
var val = textAttrs[i];
if(prevAttrs && prevAttrs[i] == val)
continue;
switch(i){
case "bold": if(val){ svgStr += "font-weight: bold; " }else if(prevAttrs){ svgStr += "font-weight: normal; " }; break;
case "italic": if(val){ svgStr += "font-style: italic; " }else if(prevAttrs){ svgStr += "font-style: normal;" }; break;
case "size": svgStr += "font-size: " + val.replace(/pt/, "px") + "; "; break;
//case "leading": svgStr += "line-interval"; break;
case "face": svgStr += "font-family: " + fw.getFamilyNameForPSFont(val) + "; "; break;
//case "paragraphSpaceBefore": svgStr += " "; break;
//case "paragraphSpaceAfter": svgStr += " "; break;
case "fillColor": svgStr += "color: " + val + "; "; break;
case "alignment": svgStr += "text-align: " + val + "; "; break;
case "underline": if(val){ svgStr += "text-decoration: underline; " }else if(prevAttrs){ svgStr += "text-decoration: none;" }; break;
//case "rangeKerning": svgStr += "kerning"; break;
//case "baselineShift": svgStr += "baseline-shift"; break;
//case "paragraphIndent": svgStr += " "; break;
}
}
return svgStr.length ? ' style="' + svgStr + '"' : "";
}
// render image
function renderImage(elem){
// export image
var imageName = getID(elem.name ? elem.name : "image") + ".png";
dom.exportElements([elem], imagesDir, imageName);
// begin image
var svgStr = renderIndent() + "<image" + renderElementProperties(elem);
// size and position
svgStr += renderPosition(elem);
svgStr += renderSize(elem);
// source
svgStr += ' xlink:href="' + imagesFolder + "/" + imageName + '"';
// finish image
svgStr += "/>" + NEWLINE;
return svgStr;
}
// render symbol instance
function renderInstance(elem){
// begin use instance
var id = getSymbolDefinition(elem);
var svgStr = renderIndent() + "<use" + renderElementProperties(elem) + ' xlink:href="#' + id + '"';
// transform
//svgStr += renderPosition(elem);
//svgStr += renderSize(elem);
svgStr += ' transform="' + transformMatrix(elem.transform.matrix) + '"';
// finish use instance
svgStr += '/>' + NEWLINE;
return svgStr;
}
// render a symbol definition
function getSymbolDefinition(instance){
// existing symbol definition
for(var i = 0; i < symbols.length; i++)
if(symbols[i].symbolID == instance.symbolID)
return symbols[i].svgID;
// new symbol definition
var id = getID(instance.symbolName.replace(/[^A-Za-z0-9_]/g, "") || "_", true);
var svgStr = INDENT + INDENT + '<symbol id="' + id + '" overflow="visible">' + NEWLINE;
// render symbol document
var oldIndent = currentIndent;
currentIndent = 3;
var symbolDOM = fw.getDocumentDOM(instance.symbolID);
var objects = [];
var layers = (symbolDOM.topLayers || symbolDOM.layers);
var i = layers.length;
while(i--)
objects.push(layers[i]);
svgStr += parseObjects(objects);
currentIndent = oldIndent;
// close symbol def
svgStr += NEWLINE + INDENT + INDENT + "</symbol>";
// add to symbol list
var symbol = {
symbolID: instance.symbolID,
svgID: id,
svg: svgStr
}
symbols.push(symbol);
return id;
}
// render path attributes
function renderPathAttributes(elem){
var attrs = elem.pathAttributes;
if(!attrs)
return ' fill="#000000" opacity="0"';
var svgStr = "";
// stroke
if(attrs.brush)
svgStr += ' stroke="' + attrs.brushColor + '" stroke-width="' + attrs.brush.diameter + '"';
// fill
if(attrs.fill){
if(attrs.fill.gradient && (attrs.fill.shape == "linear" || attrs.fill.shape == "radial" || attrs.fill.shape == "elliptical")){
// gradient fill
var gradientStart = INDENT + INDENT, gradientEnd = INDENT + INDENT, id = getID("gradient");
// gradient type and positioning
switch(attrs.fill.shape){
case "linear":
var x1 = percent(attrs.fillHandle1.x - elem.left, elem.width);
var y1 = percent(attrs.fillHandle1.y - elem.top, elem.height);
var x2 = percent(attrs.fillHandle2.x - elem.left, elem.width);
var y2 = percent(attrs.fillHandle2.y - elem.top, elem.height);
gradientStart += '<linearGradient id="' + id + '" x1="' + x1 + '" y1="' + y1 + '" x2="' + x2 + '" y2="' + y2 + '">'
gradientEnd += '</linearGradient>';
break;
case "radial":
case "elliptical":
var cx = percent(attrs.fillHandle1.x - elem.left, elem.width);
var cy = percent(attrs.fillHandle1.y - elem.top, elem.height);
var r = percent(distance(attrs.fillHandle1, attrs.fillHandle2), Math.max(elem.width, elem.height));
//var r = Math.round(distance(attrs.fillHandle1, attrs.fillHandle2)) + "px";
gradientStart += '<radialGradient id="' + id + '" cx="' + cx + '" cy="' + cy + '" r="' + r + '">'
gradientEnd += '</radialGradient>';
break;
}
/*
// gradient stops with color/opacity merging and interpolation
var nodes = [].concat(attrs.fill.gradient.nodes, attrs.fill.gradient.opacityNodes);
nodes.sort(function(a, b){return a.position - b.position});
var mergedNodes = [];
for(var n = 0; n < nodes.length; n++){
var prevNode = mergedNodes[mergedNodes.length - 1];
if(prevNode && nodes[n].position == prevNode.position)
nodes[n].isOpacityNode ? prevNode.opacity = nodes[n].color : prevNode.color = nodes[n].color;
else
mergedNodes.push(nodes[n].isOpacityNode ?
{position:nodes[n].position, opacity:nodes[n].color, color:interpolateColorNodes(nodes, n)} :
{position:nodes[n].position, opacity:interpolateOpacityNodes(nodes, n), color:nodes[n].color});
}
// render gradient stops
nodes = mergedNodes;
var gradientStops = "";
for(var n = 0; n < nodes.length; n++){
gradientStops += INDENT + INDENT + INDENT;
gradientStops += '<stop';
//if(nodes[n].color)
gradientStops += ' stop-color="' + nodes[n].color + '"';
//if(nodes[n].opacity)
gradientStops += ' stop-opacity="' + round(gradientOpacity(nodes[n].opacity)) + '"';
gradientStops += ' offset="' + percent(nodes[n].position, 1) + '"/>';
gradientStops += NEWLINE;
}*/
// gradient
var gradient = attrs.fill.gradient;
// combine color and opacity nodes and sort by position
var nodes = [].concat(gradient.nodes, gradient.opacityNodes);
nodes.sort(function(a, b){return a.position - b.position});
// build merged nodes, isolating rgb and opacity
var mergedNodes = [], node, prevNode = null;
for(var i = 0; i < nodes.length; i++){
node = nodes[i];
if(prevNode && prevNode.position == node.position)
node.isOpacityNode ? prevNode.opacity = node.color : prevNode.rgb = node.color;
else{
var newNode = {position:node.position};
node.isOpacityNode ? newNode.opacity = node.color : newNode.rgb = node.color;
mergedNodes.push(newNode);
prevNode = newNode;
}
}
// merge rgb+opacity into color, interpolate any needed rgb/a values
for(var i = 0; i < mergedNodes.length; i++){
var node = mergedNodes[i];
if(node.rgb == null)
node.rgb = interpolateNodeColor(mergedNodes, "rgb", i, node.position);
if(node.opacity == null)
node.opacity = interpolateNodeColor(mergedNodes, "opacity", i, node.position);
node.color = node.rgb.substr(0, 7) + node.opacity.substr(7);
}
// render gradient svg
nodes = mergedNodes;
var gradientStops = "";
for(var n = 0; n < nodes.length; n++){
gradientStops += INDENT + INDENT + INDENT;
gradientStops += '<stop';
gradientStops += ' stop-color="' + rgbHEX(nodes[n].color) + '"';
gradientStops += ' stop-opacity="' + round(gradientOpacity(nodes[n].opacity)) + '"';
gradientStops += ' offset="' + percent(nodes[n].position, 1) + '"/>';
gradientStops += NEWLINE;
}
// assign gradient fill
svgStr += ' fill="url(#' + id + ')"';
// add gradient to defs
defs.push(gradientStart + NEWLINE + gradientStops + gradientEnd);
}else if(attrs.fill.pattern){
var patternName = attrs.fill.pattern.name;
var id = patterns[patternName];
if(!id){
id = getID(patternName, true);
patterns[patternName] = id;
// extract pattern to image
var fill = eval("(" + attrs.fill.toSource() + ")");
fill.textureBlend = 0;
fill.feather = 0;
dom.addNewRectanglePrimitive({left:0, top:0, right:100, bottom:100}, 0);
dom.setFill(fill);
dom.setDefaultFillVector();
attrs = fw.selection[0].pathAttributes;
dom.setRectSides({left:attrs.fillHandle3.x, top:attrs.fillHandle3.y, right:attrs.fillHandle2.x, bottom:attrs.fillHandle2.y});
dom.moveSelectionTo({x:0, y:0}, false, false);
dom.flattenSelection();
// TODO: create pattern transform from handles
// render pattern
var elem = fw.selection[0];
dom.setElementName(patternName);
var svgPattern = INDENT + '<pattern id="' + id + '"' + renderSize(elem) + ' patternUnits="userSpaceOnUse">' + NEWLINE;
svgPattern += renderImage(elem);
svgPattern += INDENT + "</pattern>";
defs.push(svgPattern);
dom.deleteSelection(false);
}
// assign pattern fill
svgStr += ' fill="url(#' + id + ')"';
}else{
// solid color fill
svgStr += ' fill="' + attrs.fillColor + '"';
}
}else{
// no fill
svgStr += ' fill="none"';
}
return svgStr;
}
// render common object properties
function renderElementProperties(elem){
var svgProps = "";
// name
if(elem.name)
svgProps += ' id="' + getID(escape(elem.name), true) + '"';
// opacity
if(elem.opacity < 100)
svgProps += ' opacity="' + round(elem.opacity / 100) + '"';
// filters
if(elem.effectList && elem.effectList.effects.length){
var filterID = renderEffectList(elem.effectList, defs);
if(filterID)
svgProps += ' filter="url(#' + filterID + ')"';
}
// mask
if(elem.mask)
svgProps += renderMask(elem.mask);
// visibility
if(elem.visible == false)
svgProps += ' visibility="hidden"';
return svgProps;
}
// render x/y properties
function renderPosition(elem){
if(elem == "[object Image]")
return ' x="' + round(elem.pixelRect.left) + '" y="' + round(elem.pixelRect.top) + '"';
else
return ' x="' + round(elem.left) + '" y="' + round(elem.top) + '"';
}
// render width/height properties
function renderSize(elem){
if(elem == "[object Image]")
return ' width="' + round(elem.pixelRect.right - elem.pixelRect.left) + '" height="' + round(elem.pixelRect.bottom - elem.pixelRect.top) + '"';
else
return ' width="' + round(elem.width) + '" height="' + round(elem.height) + '"';
}
// render supported FW filters to svg filter stacks
function renderEffectList(effectList, defs){
var filters = [], /*ids = [],*/ svgFilter, previousResult = "SourceGraphic";
var filterNewline = NEWLINE + INDENT + INDENT + INDENT;
for(var i = 0; i < effectList.effects.length; i++){
var effect = effectList.effects[i];
if(!effect.EffectIsVisible)
continue;
svgFilter = "";
var svgFilterName = filterNewline + "<!-- " + effect.name + " -->";
var id = getID(effect.name.replace(/[\W]/g, "_"));
switch(effect.EffectMoaID){
// Blur
case "{f1cfce41-718e-11d1-8c8200a024cdc039}":
svgFilter += filterNewline + '<feGaussianBlur in="' + previousResult + '" stdDeviation=".5" result="' + id + '"/>';
break;
// Blur More
case "{f1cfce42-718e-11d1-8c8200a024cdc039}":
svgFilter += filterNewline + '<feGaussianBlur in="' + previousResult + '" stdDeviation="1" result="' + id + '"/>';
break;
// Gaussian Blur
case "{d04ef8c0-71b3-11d1-8c8200a024cdc039}":
svgFilter += filterNewline + '<feGaussianBlur in="' + previousResult + '" stdDeviation="' + effect.gaussian_blur_radius + '" result="' + id + '"/>';
break;
// Drop Shadow
case "{a7944db8-6ce2-11d1-8c76000502701850}":
var dx = effect.ShadowDistance * Math.cos(effect.ShadowAngle * (Math.PI / 180));
var dy = effect.ShadowDistance * Math.sin(effect.ShadowAngle * (Math.PI / 180));
svgFilter += filterNewline + '<feOffset result="out" in="' + previousResult + '" dx="' + round(dx) + '" dy="' + round(-dy) + '"/>';
svgFilter += filterNewline + '<feColorMatrix result="out" in="out" type="matrix" values="' + colorMatrix(effect.ShadowColor) + '"/>';
svgFilter += filterNewline + '<feGaussianBlur result="out" in="out" stdDeviation="' + effect.ShadowBlur + '"/>';
svgFilter += filterNewline + '<feBlend in="' + previousResult + '" in2="out" mode="normal" result="' + id + '"/>';
break;
// Glow/Bevel/Emboss
case "{7fe61102-6ce2-11d1-8c76000502701850}":
switch(effect.BevelType){
// Inner Bevel
case 0:
break;
// Outer Bevel
case 1:
break;
// Raised Emboss
case 2:
break;
// Inset Emboss
case 3:
break;
// Glow
case 4:
var alpha = Math.round(255 * (effect.BevelContrast / 100));
var color = effect.OuterBevelColor + String("00" + alpha.toString(16)).substr(-2);
svgFilter += filterNewline + '<feColorMatrix result="out" in="' + previousResult + '" type="matrix" values="' + colorMatrix(color) + '"/>';
if(effect.GlowWidth) svgFilter += filterNewline + '<feMorphology result="out" in="out" operator="dilate" radius="' + effect.GlowWidth + '"/>'
svgFilter += filterNewline + '<feGaussianBlur result="out" in="out" stdDeviation="' + (effect.MaskSoftness * .5) + '"/>';
svgFilter += filterNewline + '<feBlend in="' + previousResult + '" in2="out" mode="normal" result="' + id + '"/>';
break;
}
break;
// Inner Glow
case "{2ba87123-8220-11d3-baad0000861f4d01}":
// <feGaussianBlur stdDeviation="6" />
// <feComposite operator="arithmetic" in2="SourceAlpha" k2="-1" k3="1" />
break;
// Inner Shadow
case "{5600f702-774c-11d3-baad0000861f4d01}":
// <feOffset dx="5" dy="5" />
// <feGaussianBlur stdDeviation="6" />
// <feComposite operator="arithmetic" in2="SourceAlpha" k2="-1" k3="1" />
break;
}
if(svgFilter.length){
filters.push(svgFilterName + svgFilter);
//ids.push(id);
previousResult = id;
}
}
if(filters.length){
var id = getID("filter");
var svgFilters = INDENT + INDENT;
svgFilters += '<filter id="' + id + '" x="-100%" y="-100%" width="300%" height="300%">';
svgFilters += filters.join(INDENT + INDENT + INDENT);
/*if(filters.length > 1){
svgFilters += filterNewline + "<feMerge>";
for(var i = 0; i < filters.length; i++)
svgFilters += filterNewline + INDENT + '<feMergeNode in="' + ids[i] + '"/>';
svgFilters += filterNewline + "</feMerge>";
}*/
svgFilters += NEWLINE + INDENT + INDENT + '</filter>';
defs.push(svgFilters);
return id;
}else
return null;
}
// render a mask
function renderMask(mask){
var id = getID("mask");
if(mask.mode == "mask to path"){
// USUPPORTED: image mask set to use alpha channel
var svgMask = renderIndent() + '<clipPath id="' + id + '">' + NEWLINE;
svgMask += parsePathObjects([mask.element]);
svgMask += renderIndent() + '</clipPath>';
defs.push(svgMask);
return ' clip-path="url(#' + id + ')"';
}else{
var svgMask = renderIndent() + '<mask id="' + id + '">' + NEWLINE;
svgMask += parseObjects([mask.element]);
svgMask += renderIndent() + '</mask>';
defs.push(svgMask);
return ' mask="url(#' + id + ')"';
}
}
// render web layer
function renderHotspot(elem){
if(!elem.urlText)
return "";
var svgStr = renderIndent() + '<a xlink:href="';
// link ref, preserve in-doc page links as .svg files
var url = elem.urlText;
var pageName = url.substring(0, url.length - 4);
if(url.indexOf(".htm") != -1 && url.indexOf("/") == -1 && url.indexOf("\\") == -1){
svgStr += pageName + ".svg";
}else
svgStr += elem.urlText;
if(elem.targetText)
svgStr += '" target="' + elem.targetText;
svgStr += '">';
// render placeholder shape
svgStr += NEWLINE;
currentIndent++;
switch(elem.shape){
case "circle":
case "rectangle": svgStr += renderRectanglePrimitive(elem); break;
case "polyline": svgStr += renderPath(elem); break;
}
currentIndent--;
// finish
svgStr += renderIndent() + "</a>" + NEWLINE;
return svgStr;
}
// render the current indent
function renderIndent(){
/*var str = "";
var n = currentIndent;
while(n--)
str += INDENT;
return str;*/
return new Array(currentIndent).join(INDENT);
}
}
// get unique key ids
var ids = {};
function getID(key, allowNumberless){
if(!ids[key])
ids[key] = 1;
else
ids[key]++;
return (!allowNumberless || ids[key] > 1) ? key + ids[key] : key;
}
// convert number to svg percentage
function percent(val, max){
return round((val / max) * 100) + "%";
}
// calculate pixel distance
function distance(p1, p2){
return Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}
// extract opacity value from gradient color
function gradientOpacity(color){
return color.length > 7 ? parseInt(color.substr(7, 2), 16) / 255 : 1;
}
/*
// interpolate color values for svg's stupid braindead gradient stop format
function interpolateColorNodes(nodes, index){
var prevColor, nextColor, prevPosition, nextPosition;
var n = index;
while(n--){
if(!nodes[n].isOpacityNode){
prevColor = nodes[n].color;
prevPosition = nodes[n].position;
break;
}
}
for(var n = index; n < nodes.length; n++){
if(!nodes[n].isOpacityNode){
nextColor = nodes[n].color;
nextPosition = nodes[n].position;
break;
}
}
if(!prevColor)
return "inherit";
else if(!nextColor)
return prevColor;
var percent = (nodes[index].position - prevPosition) / (nextPosition - prevPosition)
var prevRGB = {
r:parseInt(prevColor.substr(1, 2), 16),
g:parseInt(prevColor.substr(3, 2), 16),
b:parseInt(prevColor.substr(5, 2), 16)
}
var nextRGB = {
r:parseInt(nextColor.substr(1, 2), 16),
g:parseInt(nextColor.substr(3, 2), 16),
b:parseInt(nextColor.substr(5, 2), 16)
}
var rgb = {
r:Math.round(prevRGB.r + (nextRGB.r - prevRGB.r) * percent),
g:Math.round(prevRGB.g + (nextRGB.g - prevRGB.g) * percent),
b:Math.round(prevRGB.b + (nextRGB.b - prevRGB.b) * percent)
}
var hex = {
r:String("00" + rgb.r.toString(16)).substr(-2),
g:String("00" + rgb.g.toString(16)).substr(-2),
b:String("00" + rgb.b.toString(16)).substr(-2)
}
return "#" + hex.r + hex.g + hex.b;
}
// interpolate opacity value for svg gradient stop
function interpolateOpacityNodes(nodes, index){
var prevOpacity, nextOpacity, prevPosition, nextPosition;
var n = index;
while(n--){
if(nodes[n].isOpacityNode){
prevOpacity = nodes[n].color;
prevPosition = nodes[n].position;
break;
}
}
for(var n = index; n < nodes.length; n++){
if(nodes[n].isOpacityNode){
nextOpacity = nodes[n].color;
nextPosition = nodes[n].position;
break;
}
}
if(!prevOpacity)
return "#000000ff";
else if(!nextOpacity)
return prevOpacity;
var percent = (nodes[index].position - prevPosition) / (nextPosition - prevPosition)
var prevAlpha = prevOpacity.length > 7 ? parseInt(prevOpacity.substr(-2), 16) : 255;
var nextAlpha = nextOpacity.length > 7 ? parseInt(nextOpacity.substr(-2), 16) : 255;
var alpha = Math.round(prevAlpha + (nextAlpha - prevAlpha) * percent);
return "#000000" + String("00" + alpha.toString(16)).substr(-2);
}
*/
function interpolateNodeColor(nodes, type, index, position){
// find previous/next nodes
var prevNode = null, nextNode = null;
for(var i = index; i >= 0; i--)
if(nodes[i][type] != null){
prevNode = nodes[i];
break;
}
for(var i = index; i < nodes.length; i++)
if(nodes[i][type] != null){
nextNode = nodes[i];
break;
}
// if index is beyond first/last target type node, no interpolation needed, just use the closest node
// (this assumes that a gradient has at least 2 color and 2 opacity nodes, which is required in FW)
if(!prevNode)
for(var i = index; i < nodes.length; i++)
if(nodes[i][type] != null)
return rgbaHEX(nodes[i][type]);
if(!nextNode)
for(var i = index; i >= 0; i--)
if(nodes[i][type] != null)
return rgbaHEX(nodes[i][type]);
// interpolate RGBA
var prevPos = prevNode.position;
var nextPos = nextNode.position;
var prevRGBA = hexToRGBA(prevNode[type]);
var nextRGBA = hexToRGBA(nextNode[type]);
var percent = (position - prevPos) / (nextPos - prevPos);
var output = rgbaToHEX({
r:prevRGBA.r + (nextRGBA.r - prevRGBA.r) * percent,
g:prevRGBA.g + (nextRGBA.g - prevRGBA.g) * percent,
b:prevRGBA.b + (nextRGBA.b - prevRGBA.b) * percent,
a:prevRGBA.a + (nextRGBA.a - prevRGBA.a) * percent
});
return output;
}
// hex string to rgba object
function hexToRGBA(hex){
hex = rgbaHEX(hex);
if(hex.charAt(0) == "#")
hex = hex.substr(1);
return {
r:parseInt(hex.substring(0, 2), 16),
g:parseInt(hex.substring(2, 4), 16),
b:parseInt(hex.substring(4, 6), 16),
a:parseInt(hex.substring(6, 8), 16)
}
}
// rgba object to hext string
function rgbaToHEX(rgba){
var r = String("00" + Math.round(rgba.r).toString(16)).substr(-2);
var g = String("00" + Math.round(rgba.g).toString(16)).substr(-2);
var b = String("00" + Math.round(rgba.b).toString(16)).substr(-2);
var a = String("00" + Math.round(rgba.a).toString(16)).substr(-2);
return "#" + r + g + b + a;
}
// conform hex to rgba value
function rgbaHEX(hex){
return hex.length < 8 ? hex + "FF" : hex;
}
// conform hex to rgb value
function rgbHEX(hex){
return hex.substr(0, 7);
}
// create a color matrix from a color int
function colorMatrix(color){
var col = color.length > 7 ? color : color + "ff";
var r = parseInt(col.substr(1, 2), 16) / 255;
var g = parseInt(col.substr(3, 2), 16) / 255;
var b = parseInt(col.substr(5, 2), 16) / 255;
var a = parseInt(col.substr(7, 2), 16) / 255;
return '0 0 0 ' + round(r) + ' 0 0 0 0 ' + round(g) + ' 0 0 0 0 ' + round(b) + ' 0 0 0 0 ' + round(a) + ' 0';
}
// create a lame svg matrix from a fw matrix
function transformMatrix(mat){
// FW: a, b, u, c, d, v, tx, ty, w
// SVG: a, b, c, d, e, f
var a = round(mat[0]);
var b = round(mat[1]);
var c = round(mat[3]);
var d = round(mat[4]);
var tx = round(mat[6]);
var ty = round(mat[7]);
return "matrix(" + [a, b, c, d, tx, ty].join(", ") + ")";
}
// round a number to an allowed maximum precision
function round(num){
return Number(Number(num).toFixed(NUMBER_PRECISION));
}
try{
ExportSVG();
}catch(e){alert([e.lineNumber, e.message])}
diff --git 1/Applications/Adobe Fireworks CS5/Configuration/Commands/Export/Export each as SVG.jsf 2/Applications/Adobe Fireworks CS5/Configuration/Commands/Export/Export SVG.jsf
index 7f4ba1b..2156643 100755
--- 1/Applications/Adobe Fireworks CS5/Configuration/Commands/Export/Export each as SVG.jsf
+++ 2/Applications/Adobe Fireworks CS5/Configuration/Commands/Export/Export SVG.jsf
@@ -79,7 +79,7 @@ Bugs:
*/
var dom = fw.getDocumentDOM(); // document object
-var sel = fw.selection; // saved selection
+var sel = [].concat(fw.selection); // saved selection
var docType = '<?xml version="1.0" standalone="no"?>' + NEWLINE
+ '<!-- Generator: Adobe ' + fw.appName + ', Export SVG Extension by Aaron Beall (http://fireworks.abeall.com) . Version: ' + VERSION + ' -->' + NEWLINE
@@ -151,17 +151,30 @@ function ExportSVG() {
// render
var currentIndent, defs, symbols, patterns = {};
+ if(exportPages){
+
+ // save doc state
+ var oldPage = dom.currentPageNum;
+ var oldFrame = dom.currentFrameNum;
- var svgOutput;
- elements.each_with_index(function(el, i) {
- var orig_left = el.left;
- var orig_top = el.top;
- el.set_position(0, 0);
+ // write pages
+ var numPages = dom.pagesCount;
+ for(var i = 0; i < numPages; i++){
+ dom.changeCurrentPage(i);
+ dom = fw.getDocumentDOM();
+ var svgOutput = parseDocument(dom, null);
+ var fileURL = Files.makePathFromDirAndFile(dirURL, dom.pageName + ".svg");
+ writeSVG(svgOutput, fileURL);
+ }
- svgOutput = parseDocument(dom, [el]);
- writeSVG(svgOutput, fileURL.replace('.svg', '.' + i + '.svg'));
- el.set_position(orig_left, orig_top);
- }, false);
+ // restore doc state
+ dom.changeCurrentPage(oldPage);
+ dom = fw.getDocumentDOM();
+ dom.currentFrameNum = oldFrame;
+ }else{
+ var svgOutput = parseDocument(dom, elements);
+ writeSVG(svgOutput, fileURL);
+ }
// write svg output to file
function writeSVG(svgOutput, fileURL){
@@ -196,10 +211,10 @@ function ExportSVG() {
// begin svg output
var svgOutput = docType + NEWLINE;
- var viewBox = dom.left + " " + dom.top + " " + elements[0].width + " " + elements[0].height;
+ var viewBox = dom.left + " " + dom.top + " " + dom.width + " " + dom.height;
svgOutput += '<svg id="' + escape(dom.docTitleWithoutExtension || "Untitled") + "-" + escape(dom.pageName) + '" viewBox="' + viewBox + '" style="background-color:' + dom.backgroundColor + '" version="1.1"' + NEWLINE;
svgOutput += INDENT + 'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve"' + NEWLINE;
- svgOutput += INDENT + 'x="' + dom.left + 'px" y="' + dom.top + 'px" width="' + elements[0].width + 'px" height="' + elements[0].height + 'px"' + NEWLINE;
+ svgOutput += INDENT + 'x="' + dom.left + 'px" y="' + dom.top + 'px" width="' + dom.width + 'px" height="' + dom.height + 'px"' + NEWLINE;
svgOutput += ">" + NEWLINE;
// render defs output
@@ -1164,7 +1179,6 @@ function round(num){
return Number(Number(num).toFixed(NUMBER_PRECISION));
}
-
try{
ExportSVG();
}catch(e){alert([e.lineNumber, e.message])}
@slt-uxd
Copy link

slt-uxd commented Jun 19, 2013

I think this is exactly what I need - but I'm not sure how to make use of it :-( I am on FW CS5, on Windows. Here is what I did:

  1. Downloaded the Gist from this page
  2. Extracted the files
  3. I copied the "Export each as SVG.js" into the Adobe Fireworks CS5\Configuration\Commands directory,
  4. Restarted FW
  5. Open a FW file, selected several vector shapes
  6. Commands meny > Export > Export SVG
  7. Export selected elements only? YES
  8. I get a single filesave dialog, and the vector shapes I had selected were still exported in a single SVG file :-(

What am I doing wrong?

@skybondsor
Copy link

@slt-uxd I think whatever you've selected gets exported as a single SVG. So if you want a bunch of individual SVG files, you have to select then export, select then export, etc.

@karlspencer
Copy link

Hello there.

Does anyone here know where I can download Aaron's original extension for Export SVG? It appears the site is no longer in existence as it redirect to an error. Tried contact by email but it is no longer working either :(

I had to re-install my operating system and couldn't find the original extension that I just absolutely loved.

If anyone has it handy, please let me know if you could help me out.

@Taiger
Copy link

Taiger commented Mar 1, 2016

I found the original script here: http://firetuts.com/export-svg-with-fireworks/

@suijunqiang
Copy link

suijunqiang commented Jun 5, 2019

The fireworks SVG plugging can not download anymore.
Is there any plugging replacement? because I am using fireworks CS6 and could not find any plugging easy to export SVG file only.

@WebSpectrum
Copy link

did not work for me (Adobe Fireworks CS6 on Windows

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment