Html2Canvas does not render my svg element (lastest version) - html

I have a roadmap with svg elements to represent links between two items like this :
the svg code looks like :
each time that i try to print to png, my links are not rendered:
My generation code is :
html2canvas(
document.getElementById("roadmapPrint"),
{
backgroundColor:null,
}
).then(function(canvas) {
var image = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
var a = document.createElement('a');
a.href = image ;
a.id = "tempAtoDelete"
a.download = "filename.png";
document.body.appendChild(a);
a.click();
let aToDelete = document.getElementById("tempAtoDelete");
document.body.removeChild(aToDelete);})
Do you have some advices ? i read that the latest version of html2canvas library is able to manage svg elements, is it the case ?
Thank you a lot for your help,

I have fixed my issue converting all svg elements to canvas before use html2canvas librairy.
This was an old solution (6 years old) but it works fine.
See my steps below:
install the version 1.5.3 of canvg librairy and import the canvg function (for information this version is dowloaded more than 140000 per week..) (there is no dependence vulnerability with the last version of node/npm/CRA)
npm install canvg#1.5.3
import canvg from 'canvg'
get all your svg element and create a canvas for each (track them to an array to delete them after)
//FOR EACH SVG ELEMENT, A CANVAS WILL BE CREATED => AT THE END WE NEED TO DELETE THEM
var svgNodesToRemove = [];
//GET ALL SVG ELEMENT
let svgElements = document.body.querySelectorAll('svg');
//LOOP ON EACH ELEMENT
svgElements.forEach(function(item) {
//YOU CAN PROCEED TO ELEMENT TREATMENT BEFORE CONVERT TO CANVAS
let children = item.childNodes
children.forEach(function(child) {
//IN MY CASE I USE ANIMATION FOR MY SVG PATH AND NEED TO FIX THE OPACITY STYLE
child.style.opacity = 1;
})
//TRIM EACH ITEM IN THE LOOP
let svg = item.outerHTML.trim();
//CREATE CANVAS FOR YOUR CURRENT SVG
var canvas = document.createElement('canvas');
//UPDATE THE NEEDED ATTRIBUTE WITH AND HEIGHT
canvas.width = item.getBoundingClientRect().width;
canvas.height = item.getBoundingClientRect().height;
//CONVERT SVG TO CANVAS
canvg(canvas, svg);
//UPDATE THE CANVAS POSITION TO THE SVG ELEMENT
if (item.style.position) {
canvas.style.position += item.style.position;
canvas.style.left += item.style.left;
canvas.style.top += item.style.top;
}
//APPEND THE CANVAS TO THE DOM
item.parentNode.appendChild(canvas);
//TRACK CANVAS TO BE DELTED AFTER
svgNodesToRemove.push(canvas)
});
I did not hidde my current svg element in the dom because there are not rendered...
now you are able to use html2canvas :
html2canvas(
document.getElementById("roadmapPrint"),
{
backgroundColor:null,
}
).then(function(canvas){
//YOUR LOGIC
//....
//DELETE SVG CANVAS
svgNodesToRemove.forEach(function(element){
element.remove();
})
}).catch((err)=>{
//DISPLAY ERROR
console.log(err)
//DELETE SVG CANVAS
svgNodesToRemove.forEach(function(element){
element.remove();
})
});

Related

Download canvas as image with button

What is the id of this canvas: carstenschaefer.github.io/DrawerJs/examples/fullscreen I want to add download button to under this canvas. I imported the source code into vscode. I tried various download codes but none of them worked. I think I'm writing the id wrong.
document.getElementById('download').addEventListener('click', ()=> {
var canva = document.getElementById("canvas");
var image = canva.toDataURL("image/png").replace("image/png", "image/octet-stream");
var element = document.createElement('a');
var filename = 'test.png';
element.setAttribute('href', image);
element.setAttribute('download', filename);
element.click();
})
When you create an instance of drawer
const drawer = new DrawerJs.Drawer(null, { ...options });
You can call
drawer.api.getCanvasAsImage()
To get the canvas converted to base64 which you can then use it to Post to a backend server or download it.
Took at look at download.js maybe you could try using it to help with saving.

Adding static image to Lightswitch HTML 2013 Browse Screen

In my case, I have color coded some tiles in the HTML client and I want to add a simple color code key. I have the PNG file I want to use.
I do not require the ability to upload or change the image.
This link seems to achieve what I am looking for, but I am not sure where to implement. Does all of this code go into the PostRender of the Image Control I created?
Add image to lightswitch using local property and file location
Here is what the PostRender of the simple Image data item I created as an Image Local Property, and then dragged into the Solution Designer. It was basically copied from the link above, but I did change the name of the image file to match mine, and I have already added the item to the Content\Images folder structure and it shows in the file view:
myapp.BrowseOrderLines.ColorKey_postRender = function (element, contentItem) {
// Write code here.
function GetImageProperty(operation) {
var image = new Image();
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
// XMLHttpRequest used to allow external cross-domain resources to be processed using the canvas.
// unlike crossOrigin = "Anonymous", XMLHttpRequest works in IE10 (important for LightSwitch)
// still requires the appropriate server-side ACAO header (see https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image)
var xhr = new XMLHttpRequest();
xhr.onload = function () {
var url = URL.createObjectURL(this.response);
image.onload = function () {
URL.revokeObjectURL(url);
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0);
var dataURL = canvas.toDataURL("image/png");
operation.complete(dataURL.substring(dataURL.indexOf(",") + 1));
};
image.src = url;
};
xhr.open('GET', this.imageSource, true);
xhr.responseType = 'blob';
xhr.send();
};
myapp.BrowseOrderLines.ColorKey_postRender = function (element, contentItem) {
// Write code here.
msls.promiseOperation
(
$.proxy(
GetImageProperty,
{ imageSource: "content/images/Key.png" }
)
).then(
function (result) {
contentItem.screen.ImageProperty = result;
}
);
};
}
Currently, the Image control does show on the screen in the browser, and is the custom size I choose, but it is just a light blue area that does not display my image file.
I am not sure if I have embedded the image? I am not sure if that is a missing step?
Thank you!!
The easiest method of testing this approach would be to change your postRender to the following (which embeds the helper function within the postRender function):
myapp.BrowseOrderLines.ColorKey_postRender = function (element, contentItem) {
function GetImageProperty(imageSource) {
return msls.promiseOperation(
function (operation) {
var image = new Image();
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
// XMLHttpRequest used to allow external cross-domain resources to be processed using the canvas.
// unlike crossOrigin = "Anonymous", XMLHttpRequest works in IE10 (important for LightSwitch)
// still requires the appropriate server-side ACAO header (see https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image)
var xhr = new XMLHttpRequest();
xhr.onload = function () {
var url = URL.createObjectURL(this.response);
image.onload = function () {
URL.revokeObjectURL(url);
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0);
var dataURL = canvas.toDataURL("image/png");
operation.complete(dataURL.substring(dataURL.indexOf(",") + 1));
};
image.onerror = function () {
operation.error("Image load error");
};
image.src = url;
};
xhr.open('GET', imageSource, true);
xhr.responseType = 'blob';
xhr.send();
}
);
};
GetImageProperty("content/images/Key.png").then(function onComplete(result) {
contentItem.screen.ImageProperty = result;
}, function onError(error) {
msls.showMessageBox(error);
});
};
This assumes that you named the local property ImageProperty as per my original post and the Image control on your screen is named ColorKey.
In the above example, I've also taken the opportunity to slightly simplify and improve the code from my original post. It also includes a simple error handler which may flag up if there is a problem with loading the image.
If this still doesn't work, you could test the process by changing the image source file-name to content/images/user-splash-screen.png (this png file should have been added as a matter of course when you created your LightSwitch HTML project).
As the GetImageProperty function is a helper routine, rather than embedding it within the postRender you'd normally place it within a JavaScript helper module. This will allow it to be easily reused without repeating the function's code. If you don't already have such a library and you're interested in implementing one, the following post covers some of details involved in doing this:
Lightswitch HTML global JS file to pass variable

How can I launch a website from a button

I know how to use the createAnchor to place a link in a panel, however I would like to launch a link from a button. Is this possible, if so how?
After some trail and error I found an easier way of achieving the goal.
I just create an image of the button showing my desired text and use thse setAttribute method of the anchor.
Unfortunally I stll require an AbsolutePanel.
function doGet()
{
// Problem with Google Apps :
// Not possible to click a Button and show a site
// Solutions found on StackOverflow mostly use :
// a) var panel = createAbsolutePanel --> Necessary to create an Absolute Panel
// b) var image = createImage --> The image to be shown
// c) var anchor = createAnchor --> Anchor making it possible to activate a url
// d) position the anchor on top of the image
// e) make anchor and image of same size
// f) make anchor invisible (by zIndex, opacity or visibility)
//
// One of the people showing how this works is http://stackoverflow.com/users/1368381/serge-insas who's
// efforts inspired me to have a look at other possibilities. (thanks !)
//
// Result : next step in making it easy to overcome a limitation of GAS --> no createImage required anymore
// How : using setAttribute('backgroundImage', Url) method of anchor
// Limitation : still required to create an Absolute panel instead of a Vertical panel --> who's next to improve ??
//
// Author : SoftwareTester, may 13th, 2014
var app = UiApp.createApplication().setTitle("Image Anchor");
var picButton = 'https://drive.google.com/uc?id=0BxjtiwHnjnkrTVJiR1g2SlZTLVE'; // Can on be accessed be a few people
var widthButton = 128;
var heightButton = 24;
var anchor = app.createAnchor("", "http://www.opasittardgeleen.nl")
.setHeight(heightButton).setWidth(widthButton)
// .setHeight("150").setWidth("128") // Nice effect !!
// .setHeight("150").setWidth("512") // Even more
.setStyleAttribute('backgroundImage', 'url(' + picButton + ')');
var panel = app.createAbsolutePanel().setWidth('50%').setHeight('50%');
panel.add(anchor,100,50); // I would like to avoid positioning like this and just add the anchor to a Grid or VerticalPanel
app.add(panel);
return app.close();
}
I have answered a similar post with a workaround that works nicely although it is, I admit, a bit complex in regard to what it does...
Here is a test app
and the code reproduced from the other post is below : I used an invisible anchor superimposed to the image but it could of course be anything else... a button or whatever you want.
function doGet(){
var app = UiApp.createApplication().setStyleAttribute("background", "#CCCCFF").setTitle('Anchor Test')
var top = '100PX';// define dimensions and position
var left = '100PX';
var width = '80PX';
var height = '80PX';
var mainPanel = app.createVerticalPanel();
var customAnchor = app.createHorizontalPanel().setId('usethisId')
addStyle(customAnchor,top,left,width,height,'1','1')
var image = app.createImage("https://dl.dropbox.com/u/211279/Time-change-clock_animated_TR80.gif")
addStyle(image,top,left,width,height,'1','1')
var realAnchor = app.createAnchor('This is the Anchor', 'https://sites.google.com/site/appsscriptexperiments/home')
addStyle(realAnchor,top,left,width,height,'2','0')
customAnchor.add(realAnchor);
customAnchor.add(image)
mainPanel.add(customAnchor);
app.add(mainPanel);
return app;
}
function addStyle(widget,top,left,width,height,z,visibility){
widget.setStyleAttributes(
{'position': 'fixed',
'top' : top,
'left' : left,
'width' : width,
'height':height,
'opacity' : visibility,
'zIndex' : z});
}
EDIT : I forgot to mention this post with a button example... exactly what you wanted : How do I open a web browser using google apps script?
EDIT 2
Following Software tester answer, here is a compact version that one can place in a grid or anywhere else... just place the widget called 'container'.
To illustrate I placed it in a grid in the example below/
link to test
code :
function doGet(){
var app = UiApp.createApplication().setTitle("Image Anchor");
var picButton = 'https://dl.dropboxusercontent.com/u/211279/ProgressSpinner.gif';
var img = app.createImage(picButton).setPixelSize(25,25);
var grid = app.createGrid(5,2);
for(n=0;n<5;n++){
grid.setText(n,0,'some text').setBorderWidth(1);
}
var anchor = app.createAnchor(" - ", "https://sites.google.com/site/appsscriptexperiments/").setStyleAttribute('opacity','0').setPixelSize(25,25);
var container = app.createAbsolutePanel().setPixelSize(25,25);
container.add(img,0,0).add(anchor,0,0);
grid.setWidget(4,1,container);
app.add(grid);
return app;
}
The answer EDIT 2 of Serge Insas provides extra flexibility using a grid.
Improving the world little by little by learning and using each others good ideas, also holds for software of course. Thanks again Serge!
I noticed a few differences that might or might not be of interest in certain situations.
I always try to specify constants (like width and height) and minimize using similar code like .setPixelSize(width, height) making it easier 'not to forget something while changing'. That's why I prefer to avoid creating a separate image object.
Serge Insas uses .setStyleAttribute('opacity','0'); while I'm using .setStyleAttribute('backgroundImage', url); . I don't know what are the pro's and con's of both possibilities.
Below is the generalized code
function doGet()
{ // Generalized version using : image + opacity + container
var app = UiApp.createApplication().setTitle("Image Anchor");
var width = 25;
var height = 25;
var urlImage = 'https://dl.dropboxusercontent.com/u/211279/ProgressSpinner.gif';
var urlAnchor = "https://sites.google.com/site/appsscriptexperiments/";
var grid = createImageAnchor(urlImage, urlAnchor, width, height);
app.add(grid);
return app;
}
and the function createImageAnchor can be
function createImageAnchor(urlImage, urlAnchor, width, height)
{ // Using backgroundImage
var app = UiApp.getActiveApplication();
var anchor = app.createAnchor("", urlAnchor).setPixelSize(width, height)
.setStyleAttribute('backgroundImage', 'url(' + urlImage + ')');
var panel = app.createAbsolutePanel().setPixelSize(width, height).add(anchor, 0, 0);
var grid = app.createGrid(1, 1).setWidget(0, 0, panel); // Create grid and put anchor in it
return grid;
}
or
function createImageAnchor(urlImage, urlAnchor, width, height)
{ // Using opacity
var = UiApp.getActiveApplication();
var image = app.createImage(urlImage).setPixelSize(width, height);
var anchor = app.createAnchor("", urlAnchor) .setPixelSize(width, height)
.setStyleAttribute('opacity','0');
var panel = app.createAbsolutePanel().setPixelSize(width, height)
.add(image, 0, 0).add(anchor, 0, 0); // Same position for image and anchor
var grid = app.createGrid(1, 1).setWidget(0, 0, panel); // Create grid and put image + anchor in it
return grid;
}
Using createImageAnchor makes it easier to use this 'combined object' anywhere in code, especially after adding it into a library.
As I'm new to GAS (started may 7th after 5 years of inactivity) I know I need to learn a lot and would like to know what the pro's and con's of different methods are.

