meerk40t / svgelements

SVG Parsing for Elements, Paths, and other SVG Objects.
MIT License
139 stars 29 forks source link

Process Dom and CSS correctly as part of a document-structure. #87

Open tatarize opened 3 years ago

tatarize commented 3 years ago

This is not for svgelements 1.x.x rather it would be a rather massive shift and may require an entirely new project depending on a variety of factors. The idea would be to parse the SVG dom and css independently of the SVG itself. This would require applying the dom and css rules as part of a proper document class. This could be built as well as parsed from a document. The dom nodes would be independent of and different than individual svg elements. This would process the SVG significantly more like svgwrite runs their svg code. Where the CSS rules could be altered or changed and it would have a direct effect on the document which would be parsed in a lazy fashion.

This would generalize the documents into nodes and css and facilitate building documents qua svgwrite but would also parse the documents initially. This would work equally well on any xml-like structures or even HTML data. Then the svgelements like code would simply be the creation of first order elements out of this data with direct links back into the Document structures.

Determining a particular path's transformation would then be done through rendering, which is currently more or less integrated into the parse() functionality. You could round-trip an svg document, loading all the data, and modifying some CSS or applying a change to the document and saving it back out. Or you could extract the actual paths themselves, and modify the data applied to the document.

There is some chance this could be properly executed within the svgelements 1.x.x scheme without massive breaking changes. We would introduce a Document class which would contain some svgs as needed and various dom nodes. This data would register the nodes, and still modify them. Record correct style sheet information (style sheet information can be applied after elements are parsed and should apply correctly), and apply the full style sheet rules or add your own css style sheet rules. The second phase of parse would be to turn these nodes into fully fledged svg objects. In effect the document would be parsed in memory.

While this might be out of scope for what svgelements currently does, there's some rather noteworthy ease to the project as a whole. And it would largely encompass the powers of svgwrite with parsing and processing and rendering.

tatarize commented 3 years ago

Most of the attributes are implementing CSS like lookups and locations. This should be fairly easily implemented within a Node/Tree structure. The NS() suffixed commands can just take an optional namespace argument.

Node Attributes

Node Methods

Element Methods

Element Attributes

CSS: https://www.w3schools.com/cssref/css_selectors.asp

tatarize commented 3 years ago

See: https://tinycss.readthedocs.io/en/latest/index.html https://svgwrite.readthedocs.io/en/latest/

tatarize commented 3 years ago

https://www.w3.org/TR/CSS22/syndata.html Has the syntax data. Lexicographical parsers aren't too hard. The scheme for the svg path ( https://github.com/meerk40t/svgelements/blob/d5615435b41f7a3a4f57ec490036c9321c9435cd/svgelements/svgelements.py#L210 ) would work to parse most bnf like grammars. Doing this would largely duplicate the work of https://github.com/Kozea/tinycss/blob/master/tinycss/css21.py

The general sequence of events to get to a more usable codeset would likely fit with very similar functionality to modern browsers: https://www.html5rocks.com/en/tutorials/internals/howbrowserswork/

The methods for replacing a node with the subclass node type defined is within the DOM spec: https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#DOMImplementationSource

tatarize commented 3 years ago

Reading through the functionality of modern browsers and checking with some javascript. The DOM content tree shouldn't be too hard to implement. Node objects, their relationships, CSS, and styles are applied to the object in question and are not cascaded or precomputer except during rendering. The goal then would be to create a correct content tree without rendering, and then permit rendering.

class RenderObject{
  virtual void layout();
  virtual void paint(PaintInfo);
  virtual void rect repaintRect();
  Node* node;  //the DOM node
  RenderStyle* style;  // the computed style
  RenderLayer* containgLayer; //the containing z-index layer
}

Or at least doing it in a way to calculate the computed style and making that data able to access the internal geometry of the various shapes as needed after rendering. Either attached or directly as part of the dom nodes. For example if you disable the "visibility" the correct corresponding rendered node is None. This node does not render. But, it would still be appropriate and acceptable to have a dom node exist that could enable the rendering thus forcing a re-render of the children which would render them. Also, if you directly appended text to dom node that contained a rectangle, that would likely be bootstrapped to find the Rect object and apply it correctly placed in the dom.

Implementing DOM and CSS as part of a document structure would basically allow for loading of svgwrite like classes where each of the relevant classes would be loaded up inplace for the DOM. Then a different render routine would apply the css, cascade the styles, and calculate the geometry. The render tree is not the same as the DOM tree. Though it would be possible to have the rendered objects reference their DOM counterpart but the DOM version cannot have the full rendered styles since they could have a non 1:1 relationship. DOM nodes marked visibility=none either on themselves or their parents do not exist in the render tree. If a node part of a use object it could result in many copies existing in the rendered tree. And these copies would have different renderedStyles. svgwrite nodes represent the DOM nodes, whereas svgelements objects are explicitly the rendered nodes.

It would seem as though the best first step might be implementing DOMCSS at least a bit, then bootstrapping svgwrite's DOM nodes into those places. This would ostensibly add loading to svgwrite classes. Then some magic happens. And can also generate relevant geometric information from this data in a useful way that could inform changes we'd like to make to the DOM values.

The resulting path we may have from rendering could be in part a transformation on a group parent, which would mean simply writing the altered node back into DOMPath would result in 2x application of that transformation. Though in theory you could actually push the points you changed back through through the inverse matrix of the styled change and result in a changed original path that would be correct. Likewise changing any colors could simply be applied to the local style of the DOM node which would force those relevant colors irrespective of the original renderStyle. For most other things you'd want the renderedStyles to exist but not be editable, since there are some cases like children of use that couldn't actually have their local style object changed because doing so would change every single copied object. Rather the use object would need to be replaced in the DOM with the object itself. Though these modifications being reflected back into the dom tree, would be a broad subject they could be deferred as various features later on. You'd have access to the DOM tree and the render tree, and you could directly save the DOM tree again or perhaps even just save the flattened render tree if that serves your needs. Which would fit the bulk of the project.

tatarize commented 3 years ago

There is considerable overlap with this and xml dom objects. With bootstrapping and some rendering code these could be made more closely related. For example implementing a core fusion of svgwrite and xml.dom such that it would perform correct bootstrapping of tagged objects.

https://docs.python.org/3/library/xml.dom.html#module-xml.dom

We could then read and write documents with svgwrite-like correctness. Then implementations of the CSS Length, Angle, Matrix etc. classes and we an implementation of stylesheets, rendering, and geometry might be reasonable from there.

tatarize commented 3 years ago

While xml.dom is a faithful dom implementation the lack of bootstrapping or other common dom related CSS makes it a needlessly cumbersome method of accessing xml. So most examples relate to minidom and even to element-tree since without any advantages of bootstrapping this is a bunch of rather strange functions that do not actually provide any utility to the end user.

Correctly done, loading a dom should bootstrap and load svgwrite like objects within the dom and permit the underlying required interplay. Such that the tagged rect object is actually the same as a rect within svgwrite-like dom formatting. Then loading and saving could be correctly achieved. And perhaps providing rendering and rendered objects both cached and on the fly. Probably flattened and in the correct render order.

This rendered information would then be able to be utilized to update the dom based on the rendered result. However, without bootstrapping it might be best to simply emulate xml.dom or xml.minidom without actually implementing them. Start with adding reading ability to svgwrite and then rendering ability to svgwrite.