Skip to content

Commit 2b35b35

Browse files
committed
Take DPI fix from https://github.com/nidi3/graphviz-java & Reduce default resolution
1 parent f4d20fe commit 2b35b35

File tree

25 files changed

+138
-85
lines changed

25 files changed

+138
-85
lines changed

graphviz-test-example/ex1.png

-158 Bytes
Loading

graphviz-test-example/ex1i.png

-92 Bytes
Loading

graphviz-test-example/ex1m.png

-92 Bytes
Loading

graphviz-test-example/ex2.png

-261 Bytes
Loading

graphviz-test-example/ex3.png

-190 Bytes
Loading

graphviz-test-example/ex4-1.png

-270 Bytes
Loading

graphviz-test-example/ex4-2.png

-92 Bytes
Loading

graphviz-test-example/ex5.svg

+1-1
Loading

graphviz-test-example/ex5b.png

53 Bytes
Loading

graphviz-test-example/ex5p.pdf

-48 Bytes
Binary file not shown.

graphviz-test-example/ex5s.png

-209 Bytes
Loading

graphviz-test-example/ex6a.png

-185 Bytes
Loading

graphviz-test-example/ex6d.png

-185 Bytes
Loading

graphviz-test-example/ex7.png

-12.1 KB
Loading

graphviz-test-example/ex8.png

-2.33 KB
Loading

src/main/java/guru/nidi/graphviz/engine/Format.java

