SVG to PNG with Embedded Style Sheet - css

I'm interested in creating a PNG from SVG. I followed the code given in:
https://developer.mozilla.org/en-US/docs/HTML/Canvas/Drawing_DOM_objects_into_a_canvas
But the image does not come out right due to styling from CSS. I made a local CSS file and do an import into the SVG, as described in:
How to apply a style to an embedded SVG?
But it does not appear to be using the style sheet. Any ideas why I would have this error?
Thanks.

Have a look at PhantomJS - You need to install it then either write your own script or run something along these lines:
phantomjs rasterize.js http://ariya.github.com/svg/tiger.svg tiger.png
You can also save to PDF, set Zoom setting, etc.

You could use html2canvas to generate a canvas from any dom element (including svg elements).
However, style definitions for svg elements defined in stylesheets are not applied to the generated canvas. This can be patched by adding style definitions to the svg elements before calling html2canvas.
Inspired on this article, I've created this:
function generateStyleDefs(svgDomElement) {
var styleDefs = "";
var sheets = document.styleSheets;
for (var i = 0; i < sheets.length; i++) {
var rules = sheets[i].cssRules;
for (var j = 0; j < rules.length; j++) {
var rule = rules[j];
if (rule.style) {
var selectorText = rule.selectorText;
var elems = svgDomElement.querySelectorAll(selectorText);
if (elems.length) {
styleDefs += selectorText + " { " + rule.style.cssText + " }\n";
}
}
}
}
var s = document.createElement('style');
s.setAttribute('type', 'text/css');
s.innerHTML = "<![CDATA[\n" + styleDefs + "\n]]>";
//somehow cdata section doesn't always work; you could use this instead:
//s.innerHTML = styleDefs;
var defs = document.createElement('defs');
defs.appendChild(s);
svgDomElement.insertBefore(defs, svgDomElement.firstChild);
}
// generate style definitions on the svg element(s)
generateStyleDefs(document.getElementById('svgElementId'));
// after generating the style defintions, call html2canvas
html2canvas(document.getElementById('idOfElement')).then(function(canvas) {
document.body.appendChild(canvas);
});

The example at
"How to apply a style to an embedded SVG?" as you mentioned should work. You need to define youObjectElement in this line of code when you test it.
var svgDoc = yourObjectElement.contentDocument;
try again.

Related

RStudio Visual editor - data.frame preview too narrow, does not resize

I'm trying to use the RStudio Visual Editor more for Quarto notebooks.
I really like it, but I there's an issue that makes it unusable: when I print a data.frame from a chunk, it gets printed in a very narrow rectangle that does not scale up (as it would with the normal source editor).
This makes it unusable because often I can't see the full variable names or the full values.
I tried looking at the options (Global Options > R Markdown > Visual) but I can't find anything that changes this: Editor content width only changes the width of the text and code, not the chunk output.
Coercing a data.frame to tibble also doesn't fix this.
I'm using RStudio 2022.07.2 Build 576.
Alright I know I answered this question before. I can't find that question or my answer in Stack Overflow, but I found the script I used to create & test the answer. I tested it because I think it was a few years ago. Everything seems to work.
This isn't a permanent solution. It will persist until you restart RStudio.
Step 1: Right-click anywhere in RStudio and select 'Inspect Element' from the dropdown to open developer tools. If you weren't aware, it's just like a browser that way.
Step 2: You need to use the console. Whether you use the console drawer (three dots) or the console tab (top of the inspector window, second from the left).
The first JS function
At the cursor, you'll paste two javascript functions. I store these in an RMD, within a JS chunk. They won't do anything when you knit.
allOf = document.querySelectorAll('[data-ordinal]'); //find them all
if (allOf==undefined || allOf==null) {
allOf = document.querySelector('[data-ordinal]'); // if there is only one
}
sAdd = "max-width: none;" // create style to add
try{
for(i = 0, n = allOf.length; i < n; i++){ //loop through them all
styler = allOf[i].getAttribute("style");
if (styler==undefined || styler==null) { // if there are no HTML styles set
styler = "";
console.log("No style attributes found for ", i);
}
if (styler!="width: 100%; max-width: 800px;") {// if this isn't a chunk output as expected
continue;
console.log("Attributes not changed for ", i);
}
allOf[i].setAttribute("style",styler+sAdd);
console.log("Attributes set for ", i);
}} catch(error) {
console.error(error);
}
The second JS function
allMore = document.querySelectorAll('.ace_lineWidgetContainer > div'); // first child of class
if (allMore==undefined || allMore==null) {
allMore = document.querySelector('.ace_lineWidgetContainer > div'); // if there is only one
}
sAdd2 = "overflow: visible;" // create style to add
try{
for(j = 0, m = allMore.length; j < m; j++){ //loop through them all
styler2 = allMore[j].getAttribute("style");
if (styler2==undefined || styler2==null) { // if there are no HTML styles set
styler2 = "";
console.log("No styles were found for ", j)
}
allMore[j].setAttribute("style",styler2+sAdd2); // append new styles
allMore[j].style.height = null; // remove the height style
console.log("Attributes set for ", j);
}} catch(error) {
console.error(error);
}
The console after entering the functions.
The inline rendered chunks before and after side by side

