yWorks / svg2pdf.js

A javascript-only SVG to PDF conversion utility that runs in the browser. Brought to you by yWorks - the diagramming experts
MIT License
649 stars 98 forks source link

Implement currentColor support #158

Closed Mrfence97 closed 3 years ago

Mrfence97 commented 3 years ago

This PR implements "currentcolour" support for the fill and stroke properties.

As per the SVG 1.1 (and 2.0) color spec, a user may specify a color property that is inherited by all child nodes. If the fill or stroke of any such nodes are set to "currentcolor", then the inherited color is used.

This is implemented by adding a color member to AtrributeState (that defaults to black). parseColor is amended to take in the current Context so that if "currentcolor" is passed, the color in the Context's AttributeState is returned.

Note the implementation is imperfect for two reasons:

  1. ~The spec states that "currentcolor" may also be used by stop-color in gradients. Current browser implementations only ever use the color in-scope of the gradient as defined inside the <defs> tag; however, no Context is consumed when a Gradient node is created (as far as I can tell). This means there is no way to access this 'within <defs>' context. Therefore "currentcolor" is always black if used inside a gradient.~ Done: see commit/comment below.
  2. ~Current browser implementations allow the current color to flow into the shadow-DOM if a <use> tag is used. However, the AttributeState is not passed down when a Use node is rendered, so the current color is inaccessible. So, again "currentcolor" is always black if used inside an element that is referenced with <use>.~ Done: see commit/comment below.

Hopefully someone can assist with these problems!

A number of test cases are also added.

yGuy commented 3 years ago

Very nice! Thank you so much for that - I believe we should definitely consider the "use" use-case, because in my experiencethis is one of the most valuable usages of "currentColor". We might analyse the "defs" and search for occurences of "currentColor" and then opt-out of the PDF-sharing of the elements - I don't believe you can parameterized PDF elements - any ideas, @HackbrettXXX ?

Mrfence97 commented 3 years ago

I've pushed another commit that adds <use> and mostly adds 'stop-color' support.

<use>

The current color context is passed down to the refContext used to render the element referenced by the <use>. Note the API of getRendered() in ReferencesHandler has been changed to include the color. This color is used in the key to map an element to the rendered element as a different PDF FormObject is required if a different color is used! An optimisation (as @yGuy suggests) would be look in the 'defs' to see if currentcolor is actually used. If it's not, there's no need to create a different FormObject even if the active color is different! Currently this is not implemented.

'stop-color'

The stop-color support isn't perfect. If currentcolor is used within a <stop>, browser implementations always use the color active where the <stop> is declared (and having a different color when the gradient is actually used has no effect). However, as things stand there is no easy way to access the color value active where the <stop> is declared. Opening test/specs/current-color/spec.svg is a browser, you'll see the squares located at (3,0) and (3,2) have a magenta gradient (inherited from the color in the parents <defs>). Currently the package renders these squares as fully black.

A potential fix would be to use getComputedStyles() on the <stop> (or <xGradient>) element to access the current color, but this requires the SVG to be rendered by the browser! An alternative could be to walk up the parent elements of the <stop> (or <xGradient>) to work out which color is active.

HackbrettXXX commented 3 years ago

Thanks for these improvements.

<use>

I think it's fine how you implemented it for now. If you want you can implement the performance improvement, but this has low priority for me.

stop-color

I think it would be good if we would implement this like in the browser. Walking up the DOM from the gradient element should be ok. Make sure to use the getAttribute function from the 'utils/node.tsfile to check for thecolorproperty. You can use theStyleSheets` instance from the context where the gradient is referenced. This instance is valid everywhere.

Mrfence97 commented 3 years ago

stop-color

I've pushed another commit that adds full currentcolor support for stop-color. As was suggested, in the Gradient node's apply() method, the DOM is walked up to calculate the color set in the <stop>'s context.

To easily walk up the DOM, I've added a parent member to the base SvgNode class that is set for every node by parse() to its parent SvgNode.