+73-22
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,37 @@
2424
public enum Format {
2525
PNG("svg", "png", true, true) {
2626
@Override
27-
EngineResult postProcess(EngineResult result, double fontAdjust) {
28-
return result.mapString(s -> postProcessSvg(s, true, fontAdjust));
27+
String preProcess(String src) {
28+
return encodeXml(super.preProcess(src));
29+
}
30+
31+
@Override
32+
EngineResult postProcess(Graphviz graphviz, EngineResult result) {
33+
return result.mapString(s -> postProcessSvg(graphviz, s, true));
2934
}
3035
},
3136

3237
SVG("svg", "svg", false, true) {
3338
@Override
34-
EngineResult postProcess(EngineResult result, double fontAdjust) {
35-
return result.mapString(s -> postProcessSvg(s, true, fontAdjust));
39+
String preProcess(String src) {
40+
return encodeXml(super.preProcess(src));
41+
}
42+
43+
@Override
44+
EngineResult postProcess(Graphviz graphviz, EngineResult result) {
45+
return result.mapString(s -> postProcessSvg(graphviz, s, true));
3646
}
3747
},
3848

3949
SVG_STANDALONE("svg", "svg", false, true) {
4050
@Override
41-
EngineResult postProcess(EngineResult result, double fontAdjust) {
42-
return result.mapString(s -> postProcessSvg(s, false, fontAdjust));
51+
String preProcess(String src) {
52+
return encodeXml(super.preProcess(src));
53+
}
54+
55+
@Override
56+
EngineResult postProcess(Graphviz graphviz, EngineResult result) {
57+
return result.mapString(s -> postProcessSvg(graphviz, s, false));
4358
}
4459
},
4560
DOT("dot", "dot", false, false),
@@ -57,9 +72,9 @@ EngineResult postProcess(EngineResult result, double fontAdjust) {
5772
"<svg width=\"(?<width>\\d+)(?<unit>p[tx])\" height=\"(?<height>\\d+)p[tx]\""
5873
+ "(?<between>.*?>\\R<g.*?)transform=\"scale\\((?<scaleX>[0-9.]+) (?<scaleY>[0-9.]+)\\)",
5974
Pattern.DOTALL);
60-
private static final double PIXEL_PER_POINT = 1.3333;
75+
6176
final String vizName;
62-
final String fileExtension;
77+
public final String fileExtension;
6378
final boolean image;
6479
final boolean svg;
6580

@@ -70,34 +85,70 @@ EngineResult postProcess(EngineResult result, double fontAdjust) {
7085
this.svg = svg;
7186
}
7287

73-
EngineResult postProcess(EngineResult result, double fontAdjust) {
88+
String preProcess(String src) {
89+
return replaceSubSpaces(src);
90+
}
91+
92+
EngineResult postProcess(Graphviz graphviz, EngineResult result) {
7493
return result;
7594
}
7695

77-
private static String postProcessSvg(String result, boolean prefix, double fontAdjust) {
96+
private static String replaceSubSpaces(String src) {
97+
final char[] chars = src.toCharArray();
98+
for (int i = 0; i < chars.length; i++) {
99+
if (chars[i] < ' ' && chars[i] != '\t' && chars[i] != '\r' && chars[i] != '\n') {
100+
chars[i] = ' ';
101+
}
102+
}
103+
return new String(chars);
104+
}
105+
106+
private static String encodeXml(String src) {
107+
return src.replace("&", "&amp;");
108+
}
109+
110+
private static String postProcessSvg(Graphviz graphviz, String result, boolean prefix) {
78111
final String unprefixed = prefix ? withoutPrefix(result) : result;
79-
final String pixelSized = pointsToPixels(unprefixed);
80-
return fontAdjust == 1 ? pixelSized : fontAdjusted(pixelSized, fontAdjust);
112+
final String pixelSized = pointsToPixels(unprefixed, graphviz.dpi(),
113+
graphviz.width, graphviz.height, graphviz.scale);
114+
return graphviz.fontAdjust == 1 ? pixelSized : fontAdjusted(pixelSized, graphviz.fontAdjust);
115+
}
116+
117+
private static String withoutPrefix(String svg) {
118+
final int pos = svg.indexOf("<svg ");
119+
return pos < 0 ? svg : svg.substring(pos);
81120
}
82121

83-
private static String pointsToPixels(String svg) {
122+
private static String pointsToPixels(String svg, double dpi, int width, int height, double scale) {
84123
final Matcher m = SVG_PATTERN.matcher(svg);
85124
if (!m.find()) {
86125
LOG.warn("Generated SVG has not the expected format. There might be image size problems.");
87126
return svg;
88127
}
89-
if (m.group("unit").equals("px")) {
90-
return svg;
128+
return m.replaceFirst("<svg " + svgSize(m, width, height, scale) + m.group("between") + svgScale(m, dpi));
129+
}
130+
131+
private static String svgSize(Matcher m, int width, int height, double scale) {
132+
double w = Integer.parseInt(m.group("width"));
133+
double h = Integer.parseInt(m.group("height"));
134+
if (width > 0 && height > 0) {
135+
w = width;
136+
h = height;
137+
} else if (width > 0) {
138+
h *= width / w;
139+
w = width;
140+
} else if (height > 0) {
141+
w *= height / h;
142+
h = height;
91143
}
92-
final double scaleX = Double.parseDouble(m.group("scaleX")) / PIXEL_PER_POINT;
93-
final double scaleY = Double.parseDouble(m.group("scaleY")) / PIXEL_PER_POINT;
94-
return m.replaceFirst("<svg width=\"" + m.group("width") + "px\" height=\"" + m.group("height") + "px\""
95-
+ m.group("between") + "transform=\"scale(" + scaleX + " " + scaleY + ")");
144+
return "width=\"" + Math.round(w * scale) + "px\" height=\"" + Math.round(h * scale) + "px\"";
96145
}
97146

98-
private static String withoutPrefix(String svg) {
99-
final int pos = svg.indexOf("<svg ");
100-
return pos < 0 ? svg : svg.substring(pos);
147+
private static String svgScale(Matcher m, double dpi) {
148+
final double pixelScale = m.group("unit").equals("px") ? 1 : Math.round(10000 * dpi / 72) / 10000d;
149+
final double scaleX = Double.parseDouble(m.group("scaleX")) / pixelScale;
150+
final double scaleY = Double.parseDouble(m.group("scaleY")) / pixelScale;
151+
return "transform=\"scale(" + scaleX + " " + scaleY + ")";
101152
}
102153

103154
private static String fontAdjusted(String svg, double fontAdjust) {

src/main/java/guru/nidi/graphviz/engine/Graphviz.java

+21-33
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,23 @@
1515
*/
1616
package guru.nidi.graphviz.engine;
1717

18-
import guru.nidi.graphviz.attribute.ForGraph;
1918
import guru.nidi.graphviz.model.Graph;
20-
import guru.nidi.graphviz.model.MutableAttributed;
2119
import guru.nidi.graphviz.model.MutableGraph;
2220

2321
import javax.annotation.Nullable;
24-
import java.io.File;
25-
import java.io.FileInputStream;
26-
import java.io.IOException;
27-
import java.io.InputStream;
28-
import java.util.ArrayList;
29-
import java.util.Arrays;
30-
import java.util.List;
31-
import java.util.concurrent.ArrayBlockingQueue;
32-
import java.util.concurrent.BlockingQueue;
33-
import java.util.concurrent.TimeUnit;
22+
import java.io.*;
23+
import java.util.*;
24+
import java.util.concurrent.*;
3425
import java.util.function.Consumer;
35-
import java.util.function.Function;
26+
import java.util.regex.Matcher;
27+
import java.util.regex.Pattern;
3628

3729
import static guru.nidi.graphviz.engine.IoUtils.readStream;
3830

3931
public final class Graphviz {
32+
private static final Pattern DPI_PATTERN = Pattern.compile("\"?dpi\"?\\s*=\\s*\"?([0-9.]+)\"?",
33+
Pattern.CASE_INSENSITIVE);
34+
4035
@Nullable
4136
private static volatile BlockingQueue<GraphvizEngine> engineQueue;
4237
@Nullable
@@ -62,7 +57,8 @@ private Graphviz(String src, @Nullable Rasterizer rasterizer,
6257
}
6358

6459
public static void useDefaultEngines() {
65-
useEngine(new GraphvizCmdLineEngine(), new GraphvizServerEngine(), new GraphvizJdkEngine());
60+
useEngine(new GraphvizCmdLineEngine(),
61+
new GraphvizServerEngine(), new GraphvizJdkEngine());
6662
}
6763

6864
public static void useEngine(GraphvizEngine first, GraphvizEngine... rest) {
@@ -134,13 +130,9 @@ public static void releaseEngine() {
134130
engineQueue = null;
135131
}
136132

137-
public static Graphviz fromString(String src) {
138-
return new Graphviz(src, Rasterizer.DEFAULT, 0, 0, 1, 1, Options.create());
139-
}
140-
141133
public static Graphviz fromFile(File src) throws IOException {
142134
try (final InputStream in = new FileInputStream(src)) {
143-
return fromString(readStream(in)).basedir(src.getParentFile());
135+
return fromString(readStream(in)).basedir(src.getAbsoluteFile().getParentFile());
144136
}
145137
}
146138

@@ -149,20 +141,11 @@ public static Graphviz fromGraph(Graph graph) {
149141
}
150142

151143
public static Graphviz fromGraph(MutableGraph graph) {
152-
return withDefaultDpi(graph, g -> fromString(g.toString()));
144+
return fromString(graph.toString());
153145
}
154146

155-
private static <T> T withDefaultDpi(MutableGraph graph, Function<MutableGraph, T> action) {
156-
final MutableAttributed<MutableGraph, ForGraph> attrs = graph.graphAttrs();
157-
final Object oldDpi = attrs.get("dpi");
158-
if (oldDpi == null) {
159-
attrs.add("dpi", 96);
160-
}
161-
try {
162-
return action.apply(graph);
163-
} finally {
164-
attrs.add("dpi", oldDpi);
165-
}
147+
public static Graphviz fromString(String src) {
148+
return new Graphviz(src, Rasterizer.DEFAULT, 0, 0, 1, 1, Options.create());
166149
}
167150

168151
public Graphviz engine(Engine engine) {
@@ -215,14 +198,19 @@ public Renderer render(Format format) {
215198
EngineResult execute() {
216199
final EngineResult result = options.format == Format.DOT
217200
? EngineResult.fromString(src)
218-
: getEngine().execute(src, options, rasterizer);
219-
return options.format.postProcess(result, fontAdjust);
201+
: getEngine().execute(options.format.preProcess(src), options, rasterizer);
202+
return options.format.postProcess(this, result);
220203
}
221204

222205
Format format() {
223206
return options.format;
224207
}
225208

209+
double dpi() {
210+
final Matcher matcher = DPI_PATTERN.matcher(src);
211+
return matcher.find() ? Double.parseDouble(matcher.group(1)) : 72;
212+
}
213+
226214
private static class ErrorGraphvizEngine implements GraphvizEngine {
227215
@Override
228216
public void init(Consumer<GraphvizEngine> onOk, Consumer<GraphvizEngine> onError) {

src/main/java/guru/nidi/graphviz/engine/SalamanderRasterizer.java

+2-16
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,13 @@ class SalamanderRasterizer extends SvgRasterizer {
3030
@Override
3131
public BufferedImage doRasterize(Graphviz graphviz, @Nullable Consumer<Graphics2D> graphicsConfigurer, String svg) {
3232
final SVGDiagram diagram = createDiagram(svg);
33-
double scaleX = graphviz.scale;
34-
double scaleY = graphviz.scale;
35-
if (graphviz.width != 0 || graphviz.height != 0) {
36-
scaleX = graphviz.scale * graphviz.width / diagram.getWidth();
37-
scaleY = graphviz.scale * graphviz.height / diagram.getHeight();
38-
if (scaleX == 0) {
39-
scaleX = scaleY;
40-
}
41-
if (scaleY == 0) {
42-
scaleY = scaleX;
43-
}
44-
}
45-
final int width = (int) Math.ceil(scaleX * diagram.getWidth());
46-
final int height = (int) Math.ceil(scaleY * diagram.getHeight());
47-
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
33+
final BufferedImage image = new BufferedImage(
34+
(int) diagram.getWidth(), (int) diagram.getHeight(), BufferedImage.TYPE_INT_ARGB);
4835
final Graphics2D graphics = image.createGraphics();
4936
configGraphics(graphics);
5037
if (graphicsConfigurer != null) {
5138
graphicsConfigurer.accept(graphics);
5239
}
53-
graphics.scale(scaleX, scaleY);
5440
renderDiagram(diagram, graphics);
5541
return image;
5642
}

src/main/java/guru/nidi/graphviz/model/ThrowingFunction.java

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ public interface ThrowingFunction<T, R> {
2222
default R applyNotThrowing(T t) {
2323
try {
2424
return apply(t);
25+
} catch (RuntimeException e) {
26+
throw e;
2527
} catch (Exception e) {
2628
throw new RuntimeException(e);
2729
}

src/main/java/org/contextmapper/contextmap/generator/ContextMapGenerator.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ public class ContextMapGenerator {
4747
private Map<String, MutableNode> bcNodesMap;
4848

4949
protected int labelSpacingFactor = 1;
50-
protected int height = 1500;
51-
protected int width = 3600;
50+
protected int height = 1000;
51+
protected int width = 2000;
5252
protected boolean useHeight = false;
5353
protected boolean useWidth = true;
5454

src/test/java/guru/nidi/graphviz/engine/FormatTest.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ class FormatTest {
4848
@ParameterizedTest
4949
@MethodSource
5050
void postProcess(Entry<String, String> values) {
51-
assertEquals(EngineResult.fromString(values.getValue()),
52-
SVG.postProcess(EngineResult.fromString(START1_7 + values.getKey()), .5));
51+
assertEquals(EngineResult.fromString(values.getValue()), SVG.postProcess(
52+
Graphviz.fromString("graph {dpi=96}").fontAdjust(.5),
53+
EngineResult.fromString(START1_7 + values.getKey())));
5354
}
5455

5556
static Set<Entry<String, String>> postProcess() {

src/test/java/guru/nidi/graphviz/engine/GraphvizTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,15 @@ void widthMethodChainCheck() {
6464
void executeWithTotalMemory() {
6565
final Graph graph = graph().with(node("a").link("b"));
6666
final String result = Graphviz.fromGraph(graph).totalMemory(32000).render(Format.SVG).toString();
67-
assertThat(result, is("totalMemory=32000;render('graph { graph [\"dpi\"=\"96\"] \"a\" -- \"b\" }',"
67+
assertThat(result, is("totalMemory=32000;render('graph { \"a\" -- \"b\" }',"
6868
+ "{format:'svg',engine:'dot',totalMemory:'32000',basedir:'" + new File(".").getAbsolutePath() + "',images:[]});"));
6969
}
7070

7171
@Test
7272
void executeWithoutTotalMemory() {
7373
final Graph graph = graph().with(node("a").link("b"));
7474
final String result = Graphviz.fromGraph(graph).render(Format.SVG).toString();
75-
assertThat(result, is("render('graph { graph [\"dpi\"=\"96\"] \"a\" -- \"b\" }',"
75+
assertThat(result, is("render('graph { \"a\" -- \"b\" }',"
7676
+ "{format:'svg',engine:'dot',basedir:'" + new File(".").getAbsolutePath() + "',images:[]});"));
7777
}
7878

src/test/java/guru/nidi/graphviz/engine/OptionsTest.java

+26-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,23 @@ void fromJsonEmptyImages() {
4343
assertEquals(expected, options);
4444
}
4545

46+
@Test
47+
void fromJsonOneImage() {
48+
final Options options = Options.fromJson("{engine:'DOT',format:'PNG',images:["
49+
+ "{path:'" + uriPathOf(new File("./graphviz-test-example/ex1.png")) + "',width:'550px',height:'100px'}]}");
50+
final Options expected = Options.create().engine(Engine.DOT).format(Format.PNG).image("graphviz-test-example/ex1.png");
51+
assertEquals(expected, options);
52+
}
53+
54+
@Test
55+
void fromJsonTwoImages() {
56+
final Options options = Options.fromJson("{engine:'DOT',format:'PNG',images:["
57+
+ "{path:'" + uriPathOf(new File("./graphviz-test-example/ex1.png")) + "',width:'550px',height:'100px'},"
58+
+ "{path:'" + uriPathOf(new File("./graphviz-test-example/ex2.png")) + "',width:'900px',height:'964px']}");
59+
final Options expected = Options.create().engine(Engine.DOT).format(Format.PNG).image("graphviz-test-example/ex1.png").image("graphviz-test-example/ex2.png");
60+
assertEquals(expected, options);
61+
}
62+
4663
@Test
4764
void toJsonMinimal() {
4865
final String s = Options.create().engine(Engine.DOT).format(Format.PNG).toJson(false);
@@ -60,7 +77,15 @@ void toJsonEmptyImages() {
6077
void toJsonOneImage() {
6178
final String s = Options.create().engine(Engine.DOT).format(Format.PNG).basedir(new File("graphviz-test-example")).image("ex1.png").toJson(false);
6279
assertEquals("{format:'svg',engine:'dot',basedir:'" + new File("graphviz-test-example").getAbsolutePath() + "',images:[" +
63-
"{path:'" + uriPathOf(new File("graphviz-test-example/ex1.png")) + "',width:'548px',height:'100px'}]}", s);
80+
"{path:'" + uriPathOf(new File("graphviz-test-example/ex1.png")) + "',width:'550px',height:'100px'}]}", s);
81+
}
82+
83+
@Test
84+
void toJsonTwoImages() {
85+
final String s = Options.create().engine(Engine.DOT).format(Format.PNG).basedir(new File("graphviz-test-example")).image("ex1.png").image("ex2.png").toJson(false);
86+
assertEquals("{format:'svg',engine:'dot',basedir:'" + new File("graphviz-test-example").getAbsolutePath() + "',images:["
87+
+ "{path:'" + uriPathOf(new File("graphviz-test-example/ex1.png")) + "',width:'550px',height:'100px'},"
88+
+ "{path:'" + uriPathOf(new File("graphviz-test-example/ex2.png")) + "',width:'900px',height:'964px'}]}", s);
6489
}
6590

6691
}

src/test/java/guru/nidi/graphviz/engine/RendererTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ void image() throws IOException {
7676
final File out = new File("target/image.png");
7777
final Graphviz g = Graphviz.fromGraph(graph().with(node(" ").with(Image.of("graphviz.png"))));
7878
g.basedir(new File("graphviz-test-example")).render(Format.PNG).toFile(out);
79-
assertThat((int) out.length(), greaterThan(20000));
79+
assertThat((int) out.length(), greaterThan(19000));
8080
}
8181

8282
@Test

0 commit comments

Comments
 (0)