Write latex formulas in fabric.js - javascript

I would like to be able to write formulas in latex in a fabric.js canvas, perhaps with MathJax.
http://fabricjs.com/fabric-intro-part-2#text
Is it possible to do that?

I would use the SVG renderer of MathJax to create a SVG file and then load that SVG into fabric.js. Here's an example:
<!-- It is important to use the SVG configuration of MathJax! -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS-MML_SVG">
</script>
<!-- You might need at least Fabric 1.4.6, because of a bug with data URLs -->
<script type="text/javascript" src="https://raw.githubusercontent.com/kangax/fabric.js/master/dist/fabric.js">
</script>
<body>
<canvas id="canvas" width="400" height="400"></canvas>
<script type="text/javascript">
// This function does the actual work
function tex(text, callback) {
// Create a script element with the LaTeX code
var div = document.createElement("div");
div.style.position = "absolute";
div.style.left = "-1000px";
document.body.appendChild(div);
var se = document.createElement("script");
se.setAttribute("type", "math/tex");
se.innerHTML = text;
div.appendChild(se);
MathJax.Hub.Process(se, function() {
// When processing is done, remove from the DOM
// Wait some time before doing tht because MathJax calls this function before
// actually displaying the output
var display = function() {
// Get the frame where the current Math is displayed
var frame = document.getElementById(se.id + "-Frame");
if(!frame) {
setTimeout(display, 500);
return;
}
// Load the SVG
var svg = frame.getElementsByTagName("svg")[0];
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svg.setAttribute("version", "1.1");
var height = svg.parentNode.offsetHeight;
var width = svg.parentNode.offsetWidth;
svg.setAttribute("height", height);
svg.setAttribute("width", width);
svg.removeAttribute("style");
// Embed the global MathJAX elements to it
var mathJaxGlobal = document.getElementById("MathJax_SVG_glyphs");
svg.appendChild(mathJaxGlobal.cloneNode(true));
// Create a data URL
var svgSource = '<?xml version="1.0" encoding="UTF-8"?>' + "\n" + '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' + "\n" + svg.outerHTML;
var retval = "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svgSource)));
// Remove the temporary elements
document.body.removeChild(div);
// Invoke the user callback
callback(retval, width, height);
};
setTimeout(display, 1000);
});
}
document.onload = function() {
var canvas = new fabric.Canvas("canvas");
tex("1+\\int_x^y e^x dx + \\ldots", function(svg, width, height) {
// Here you have a data url for a svg file
// Draw using FabricJS:
fabric.Image.fromURL(svg, function(img) {
img.height = height;
img.width = width;
canvas.add(img);
});
});
};
</script>
</body>
I've also put that into a JsFiddle: http://jsfiddle.net/3aHQc/39/

Related

SVG to Image returning blank image blob