How can I paste an image from the clipboard onto a Canvas element using Dart?

I'm using Dart to develop a personal whiteboard Chrome app and it is sometimes useful to be able to quickly copy and paste an image (e.g. a slide from a presentation, a diagram or a handout) so that I can add notes over the image while teaching a class or giving a presentation.
How can I paste an image stored on the clipboard onto a canvas element in Dart?
Actually, this answer to the same question for JS is almost directly applicable. A Dart translation might look something like:
import 'dart:html';
void main() {
var can = new CanvasElement()
..width = 600
..height = 600
;
var con = can.getContext('2d');
document.onPaste.listen((e) {
var blob = e.clipboardData.items[0].getAsFile();
var reader = new FileReader();
reader.onLoad.listen((e) {
var img = new ImageElement()
..src = (e.target as FileReader).result;
img.onLoad.listen((_) {
con.drawImage(img, 0, 0);
});
});
reader.readAsDataUrl(blob);
});
document.body.children.add(can);
}

Rendering HTML elements to <canvas>

Is there a way to have an arbitrary HTML element rendered in a canvas (and then access its buffer...).
You won't get real HTML rendering to <canvas> per se currently, because canvas context does not have functions to render HTML elements.
There are some emulations:
html2canvas project http://html2canvas.hertzen.com/index.html (basically a HTML renderer attempt built on Javascript + canvas)
HTML to SVG to <canvas> might be possible depending on your use case:
https://github.com/miohtama/Krusovice/blob/master/src/tools/html2svg2canvas.js
Also if you are using Firefox you can hack some extended permissions and then render a DOM window to <canvas>
https://developer.mozilla.org/en-US/docs/HTML/Canvas/Drawing_Graphics_with_Canvas?redirectlocale=en-US&redirectslug=Drawing_Graphics_with_Canvas#Rendering_Web_Content_Into_A_Canvas
Take a look on MDN (archived)
It will render html element using creating SVG images.
For Example:
There is <em>I</em> like <span style="color:white; text-shadow:0 0 2px blue;">cheese</span> HTML element. And I want to add it into <canvas id="canvas" style="border:2px solid black;" width="200" height="200"></canvas> Canvas Element.
Here is Javascript Code to add HTML element to canvas.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var data = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +
'<foreignObject width="100%" height="100%">' +
'<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px">' +
'<em>I</em> like <span style="color:white; text-shadow:0 0 2px blue;">cheese</span>' +
'</div>' +
'</foreignObject>' +
'</svg>';
var DOMURL = window.URL || window.webkitURL || window;
var img = new Image();
var svg = new Blob([data], {
type: 'image/svg+xml;charset=utf-8'
});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
DOMURL.revokeObjectURL(url);
}
img.src = url;
<canvas id="canvas" style="border:2px solid black;" width="200" height="200"></canvas>
Here is code to render arbitrary HTML into a canvas:
function render_html_to_canvas(html, ctx, x, y, width, height) {
var xml = html_to_xml(html);
xml = xml.replace(/\#/g, '%23');
var data = "data:image/svg+xml;charset=utf-8,"+'<svg xmlns="http://www.w3.org/2000/svg" width="'+width+'" height="'+height+'">' +
'<foreignObject width="100%" height="100%">' +
xml+
'</foreignObject>' +
'</svg>';
var img = new Image();
img.onload = function () {
ctx.drawImage(img, x, y);
}
img.src = data;
}
function html_to_xml(html) {
var doc = document.implementation.createHTMLDocument('');
doc.write(html);
// You must manually set the xmlns if you intend to immediately serialize
// the HTML document to a string as opposed to appending it to a
// <foreignObject> in the DOM
doc.documentElement.setAttribute('xmlns', doc.documentElement.namespaceURI);
// Get well-formed markup
html = (new XMLSerializer).serializeToString(doc.body);
return html;
}
example:
const ctx = document.querySelector('canvas').getContext('2d');
const html = `
<p>this
<p>is <span style="color:red; font-weight: bold;">not</span>
<p><i>xml</i>!
<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABWElEQVQ4jZ2Tu07DQBBFz9jjvEAQqAlQ0CHxERQ0/AItBV9Ew8dQUNBQIho6qCFE4Nhex4u85OHdWAKxzfWsx0d3HpazdGITA4kROjl0ckFrnYJmQlJrKsQZxFOIMyEqIMpADGhSZpikB1hAGsovdxABGuepC/4L0U7xRTG/riG3J8fuvdifPKnmasXp5c2TB1HNPl24gNTnpeqsgmj1eFgayoHvRDWbLBOKJbn9WLGYflCCpmM/2a4Au6/PTjdH+z9lCJQ9vyeq0w/ve2kA3vaOnI6k4Pz+0Y24yP3Gapy+Bw6qdfsCRZfWSWgclCCVXTZu5LZFXKJJ2sepW2KYNCENB3U5pw93zLoDjNK6E7rTFcgbkGYJtiLckxCiw4W1OURsxUE5BokQiQj3JIToVtKwlhsurq+YDYbMBjuU/W3KtT3xIbrpAD7E60lwQohuaMtP8ldI0uMbGfC1r1zyWPUAAAAASUVORK5CYII=">`;
render_html_to_canvas(html, ctx, 0, 0, 300, 150);
function render_html_to_canvas(html, ctx, x, y, width, height) {
var data = "data:image/svg+xml;charset=utf-8," + '<svg xmlns="http://www.w3.org/2000/svg" width="' + width + '" height="' + height + '">' +
'<foreignObject width="100%" height="100%">' +
html_to_xml(html) +
'</foreignObject>' +
'</svg>';
var img = new Image();
img.onload = function() {
ctx.drawImage(img, x, y);
}
img.src = data;
}
function html_to_xml(html) {
var doc = document.implementation.createHTMLDocument('');
doc.write(html);
// You must manually set the xmlns if you intend to immediately serialize
// the HTML document to a string as opposed to appending it to a
// <foreignObject> in the DOM
doc.documentElement.setAttribute('xmlns', doc.documentElement.namespaceURI);
// Get well-formed markup
html = (new XMLSerializer).serializeToString(doc.body);
return html;
}
<canvas></canvas>
The CSS element() function may eventually help some people here, even though it's not a direct answer to the question. It allows you to use an element (and all children, including videos, cross-domain iframes, etc.) as a background image (and anywhere else that you'd normally use url(...) in your CSS code). Here's a blog post that shows what you can do with it.
It has been implemented in Firefox since 2011, and is being considered in Chromium/Chrome (don't forget to give the issue a star if you care about this functionality).
RasterizeHTML is a very good project, but if you need to access the canvas it wont work on chrome. due to the use of <foreignObject>.
If you need to access the canvas then you can use html2canvas
I am trying to find another project as html2canvas is very slow in performance
According to the HTML specification you can't access the elements of the Canvas. You can get its context, and draw in it manipulate it, but that is all.
BUT, you can put both the Canvas and the html element in the same div with a aposition: relative and then set the canvas and the other element to position: absolute.
This ways they will be on the top of each other. Then you can use the left and right CSS properties to position the html element.
If the element doesn't shows up, maybe the canvas is before it, so use the z-index CSS property to bring it before the canvas.