In my angular app I transform an SVG to an Image using canvg. Before applying canvg I attach style rules to the serialized svg string. See code:
var svg = document.getElementsByTagName("svg");
var svgClone = svg[0].cloneNode(true);
var viewbox = svgClone.getAttribute( 'viewBox' ).split( ' ' )
var width = 2*viewbox[2] - viewbox[0];
var height = 2*viewbox[3] - viewbox[1];
svgClone.setAttribute("width", width);
svgClone.setAttribute("height", height);
console.log(svgClone)
var serializer = new XMLSerializer();
var svgString;
var canvas = document.getElementById("empty-canvas");
var str;
var styleTag;
var style = "\n";
for (var i = 1; i < document.styleSheets.length; i++) {
str = document.styleSheets[i].href.split("/");
if (str[str.length - 1] == "svg.css") {
var rules = document.styleSheets[i].rules;
if (!rules) {
var rules = document.styleSheets[i].cssRules; //firefox
};
for (var j = 0; j < rules.length; j++) {
style += (rules[j].cssText + "\n");
}
break;
}
}
styleTag = "<style type='text/css'>" + style + "</style>";
$(svgClone).prepend(styleTag);
svgString = serializer.serializeToString(svgClone);
canvg(canvas, svgString, {
renderCallback: function() {
var pngImage = canvas.toDataURL('image/png');
$scope.image = pngImage;
}
});
This works fine in development mode. However when I minify all js files for production mode this code does not seem to work anymore because the resulting images lack styling (some colors, font-size etc.).
I'm pretty sure the problem is that the minified code is not prepending the style tag but I am not sure how to solve it.
Any hints are appreciated
Jean
Looks like it was an issue with the file that is being specified
Related
I'm trying to speed up my workflow. What i have is a picture of food, that needs to be exported with different colored backgrounds.
Right now I'm hiding the colored background layers one by one, as i export the jpgs. But i feel there has to be a quicker way to do this?
Any help or tips would be much appreciated.
Assuming the bottom most layer is the Background Layer, above that you have three images which are coloured backgrounds. Above those is your art.
it's just a case of identifying which layers are which and first switching them all OFF and then ON on turn.
var srcDoc = app.activeDocument;
var numOfLayers = srcDoc.layers.length;
var n = (numOfLayers - backgrounds.length)-1;
var backgrounds = ["Red", "Yellow", "Blue"];
// switch backgrounds OFF
for (var i = n; i < numOfLayers-1; i++)
{
srcDoc.layers[i].visible = false;
}
// switch them ON one at at time
for (var i = n; i < numOfLayers-1; i++)
{
srcDoc.layers[i].visible = true;
// save
var myFileName = "C:\\temp\\my_picture_" + i + ".jpg";
save_as_jpg(myFileName);
// Switch it off again
srcDoc.layers[i].visible = false;
}
function save_as_jpg(afilepath)
{
duplicate_it();
// Flatten the jpg
activeDocument.flatten();
// jpg file options
var jpgFile = new File(afilepath);
jpgSaveOptions = new JPEGSaveOptions();
jpgSaveOptions.formatOptions = FormatOptions.OPTIMIZEDBASELINE;
jpgSaveOptions.embedColorProfile = true;
jpgSaveOptions.matte = MatteType.NONE;
jpgSaveOptions.quality = 12;
activeDocument.saveAs(jpgFile, jpgSaveOptions, true, Extension.LOWERCASE);
//close without saving
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
function duplicate_it()
{
// duplicate image into new document
var str = "temp";
var id428 = charIDToTypeID( "Dplc" );
var desc92 = new ActionDescriptor();
var id429 = charIDToTypeID( "null" );
var ref27 = new ActionReference();
var id430 = charIDToTypeID( "Dcmn" );
var id431 = charIDToTypeID( "Ordn" );
var id432 = charIDToTypeID( "Frst" );
ref27.putEnumerated( id430, id431, id432 );
desc92.putReference( id429, ref27 );
var id433 = charIDToTypeID( "Nm " );
desc92.putString( id433, str ); // name
executeAction( id428, desc92, DialogModes.NO );
}
I am trying to convert an external svg icon to a base64 png using a canvas. It is working in all browsers except Firefox, which throws an error "NS_ERROR_NOT_AVAILABLE".
var img = new Image();
img.src = "icon.svg";
img.onload = function() {
var canvas = document.createElement("canvas");
canvas.width = this.width;
canvas.height = this.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(this, 0, 0);
var dataURL = canvas.toDataURL("image/png");
return dataURL;
};
Can anyone help me on this please? Thanks in advance.
Firefox does not support drawing SVG images to canvas unless the svg file has width/height attributes on the root <svg> element and those width/height attributes are not percentages. This is a longstanding bug.
You will need to edit the icon.svg file so it meets the above criteria.
As mentioned, this is an open bug caused by limitations on what Firefox accepts as specification for SVG sizes when drawing to a canvas. There is a workaround.
Firefox requires explicit width and height attributes in the SVG itself. We can add these by getting the SVG as XML and modifying it.
var img = new Image();
var src = "icon.svg";
// request the XML of your svg file
var request = new XMLHttpRequest();
request.open('GET', src, true)
request.onload = function() {
// once the request returns, parse the response and get the SVG
var parser = new DOMParser();
var result = parser.parseFromString(request.responseText, 'text/xml');
var inlineSVG = result.getElementsByTagName("svg")[0];
// add the attributes Firefox needs. These should be absolute values, not relative
inlineSVG.setAttribute('width', '48px');
inlineSVG.setAttribute('height', '48px');
// convert the SVG to a data uri
var svg64 = btoa(new XMLSerializer().serializeToString(inlineSVG));
var image64 = 'data:image/svg+xml;base64,' + svg64;
// set that as your image source
img.src = img64;
// do your canvas work
img.onload = function() {
var canvas = document.createElement("canvas");
canvas.width = this.width;
canvas.height = this.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(this, 0, 0);
var dataURL = canvas.toDataURL("image/png");
return dataURL;
};
}
// send the request
request.send();
This is the most basic version of this solution, and includes no handling for errors when retrieving the XML. Better error handling is demonstrated in this inline-svg handler (circa line 110) from which I derived part of this method.
This isn't the most robust solution, but this hack worked for our purposes. Extract viewBox data and use these dimensions for the width/height attributes.
This only works if the first viewBox encountered has a size that accurately can represent the size of the SVG document, which will not be true for all cases.
// #svgDoc is some SVG document.
let svgSize = getSvgViewBox(svgDoc);
// No SVG size?
if (!svgSize.width || !svgSize.height) {
console.log('Image is missing width or height');
// Have size, resolve with new SVG image data.
} else {
// Rewrite SVG doc
let unit = 'px';
$('svg', svgDoc).attr('width', svgSize.width + unit);
$('svg', svgDoc).attr('height', svgSize.height + unit);
// Get data URL for new SVG.
let svgDataUrl = svgDocToDataURL(svgDoc);
}
function getSvgViewBox(svgDoc) {
if (svgDoc) {
// Get viewBox from SVG doc.
let viewBox = $(svgDoc).find('svg').prop('viewBox').baseVal;
// Have viewBox?
if (viewBox) {
return {
width: viewBox.width,
height: viewBox.height
}
}
}
// If here, no viewBox found so return null case.
return {
width: null,
height: null
}
}
function svgDocToDataURL(svgDoc, base64) {
// Set SVG prefix.
const svgPrefix = "data:image/svg+xml;";
// Serialize SVG doc.
var svgData = new XMLSerializer().serializeToString(svgDoc);
// Base64? Return Base64-encoding for data URL.
if (base64) {
var base64Data = btoa(svgData);
return svgPrefix + "base64," + base64Data;
// Nope, not Base64. Return URL-encoding for data URL.
} else {
var urlData = encodeURIComponent(svgData);
return svgPrefix + "charset=utf8," + urlData;
}
}
I'm pretty basic on querySelector, so I excuse me if my question is pretty basic but I'm not able to find the solution online
Basically i'm trying to set the width of a table header based on the width of the cell under it
The code i did until now is this :
var headerElement = elem.querySelector('thead tr:first-child th:first-child');
var rowElement = elem.querySelector ('tbody tr:first-child td:first-child');
headerElement.style.width = (rowElement.scrollWidth) + 'px';
what i want to achieve/understand is how to dinamically loop through all "headers object" in that table and using the same "index" to iterate the row under to have something like this:
while (headerElements > i){
var headerElement = elem.querySelector('thead tr:first-child th:i');
var rowElement = elem.querySelector ('tbody tr:first-child td:i');
headerElement.style.width = (rowElement.scrollWidth) + 'px';
}
I resolved it this way:
var header = elem.querySelector('thead tr:first-child');
var row = elem.querySelector ('tbody tr:first-child');
for (i=0;header.cells.length > i;i++){
header.cells[i].style.width = (row.cells[i].scrollWidth) + 'px';
}
Below is code that populates a menu. Everything seems to work great, with no errors thrown, except for one crucial part. My megaPages array has the values ["HOME","BABIES","BRIDALS","MISC","WEDDINGS","ABOUT"], but the actual text that displays on screen (which is produced by megaPages) is like this:
As you can see, some of the text is arbitrarily being truncated. I've traced the text strings as they get passed through the various functions at various stages of the menu-build, and they are always right, but somehow when each DisplayObject make it on screen, letters get ommitted (notice though that 'HOME' abd 'ABOUT' are fine). I don't even know where to start with this problem.
function buildMenu() {
var itemMCs = new Array();
for (var i = 0; i < megaPages.length; i++) {
megaPages[i] = megaPages[i].toUpperCase();
trace(megaPages[i]); // at each iteration, traces as follows "HOME","BABIES","BRIDALS","MISC","WEDDINGS","ABOUT"
var textMC = createText(megaPages[i]);
var itemMC = new MovieClip();
if (i!=0) {
var newLink = new PlateLink();
newLink.y = 0;
itemMC.addChild(newLink);
}
var newPlate = new Plate();
if (i==0) {
newPlate.y = 0;
} else {
newPlate.y = newLink.height - 2;
}
newPlate.x = 0;
newPlate.width = textMC.width + (plateMargin*2);
itemMC.addChild(newPlate);
if (i!=0) {
newLink.x = (newPlate.width/2) - (newLink.width/2);
}
textMC.x = plateMargin;
textMC.y = newPlate.y + .5;
itemMC.addChild(textMC);
itemMCs.push(itemMC);
itemMC.x = (homeplateref.x + (homeplateref.width/2)) - (itemMC.width/2);
if (i==0) {
itemMC.y = homeplateref.y;
} else {
itemMC.y = itemMCs[i-1].y + (itemMCs[i-1].height - 6);
}
menuRef.addChild(itemMC);
}
}
function createText(menuTitle) {
trace(menuTitle);
var textContainer : MovieClip = new MovieClip();
var myFont = new Font1();
var backText = instantText(menuTitle, 0x000000);
backText.x = 1;
backText.y = 1;
var frontText = instantText(menuTitle, 0xFFFFFF);
frontText.x = 0;
frontText.y = 0;
textContainer.addChild(backText);
textContainer.addChild(frontText);
return textContainer;
}
function instantText(textContent, color) {
trace(textContent); // again, traces the right text each time it is fired
var myFont = new Font1();
var myFormat:TextFormat = new TextFormat();
myFormat.size = 18;
myFormat.align = TextFormatAlign.CENTER;
myFormat.font = myFont.fontName;
var myText:TextField = new TextField();
myText.defaultTextFormat = myFormat;
myText.embedFonts = true;
myText.antiAliasType = AntiAliasType.ADVANCED;
myText.text = textContent;
myText.textColor = color;
myText.autoSize = TextFieldAutoSize.LEFT;
trace(myText.text);
return myText;
}
You need to embed all the necessary characters for the font you're using.
For textfields created in Flash:
Select the TextField, and hit the 'Embed' button in the properties panel.
For dynamically created textfields:
When you set the font to export (Font1 in your case) make sure to include all the characters you need.
You can choose to embed all uppercase characters, or just type in the ones you need for those specific menu items.
I'm using ComboBox and FilteringSelect in a dialog and have yet been unable to make the controls have the minimal required width only, i.e. being just large enough to display the longest text from a drop-down list. Also the control must not be set to a fixed width since the actual content of the drop-down lists gets filled in from a translation database.
In plain html with a simple input of type text it works smooth just by default. However since even all examples at dojotoolkit.org show the very same behavior it seems to me that dojo introduces a minimum width for all those input controls. Thus I wonder if it can be done at all...
Thanks in advance!
I had the same problem; after some struggle, I decided to adapt this to my problem.
In my case, I was forced to use an old version of dojo, and the FilteringSelect were declarative, so I had to use a hack (the last three lines of the code below) to be sure my function would be executed at the right time.
So, the function below takes all dijit widgets, looks for those stored element is a select (getAllDropdowns), and for each it takes its options, copies the content in a new element moved outside of the visible screen and takes the width of that element, adjusted with padding (this may not be your case, so check getWidth); then it takes the max of those widths and compare it with the current length of the input element, and if the longest option is bigger, adjust the input and the outmost div widths.
This answer comes quite late, but since it was not easy for me to come to this solution, I thought it may be worth sharing.
// change dropdowns width to fit the largest option
function fixDropdownWidth() {
var getAllDropdowns = function() {
var dropdowns = [];
dijit.registry.forEach(function(widget, idx, hash) {
if (widget.store) {
var root = widget.store.root;
if (root && root.nodeName.toLowerCase() == 'select') {
dropdowns.push(widget);
}
}
});
return dropdowns;
};
var getTesterElement = function() {
var ret = dojo.query('tester');
if (ret.length) {
return ret;
}
else {
document.body.appendChild(document.createElement('tester'));
return dojo.query('tester');
}
};
var getWidth = function(el) {
var style = dojo.getComputedStyle(el);
return el.clientWidth + parseInt(style.paddingLeft) + parseInt(style.paddingRight);
};
var getOptionWidth = function(option) {
var testEl = getTesterElement();
testEl[0].innerHTML = option.innerHTML;
return getWidth(testEl[0]);
};
var dropdowns = getAllDropdowns();
var testEl = getTesterElement();
dojo.style(testEl[0], {
position: 'absolute',
top: -9999,
left: -9999,
width: 'auto',
whiteSpace: 'nowrap'
});
for (var i = 0; i < dropdowns.length; i++) {
var input = dropdowns[i].textbox;
dojo.style(testEl[0], {
fontSize: dojo.style(input, 'fontSize'),
fontFamily: dojo.style(input, 'fontFamily'),
fontWeight: dojo.style(input, 'fontWeight'),
letterSpacing: dojo.style(input, 'letterSpacing')
});
var max = 0;
var treshold = 5;
dojo.query('option', dropdowns[i].store.root).forEach(function(el, idx, list) {
max = Math.max(max, getOptionWidth(el) + treshold);
});
if (max > getWidth(dropdowns[i].textbox)) {
var icon = dojo.query('.dijitValidationIcon', dropdowns[i].domNode)[0];
dojo.style(dropdowns[i].textbox, {width: max + 'px'});
var width = max + getWidth(icon) + getWidth(dropdowns[i].downArrowNode) + treshold;
dojo.style(dropdowns[i].domNode, {
width: width + 'px'
});
}
}
}
dojo.addOnLoad(function() {
dojo._loaders.push(fixDropdownWidth);
});
var dropDowns = [];
var getAllDropdowns = function (dropDowns) {
array.forEach(dijit.registry.toArray(), function (widget) {
if (widget.store) {
if (widget.domNode.classList.contains("dijitComboBox")) {
dropDowns.push(widget);
}
}
});
};
getAllDropdowns(dropDowns);
var maxLength = 0;
array.forEach(dropDowns, function (dropDown) {
var opts = dropDown.get("store").data;
array.forEach(opts, function (option) {
var optionValue = option[dropDown.get("searchAttr")];
var dropDownCurrentStyle = window.getComputedStyle(dropDown.domNode);
var currentOptionWidth = getTextWidth(optionValue, dropDownCurrentStyle.fontStyle, dropDownCurrentStyle.fontVariant, dropDownCurrentStyle.fontWeight, dropDownCurrentStyle.fontSize, dropDownCurrentStyle.fontFamily);
if (currentOptionWidth > maxLength) {
maxLength = currentOptionWidth;
}
});
dropDown.domNode.style.width = maxLength + "px";
maxLength = 0;
});
function getTextWidth(text, fontStyle, fontVariant, fontWeight, fontSize, fontFamily) {
// re-use canvas object for better performance
var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
var context = canvas.getContext("2d");
var font = fontStyle + " " + fontVariant + " " + fontWeight + " " + fontSize + " " + fontFamily;
context.font = font;
canvas.innerText = text;
var metrics = context.measureText(text);
return metrics.width + 25; //change this to what you need it to be
}