fabricjs: v2.2.1 Where are SVG paths stored on fabric object if only a single path exists on SVG - svg

I am upgrading fabricjs in my app from version 1.7.22 to 2.2.1.
I am mainly working with SVG objects that I am creating in Illustrator. Then in the app I am manipulating paths within the SVG objects.
In v1.7.22, the svg paths would be under a 'paths' property for each object...
Now with 2.2.1, I see that 'paths' is gone, and there is an '_objects' property.
This is all good.
My problem is that the '_objects' property exists only if there is more than 1 path on the SVG object, otherwise, I can't see where the SVG path is stored.
So if it's like this...
...then '_objects' property exists.
But if it's like this...
..then no '_objects' property.
So if there is an SVG object made with only a single path, where is the path stored in the fabric object?

As it is mentioned in doc groupSVGElements, it returns a fabric.Object (if svg contains one path)/ fabric.Group(if svg contains more than one path).
And stated in change log fabric.PathGroup has been removed.
If it contains more than one path, creates a fabric.Group object containing all the Path objects, else returns only the path object.
To get the path use object.path.
DEMO
var canvas = new fabric.Canvas('c');
var svg = '<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 375 200" enable-background="new 0 0 400.5 477" xml:space="preserve"> <path d="M 100, 100m -75, 0a 75,75 0 1,0 150,0a 75,75 0 1,0 -150,0"/><path d="M250 0 L175 200 L325 200 Z" /> </svg>';
fabric.loadSVGFromString(svg, function(objects, options) {
var shape = fabric.util.groupSVGElements(objects, options);
console.log(shape.type)
if(shape.type == 'group'){
shape.forEachObject(function(obj){
console.log(obj.path)
})
}
else if(shape.type == 'path'){
console.log(shape.path)
}
canvas.add(shape);
document.getElementById('label').innerHTML = "Object is " + shape.type ;
});
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<label id="label"></label>
<canvas id="c" height=300 width=400 style="border:1px solid #ccc"></canvas>

Related

Magick.NET in Azure converting SVG to PNG gives black boxes

Using Magick.NET-Q16-AnyCPU I am converting an SVG to PNG with this minimal code:
var bytes = File.ReadAllBytes("test.svg");
var magickReadSettings = new MagickReadSettings { Format = MagickFormat.Svg };
using var image = new MagickImage(bytes, magickReadSettings) { Format = MagickFormat.Png };
image.Write("test.png");
Locally that works fine, and the PNG looks correct having a text (expected):
But when deployed to an Azure App Service, the PNG gets black boxes (actual):
The SVG file looks like this (minimal):
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"[]>
<svg width="100px" height="50px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xml="http://www.w3.org/XML/1998/namespace" version="1.1">
<g transform="translate(10, 10)">
<text transform="translate(50, 0)">foo</text>
</g>
</svg>
How can that be?
This is an issue related to fonts installed on App Service. There is a discussion of this issue and resolution at the bottom of the discussion here.

gulp-imagemin is minifying SVG file down to 0kb

I have a single SVG file that holds several <symbol> tags that represent icons. It looks something like this:
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="icon-arrow_carrot_up_alt" viewBox="0 0 0 0">
<path d="M544 1024c265.088 0 480-214.912 480-480s-214.912-480-480-480-480 214.912-480 480 214.912 480 480 480zM313.728 601.152l207.552-207.552c6.272-6.272 14.464-9.344 22.72-9.344s16.448 3.072 22.72 9.344l207.552 207.552c12.48 12.48 12.48 32.768 0 45.248s-32.768 12.48-45.248 0l-185.024-185.024-185.024 185.024c-12.48 12.48-32.768 12.48-45.248 0s-12.48-32.768 0-45.248z" fill="none"></path>
</symbol>
<symbol id="icon-arrow_carrot-2down" viewBox="0 0 0 0">
<path d="M702.592 523.648l-191.744 169.6-191.744-169.6c-12.544-12.544-32.96-12.544-45.504 0s-12.544 32.96 0 45.504l214.080 189.376c6.4 6.4 14.784 9.472 23.168 9.344 8.384 0.128 16.768-2.88 23.168-9.28l214.080-189.376c12.544-12.544 12.544-32.96 0-45.504s-32.96-12.608-45.504-0.064zM748.096 294.144c-12.544-12.544-32.96-12.544-45.504 0l-191.744 169.6-191.744-169.6c-12.608-12.544-33.024-12.544-45.568 0s-12.544 32.96 0 45.504l214.080 189.376c6.4 6.4 14.784 9.472 23.168 9.344 8.384 0.128 16.832-2.944 23.168-9.344l214.080-189.376c12.672-12.544 12.672-32.896 0.064-45.504z" fill="none"></path>
</symbol>
...
</svg>
I'm running this file through a gulp imagemin task like so:
var gulp = require('gulp');
var changed = require('gulp-changed');
var imagemin = require('gulp-imagemin');
gulp.task('optimize-images', function() {
return gulp.src(paths.img.src + '/**/*')
.pipe(changed(paths.img.dest))
.pipe(imagemin([
imagemin.svgo({
plugins: [
{removeViewBox: true},
{cleanupIDs: false}
]
})
]))
.pipe(gulp.dest(paths.img.dest));
});
The file gets minified and put in the correct dest directory, however it says it has optimized 100% of the file, and its size goes down to 0kb with no file contents. The SVG is completely stripped from the file.
Why are all of the file contents of this svg getting destroyed and how do I fix it?
Lol woops, I found this issue on the svgo/svgo plugin page that gulp-imagemin uses. It was noted that:
<symbol> is considered empty by itself, because it's not rendered. That's why it's being removed by default. You need to turn off removeUselessDefs as suggested above.
So I added the following options to my task like so:
plugins: [
{cleanupIDs: false},
{removeUselessDefs: false},
{removeViewBox: true},
]
It was still not working, then I realized that style="display: none;" was on the <svg> tag, which means it's not rendered. The SVG is auto-generated by the gulp-icomoon-builder package so that explains it. That package offers an option to specify a custom external template to generate the file off of, so I will create one without the inline display style.
So taking off the display: none; style fixed this.

How to display svg markers that contain links to other svgs in HERE maps?

I am trying to display svg markers using the HERE Javascript API. I have followed the documentation, however my twist is that my svg contains links to other svgs. The reason for this is that I would like to display markers that have the same pin shape, but a different icon in the centre of the pin. As the icons will also be used in other places on my website, it makes sense to save the svgs separately so they only need updating in one place.
I can get standard svgs to display as markers, but when I try and nest the svgs, no marker is displayed on the map.
This is my code so far:
pin.svg - This is the basic pin shape that all markers will use
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="46.093765"
height="63.352634">
<g
transform="translate(-8.9327811,-0.45623957)"
id="layer1">
<path
d="M 22.15625,0 A 21.544797,22.053723 0 0 0 0,22.0625 21.544797,22.053723 0 0 0 10.40625,40.9375 c 1.062265,1.795846 11.125,19.40625 11.125,19.40625 l 12.125,-20.0625 A 21.544797,22.053723 0 0 0 43.09375,22.0625 21.544797,22.053723 0 0 0 22.15625,0 z"
transform="translate(10.432788,1.9651232)"
id="path5014"
style="fill:#cccccc;fill-opacity:1;stroke:#6e6e6e;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
d="m 50.723066,23.370655 a 17.218699,16.540131 0 1 1 -34.437397,0 17.218699,16.540131 0 1 1 34.437397,0 z"
transform="matrix(0.94189641,0,0,1.0010512,0.4199413,0.48436132)"
id="path5019"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
</g>
</svg>
here-example.svg - This is the example from the HERE site. I am using it as an icon that gets placed inside the pin
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg">
<rect stroke="black" fill="red" x="1" y="1" width="22" height="22" />
<text x="12" y="18" font-size="12pt" font-family="Arial" font-weight="bold" text-anchor="middle" fill="yellow">
C
</text>
</svg>
pin-with-icon.svg - This is my nested svg - it pulls in the pin and the icon and overlaps them
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<image x="20" y="20" width="300" height="80" xlink:href="pin.svg" />
<image x="120" y="32" width="100" height="30" xlink:href="here-example.svg" />
</svg>
index.html - This is where the HERE map is used and I try and display the markers. It makes heavy use of the HERE developer documentation. Replace <App_ID> and <App_Code> with the HERE credentials. In this page I also try and load the problematic svg into a div to prove that it works.
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<script src="http://js.api.here.com/v3/3.0/mapsjs-core.js" type="text/javascript" charset="utf-8"></script>
<script src="http://js.api.here.com/v3/3.0/mapsjs-service.js" type="text/javascript" charset="utf-8"></script>
<script src="http://js.api.here.com/v3/3.0/mapsjs-ui.js" type="text/javascript" charset="utf-8"></script>
<script src="http://js.api.here.com/v3/3.0/mapsjs-mapevents.js" type="text/javascript" charset="utf-8"></script>
<script src="http://code.jquery.com/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<link rel="stylesheet" type="text/css" href="http://js.api.here.com/v3/3.0/mapsjs-ui.css" />
</head>
<body>
<div style="width: 640px; height: 480px" id="mapContainer"></div>
<!-- Display the svg just to prove that the loads correctly -->
<div id="svgPin"></div>
<script>
// Initialize the platform object:
var platform = new H.service.Platform({
'app_id': '<App_ID>',
'app_code': '<App_Code>'
});
// Obtain the default map types from the platform object
var maptypes = platform.createDefaultLayers();
// Instantiate (and display) a map object:
var map = new H.Map(
document.getElementById('mapContainer'),
maptypes.normal.map,
{
zoom: 6,
center: { lng: 13.4, lat: 52.51 }
});
// MapEvents enables the event system
// Behavior implements default interactions for pan/zoom (also on mobile touch environments)
var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
// Example with svg pins not showing (svg contains links to other svgs)
var svgMarkupRetrieval = $.get('pin-with-icon.svg', function (svg) {
// Just to prove that the svg is loaded correctly
$('#svgPin').html(svg);
var icon = new H.map.Icon(svg);
// Add the first marker
var marker1 = new H.map.Marker({ lat: 52.4, lng: 13.3 },
{ icon: icon });
map.addObject(marker1);
// Add the second marker.
var marker2 = new H.map.Marker({ lat: 51.45, lng: 13.3 },
{ icon: icon });
map.addObject(marker2);
}, 'text');
// Example with svg pins showing (from HERE developer guide)
var svgMarkupRetrieval = $.get('here-example.svg', function (svg) {
var icon = new H.map.Icon(svg);
// Add the first marker
var marker3 = new H.map.Marker({ lat: 50.4, lng: 13.3 },
{ icon: icon });
map.addObject(marker3);
// Add the second marker.
var marker4 = new H.map.Marker({ lat: 49.45, lng: 13.3 },
{ icon: icon });
map.addObject(marker4);
}, 'text');
</script>
</body>
</html>
To use the code, create each file with the name I specify and put them in a folder in IIS. index.html has to be run through IIS, otherwise a cross origin request error occurs.
The pin-with-icon.svg loads separately (I load it under the map), as do the standard svg markers. But I cannot see why the markers I create using pin-with-icon.svg do not show on the map. Any help would be appreciated.
Use H.map.DomMarker and H.map.DomIcon

How to properly set width and height of SVG in javascript?

I am setting the width and height of a SVG image in javascript and it does not work. My code is like this:
var url = "https://dl.dropboxusercontent.com/u/74728667/left_arrow.svg";
var main = document.getElementById("main");
var xhr = new XMLHttpRequest();
xhr.open('get', url, true);
xhr.onreadystatechange = function(ev)
{
if (xhr.readyState === 4)
{
if (xhr.status === 200)
{
var e = xhr.responseXML.documentElement;
var svg = document.importNode(e, true);
svg.setAttribute("width", "28px");
svg.setAttribute("height", "28px");
svg.style.border="1px solid black";
svg.style.position="absolute";
svg.style.left="50px";
svg.style.top="50px";
main.appendChild(svg);
}
else
{
alert("request failed");
}
}
};
xhr.send();
Above code produces a result like this:
Note the SVG is not resized to the specified width and height. how can i fix this?. i have tried using svg.setAttributeNS(null, "width", "28px");
I inspected the resulting html produced by above code and if I copy paste it in a separate fiddle, I get this:
HTML:
<div id="main" style="position:relative"><svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 18 18" width="28px" height="28px" style="border: 1px solid black; position: absolute; left: 50px; top: 50px;">
<path fill="#a00" fill-opacity="1" stroke="none" d="M 0,9 l 17,9 -6,-9 6,-9 Z" onmouseover="evt.target.setAttribute('fill', '#ac0');" onmouseout="evt.target.setAttribute('fill','#a00');"></path>
</svg></div>
Output:
How can I make my js code produce the same result as above?
This probrem is caused by spell miss of view"b"ox in source svg file.
Web browser doesn't treat view"b"ox attribute as view"B"ox attribute, because stand alone svg file is xml file.
Thus you should correct the svg source like this.
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 18 18"
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"
But these attributes are treated as same at inline svg in html.
This act is defined by HTML parsing rule, see this.
http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#creating-and-inserting-nodes

Pure SVG way to fit text to a box

Box size known. Text string length unknown. Fit text to box without ruining its aspect ratio.
After an evening of googling and reading the SVG spec, I'm pretty sure this isn't possible without JavaScript. The closest I could get was using the textLength and lengthAdjust text attributes, but that stretches the text along one axis only.
<svg width="436" height="180"
style="border:solid 6px"
xmlns="http://www.w3.org/2000/svg">
<text y="50%" textLength="436" lengthAdjust="spacingAndGlyphs">UGLY TEXT</text>
</svg>
I am aware of SVG Scaling Text to fit container and fitting text into the box
I didn't find a way to do it directly without Javascript, but I found a JS quite easy solution, without for loops and without modify the font-size and fits well in all dimensions, that is, the text grows until the limit of the shortest side.
Basically, I use the transform property, calculating the right proportion between the desired size and the current one.
This is the code:
<?xml version="1.0" encoding="UTF-8" ?>
<svg version="1.2" viewBox="0 0 1000 1000" width="1000" height="1000" xmlns="http://www.w3.org/2000/svg" >
<text id="t1" y="50" >MY UGLY TEXT</text>
<script type="application/ecmascript">
var width=500, height=500;
var textNode = document.getElementById("t1");
var bb = textNode.getBBox();
var widthTransform = width / bb.width;
var heightTransform = height / bb.height;
var value = widthTransform < heightTransform ? widthTransform : heightTransform;
textNode.setAttribute("transform", "matrix("+value+", 0, 0, "+value+", 0,0)");
</script>
</svg>
In the previous example the text grows until the width == 500, but if I use a box size of width = 500 and height = 30, then the text grows until height == 30.
first of all: just saw that the answer doesn't precisely address your need - it might still be an option, so here we go:
you are rightly observing that svg doesn't support word-wrapping directly. however, you might benefit from foreignObject elements serving as a wrapper for xhtml fragments where word-wrapping is available.
have a look at this self-contained demo (available online):
<?xml version="1.0" encoding="utf-8"?>
<!-- SO: http://stackoverflow.com/questions/15430189/pure-svg-way-to-fit-text-to-a-box -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
width="20cm" height="20cm"
viewBox="0 0 500 500"
preserveAspectRatio="xMinYMin"
style="background-color:white; border: solid 1px black;"
>
<title>simulated wrapping in svg</title>
<desc>A foreignObject container</desc>
<!-- Text-Elemente -->
<foreignObject
x="100" y="100" width="200" height="150"
transform="translate(0,0)"
>
<xhtml:div style="display: table; height: 150px; overflow: hidden;">
<xhtml:div style="display: table-cell; vertical-align: middle;">
<xhtml:div style="color:black; text-align:center;">Demo test that is supposed to be word-wrapped somewhere along the line to show that it is indeed possible to simulate ordinary text containers in svg.</xhtml:div>
</xhtml:div>
</xhtml:div>
</foreignObject>
<rect x="100" y="100" width="200" height="150" fill="transparent" stroke="red" stroke-width="3"/>
</svg>
I've developed #Roberto answer, but instead of transforming (scaling) the textNode, we simply:
give it font-size of 1em to begin with
calculate the scale based on getBBox
set the font-size to that scale
(You can also use 1px etc.)
Here's the React HOC that does this:
import React from 'react';
import TextBox from './TextBox';
const AutoFitTextBox = TextBoxComponent =>
class extends React.Component {
constructor(props) {
super(props);
this.svgTextNode = React.createRef();
this.state = { scale: 1 };
}
componentDidMount() {
const { width, height } = this.props;
const textBBox = this.getTextBBox();
const widthScale = width / textBBox.width;
const heightScale = height / textBBox.height;
const scale = Math.min(widthScale, heightScale);
this.setState({ scale });
}
getTextBBox() {
const svgTextNode = this.svgTextNode.current;
return svgTextNode.getBBox();
}
render() {
const { scale } = this.state;
return (
<TextBoxComponent
forwardRef={this.svgTextNode}
fontSize={`${scale}em`}
{...this.props}
/>
);
}
};
export default AutoFitTextBox(TextBox);
This is still an issue in 2022. There is no way to define bounds and get text to scale in a pure scalable vector graphic. Adjusting the font size manually is still the only solution it seems, and the examples given are quite buggy. Has anybody figured out a clean solution that works? Judging by the svg spec it looks like a pure solution doesn't exist.
And to provide some sort of answer myself, this resource is the best I've found, is hacky, but works much more robustly: fitrsvgtext - storybook | fitrsvgtext - GitHub
I don't think its the solution for what you want to do but you can use textLength
with percentage ="100%" for full width.
<svg width="436" height="180"
style="border:solid 6px"
xmlns="http://www.w3.org/2000/svg">
<text x="0%" y="50%" textLength="100%">blabla</text>
</svg>
you can also add text-anchor="middle" and change the x position to center perfectly your text
this will not change the fontsize and you will have weird space letterspacing...
JSFIDDLE DEMO

Resources