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

ISSUE-6566: Fix SVGs for special Arc lines #6571

Merged

Conversation

gloriousjob
Copy link
Contributor

#6566 noticed an issue with SVGs not loading properly. Chasing this down revealed that the 'a' instruction apparently allows for a numbers like '00' but expects two numbers from that. The normal regex did not handle this case, resulting in incorrect arc handling.
I pulled some code from #6023 and modified it to be es5 compliant to help with this. Thanks @asturur for pointing out a clue may be there!

@gloriousjob
Copy link
Contributor Author

I'm not sure what to do for a unit test. The eStore svg in the issue returned an array of length 40 when loaded broken but returns length 41 when good. Should that be the test?

@gloriousjob gloriousjob changed the title ISSUE-6566: Fix SVGs so for special Arc lines ISSUE-6566: Fix SVGs for special Arc lines Sep 4, 2020
@asturur
Copy link
Member

asturur commented Sep 4, 2020

yes definitely we should test that arcs get parsed correctly.
I m also sure we can get away with less cose changes because some of those regex are already available in the codebase

@gloriousjob
Copy link
Contributor Author

I pulled out one variable that seemed easy enough. Not sure I saw anything for the rest though. I added a test that failed with the old code.

@asturur
Copy link
Member

asturur commented Sep 4, 2020

i ll find some time to help you with this.

@gloriousjob
Copy link
Contributor Author

Please do. It seems some visual tests are failing after changing that variable. I'd also appreciate help on my other issue as well.

@fubuk-i
Copy link

fubuk-i commented Sep 5, 2020

I'm not sure what to do for a unit test. The eStore svg in the issue returned an array of length 40 when loaded broken but returns length 41 when good. Should that be the test?

If you need more svg files for testing I can provide that @gloriousjob

@asturur
Copy link
Member

asturur commented Sep 5, 2020

is enough to write a simple path string 'A 001...' in al combinationations without spaces and check it gets parsed correctly. so one normal one with 2 space missing, another with just one... i do not know exactly which are the spaces that can be omitted.

@gloriousjob
Copy link
Contributor Author

gloriousjob commented Sep 5, 2020 via email

@gloriousjob
Copy link
Contributor Author

gloriousjob commented Sep 6, 2020

I don't know if this helps but the grammar is here:
https://svgwg.org/svg2-draft/paths.html#PathDataBNF
https://www.w3.org/TR/SVG11/paths.html#PathDataBNF

I don't know if we're striving for a particular version of svg... but anyways, that's where you can see in elliptical_arc_argument that before the first flag, there must be comma and/or space but after each flag, it's commawSp?. What's being thrown off in the normal regex is it's expecting the numbers to be separated, which is why we're getting 6 or 5 values instead of 7 (6 if the number after the flags is negative, 5 if positive without a sign).

src/util/path.js Outdated Show resolved Hide resolved
coords.push(args[j]);
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be this written like:

if (command.toLowerCase() === 'a') {
  // arcs have special flags that apparently don't require spaces so handle special
  coords = regArcArgumentSequence.exec(coordsStr);
}
else {
  coords = [];
  while ((match = re.exec(coordsStr))) {
     coords.push(match[0]);
  }
}

would it work?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line 613
https://github.com/fabricjs/fabric.js/pull/6571/files#diff-b4a17ab08e7f28e3bf83badb9236d01cR613
could go away

and so the pre declaration here would be just
https://github.com/fabricjs/fabric.js/pull/6571/files#diff-b4a17ab08e7f28e3bf83badb9236d01cR589

var coords,

thre is no point to reuse the same array this is not hot code to optimize.

Copy link
Contributor Author

@gloriousjob gloriousjob Sep 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This failed unit tests when I tried it. An arc can have multiple strings of numbers behind it so there needs to be a loop. Also, the exec returns an array that has [0] with a list of matches and then [1..n] with the matches (hence why j starts at 1). Maybe it can be reworked but I'm not sure there's a gain there.

Are you saying you don't like 'coords.length = 0' and would rather have 'coords = []' at 613? I didn't care for the former. Or are you saying you want 613 to be 'var coords = [];' and remove the top declaration? Or do you have other concerns as well?

src/util/path.js Outdated
rFlagCommaWsp = '([01])' + rCommaWsp + '?',
rCoordinatePair = '(' + fabric.reNum + ')' + rCommaWsp + '?(' + fabric.reNum + ')',
rArcSeq = rNumberCommaWsp + '?' + rNumberCommaWsp + '?' + rNumberCommaWsp + rFlagCommaWsp + rFlagCommaWsp +
rCoordinatePair,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't rCoordinatePair just another 2 rNumberCommaWsp?

is rArcSeq just:

rArcSeq = rNumberCommaWsp + '?' + rNumberCommaWsp + '?' + rNumberCommaWsp + rFlagCommaWsp + rNumberCommaWsp + '?'  + rNumberCommaWsp;

or am i over simplifying?

Also, what the ? is doing? i see is between everything but not after the third rNumberCommaWsp

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll change up the code. rCoordinatePair is rNumberCommaWsp + '?' + rNumber (no comma/spaces after). We don't need the abstraction unless we want to be clear we're matching the grammar with variable names.

The grammar claims the first two have optional commaspaces, which is what that '?' is handling (we'd need a different variable if '?' handled the whole rNumberCommaWsp but we don't want that). I question how accurate that is but matching the grammar is probably best here.

Comment on lines 25 to 39
QUnit.test('fabric.util.parsePath test special arcs', function(assert) {
assert.ok(typeof fabric.util.parsePath === 'function');
assert.ok(typeof fabric.util.makePathSimpler === 'function');
// eslint-disable-next-line max-len
var pathWithWeirdArc = 'p m19.801 17.771h1.457v-1.84c0-1.918.247-3.442.73-4.57.49-1.134 1.313-1.954 2.474-2.473 1.16-.512 2.75-.774 4.773-.774 3.578 0 5.367.875 5.367 2.629 0 .566-.187 1.055-.562 1.457-.371.406-.817.61-1.325.61-.238 0-.652-.047-1.234-.137a10.56 10.56 0 00-1.484-.133c-1.11 0-1.82.324-2.133.976-.316.653-.473 1.583-.473 2.797v1.457h1.504c2.336 0 3.504.707 3.504 2.114 0 1.004-.309 1.64-.93 1.91-.62.27-1.48.402-2.574.402h-1.504v16.238c0 1.215-.289 2.141-.863 2.778-.578.633-1.324.953-2.234.953-.871 0-1.594-.32-2.168-.953-.578-.637-.868-1.563-.868-2.778v-16.238h-1.683c-.914 0-1.617-.207-2.11-.613-.496-.414-.742-.95-.742-1.61 0-1.468 1.024-2.203 3.078-2.203';
var parsed = fabric.util.parsePath(pathWithWeirdArc);
var atLeastOneArc = false;
parsed.forEach(function(command) {
if (command.length > 1 && (command[0] === 'a' || command[0] === 'A')) {
assert.deepEqual(command.length, 8, 'Arc in SVG should always have size 8 but had less. Did not parse correctly.');
atLeastOneArc = true;
}
});
assert.ok(atLeastOneArc, 'No arcs found from SVG but at least one should have been parsed.');
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a fine test is:

Suggested change
QUnit.test('fabric.util.parsePath test special arcs', function(assert) {
assert.ok(typeof fabric.util.parsePath === 'function');
assert.ok(typeof fabric.util.makePathSimpler === 'function');
// eslint-disable-next-line max-len
var pathWithWeirdArc = 'p m19.801 17.771h1.457v-1.84c0-1.918.247-3.442.73-4.57.49-1.134 1.313-1.954 2.474-2.473 1.16-.512 2.75-.774 4.773-.774 3.578 0 5.367.875 5.367 2.629 0 .566-.187 1.055-.562 1.457-.371.406-.817.61-1.325.61-.238 0-.652-.047-1.234-.137a10.56 10.56 0 00-1.484-.133c-1.11 0-1.82.324-2.133.976-.316.653-.473 1.583-.473 2.797v1.457h1.504c2.336 0 3.504.707 3.504 2.114 0 1.004-.309 1.64-.93 1.91-.62.27-1.48.402-2.574.402h-1.504v16.238c0 1.215-.289 2.141-.863 2.778-.578.633-1.324.953-2.234.953-.871 0-1.594-.32-2.168-.953-.578-.637-.868-1.563-.868-2.778v-16.238h-1.683c-.914 0-1.617-.207-2.11-.613-.496-.414-.742-.95-.742-1.61 0-1.468 1.024-2.203 3.078-2.203';
var parsed = fabric.util.parsePath(pathWithWeirdArc);
var atLeastOneArc = false;
parsed.forEach(function(command) {
if (command.length > 1 && (command[0] === 'a' || command[0] === 'A')) {
assert.deepEqual(command.length, 8, 'Arc in SVG should always have size 8 but had less. Did not parse correctly.');
atLeastOneArc = true;
}
});
assert.ok(atLeastOneArc, 'No arcs found from SVG but at least one should have been parsed.');
});
QUnit.test('fabric.util.parsePath can parse arcs correctly when no spaces between flags', function(assert) {
// eslint-disable-next-line max-len
var pathWithWeirdArc = 'a10.56 10.56 0 00-1.484-.133';
var expected = ['a', 10.56, 10.56, 0, 0, 0, -1.484, -0.133];
var parsed = fabric.util.parsePath(pathWithWeirdArc);
var command = parsed[0];
assert.deepEqual(command, expected, 'Arc should be parsed correctly.');
});

Copy link
Member

@asturur asturur Sep 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be careful i did not run that test, so it can be wrong.

Other cases to tests:

path = 'a10.56 10.56 0 001-0.133'

(gonna have a look if they work)

This extra case should work and we should test it gets parsed

Copy link
Contributor Author

@gloriousjob gloriousjob Sep 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This failed when I tried it. It appears that the problem is fabric.reNum doesn't match rNumber is the parsePath method. I reverted to rNumber and the test passed. I think this is also the cause of the visual test failures. I'm checking in the code.

<?xml version="1.0" encoding="UTF-8"?>
<svg width="50mm" height="50mm" version="1.1" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<path d="m19.801 17.771h1.457v-1.84c0-1.918.247-3.442.73-4.57.49-1.134 1.313-1.954 2.474-2.473 1.16-.512 2.75-.774 4.773-.774 3.578 0 5.367.875 5.367 2.629 0 .566-.187 1.055-.562 1.457-.371.406-.817.61-1.325.61-.238 0-.652-.047-1.234-.137a10.56 10.56 0 00-1.484-.133c-1.11 0-1.82.324-2.133.976-.316.653-.473 1.583-.473 2.797v1.457h1.504c2.336 0 3.504.707 3.504 2.114 0 1.004-.309 1.64-.93 1.91-.62.27-1.48.402-2.574.402h-1.504v16.238c0 1.215-.289 2.141-.863 2.778-.578.633-1.324.953-2.234.953-.871 0-1.594-.32-2.168-.953-.578-.637-.868-1.563-.868-2.778v-16.238h-1.683c-.914 0-1.617-.207-2.11-.613-.496-.414-.742-.95-.742-1.61 0-1.468 1.024-2.203 3.078-2.203" fill="#0cc0f4"/>
</svg>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this file still needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't now. I can remove it. Don't know if we'd want to place it in a visual test.

@asturur
Copy link
Member

asturur commented Sep 6, 2020

All in all this is an improvement, regradless if between svg 1.1 and 2.0 there are small differences.
Let me know if you can do the extra clean up.

@asturur
Copy link
Member

asturur commented Sep 6, 2020

in your branch anyway those are failing:
https://github.com/fabricjs/fabric.js/blob/master/test/visual/golden/arc1.png
https://github.com/fabricjs/fabric.js/blob/master/test/visual/golden/arc2.png
https://github.com/fabricjs/fabric.js/blob/master/test/visual/golden/arc3.png
(they have the matching SVG in the test/visual/assets folder).
Something is changing in how they are rendered and is not good

@asturur
Copy link
Member

asturur commented Sep 7, 2020

I think we can merge, old test pass, i ll make a visual test with one of your failing SVGs and i ll try my personal battle with that loop for arcs. Thanks this was a great PR we kept changes super tidy and we reused some code we had around.

@asturur asturur merged commit 014b91b into fabricjs:master Sep 7, 2020
@gloriousjob gloriousjob deleted the ISSUE-6566-Fix-svg-render-arc-wrong branch September 7, 2020 18:22
@asturur asturur mentioned this pull request Sep 26, 2020
shanicerae pushed a commit to shanicerae/fabric.js that referenced this pull request Jan 16, 2021
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

Successfully merging this pull request may close these issues.

3 participants