-
Notifications
You must be signed in to change notification settings - Fork 2
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
Render MermaidJS diagrams #35
Comments
Thank you for your bug report, Dave.
@Test
public void testMermaid() throws TranscoderException, IOException {
test("samples/mermaid.svg");
} You want to use The following stylesheet represents an invalid
CSS document.
#mermaid-1637216322989 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-1637216322989 .error-icon{fill:#552222;}#mermaid-1637216322989 .error-text{fill:#552222;stroke:#552222;}#mermaid-1637216322989 .edge-thickness-normal{stroke-width:2px;}#mermaid-1637216322989 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-1637216322989 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-1637216322989 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-1637216322989 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-1637216322989 .marker{fill:#333333;stroke:#333333;}#mermaid-1637216322989 .marker.cross{stroke:#333333;}#mermaid-1637216322989 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-1637216322989 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-1637216322989 .cluster-label text{fill:#333;}#mermaid-1637216322989 .cluster-label span{color:#333;}#mermaid-1637216322989 .label text,#mermaid-1637216322989 span{fill:#333;color:#333;}#mermaid-1637216322989 .node rect,#mermaid-1637216322989 .node circle,#mermaid-1637216322989 .node ellipse,#mermaid-1637216322989 .node polygon,#mermaid-1637216322989 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-1637216322989 .node .label{text-align:center;}#mermaid-1637216322989 .node.clickable{cursor:pointer;}#mermaid-1637216322989 .arrowheadPath{fill:#333333;}#mermaid-1637216322989 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-1637216322989 .flowchart-link{stroke:#333333;fill:none;}#mermaid-1637216322989 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-1637216322989 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-1637216322989 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-1637216322989 .cluster text{fill:#333;}#mermaid-1637216322989 .cluster span{color:#333;}#mermaid-1637216322989 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-1637216322989 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
Original message:
you try to use an empty method in Adapter-class which is caused by the custom property Neither EchoSVG nor Batik support custom properties (which implies supporting My long-term intention is to land an alternative SVGOM implementation in EchoSVG, based on CSS4J's native DOM. Native DOM fully supports To write that thing I was waiting for Houdini's TypedOM to be implemented by a browser so I could use a supported standard CSSOM API (the CSS Object Model that EchoSVG/Batik implement is deprecated and no browser supports it). However, I may still write a SVGOM implementation inheriting from native DOM, one that does not implement any TypedOM yet; that thing would allow people to render SVG that includes advanced CSS like |
Consider renaming |
Found a few implementations:
There's also CodeGen, a transpiler for C++/Java: |
Typed OM is a work in progress (see https://github.com/w3c/css-houdini-drafts/issues to have an idea, you'll find even one issue that I opened in May) and for example the color API is still immature. The following link lists Chrome's "supported properties" but even those properties are featuring a subset of a full typed-om API: See also the Typed OM feature Chrome Status.
The actual implementation is here: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/third_party/blink/renderer/core/css/cssom/ Note that I'm always talking about a Typed OM implementation that's usable from Javascript (which is Typed OM's target and what the above classes provide).
CSS4J already has its own full-featured typed-value Object Model. It has problematic legacy stuff like What I meant in my previous post is that I wanted to provide a Typed OM front-end to my own typed object model, one that can be used from Javascript, but Typed OM isn't ready yet. I do not think that your SVG files use Typed OM at all, therefore —as I said in my previous post— I could implement a rendering process that uses advanced CSS, but without exposing (immature) Typed OM to Javascript. Hope that my intent is clearer now. |
Have you looked into CSSelly?
Maybe there's some room for collaboration? |
Seems a competing project to CSS4J, allowing a subset of what CSS4J supports. IMHO the combination of the validator.nu htmlparser and CSS4J is far more functional (and API-standard) than the whole Jodd Lagarto suite. See https://css4j.github.io/usage.html for details about the usage of CSS4J with htmlparser.
Why? It is a competing project offering a small fraction of what CSS4J (which is what EchoSVG uses) gives. What feature are you finding there that you miss in CSS4J? CSS4J has all the features needed to implement this issue, it is EchoSVG (Batik) which needs to be changed. For example, I need to change the Batik transcoding API so one can render a specific All of this takes time, please be patient. |
To be more specific, the level of refactor required to support arbitrary CSS style computation providers in EchoSVG/Batik amounts practically to a rewrite. The current code is too closely tied to the way that Batik does CSS (which in turn is tied to old CSS APIs), and refactoring classes and interfaces in a way that CSS becomes really pluggable is going to be hard. This could take more than a year or may not be done at all. |
I opened issues #39 and #40 which describe more precisely my intentions about upgrading this project to more recent SVG and CSS, please feel free to comment there if you have something to add. As I know that some of you are eagerly waiting news about this issue, here is a status update albeit a disappointing one: I haven't worked on this nor do I have expectations to do that in the foreseeable future. A reminder of the scale of this project: according to EchoSVG's OpenHub page, EchoSVG is over 170K lines of code (LoC). In case you want to compare it to Batik beware that LoC are being counted twice there, the real figure would be like 220K LoC; the difference with EchoSVG is mostly due to the CSS parser and the old testing framework having been removed (this project uses CSS4J which in turn is 180K LoC). Meanwhile, if anyone is willing to contribute patches I'll be happy to review and eventually merge them. |
If a user agent finds something that it does not support, should ignore it and proceed. See https://www.w3.org/TR/CSS22/syndata.html#parsing-errors "User agents must ignore a declaration with an unknown property." See also #35.
When EchoSVG finds a style that it does not support, should ignore it. That's what user agents are supposed to do, this way authors can create style sheets that are valid for both modern and legacy user agents. I just applied a patch that ignores declarations of custom properties. More changes of this kind need to be done to the codebase, but at least with said patch I can produce this: It has to be investigated why the texts aren't being drawn, but it is a beginning. Furthermore, there is a relatively easy workaround that can be applied: I'm preparing an example of how to combine CSS4J and EchoSVG to produce images that use modern CSS like |
This is used by FontAwesome which in turn is used by Mermaid. Related to #35
Custom properties aren't the real problem here, so I'll modify the title of this issue. The real problem is that foreign HTML elements are being used and EchoSVG does not support that. However, I managed to create a workaround that allows the transcoding of SVG images that use advanced CSS and also foreign HTML elements. I'm not done yet, but this is a Mermaid diagram that I rendered today with EchoSVG: What I do is to take the complex, unrenderable document and transform it into something that EchoSVG can understand. The thing isn't perfect but should be enough to get the work done most of the times. |
Here is the main javadoc description of the utility that I created. Comments are welcome (for example do you like the class name?): CSSTranscodingHelperUtility for transcoding documents that use modern CSS, bypassing the EchoSVG style computations. To obtain the best results, your document should define style properties in a style sheet or the style attribute, instead of using style-like attributes like For example it is preferable: <text style="font-size: 20px;"> to: <text font-size="20"> Supported CSSModern CSS is allowed, with most of the following specifications being supported: • Selectors Level 4 Configuring for Media QueriesMedia Queries use the SVG viewport dimensions by default, but you can set the dimensions used by queries by setting the For example: Transcoder transcoder = new PNGTranscoder();
CSSTranscodingHelper helper = new CSSTranscodingHelper(transcoder);
helper.getTranscoder().addTranscodingHint(SVGAbstractTranscoder.KEY_MEDIA, "screen");
helper.getTranscoder().addTranscodingHint(SVGAbstractTranscoder.KEY_WIDTH, 450);
helper.getTranscoder().addTranscodingHint(SVGAbstractTranscoder.KEY_HEIGHT, 500);
String uri = "https://www.example.com/my_image.svg";
java.io.Reader re = ... [SVG document reader]
java.io.OutputStream os = ... [where to write the image]
TranscoderOutput dst = new TranscoderOutput(os);
helper.transcode(re, uri, dst, null); Using a selectorYou can also render helper.transcode(re, uri, dst, "#mySvg"); Foreign ElementsThe processing of foreign elements is performed via SVG 1.2 features. Therefore, if a document contains foreign elements, the |
I could not find this issue in the open list, and it was because it was closed automatically when I merged the PR. Anyway, if there are cases where the workaround is not giving the expected results, a new issue could be opened. |
Were you able to use the new transcoding helper, @DaveJarvis ? Did it work with your use case? |
Here's the code KeenWrite uses to transcode SVG documents into /**
* Creates a rasterized image of the given source document.
*
* @param input The source document to transcode.
* @param key Transcoding hint key.
* @param width Transcoding hint value.
* @return A new {@link BufferedImageTranscoder} instance with the given
* transcoding hint applied.
*/
private static BufferedImage rasterize(
final TranscoderInput input, final Key key, final float width )
throws TranscoderException {
final var transcoder = new BufferedImageTranscoder();
transcoder.setErrorHandler( sErrorHandler );
transcoder.addTranscodingHint( key, width );
transcoder.transcode( input, null );
return transcoder.getImage();
} From KeenWrite's perspective, this is:
Whether the SVG document requires CSS transcoding is really an implementation detail that, preferably, applications would need not be forced to consider. If there are performance trade-offs, then perhaps the helper class could be enabled using a transcoding hint, such as: final var transcoder = new BufferedImageTranscoder();
transcoder.setErrorHandler( sErrorHandler );
// Always enable CSS transcoding (used by MermaidJS).
transcoder.addTranscodingHint( KEY_CSS_TRANSCODER, new BooleanKey( true ) );
transcoder.addTranscodingHint( key, width );
transcoder.transcode( input, null );
return transcoder.getImage(); This would be backwards compatible, as well. That is, if the SVG transcoder receives an SVG document that does not require the CSS transcoder helper, then we'd expect the Using this approach, the name Conversely, if we wanted the SVG transcoding to "just work", we could make I've switched to EchoSVG with KeenWrite. Looking forward to seeing the reduced footprint! To answer your question: No, it doesn't really work with my use case, as it stands. |
FWIW, it's quite exciting to see these changes come to fruition. This will be the first non-browser-based SVG implementation that can rasterize MermaidJS diagrams (that I know of). The gargantuan amount of effort gone into getting the code to this point is very much appreciated. I'm looking forward to trying out the demo diagrams simply by adding the following lines in a document:
|
This is something that you could have commented before the pull request was merged and/or the release published. But please consider these points:
I want people to be aware of the previous points, and that is more easily achieved if they use a separate
Instead of transcoder.transcode( input, null ); you would have: CSSTranscodingHelper helper = new CSSTranscodingHelper(transcoder);
helper.transcode(new StringReader(xml), uri, null, null); and that would work. |
Thank you, but the core of the Helper was written in like a day. Native support is going to be much harder, and may never happen. Updating Batik/EchoSVG to browser-level spec compliance is a task for a team of full-time developers during years.
One of the planned features is to support triggering DOM events in the Helper, so in principle |
Can the URI be derived from the private static BufferedImage rasterize(
final TranscoderInput input, final Key key, final float width )
throws TranscoderException {
final var transcoder = new BufferedImageTranscoder();
transcoder.setErrorHandler( sErrorHandler );
transcoder.addTranscodingHint( key, width );
final var helper = new CSSTranscodingHelper( transcoder );
helper.transcode( input, null, null );
return transcoder.getImage();
} |
Also, would it be possible to wrap the public void transcode(Reader reader, String documentURI, TranscoderOutput output, String selector) throws TranscoderException { |
Here's the revised code: private static BufferedImage rasterize(
final TranscoderInput input, final Key key, final float width )
throws TranscoderException {
final var transcoder = new BufferedImageTranscoder();
transcoder.setErrorHandler( sErrorHandler );
transcoder.addTranscodingHint( key, width );
try {
final var helper = new CSSTranscodingHelper( transcoder );
helper.transcode( input.getReader(), input.getURI(), null, null );
}
catch(final IOException ex) {
throw new TranscoderException( ex );
}
return transcoder.getImage();
} Unfortunately, this doesn't work. No regular SVG images are rendered. It may be that Mermaid diagrams work, but both need to work with the same API calls. KeenWrite doesn't distinguish between different types of
Oof. One of the reasons I went with FlyingSaucer was to avoid having to bundle the JavaScript engine Rhino into my application. It feels like the tech stack for SVG images is moving in the wrong direction if JavaScript is now a requirement to rasterize SVG documents (that's a mermaid complaint, btw, EchoSVG is awesome). Hopefully there will be a way to "opt out" so that EchoSVG library users can still get partial rendering of SVG without the overhead of pulling in an entire JavaScript engine. |
Not unless you put it there first.
I'd rather not. It is generally better to handle possibly transient exceptions (like
That's because you aren't providing a real input.
Javascript is not a requirement. Both Batik and EchoSVG carry Rhino as an optional dependency. Scripts are supported but not mandatory. |
Maybe a little more background would be useful on what I'm trying to accomplish. KeenWrite rasterizes SVG images from the following sources:
Both of these scenarios currently render using the same underlying code. This is why I first convert the SVG into a public static BufferedImage rasterize(
final Document svg, final int width ) throws TranscoderException {
return rasterize(
new TranscoderInput( svg ),
KEY_WIDTH,
fit( svg.getDocumentElement(), width )
);
}
public static BufferedImage rasterize(
final InputStream svg, final float dpi ) throws TranscoderException {
return rasterize(
new TranscoderInput( svg ),
KEY_PIXEL_UNIT_TO_MILLIMETER,
1f / dpi * 25.4f
);
} The public class CSSTranscodingHelper {
public void transcode(TranscoderInput input, OutputStream out) throws TranscoderException {
assert input != null;
final var uri = input.getUR();
final var doc = input.getDocument();
if( doc != null ) {
transcode( doc, uri, out );
}
else {
final var reader = input.getReader();
if( reader != null ) {
transcode( reader, uri, out );
}
}
}
} Something like that would allow for parsing the DOM when needed (via the |
Please see #64 for why I did not use Please create a new discussion if you want to keep this conversation going. Quite a few people subscribed to this thread and we do not want to spam them. |
Background
MermaidJS is a popular (38k ⭐) server-side rendering suite for translating text-based diagram descriptions into scalable vector graphics. No open source rendering library that I tried can render its diagrams (see issue). Kroki is a REST-based service for rendering text-based diagrams, including mermaid, as SVG.
Replicate
mermaid.svg.txt
assamples/mermaid.svg
(see Kroki render).SamplesRenderingTest.java
:Expected
Diagram is rendered.
Actual
Exception while validating the XML: the
<div>
element is unexpected.Work around
Comment out XML validation in
SAXDocumentFactory.java
:Continue
Expected
The diagram renders.
Actual
<style>
is expected to be<style type="text/css">
.Work around
Add the missing
type
attribute. Consider relaxing the DTD.Continue
Expected
Diagram is rendered.
Actual
Cannot parse the CSS.
Work around
None. At this point the CSS parser cannot handle the semi-colon. Specifically, in
CSSParser.java
, thehandleSemicolon
method fires anendOfPropertyDeclaration
. Is parsing the double-quotes for"trebuchet ms"
causing the parser to get into an invalid state?Addendum
Using the W3C validator service, the document validates correctly as an SVG 1.1 document.
The text was updated successfully, but these errors were encountered: