Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SVGs with percentages for width and height result in inconsistent output #933

Open
jseppi opened this issue Nov 14, 2019 · 5 comments
Open
Assignees

Comments

@jseppi
Copy link
Member

jseppi commented Nov 14, 2019

If an SVG buffer with percentage-based width and height attributes is passed to mapnik.fromSVGBytes (or I assume any of the other fromSVG methods), the callback receives no error and a non-null image result.

However, the image result is inconsistent. If the SVG has width="100%" height="100%", we get back an image that, when encoded to PNG, is 1px x 1px.

If the SVG has non-100% values, like width="90%" height="90%", we get back an image, that when encoded to PNG, throws an error: Could not write to empty stream.

Desired behavior: Since it seems like percentage-based width and height values do not really produce a usable image in any case, I think the callback to fromSVGBytes should instead receive a error (maybe like "svg must have pixel-based width and height values") and null result.


Example test cases:

test('SVG with 100% width and height', t => {
  const percent = '100%';
  const svgPercentageWidthHeight = Buffer.from(
    `<svg xmlns='http://www.w3.org/2000/svg' width='${percent}' height='${percent}'><rect fill='#f0f' width='24' height='24'/></svg>`
  );

  const options = {
    max_size: 512,
    scale: 1
  };

  mapnik.Image.fromSVGBytes(svgPercentageWidthHeight, options, (err, image) => {
    // These two tests fail, but we would like to have have an error and not get an image
    t.ok(err, 'should have an error');
    t.notOk(image, 'should not have an image'); 

    // The image when encoded to PNG produces a 1px x 1px image
    const buffer = image.encodeSync('png');
    fs.writeFileSync('./100percentage_image.png', buffer);
    t.end();
  });
});

test('SVG with other percentage width and height', t => {
  const percent = '90%';
  const svgPercentageWidthHeight = Buffer.from(
    `<svg xmlns='http://www.w3.org/2000/svg' width='${percent}' height='${percent}'><rect fill='#f0f' width='24' height='24'/></svg>`
  );

  const options = {
    max_size: 512,
    scale: 1
  };

  mapnik.Image.fromSVGBytes(svgPercentageWidthHeight, options, (err, image) => {
    // These two tests fail, but we would like to have have an error and not get an image
    t.ok(err, 'should have an error');
    t.notOk(image, 'should not have an image'); 

    // The image when encoded to PNG throws an error:
    // Error: Could not write to empty stream
    const buffer = image.encodeSync('png');
    fs.writeFileSync('./percentage_image.png', buffer);
    t.end();
  });
});

test('SVG with no width and height', t => {
  const svgNoHeightWidth = Buffer.from(
    "<svg xmlns='http://www.w3.org/2000/svg'><rect fill='#f0f' width='24' height='24'/></svg>"
  );

  const options = {
    max_size: 512,
    scale: 1
  };

  mapnik.Image.fromSVGBytes(svgNoHeightWidth, options, (err, image) => {
    // These tests pass, as expected
    t.ok(err, 'should have an error'); 
    t.equal(
      err.message, 
      'image created from svg must have a width and height greater then zero',
      'has expected message'
    );
    t.notOk(image, 'should not have an image');
    t.end();
  });
});

cc @artemp

@jseppi jseppi changed the title SVG with percentages for width and height results in inconsistent output SVG with percentages for width and height result in inconsistent output Nov 14, 2019
@jseppi jseppi changed the title SVG with percentages for width and height result in inconsistent output SVGs with percentages for width and height result in inconsistent output Nov 14, 2019
@artemp
Copy link
Member

artemp commented Nov 14, 2019

@jseppi - looks inconsistent indeed. I'll take a look, thanks.

@artemp artemp self-assigned this Nov 14, 2019
@springmeyer
Copy link
Member

@artemp what is the status of this ticket?

@artemp
Copy link
Member

artemp commented Apr 14, 2021

I'm working to address this issue as part of mapnik/mapnik#4225
Throwing an exception when meaningful image dimensions can't be deduced (e.g width and height defined using % and no viewBox attribute) would be reasonable approach but I think we can do better. I'm planning to add "fallback" width and height as optional parameters. When specified these params will allow interpreting % values as relative to them. I'll post detailed explanation once implementation is in place, but from user point of view following SVG

<svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%'> <!-- NOTE: no "viewBox"-->
<rect fill='#f0f' width='100%' height='100%'/>
</svg>

and

mapnik.Image.fromSVGBytes(svg_bytes, {width:32, height:24} , (err, image) => {
}

will result in 32x24 image

/cc @springmeyer @jseppi

@artemp
Copy link
Member

artemp commented Apr 15, 2021

mapnik/mapnik@654a3c1
improved viewport logic [WIP]
/cc @jseppi @springmeyer

@artemp
Copy link
Member

artemp commented Apr 16, 2021

Initial implementation : https://github.com/mapnik/mapnik/tree/svg-viewport

svg2png --scale-factor= 4 --width=25 --height=20 // overwrite "width" and  "height" outermost `<svg>` element

Resulting png is 100x80px. I'm still wondering if this logic should only apply to width and height expressed in % (?)
There is also interesting effects when SVG paths are using absolute coordinates..
/cc @springmeyer @jseppi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants