Skip to content

Commit c1b961b

Browse files
authored
svggen: customizable serialization in SVGGraphics2D (#132)
Some people have complained that the SVG output by Batik's `SVGGraphics2D` is ugly or too big, see BATIK-502 or BATIK-1337. This commit introduces: - A public serializer that has general validity. - A new `XmlSerializer` interface that can be used to customize the serialization. Its default methods mimic what the previous code did. - A `CompactXmlSerializer` implementation which significantly decreases the size of the output files. - Methods for setting and getting a custom serializer to `SVGGeneratorContext`.
1 parent 5a4fce2 commit c1b961b

File tree

12 files changed

+615
-138
lines changed

12 files changed

+615
-138
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
3+
See the NOTICE file distributed with this work for additional
4+
information regarding copyright ownership.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
18+
*/
19+
20+
package io.sf.carte.echosvg.svggen;
21+
22+
import java.io.IOException;
23+
import java.io.Writer;
24+
25+
import org.w3c.dom.DocumentType;
26+
import org.w3c.dom.Node;
27+
28+
/**
29+
* Compact SVG serialization.
30+
*/
31+
public class CompactXmlSerializer implements XmlSerializer {
32+
33+
@Override
34+
public void serializeXML(Node node, Writer writer, boolean escaped)
35+
throws IOException, SVGGraphics2DRuntimeException {
36+
XmlWriter.IndentWriter iwriter = new XmlWriter.IndentWriter(writer) {
37+
38+
@Override
39+
public void printIndent() throws IOException {
40+
}
41+
42+
};
43+
44+
XmlWriter xmlWri = new XmlWriter(iwriter, escaped) {
45+
46+
@Override
47+
protected void writeXml(DocumentType docType) throws IOException {
48+
IndentWriter out = getWriter();
49+
out.write("<!DOCTYPE ");
50+
out.write(docType.getName());
51+
52+
String publicId = docType.getPublicId();
53+
if (publicId != null) {
54+
out.write(" PUBLIC '");
55+
out.write(publicId);
56+
out.write('\'');
57+
}
58+
59+
String systemId = docType.getSystemId();
60+
if (systemId != null) {
61+
out.write(" '");
62+
out.write(systemId);
63+
out.write('\'');
64+
}
65+
66+
out.write('>');
67+
}
68+
69+
};
70+
71+
xmlWri.writeXml(node);
72+
}
73+
74+
@Override
75+
public void writeDocumentType(Writer out, String name, String publicId, String systemId)
76+
throws IOException {
77+
out.write("<!DOCTYPE ");
78+
out.write(name);
79+
if (publicId != null) {
80+
out.write(" PUBLIC '");
81+
out.write(publicId);
82+
out.write('\'');
83+
}
84+
if (systemId != null) {
85+
out.write(" '");
86+
out.write(systemId);
87+
out.write('\'');
88+
}
89+
out.write('>');
90+
}
91+
92+
}

echosvg-svggen/src/main/java/io/sf/carte/echosvg/svggen/ErrorConstants.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public interface ErrorConstants {
4646
String ERR_ACI = "AttributedCharacterIterator not supported yet";
4747

4848
// XmlWriter
49-
String ERR_PROXY = "proxy should not be null";
49+
String ERR_WRITER = "Writer should not be null";
5050
String INVALID_NODE = "Unable to write node of type ";
5151

5252
// DOMGroup/TreeManager

echosvg-svggen/src/main/java/io/sf/carte/echosvg/svggen/SVGGeneratorContext.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ public class SVGGeneratorContext implements ErrorConstants {
117117
*/
118118
private Integer compressionLevel = null;
119119

120+
private XmlSerializer xmlSerializer = null;
121+
120122
/**
121123
* Class to describe the GraphicContext defaults to be used. Note that this
122124
* class does *not* contain a default for the initial transform, as this
@@ -473,4 +475,23 @@ public final String doubleString(double value) {
473475
}
474476
}
475477

478+
/**
479+
* Get the object to use in SVG serialization.
480+
*
481+
* @return the SVG serializer, or {@code null} if the default should be used.
482+
*/
483+
public XmlSerializer getXmlSerializer() {
484+
return xmlSerializer;
485+
}
486+
487+
/**
488+
* Set the object to use in SVG serialization.
489+
*
490+
* @param xmlSerializer the SVG serializer, or {@code null} if the default
491+
* should be used.
492+
*/
493+
public void setXmlSerializer(XmlSerializer xmlSerializer) {
494+
this.xmlSerializer = xmlSerializer;
495+
}
496+
476497
}

echosvg-svggen/src/main/java/io/sf/carte/echosvg/svggen/SVGGraphics2D.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -448,15 +448,31 @@ public void stream(Element svgRoot, Writer writer, boolean useCss, boolean escap
448448
//
449449
svgRoot.setAttributeNS(XMLNS_NAMESPACE_URI, XMLNS_PREFIX, SVG_NAMESPACE_URI);
450450

451-
svgRoot.setAttributeNS(XMLNS_NAMESPACE_URI, XMLNS_PREFIX + ":" + XLINK_PREFIX, XLINK_NAMESPACE_URI);
451+
svgRoot.setAttributeNS(XMLNS_NAMESPACE_URI, XMLNS_PREFIX + ":" + XLINK_PREFIX,
452+
XLINK_NAMESPACE_URI);
452453

453454
DocumentFragment svgDocument = svgRoot.getOwnerDocument().createDocumentFragment();
454455
svgDocument.appendChild(svgRoot);
455456

456-
if (useCss)
457+
if (useCss) {
457458
SVGCSSStyler.style(svgDocument);
459+
}
460+
461+
XmlSerializer xmlWri = getGeneratorContext().getXmlSerializer();
462+
463+
if (xmlWri == null) {
464+
xmlWri = new XmlSerializer() {
465+
};
466+
}
467+
468+
// <?xml version="1.0" encoding="..."?>
469+
// Should end with a newline so the serializer starts at column 1
470+
writeDocumentHeader(writer);
471+
472+
// Write DOCTYPE declaration here.
473+
xmlWri.writeDocumentType(writer, SVG_SVG_TAG, SVG_PUBLIC_ID, SVG_SYSTEM_ID);
474+
xmlWri.serializeXML(svgDocument, writer, escaped);
458475

459-
XmlWriter.writeXml(svgDocument, writer, escaped);
460476
writer.flush();
461477
} catch (SVGGraphics2DIOException e) {
462478
// this catch prevents from catching an SVGGraphics2DIOException
@@ -466,6 +482,12 @@ public void stream(Element svgRoot, Writer writer, boolean useCss, boolean escap
466482
generatorCtx.getErrorHandler().handleError(e);
467483
} catch (IOException io) {
468484
generatorCtx.getErrorHandler().handleError(new SVGGraphics2DIOException(io));
485+
} catch (SVGGraphics2DRuntimeException e) {
486+
// this catch prevents from catching an SVGGraphics2DRuntimeException
487+
// and wrapping it again in another SVGGraphics2DRuntimeException
488+
generatorCtx.getErrorHandler().handleError(e);
489+
} catch (RuntimeException e) {
490+
generatorCtx.getErrorHandler().handleError(new SVGGraphics2DRuntimeException(e));
469491
} finally {
470492
// Restore the svgRoot to its original tree position
471493
if (rootParent != null) {
@@ -478,6 +500,17 @@ public void stream(Element svgRoot, Writer writer, boolean useCss, boolean escap
478500
}
479501
}
480502

503+
private void writeDocumentHeader(Writer out) throws IOException {
504+
String encoding = null;
505+
506+
if (out instanceof OutputStreamWriter) {
507+
OutputStreamWriter osw = (OutputStreamWriter) out;
508+
encoding = XmlWriter.java2std(osw.getEncoding());
509+
}
510+
511+
XmlWriter.writeXmlHeader(out, encoding);
512+
}
513+
481514
/**
482515
* Invoking this method will return a set of definition element that contain all
483516
* the definitions referenced by the attributes generated by the various
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
3+
See the NOTICE file distributed with this work for additional
4+
information regarding copyright ownership.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
18+
*/
19+
20+
package io.sf.carte.echosvg.svggen;
21+
22+
import java.io.IOException;
23+
import java.io.Writer;
24+
25+
import org.w3c.dom.Node;
26+
27+
/**
28+
* XML serializer.
29+
*
30+
* <p>
31+
* Implementations should be able to correctly serialize SVG.
32+
* </p>
33+
*/
34+
public interface XmlSerializer {
35+
36+
/**
37+
* Serialize the given XML node to the given writer.
38+
* <p>
39+
* Callers of this method should catch {@code RuntimeException} in addition to
40+
* the {@link SVGGraphics2DRuntimeException}.
41+
* </p>
42+
*
43+
* @param node the node to serialize.
44+
* @param writer the writer to write the serialization output.
45+
* @param escaped defines if the characters in Text nodes and attribute values
46+
* should be escaped.
47+
* @throws IOException if an I/O error occurs.
48+
* @throws SVGGraphics2DRuntimeException or other runtime exception if the node
49+
* hierarchy is inconsistent or contains
50+
* unknown nodes.
51+
*/
52+
default void serializeXML(Node node, Writer writer, boolean escaped)
53+
throws IOException, SVGGraphics2DRuntimeException {
54+
XmlWriter xmlWri = new XmlWriter(writer, escaped);
55+
xmlWri.writeXml(node);
56+
}
57+
58+
/**
59+
* Serialize a document type with the given identifiers.
60+
*
61+
* @param writer the writer to write the serialization output.
62+
* @param name the document type name.
63+
* @param publicId the public identifier, or {@code null} if none.
64+
* @param systemId the system identifier, or {@code null} if none.
65+
* @throws IOException if an I/O error occurs.
66+
*/
67+
default void writeDocumentType(Writer writer, String name, String publicId, String systemId)
68+
throws IOException {
69+
XmlWriter.writeDocumentType(writer, name, publicId, systemId);
70+
}
71+
72+
}

0 commit comments

Comments
 (0)