I have an interactive drawing app on my website, and I want to create a button where one could share their drawing on FB.
I'm trying to convert the SVG element to a blob, to then pass it to og:image, but I'm having some issues with the conversion.
I have two trials:
one doesn't trigger the onload function for some reason.
The other returns an empty blob
both trials work fine on jsfiddle however.
First Attempt
var xmlSerializer = new XMLSerializer();
var svgString = xmlSerializer.serializeToString(document.querySelector("#svg"));
var canvas = document.createElement("canvas");
var bounds = {
width: 1040,
height: 487
};
canvas.width = bounds.width;
canvas.height = bounds.height;
var ctx = canvas.getContext("2d");
var DOMURL = self.URL || self.webkitURL || self;
var img = new Image();
var svg = new Blob([svgString], {
type: "image/svg+xml;charset=utf-8"
});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
var png = canvas.toDataURL("image/png");
var mg = document.createElement("img");
mg.setAttribute("src", png);
document.body.appendChild(mg);
DOMURL.revokeObjectURL(png);
};
img.id = "testimg";
img.setAttribute("src", url);
Second Attempt
var svgString = new XMLSerializer().serializeToString(document.querySelector("svg"));
var canvas = document.createElement('CANVAS');
var ctx = canvas.getContext("2d");
var DOMURL = self.URL || sel.webkitURL || self;
var img = new Image();
var svg = new Blob([svgString], {
type: "image/svg+xml;charset=utf-8"
});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
var png = canvas.toDataURL("image/png");
var container = document.createElement('DIV');
container.innerHTML = '<img src="' + png + '"/>';
DOMURL.revokeObjectURL(png);
};
img.src = url;
document.body.appendChild(img);
Here's the app with the two attempts triggered by the two buttons "test1" and "test2"
The problem lies in the way you did define the xmlns:xlink attributes.
Currently from your page doing document.querySelector("use").attributes.getNamedItem("xmlns:xlink").namespaceURI will return null. This means that this attribute has been defined in the Document's namespace (HTML), so when you'll stringify it using the XMLSerializer, you will actually have two xmlns:xlink attributes on your elements, one in the HTML namespace, and the SVG one that is implied in an SVG embed in an HTML document.
It is invalid to have two same attributes on the same element in SVG, and thus your file is invalid and the image won't load.
If you are facing this issue it's certainly because you did set this attribute through JavaScript:
const newUse = document.createElementNS("http://www.w3.org/2000/svg", "use");
newUse.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
newUse.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "#foo");
document.querySelector("svg").append(newUse);
console.log("set from markup:", document.querySelector("use").attributes.getNamedItem("xmlns:xlink").namespaceURI);
console.log("(badly) set from JS:", document.querySelector("use+use").attributes.getNamedItem("xmlns:xlink").namespaceURI);
// the last <use> has two xmlns:xlink attributes
console.log("serialization:", new XMLSerializer().serializeToString(document.querySelector("svg")));
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#foo"/>
</svg>
To set it correctly, you need to use setAttributeNS() and use the XMLNS namespace:
const newUse = document.createElementNS("http://www.w3.org/2000/svg", "use");
document.querySelector("svg").append(newUse);
// beware the last "/"
newUse.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
console.log("set from markup", document.querySelector("use").attributes.getNamedItem("xmlns:xlink").namespaceURI);
console.log("(correctly) set from JS", document.querySelector("use+use").attributes.getNamedItem("xmlns:xlink").namespaceURI);
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30">
<use xmlns:xlink="http://www.w3.org/1999/xlink"/>
</svg>
However the best is to not set at all these attributes.
As I said above, SVGs embedded in HTML do automatically have the correct xmlns and xlink namespaces defined without the need for attributes. And since you are creating your elements through JS, you already do define them in the correct namespace too.
So don't bother with these attributes:
const SVGNS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(SVGNS, "svg");
// To be able to draw an SVG image on a canvas in Firefox
// you must set absolute width and height to the root svg node
svg.setAttribute("width", 50);
svg.setAttribute("height", 50);
const target = document.createElementNS(SVGNS, "symbol");
target.id = "target";
const rect = document.createElementNS(SVGNS, "rect");
rect.setAttribute("width", 50);
rect.setAttribute("height", 50);
rect.setAttribute("fill", "green");
const use = document.createElementNS(SVGNS, "use");
// since SVG2 we don't even need to set href in the xlink NS
use.setAttribute("href", "#target");
target.append(rect);
svg.append(target, use);
const svgString = new XMLSerializer().serializeToString(svg);
console.log(svgString); // contains all the NS attributes
const blob = new Blob([svgString], { type: "image/svg+xml" });
const img = new Image();
img.src = URL.createObjectURL(blob);
document.body.append(img);

how to convert frames of GIF into spritesheet