How do I insert line breaks in an HtmlDocument?

I'm using the Html Agility Pack to copy html content from several files that only contain body elements and their inner HTML, into a single, new html file. The essential code that does this is as follows. The _pageDoc is the HtmlDocument for the new file, and contentNode is a child of that doc, while for each file I build a div and append it to contentNode.
var contents = GetContentDescs(courseId);
foreach (var content in contents)
{
var html = File.ReadAllText(Path.Combine(HtmlDir, content.ContentId + ".html"));
var contentDoc = new HtmlDocument();
contentDoc.LoadHtml(html);
var bodyNode = contentDoc.DocumentNode.Descendants("body").Single();
var contentDiv = _pageDoc.CreateElement("div");
contentDiv.InnerHtml = bodyNode.InnerHtml;
contentNode.AppendChild(contentDiv);
}
This works as expected and the rendered html is perfect, but every contentDiv is on one line, and not very readable. How can I insert a line break in the html file (vs in the rendered html) between each contentDiv?
Something like
HtmlNode linebreak = doc.CreateElement("br");
var contentDivs= doc.DocumentNode.SelectNodes("contentDiv");
for (int i = 0; i < contentDivs.Count; i++)
{
if (i > 0)
{
doc.DocumentNode.InsertBefore(linebreak1, contentDivs[i]);
}
}
I've put up some dirty code by reading documentation but I've never used Agility pack

SVG internal url links and iframes on wirecloud

