How to preserve spaces in svg text - svg

To preserve spaces in a textelement of svg, one should use 'xml:space="preserve"' as an attribute of the text (jsfiddle). However, it isn't working. What am I doing wrong?
// init snap
var svgElement=document.getElementById("mainSvgId");
var s = Snap(svgElement).attr({height: 300, width: 300});
// greate group with rectanle
var parentGroup=s.g().attr({id: "parent"});
var rect1 = s.rect(0, 0, 200, 200).attr({fill: "#bada55"});
parentGroup.add(rect1);
// add text with preserve attribute
var text = s.text(0, 20, " text1 text2");
text.node.setAttribute("xml:space", "preserve");
parentGroup.add(text);

You're almost there. You need to properly create the attribute in the xml namespace for which you need setAttributeNS rather than setAttribute
text.node.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:space", "preserve");

Related

I can't get the Font size on my canvas to work [duplicate]

Is there a way to only change the font size of a canvas context without having to know/write the font family.
var ctx = document.getElementById("canvas").getContext("2d");
ctx.font = '20px Arial'; //Need to speficy both size and family...
Note:
ctx.fontSize = '12px'; //doesn't exist so won't work...
ctx.style.fontSize = '20 px' //doesn't exist so won't work...
//we are changing the ctx, not the canvas itself
Other note: I could do something like: detect where 'px' is, remove what's before 'px' and replace it by my font size. But I'd like something easier than that if possible.
Here is an easier and cleaner way of changing the font size that will work regardless if you are using font-variant or font-weight or not.
First some RegEx to match and return the current font size
const match = /(?<value>\d+\.?\d*)/;
Set an absolute size, assuming your new font size is 12px
ctx.font = ctx.font.replace(match, 12);
Set a relative size, increase by 5:
const newSize = parseFloat(ctx.font.match(match).groups.value + 5);
ctx.font = ctx.font.replace(match, newSize);
Working example:
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// Match on digit at least once, optional decimal, and optional digits
// Named group `value`
const match = /(?<value>\d+\.?\d*)/;
const setFontSize = (size) => ctx.font = ctx.font.replace(match, size);
const adjustFontSize = (amount) => {
const newSize = parseFloat(ctx.font.match(match).groups.value + amount);
return ctx.font = ctx.font.replace(match, newSize);
}
// Default font is 12px sans-serif
console.log( setFontSize(18) ); // 18px sans-serif
console.log( adjustFontSize(2) ); // 20px sans-serif
console.log( adjustFontSize(-5) ); // 15px sans-serif
<canvas id="canvas"></canvas>
Update: (from comments) There is no way around specifying font. The Canvas' font is modeled after the short-hand version of font in CSS.
However, there is always a font set on the canvas (or a font type) so what you can do is to first extract the current font by using it like this:
var cFont = ctx.font;
Then replace size arguments and set it back (note that there might be a style parameter there as well).
A simple split for the sake of example:
var fontArgs = ctx.font.split(' ');
var newSize = '12px';
ctx.font = newSize + ' ' + fontArgs[fontArgs.length - 1]; /// using the last part
You will need support for style if needed (IIRC it comes first if used).
Notice that font-size is fourth parameter, so this will not work if you will have/not have font-variant(bold,italic,oblique), font-variant(normal, small-caps) and font-weight(bold etc).
A cleaner way to not worry about scraping every other parameter:
canvas.style.font = canvas.getContext("2d").font;
canvas.style.fontSize = newVal + "px";
canvas.getContext("2d").font = canvas.style.font;
Explanation:
As mentioned in the first answer, the canvas context will always have a font. So canvas.getContext("2d").font might return something like "10px sans-serif". To not scrape this string we can use the Canvas DOM element (or any other element) to parse the full font spec and populate the other font fields. Which means that after setting:
canvas.style.font = "10px sans-serif";
canvas.style.fontSize = "26px";
the canvas.style.font will become "26px sans-serif". We can then pass this font spec back to the canvas context.
To correctly answer your question, there is no way to change the font size of a canvas context without having to know/write the font family.
try this (using jquery):
var span = $('<span/>').css('font', context.font).css('visibility', 'hidden');
$('body').append(span);
span[0].style.fontWeight = 'bold';
span[0].style.fontSize = '12px';
//add more style here.
var font = span[0].style.font;
span.remove();
return font;

KonvaJS, positioning editable text inputs

I need to position text inputs at various places on a KonvaJS layer. I found the following code at https://konvajs.github.io/docs/sandbox/Editable_Text.html and I'm trying to understand the textPosition, stageBox, and areaPosition vars in this code. I want my stage centered in the browser window, but when I do that, the textarea (activated on dblclick) pops up way off to the left. I can't get a console readout of the x/y coordinates, so I can't visualize how the positioning works &, thus, how to change it. Can anyone explain, or point me in the right direction?
var text_overlay = new Konva.Layer();
stage.add(text_overlay);
var textNode = new Konva.Text({
text: 'Some text here',
x: 20,
y: 50,
fontSize: 20
});
text_overlay.add(textNode);
text_overlay.draw();
textNode.on('dblclick', () => {
// create textarea over canvas with absolute position
// first we need to find its position
var textPosition = textNode.getAbsolutePosition();
var stageBox = stage.getContainer().getBoundingClientRect();
var areaPosition = {
x: textPosition.x + stageBox.left,
y: textPosition.y + stageBox.top
};
// create textarea and style it
var textarea = document.createElement('textarea');
document.body.appendChild(textarea);
textarea.value = textNode.text();
textarea.style.position = 'absolute';
textarea.style.top = areaPosition.y + 'px';
textarea.style.left = areaPosition.x + 'px';
textarea.style.width = textNode.width();
textarea.focus();
textarea.addEventListener('keydown', function (e) {
// hide on enter
if (e.keyCode === 13) {
textNode.text(textarea.value);
text_overlay.draw();
document.body.removeChild(textarea);
}
});
})
// add the layer to the stage
stage.add(text_overlay);
UPDATE: I solved part of the problem--the textarea showing up way out of position. You need to use 2 divs in the HTML file instead of one, like so:
<div id="containerWrapper" align="center"><div id="container"></div></div>
Thanks to Frens' answer on Draw border edges of the Konvajs container Stage in html for that one!

Is there a way to crop only the design from a the canvas and ignoring all the white/transparent space?

I have a canvas built using fabricJS with the dimension of 600x500. I have added an image to this canvas which is of size 200x300 and also a text element just below it.
$canvasObj.toDataURL();
exports the whole canvas area including the white spaces surrounding the design on the canvas.
Is there a way to get the cropped output of the design on the canvas alone instead of all the whitespace?
This can be done by cloning objects to a group, getting the group boundingRect, and then passing the boundingRect parameters to toDataUrl() function (see fiddle).
e.g.
// make a new group
var myGroup = new fabric.Group();
canvas.add(myGroup);
// ensure originX/Y 'center' is being used, as text uses left/top by default.
myGroup.set({ originX: 'center', originY: 'center' });
// put canvas things in new group
var i = canvas.getObjects().length;
while (i--) {
var objType = canvas.item(i).get('type');
if (objType==="image" || objType==="text" || objType==="itext" || objType==="rect") {
var clone = fabric.util.object.clone(canvas.item(i));
myGroup.addWithUpdate(clone).setCoords();
// remove original lone object
canvas.remove(canvas.item(i));
}
}
canvas.renderAll();
// get bounding rect for new group
var i = canvas.getObjects().length;
while (i--) {
var objType = canvas.item(i).get('type');
if (objType==="group") {
var br = canvas.item(i).getBoundingRect();
}
}
fabric.log('cropped png dataURL: ', canvas.toDataURL({
format: 'png',
left: br.left,
top: br.top,
width: br.width,
height: br.height
}));
p.s. I should probably mention that i've not worked with image types, so i just guessed that it's called 'image'..

How do I fill/stroke an SVG file on my website?

I Googled this issue for about 30 minutes and was surprised nobody has asked so maybe it's not possible.
I'm using this line to embed the SVG file that I made in AI (note that when I saved the SVG, I had no fill OR stroke on the paths):
<object type="image/svg+xml" data="example.svg" height=600px width=600px>Your browser does not support SVG</object>
This obviously comes up with no image because the SVG file has no fill or stroke.
I tried adding in
...fill=yellow>
or
...style="fill:yellow;">
but I'm not having any luck. Thanks!
Have a nice trick: Embed it as <img> and use javascript to convert it into inline <svg> with this code (that came from SO I think). Now you can manipulate this SVG object
CODE::
/*
* Replace all SVG images with inline SVG
*/
jQuery('img.svg').each(function(){
var $img = jQuery(this);
var imgID = $img.attr('id');
var imgClass = $img.attr('class');
var imgURL = $img.attr('src');
jQuery.get(imgURL, function(data) {
// Get the SVG tag, ignore the rest
var $svg = jQuery(data).find('svg');
// Add replaced image's ID to the new SVG
if(typeof imgID !== 'undefined') {
$svg = $svg.attr('id', imgID);
}
// Add replaced image's classes to the new SVG
if(typeof imgClass !== 'undefined') {
$svg = $svg.attr('class', imgClass+' replaced-svg');
}
// Remove any invalid XML tags as per http://validator.w3.org
$svg = $svg.removeAttr('xmlns:a');
// Replace image with new SVG
$img.replaceWith($svg);
});
});
Are you trying to add the fill or style on the object element? If so, that's not going to work. Those properties are not supported on the object element. You're going to have to add it into the SVG in the SVG file.

Adding background image to markers in jvectormap

Found a couple of solutions here about adding SVG patterns dynamically but it doesn't seem to work with jvectormap. I think the problem may be that there is no XMLNS attribute defined on the <SVG> tag by jvectormap but my attempt to add these attributes does not work.
I also tried changing all of the setAttribute to setAttributeNS for pattern and image. But no dice.
Here is my attempt (based on this solution: How to dynamically change the image pattern in SVG using Javascript):
// Set namespace for SVG elements.
var svgMap = $('.jvectormap-container > svg').get(0);
var svgNS = 'http://www.w3.org/2000/svg';
var svgNSXLink = 'http://www.w3.org/1999/xlink';
svgMap.setAttribute('xmlns', svgNS);
svgMap.setAttribute('xmlns:link', svgNSXLink);
svgMap.setAttribute('xmlns:ev', 'http://www.w3.org/2001/xml-events');
// Create pattern for markers.
var pattern = document.createElementNS(svgNS, 'pattern');
pattern.setAttribute('id', 'markeryellow');
pattern.setAttribute('patternUnits', 'userSpaceOnUse');
pattern.setAttribute('width', '38');
pattern.setAttribute('height', '38');
// Create image for pattern.
var image = document.createElementNS(svgNS, 'image');
image.setAttribute('x', '0');
image.setAttribute('y', '0');
image.setAttribute('width', '38');
image.setAttribute('height', '38');
image.setAttributeNS(svgNSXLink, 'xlink:href', '/path/to/image.png');
// Put it together
pattern.appendChild(image);
var defs =
svgMap.querySelector('defs') ||
svgMap.insertBefore(document.createElementNS(svgNS, 'defs'), svgMap.firstChild);
defs.appendChild(pattern);
var $markers = $(svgMap).find('.jvectormap-marker');
$.each($markers, function(i, elem) {
$(elem)
.attr({
'fill': 'url(#markeryellow)'
});
});

Resources