I am trying to merge multiple frames from a GIF to convert them into spritesheet. I am somehow able to extract frames using libgif.js
here is my code. The Canvas in which i want to put all my images is empty and is at the end i dont know what is wrong with it.
<img hidden src="https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif" rel:auto_play="0"
rel:rubbable="0" />
<div id="frames">
</div>
<canvas id="myCanvas" style="border:1px solid #d3d3d3;">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://unpkg.com/jsgif#1.0.2/libgif.js"></script>
<script>
$(document).ready(function () {
$('img').each(function (idx, img_tag) {
var total = 0;
if (/^.+\.gif$/.test($(img_tag).prop("src"))) {
var rub = new SuperGif({ gif: img_tag, progressbar_height: 0 });
rub.load(function () {
for (var i = 0; i < rub.get_length(); i++) {
total += 1;
rub.move_to(i);
var canvas = cloneCanvas(rub.get_canvas());
$("#frames").append('<img id = "' + i + '"src= "' + canvas + '">');
}
for (var i = 0; i <= total; i++) {
id = i.toString();
var img = document.getElementById(id);
window.onload = function () {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.drawImage(img, 10, 10);
}
}
});
}
});
});
function cloneCanvas(oldCanvas) {
var img = oldCanvas.toDataURL("image/png");
return img;
}
</script>
The main issue in your solution is that you are looping through all inserted frame-images and using window.onload with the expectation to draw images on the canvas. However, you are assigning windows.onload on the image elements after the images have already been attached to the dom in the previous (for i = 0) iteration, through $("#frames").append(.... This means that your onload handlers are getting registered after the event has already fired.
I rearranged your code into the snippet below to make sure that onload is registered before appending the image-frames into the document.
In this solution here I created the "spritesheet" by putting the image-frames vertically one-after-the-other. Tweak the solution accordingly to create your desired "spritesheet layout".
The tricky part is that you need to set a width and height for the canvas in a dynamic fashion, which means you have to wait for all image-frames to be loaded into the document and meassure their collective widths and heights.
The algorithm should be:
Collect all widths and heights of image-frames onload
Calculate the total canvas.width and canvas.height depending on
your desired layout (in my vertical-one-ofter-the-other solution
this means to use the maximal image width as canvas width and the
sum of all image heights as height. If you need a different
spritesheet layout then you need to adapt this logic differently)
Draw all the images on the canvas, which by this point has enough
width and height to fit all your gif-frame-images.
In my solution I did not implement this algorith, I rather hard-coded the canvas width and height after manually calculating the max-img-width and total-img-height for your particular gif example.
$(document).ready(function () {
const c = document.getElementById("myCanvas");
const ctx = c.getContext("2d");
let canvasWidth = 0;
let canvasHeight = 0;
$('img').each(function (idx, img_tag) {
var total = 0;
if (/^.+\.gif$/.test($(img_tag).prop("src"))) {
var rub = new SuperGif({ gif: img_tag, progressbar_height: 0 });
rub.load(function () {
for (let i = 0; i < rub.get_length(); i++) {
total += 1;
rub.move_to(i);
var canvas = cloneCanvas(rub.get_canvas());
const img = $('<img id = "' + i + '"src= "' + canvas + '">');
img.on('load', function() {
// draw the image
ctx.drawImage(this, 0, canvasHeight);
// extend canvas width, if newly loaded image exceeds current width
canvasWidth = $(this).width() > canvasWidth ? $(this).width() : canvasWidth;
// add canvas height by the newly loade image height value
canvasHeight += $(this).height();
// resize sprite-canvas to host the new image
canvas.width = canvasWidth;
canvas.height = canvasHeight;
});
$("#frames").append(img);
}
});
}
});
});
function cloneCanvas(oldCanvas) {
var img = oldCanvas.toDataURL("image/png");
return img;
}
Gif:
<img hidden src="https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif" rel:auto_play="0"
rel:rubbable="0" />
<hr />
Frames:
<div id="frames">
</div>
<hr />
Canvas:
<hr />
<canvas id="myCanvas" style="border:1px solid #d3d3d3;" width="400" height="17600">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://unpkg.com/jsgif#1.0.2/libgif.js"></script>

Is there a way to find the center of a browser?

I want my text to be in the middle of the browser window, is there any way I can find (in pixels) the middle of the browser? This isn't just HTML text, so I can't use CSS or anything. Here's part of my code in case you don't understand my confusing explanation:
<canvas id = "my-canvas" width = "screen.width" height = "screen.height"></canvas>
<script src = "https://cdn.jsdelivr.net/processing.js/1.4.8/processing.min.js"></script>
<script type = "application/javascript">
// program code {
var sketchProc = function(processingInstance) {
with (processingInstance) {
background(255);
fill(0);
text("Hi", 300, 300);
}
};
// }
// store canvas {
var canvas = document.getElementById("my-canvas");
// }
// run Processing.js {
var processingInstance = new Processing(canvas, sketchProc);
// }
</script>
This might work for ya
const width = window.innerWidth / 2;
const height = window.innerHeight / 2;
text("Hi", width, height);

Is there a way to draw a SVG in PDFTron?

Previously, I tried to use an Annotation.StampAnnotation to create a custom annotation while using a SVG as the base image, however I discovered that the user cannot change or set the colour of a StampAnnotation. Hence, I am now using an Annotation.MarkupAnnotation.
As of right now, I am having trouble drawing the inline SVG (string) on the CanvasRenderingContext2D using drawImage(). I saw some code on TutorialsPoint, and I tested it in a Javascript sandbox, and it seems to work. However, when I implemented it with PDFTron, it only draws an empty box on the PDF.
Here's my code:
createCustomAnnotation: function (Annotations, annotManager, subject, inlineSVGString) {
const CustomAnnotation = function () {
Annotations.MarkupAnnotation.call(this);
this.Subject = subject;
}
CustomAnnotation.prototype = new Annotations.MarkupAnnotation();
CustomAnnotation.prototype.draw = function (ctx, pageMatrix) {
this.setStyles(ctx, pageMatrix);
var DOMURL = window.URL || window.webkitURL || window;
var img = new Image();
var svg = new Blob([inlineSVGString], {type: 'image/svg+xml'});
var url = DOMURL.createObjectURL(svg);
var x = this.X;
var y = this.Y;
img.onload = function() {
ctx.drawImage(img, x, y, 30, 30);
DOMURL.revokeObjectURL(url);
}
img.src = url;
}
return CustomAnnotation;
}
Any help would be appreciated!
Thank you!
there
Looks like the sample from Tutorials Point has a syntax error and it only draws an empty box, if you try this sample it draws the text correctly.
<!DOCTYPE html>
<html>
<head>
<title>SVG file on HTML Canvas </title>
</head>
<body>
<canvas id="myCanvas" style="border:2px solid green;" width="300" height="300"></canvas>
<script>
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
var data = '<svg xmlns="http://www.w3.org/2000/svg" width="300" height="200">' +
'<foreignObject width="100%" height="100%">' +
'<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:50px">' +
'Simply Easy ' +
'<span style="color:blue;">' +
'Learning</span>' +
'</div>' +
'</foreignObject>' +
'</svg>';
var DOMURL = window.URL || window.webkitURL || window;
var img1 = new Image();
var svg = new Blob([data], {type: 'image/svg+xml'});
var url = DOMURL.createObjectURL(svg);
img1.onload = function() {
ctx.drawImage(img1, 25, 70);
DOMURL.revokeObjectURL(url);
}
img1.src = url;
</script>
</body>
</html>

SVG to Canvas with d3.js

Has anyone tried using a svg to canvas library when creating d3.js visualizations? I've tried to use canvg.js and d3.js to convert svg to canvas from within an android 2.3 application webview, but when I call:
svg.selectAll(".axis")
.data(d3.range(angle.domain()[1]))
.enter().append("g")
.attr("class", "axis")
.attr("transform", function(d) { return "rotate(" + angle(d) * 180 / Math.PI + ")"; })
.call(d3.svg.axis()
.scale(radius.copy().range([-5, -outerRadius]))
.ticks(5)
.orient("left"))
.append("text")
.attr("y",
function (d) {
if (window.innerWidth < 455){
console.log("innerWidth less than 455: ",window.innerWidth);
return -(window.innerHeight * .33);
}
else {
console.log("innerWidth greater than 455: ",window.innerWidth);
return -(window.innerHeight * .33);
}
})
.attr("dy", ".71em")
.attr("text-anchor", "middle")
.text(function(d, i) { return capitalMeta[i]; })
.attr("style","font-size:12px;");
I get the error: Uncaught TypeError: Cannot call method setProperty of null http://mbostock.github.com/d3/d3.js?2.5.0:1707
Would some sort of headless browser application, or a server side js parser work? Has anyone encountered this before?
Here's one way you could write your svg to canvas (and then save the result as a png or whatever):
// Create an export button
d3.select("body")
.append("button")
.html("Export")
.on("click",svgToCanvas);
var w = 100, // or whatever your svg width is
h = 100;
// Create the export function - this will just export
// the first svg element it finds
function svgToCanvas(){
// Select the first svg element
var svg = d3.select("svg")[0][0],
img = new Image(),
serializer = new XMLSerializer(),
svgStr = serializer.serializeToString(svg);
img.src = 'data:image/svg+xml;base64,'+window.btoa(svgStr);
// You could also use the actual string without base64 encoding it:
//img.src = "data:image/svg+xml;utf8," + svgStr;
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
canvas.width = w;
canvas.height = h;
canvas.getContext("2d").drawImage(img,0,0,w,h);
// Now save as png or whatever
};
The answer by #ace is very good, however it doesn't handle the case of external CSS stylesheets. My example below will automatically style the generated image exactly how the original SVG looks, even if it's pulling styles form separate stylesheets.
// when called, will open a new tab with the SVG
// which can then be right-clicked and 'save as...'
function saveSVG(){
// get styles from all required stylesheets
// http://www.coffeegnome.net/converting-svg-to-png-with-canvg/
var style = "\n";
var requiredSheets = ['phylogram_d3.css', 'open_sans.css']; // list of required CSS
for (var i=0; i<document.styleSheets.length; i++) {
var sheet = document.styleSheets[i];
if (sheet.href) {
var sheetName = sheet.href.split('/').pop();
if (requiredSheets.indexOf(sheetName) != -1) {
var rules = sheet.rules;
if (rules) {
for (var j=0; j<rules.length; j++) {
style += (rules[j].cssText + '\n');
}
}
}
}
}
var svg = d3.select("svg"),
img = new Image(),
serializer = new XMLSerializer(),
// prepend style to svg
svg.insert('defs',":first-child")
d3.select("svg defs")
.append('style')
.attr('type','text/css')
.html(style);
// generate IMG in new tab
var svgStr = serializer.serializeToString(svg.node());
img.src = 'data:image/svg+xml;base64,'+window.btoa(unescape(encodeURIComponent(svgStr)));
window.open().document.write('<img src="' + img.src + '"/>');
};
And, to be complete, the button that calls the function:
// save button
d3.select('body')
.append("button")
.on("click",saveSVG)
.attr('class', 'btn btn-success')
Have you tried the same code on a browser supporting SVG to see if it's a problem with webview? Then try this example using canvg or this one using DOM serialization. For server-side rendering, you could start with this example for how to render it to canvas server-side using Node.js.
I've not tried a library, but have rendered a d3-produced SVG to a canvas following this post on MDN.
This code is a quick mish-mash of the MDN and some jQuery, that'll you'll need to tidy up, and it has no error- or platfrom-checking, but it works, and I hope it helps.
$(document.body).append(
'<canvas id="canvas" width="'+diameter+'" height="'+diameter+'"></canvas>'
);
// https://developer.mozilla.org/en/docs/HTML/Canvas/Drawing_DOM_objects_into_a_canvas
var el = $($('svg')[0]);
var svgMarkup = '<svg xmlns="http://www.w3.org/2000/svg"'
+ ' class="' + el.attr('class') +'"'
+ ' width="' + el.attr('width') +'"'
+ ' height="' + el.attr('height') +'"'
+ '>'
+ $('svg')[0].innerHTML.toString()+'</svg>';
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var DOMURL = this.URL || this.webkitURL || this;
var img = new Image();
var svg = new Blob([svgMarkup], {type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
alert('ok');
DOMURL.revokeObjectURL(url);
};
img.src = url;

Categories

Resources