Wirecloud uses iframes to render the html of widgets. This seems to create problems for some advanced svg features that require references to internal definitions (see also this discussion).
The js code producing the svg works fine in single page app or django view.
There are no error messages.
The entire code is too big to post here, but the key relevant elements are:
var canvas = document.getElementById("canvas");
var svgns = "http://www.w3.org/2000/svg";
var svg = document.createElementNS(svgns, 'svg');
then a typical definition (for example a gradient):
var defs = document.createElementNS(svgns, "defs");
var linearGradient = document.createElementNS(svgns, "linearGradient");
and finally the use of the definition
arc.setAttribute('style', "fill:url(about:srcdoc#linearGradient);");
It is the reference (here to #linearGradient) that is not properly linked to when the svg object is created (when inspecting the created graphics the fill attribute is null)
Normally in svg you just use url(#reference). Have tried "about:blank" and "about:srcdoc" as workarounds as suggested elsewhere on SO but somehow they don't seem to work (chrome / firefox)
This seems to be an issue with svg / iframe (not wirecloud specific) but I have not tried to render the svg in iframe outside wirecloud to confirm this.
If there is no workaround this would limit the type of svg graphics that can be rendered within a wirecloud widget programmatically using javascript. Maybe some of the other ways of embedding svg would work (fetching it from the server) but its not optimal design for interactive widgets, or maybe an iframe alternative :-)
All the architecture of WireCloud is based on iframes. Those elements are used for providing the isolation between each of the widgets and operators and, currently, cannot be disabled. Moreover, the proposed alternative (webcomponents) is not matured enough to be a real alternative :(.
Anyway, WireCloud doesn't make use of the srcdoc attribute, so your problem rendering SVG images inside widgets should not be related to the linked question. The problem seems to be related to the use of the <base> element (which is injected by WireCloud into the HTML of the widget). See this answer for more details. We have created a ticket for analysing if we can remove the <base> element, but for now, you will have to use absolute URLs. E.g.:
var baseUrl = window.location.origin + window.location.pathname + window.location.search;
arc.setAttribute("fill", "url(" + baseUrl + "#MyGradient)");
This is the full code of my "SVG example widget":
var baseUrl = window.location.origin + window.location.pathname + window.location.search;
var svgns = "http://www.w3.org/2000/svg";
var svg = document.createElementNS(svgns, 'svg');
document.body.appendChild(svg);
var defs = document.createElementNS(svgns, "defs");
var linearGradient = document.createElementNS(svgns, "linearGradient");
linearGradient.setAttribute("id", "MyGradient");
defs.appendChild(linearGradient);
svg.appendChild(defs);
var stop = document.createElementNS(svgns, "stop");
stop.setAttribute("offset", "5%");
stop.setAttribute("stop-color", "green");
linearGradient.appendChild(stop);
stop = document.createElementNS(svgns, "stop");
stop.setAttribute("offset", "95%");
stop.setAttribute("stop-color", "gold");
linearGradient.appendChild(stop);
var rect = document.createElementNS(svgns, "rect");
rect.setAttribute("x", "10");
rect.setAttribute("y", "10");
rect.setAttribute("width", "100");
rect.setAttribute("height", "100");
rect.setAttribute("fill", "url(" + baseUrl + "#MyGradient)");

How to attach style when convert svg to image

All:
Right now, I want to draw a svg and style it with external style. Everything goes well until I try to down load it:
function chartExporter(svg){
// svg is a D3 object
svg.attr("version", 1.1)
.attr("xmlns", "http://www.w3.org/2000/svg");
var svgDOM = svg.node();
var html = svgDOM.outerHTML;
var imgsrc = 'data:image/svg+xml;base64,'+ btoa(html);
imgobj.src = imgsrc;
imgobj.onload = function(){
var a = document.createElement("a");
a.download = "sample.svg";
a.href = imgsrc;
a.click();
}
}
The problem here is: If I use D3 to add inline style or attributes, the svg downloaded seems good, but if I use external css file to include style, those styles can not be applied to svg when converted( it make sense, cos they are not in that html string), I wonder how to bring those style into svg?
Thanks

Styling UITextField

I've been playing around with different methods of determining at runtime the width of a "label" so that I can resize the "label" because I don't want it to truncate. I've finally found an easy solution through UITextField which allows me to set the .autoSize which is great! However, now I'm trying to "style" (simply adjust font and font size) of the UITextField but it seems that I have to do it manually with '.htmlText' (which I'll gladly accept if that is the ONLY way).
I'm using the .text to set the value of the label.
My test case involves a HBox (I'm actually using a Grid but they should be the same and I've done testing on both):
I style the HBox and the style carries through to the UITextField. I don't believe this will work for me because I have other components inside that I need to style differently.
I've tried: UITextFormat and TextFormat (I see that the .htmlText being updated accordingly but the output doesn't update. Then I noticed that whenever I called hbox.addChild(myUITextField) it would override the .htmlText
I've tried setting the style with myUITextField.setStyle("fontSize", 20) before and/or after the call to addChild neither of which made an impact on the display as per what I noted above.
Changes are being made but they seem to be overrided when I add it to the display.
So what do I need to do in order to style the UITextField aside from manually setting it along with my contents in .htmlText? Solutions not using UITextField is fine as long as there is some easy way of not truncating the text.
EDIT: I want to just do textField.setStyle('fontSize', 20) and expect that every time I change the text, I wouldn't need to use HTML to go with it (so I can just do textField.text = 'something else' and expect that it will still have a font size of 20). This is what I meant by not using .htmlText (sorry if I wasn't clear before).
2nd EDIT: I guess I should present the whole issue and maybe that'll clarify what I did wrong or couldn't achieve.
My intent is to have a Grid and add text into it. I do not want it to wrap or scroll so I add it to the next row in the Grid when the current row's children total width exceeds some number. In order to add it to the next row, I need to be able to calculate the width of the text. I would like to be able to style that text individually based on cases and there might be other components (like a TextInput). Essentially what I'm trying to accomplish is "Fill in the Blank".
I've included code to show what I'm currently doing and it works somewhat. It might be un-related to the original issue of styling but I can't figure out how to adjust the distance between each UITextField but aside from that this fits what I would like to accomplish. Relevant to the question is: I would like to change the way I style each UITextField (currently setting .htmlText) into something a bit straightforward though like I previously mentioned I'll gladly accept using .htmlText if that's the only solution.
So I have a Grid with x Rows in it and in each row, I have exactly one GridItem. Based on the input, I add UITextField and TextInput into the GridItem going on to the next GridItem when necessary. If you have a better way of doing so then that would be better but I guess what I really want is to find a different way of styling.
Also another problem, I'm not sure of the exact way to add a TextField into the display. I tried:
var t : TextField = new TextField();
t.text = "I'm a TextField";
hBox.addChild(t); // doesn't work
//this.addChild(t); // doesn't work either
But I get the following error:
TypeError: Error #1034: Type Coercion failed: cannot convert flash.text::TextField#172c8f9 to mx.core.IUIComponent.
Here's what I have that's working.
private function styleQuestionString(str : String) : String {
return '<FONT leading="1" face="verdana" size="20">' + str + '</FONT>';
}
private function loadQuestion(str : String) : void {
/* Split the string */
var tmp : Array = str.split("_");
/* Track the current width of the GridItem */
var curWidth : int = 0;
/* Display components that we will add */
var txtField : UITextField = null;
var txtInput : TextInput = null;
/* Track the current GridItem */
var curGridItem : GridItem = null;
/* Track the GridItem we can use */
var gridItemAC : ArrayCollection = new ArrayCollection();
var i : int = 0;
/* Grab the first GridItem from each GridRow of Grid */
var tmpChildArray : Array = questionGrid.getChildren();
for (i = 0; i < tmpChildArray.length; i++) {
gridItemAC.addItem((tmpChildArray[i] as GridRow).getChildAt(0));
}
curGridItem = gridItemAC[0];
gridItemAC.removeItemAt(0);
/* Used to set the tab index of the TextInput */
var txtInputCounter : int = 1;
var txtFieldFormat : UITextFormat = new UITextFormat(this.systemManager);
txtFieldFormat.leading = "1";
//var txtFieldFormat : TextFormat = new TextFormat();
//txtFieldFormat.size = 20;
/* Proper Order
txtField = new UITextField();
txtField.text = tmp[curItem];
txtField.autoSize = TextFieldAutoSize.LEFT;
txtField.setTextFormat(txtFieldFormat);
*/
var txtLineMetrics : TextLineMetrics = null;
var tmpArray : Array = null;
curGridItem.setStyle("leading", "1");
var displayObj : DisplayObject = null;
for (var curItem : int= 0; curItem < tmp.length; curItem++) {
/* Using UITextField because it can be auto-sized! */
/** CORRECT BLOCK (ver 1)
txtField = new UITextField();
txtField.text = tmp[curItem];
txtField.autoSize = TextFieldAutoSize.LEFT;
txtField.setTextFormat(txtFieldFormat);
***/
tmpArray = (tmp[curItem] as String).split(" ");
for (i = 0; i < tmpArray.length; i++) {
if (tmpArray[i] as String != "") {
txtField = new UITextField();
txtField.htmlText = styleQuestionString(tmpArray[i] as String);
//txtField.setTextFormat(txtFieldFormat); // No impact on output
txtLineMetrics = curGridItem.measureHTMLText(txtField.htmlText);
curWidth += txtLineMetrics.width + 2;
if (curWidth >= 670) {
curGridItem = gridItemAC[0];
curGridItem.setStyle("leading", "1");
if (gridItemAC.length != 1) {
gridItemAC.removeItemAt(0);
}
// TODO Configure the proper gap distance
curWidth = txtLineMetrics.width + 2;
}
displayObj = curGridItem.addChild(txtField);
}
}
//txtField.setColor(0xFF0000); // WORKS
if (curItem != tmp.length - 1) {
txtInput = new TextInput();
txtInput.tabIndex = txtInputCounter;
txtInput.setStyle("fontSize", 12);
txtInputCounter++;
txtInput.setStyle("textAlign", "center");
txtInput.width = TEXT_INPUT_WIDTH;
curWidth += TEXT_INPUT_WIDTH;
if (curWidth >= 670) {
curGridItem = gridItemAC[0];
if (gridItemAC.length != 1) {
gridItemAC.removeItemAt(0);
}
// TODO Decide if we need to add a buffer
curWidth = TEXT_INPUT_WIDTH + 2;
}
curGridItem.addChild(txtInput);
txtInputAC.addItem(txtInput);
/* Adds event listener so that we can perform dragging into the TextInput */
txtInput.addEventListener(DragEvent.DRAG_ENTER, dragEnterHandler);
txtInput.addEventListener(DragEvent.DRAG_DROP, dragDropHandler);
txtInput.addEventListener(DragEvent.DRAG_EXIT, dragExitHandler);
}
/* Add event so that this label can be dragged */
//txtField.addEventListener(MouseEvent.MOUSE_MOVE, dragThisLabel(event, txtField.text));
}
}
After about 8 hours of searching for a solution to what would seem to be such a simple issue I FINALLY stumbled on your posts here... Thankyou!!!
I have been stumbling around trying to get TextField to work and had no Joy, Label was fine, but limited formatting, and I need to be able to use embedded fonts and rotate. After reading the above this finally worked for me:
var myFormat:TextFormat = new TextFormat();
myFormat.align = "center";
myFormat.font = "myFont";
myFormat.size = 14;
myFormat.color = 0xFFFFFF;
var newTxt:UITextField = new UITextField();
newTxt.text = "HELLO";
addChild(newTxt);
newTxt.validateNow();
newTxt.setTextFormat(myFormat);
The order of addChild before the final 2 steps was critical! (myFont is an embedded font I am using).
One again... a thousand thankyou's...
John
EDIT BASED ON THE ASKERS FEEDBACK:
I didn't realize you wanted to just apply one style to the whole textfield, I thought you wanted to style individual parts. This is even simpler for you, won't give you any trouble at all :)
var textFormat: TextFormat = new TextFormat("Arial", 12, 0xFF0000);
myText.setTextFormat(textFormat);
Be aware that this sets the style to the text that is in the TextField, not necessarily future text you put in. So have your text in the field before you call setTextFormat, and set it again every time you change it just to be sure it stays.
It's probably best if you use a normal TextField as opposed to the component. If you still want the component you may need to call textArea.validateNow() to get it to update with the new style (not 100% sure on that one though) Adobe components are notoriously bad, and should be avoided. :(
To see all available options on the TextFormat object see here
END EDIT ---------
This is easy enough to just do with CSS in a normal old TextField.
var myCSS: String = "Have some CSS here, probably from a loaded file";
var myHTML: String = "Have your HTML text here, and have it use the CSS styles";
// assuming your textfield's name is myText
var styleSheet: StyleSheet = new StyleSheet();
styleSheet.parseCSS(myCSS);
myText.autoSize = TextFieldAutoSize.LEFT;
myText.styleSheet = styleSheet;
myText.htmlText = myHTML;
Supported HTML tags can be found here
Supported CSS can be found here
The reason you have a problem adding Textfield to containers is that it doesn't implement the IUIComponent interface. You need to use UITextField if you want to add it. However, that's presenting me with my own styling issues that brought me to this question.
A few things I know:
TextField is styled using the TextFormat definition, and applying it to the textfield. As Bryan said, order matters.
setStyle does nothing on IUITextField, and the TextFormat method doesn't seem to work the same as in normal TextFields. (Edit #2: Ahah. You need to override the "validateNow" function on UITextFields to use the setTextFormat function)
To autosize a TextArea, you need to do something like this (inheriting from TextArea):
import mx.core.mx_internal;
use namespace mx_internal;
...
super.mx_internal::getTextField().autoSize = TextFieldAutoSize.LEFT;
this.height = super.mx_internal::getTextField().height;
Found this code on, I think, on StackOverflow a while back. Apologies to the original author. But the idea is that you need to access the "mx_internal" raw textfield.
Text and TextArea have wrapping options. (Label does not). So if you set the explicit width of a Text object, you might be able to size using the measuredHeight option and avoid truncation.
(edit: That was #4, but stackoverflow parsed it into a 1...)

Resources