Skip to content

Commit

Permalink
Take DPI fix from https://github.com/nidi3/graphviz-java & Reduce def…
Browse files Browse the repository at this point in the history
…ault resolution
  • Loading branch information
stefan-ka committed Nov 15, 2019
1 parent f4d20fe commit 2b35b35
Show file tree
Hide file tree
Showing 25 changed files with 138 additions and 85 deletions.
Binary file modified graphviz-test-example/ex1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified graphviz-test-example/ex1i.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified graphviz-test-example/ex1m.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified graphviz-test-example/ex2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified graphviz-test-example/ex3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified graphviz-test-example/ex4-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified graphviz-test-example/ex4-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion graphviz-test-example/ex5.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified graphviz-test-example/ex5b.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified graphviz-test-example/ex5p.pdf
Binary file not shown.
Binary file modified graphviz-test-example/ex5s.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified graphviz-test-example/ex6a.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified graphviz-test-example/ex6d.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified graphviz-test-example/ex7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified graphviz-test-example/ex8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
95 changes: 73 additions & 22 deletions src/main/java/guru/nidi/graphviz/engine/Format.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,37 @@
public enum Format {
PNG("svg", "png", true, true) {
@Override
EngineResult postProcess(EngineResult result, double fontAdjust) {
return result.mapString(s -> postProcessSvg(s, true, fontAdjust));
String preProcess(String src) {
return encodeXml(super.preProcess(src));
}

@Override
EngineResult postProcess(Graphviz graphviz, EngineResult result) {
return result.mapString(s -> postProcessSvg(graphviz, s, true));
}
},

SVG("svg", "svg", false, true) {
@Override
EngineResult postProcess(EngineResult result, double fontAdjust) {
return result.mapString(s -> postProcessSvg(s, true, fontAdjust));
String preProcess(String src) {
return encodeXml(super.preProcess(src));
}

@Override
EngineResult postProcess(Graphviz graphviz, EngineResult result) {
return result.mapString(s -> postProcessSvg(graphviz, s, true));
}
},

SVG_STANDALONE("svg", "svg", false, true) {
@Override
EngineResult postProcess(EngineResult result, double fontAdjust) {
return result.mapString(s -> postProcessSvg(s, false, fontAdjust));
String preProcess(String src) {
return encodeXml(super.preProcess(src));
}

@Override
EngineResult postProcess(Graphviz graphviz, EngineResult result) {
return result.mapString(s -> postProcessSvg(graphviz, s, false));
}
},
DOT("dot", "dot", false, false),
Expand All @@ -57,9 +72,9 @@ EngineResult postProcess(EngineResult result, double fontAdjust) {
"<svg width=\"(?<width>\\d+)(?<unit>p[tx])\" height=\"(?<height>\\d+)p[tx]\""
+ "(?<between>.*?>\\R<g.*?)transform=\"scale\\((?<scaleX>[0-9.]+) (?<scaleY>[0-9.]+)\\)",
Pattern.DOTALL);
private static final double PIXEL_PER_POINT = 1.3333;

final String vizName;
final String fileExtension;
public final String fileExtension;
final boolean image;
final boolean svg;

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

EngineResult postProcess(EngineResult result, double fontAdjust) {
String preProcess(String src) {
return replaceSubSpaces(src);
}

EngineResult postProcess(Graphviz graphviz, EngineResult result) {
return result;
}

private static String postProcessSvg(String result, boolean prefix, double fontAdjust) {
private static String replaceSubSpaces(String src) {
final char[] chars = src.toCharArray();
for (int i = 0; i < chars.length; i++) {
if (chars[i] < ' ' && chars[i] != '\t' && chars[i] != '\r' && chars[i] != '\n') {
chars[i] = ' ';
}
}
return new String(chars);
}

private static String encodeXml(String src) {
return src.replace("&", "&amp;");
}

private static String postProcessSvg(Graphviz graphviz, String result, boolean prefix) {
final String unprefixed = prefix ? withoutPrefix(result) : result;
final String pixelSized = pointsToPixels(unprefixed);
return fontAdjust == 1 ? pixelSized : fontAdjusted(pixelSized, fontAdjust);
final String pixelSized = pointsToPixels(unprefixed, graphviz.dpi(),
graphviz.width, graphviz.height, graphviz.scale);
return graphviz.fontAdjust == 1 ? pixelSized : fontAdjusted(pixelSized, graphviz.fontAdjust);
}

private static String withoutPrefix(String svg) {
final int pos = svg.indexOf("<svg ");
return pos < 0 ? svg : svg.substring(pos);
}

private static String pointsToPixels(String svg) {
private static String pointsToPixels(String svg, double dpi, int width, int height, double scale) {
final Matcher m = SVG_PATTERN.matcher(svg);
if (!m.find()) {
LOG.warn("Generated SVG has not the expected format. There might be image size problems.");
return svg;
}
if (m.group("unit").equals("px")) {
return svg;
return m.replaceFirst("<svg " + svgSize(m, width, height, scale) + m.group("between") + svgScale(m, dpi));
}

private static String svgSize(Matcher m, int width, int height, double scale) {
double w = Integer.parseInt(m.group("width"));
double h = Integer.parseInt(m.group("height"));
if (width > 0 && height > 0) {
w = width;
h = height;
} else if (width > 0) {
h *= width / w;
w = width;
} else if (height > 0) {
w *= height / h;
h = height;
}
final double scaleX = Double.parseDouble(m.group("scaleX")) / PIXEL_PER_POINT;
final double scaleY = Double.parseDouble(m.group("scaleY")) / PIXEL_PER_POINT;
return m.replaceFirst("<svg width=\"" + m.group("width") + "px\" height=\"" + m.group("height") + "px\""
+ m.group("between") + "transform=\"scale(" + scaleX + " " + scaleY + ")");
return "width=\"" + Math.round(w * scale) + "px\" height=\"" + Math.round(h * scale) + "px\"";
}

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

private static String fontAdjusted(String svg, double fontAdjust) {
Expand Down
54 changes: 21 additions & 33 deletions src/main/java/guru/nidi/graphviz/engine/Graphviz.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,23 @@
*/
package guru.nidi.graphviz.engine;

import guru.nidi.graphviz.attribute.ForGraph;
import guru.nidi.graphviz.model.Graph;
import guru.nidi.graphviz.model.MutableAttributed;
import guru.nidi.graphviz.model.MutableGraph;

import javax.annotation.Nullable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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

public final class Graphviz {
private static final Pattern DPI_PATTERN = Pattern.compile("\"?dpi\"?\\s*=\\s*\"?([0-9.]+)\"?",
Pattern.CASE_INSENSITIVE);

@Nullable
private static volatile BlockingQueue<GraphvizEngine> engineQueue;
@Nullable
Expand All @@ -62,7 +57,8 @@ private Graphviz(String src, @Nullable Rasterizer rasterizer,
}

public static void useDefaultEngines() {
useEngine(new GraphvizCmdLineEngine(), new GraphvizServerEngine(), new GraphvizJdkEngine());
useEngine(new GraphvizCmdLineEngine(),
new GraphvizServerEngine(), new GraphvizJdkEngine());
}

public static void useEngine(GraphvizEngine first, GraphvizEngine... rest) {
Expand Down Expand Up @@ -134,13 +130,9 @@ public static void releaseEngine() {
engineQueue = null;
}

public static Graphviz fromString(String src) {
return new Graphviz(src, Rasterizer.DEFAULT, 0, 0, 1, 1, Options.create());
}

public static Graphviz fromFile(File src) throws IOException {
try (final InputStream in = new FileInputStream(src)) {
return fromString(readStream(in)).basedir(src.getParentFile());
return fromString(readStream(in)).basedir(src.getAbsoluteFile().getParentFile());
}
}

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

public static Graphviz fromGraph(MutableGraph graph) {
return withDefaultDpi(graph, g -> fromString(g.toString()));
return fromString(graph.toString());
}

private static <T> T withDefaultDpi(MutableGraph graph, Function<MutableGraph, T> action) {
final MutableAttributed<MutableGraph, ForGraph> attrs = graph.graphAttrs();
final Object oldDpi = attrs.get("dpi");
if (oldDpi == null) {
attrs.add("dpi", 96);
}
try {
return action.apply(graph);
} finally {
attrs.add("dpi", oldDpi);
}
public static Graphviz fromString(String src) {
return new Graphviz(src, Rasterizer.DEFAULT, 0, 0, 1, 1, Options.create());
}

public Graphviz engine(Engine engine) {
Expand Down Expand Up @@ -215,14 +198,19 @@ public Renderer render(Format format) {
EngineResult execute() {
final EngineResult result = options.format == Format.DOT
? EngineResult.fromString(src)
: getEngine().execute(src, options, rasterizer);
return options.format.postProcess(result, fontAdjust);
: getEngine().execute(options.format.preProcess(src), options, rasterizer);
return options.format.postProcess(this, result);
}

Format format() {
return options.format;
}

double dpi() {
final Matcher matcher = DPI_PATTERN.matcher(src);
return matcher.find() ? Double.parseDouble(matcher.group(1)) : 72;
}

private static class ErrorGraphvizEngine implements GraphvizEngine {
@Override
public void init(Consumer<GraphvizEngine> onOk, Consumer<GraphvizEngine> onError) {
Expand Down
18 changes: 2 additions & 16 deletions src/main/java/guru/nidi/graphviz/engine/SalamanderRasterizer.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,13 @@ class SalamanderRasterizer extends SvgRasterizer {
@Override
public BufferedImage doRasterize(Graphviz graphviz, @Nullable Consumer<Graphics2D> graphicsConfigurer, String svg) {
final SVGDiagram diagram = createDiagram(svg);
double scaleX = graphviz.scale;
double scaleY = graphviz.scale;
if (graphviz.width != 0 || graphviz.height != 0) {
scaleX = graphviz.scale * graphviz.width / diagram.getWidth();
scaleY = graphviz.scale * graphviz.height / diagram.getHeight();
if (scaleX == 0) {
scaleX = scaleY;
}
if (scaleY == 0) {
scaleY = scaleX;
}
}
final int width = (int) Math.ceil(scaleX * diagram.getWidth());
final int height = (int) Math.ceil(scaleY * diagram.getHeight());
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
final BufferedImage image = new BufferedImage(
(int) diagram.getWidth(), (int) diagram.getHeight(), BufferedImage.TYPE_INT_ARGB);
final Graphics2D graphics = image.createGraphics();
configGraphics(graphics);
if (graphicsConfigurer != null) {
graphicsConfigurer.accept(graphics);
}
graphics.scale(scaleX, scaleY);
renderDiagram(diagram, graphics);
return image;
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/guru/nidi/graphviz/model/ThrowingFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public interface ThrowingFunction<T, R> {
default R applyNotThrowing(T t) {
try {
return apply(t);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ public class ContextMapGenerator {
private Map<String, MutableNode> bcNodesMap;

protected int labelSpacingFactor = 1;
protected int height = 1500;
protected int width = 3600;
protected int height = 1000;
protected int width = 2000;
protected boolean useHeight = false;
protected boolean useWidth = true;

Expand Down
5 changes: 3 additions & 2 deletions src/test/java/guru/nidi/graphviz/engine/FormatTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ class FormatTest {
@ParameterizedTest
@MethodSource
void postProcess(Entry<String, String> values) {
assertEquals(EngineResult.fromString(values.getValue()),
SVG.postProcess(EngineResult.fromString(START1_7 + values.getKey()), .5));
assertEquals(EngineResult.fromString(values.getValue()), SVG.postProcess(
Graphviz.fromString("graph {dpi=96}").fontAdjust(.5),
EngineResult.fromString(START1_7 + values.getKey())));
}

static Set<Entry<String, String>> postProcess() {
Expand Down
4 changes: 2 additions & 2 deletions src/test/java/guru/nidi/graphviz/engine/GraphvizTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ void widthMethodChainCheck() {
void executeWithTotalMemory() {
final Graph graph = graph().with(node("a").link("b"));
final String result = Graphviz.fromGraph(graph).totalMemory(32000).render(Format.SVG).toString();
assertThat(result, is("totalMemory=32000;render('graph { graph [\"dpi\"=\"96\"] \"a\" -- \"b\" }',"
assertThat(result, is("totalMemory=32000;render('graph { \"a\" -- \"b\" }',"
+ "{format:'svg',engine:'dot',totalMemory:'32000',basedir:'" + new File(".").getAbsolutePath() + "',images:[]});"));
}

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

Expand Down
27 changes: 26 additions & 1 deletion src/test/java/guru/nidi/graphviz/engine/OptionsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,23 @@ void fromJsonEmptyImages() {
assertEquals(expected, options);
}

@Test
void fromJsonOneImage() {
final Options options = Options.fromJson("{engine:'DOT',format:'PNG',images:["
+ "{path:'" + uriPathOf(new File("./graphviz-test-example/ex1.png")) + "',width:'550px',height:'100px'}]}");
final Options expected = Options.create().engine(Engine.DOT).format(Format.PNG).image("graphviz-test-example/ex1.png");
assertEquals(expected, options);
}

@Test
void fromJsonTwoImages() {
final Options options = Options.fromJson("{engine:'DOT',format:'PNG',images:["
+ "{path:'" + uriPathOf(new File("./graphviz-test-example/ex1.png")) + "',width:'550px',height:'100px'},"
+ "{path:'" + uriPathOf(new File("./graphviz-test-example/ex2.png")) + "',width:'900px',height:'964px']}");
final Options expected = Options.create().engine(Engine.DOT).format(Format.PNG).image("graphviz-test-example/ex1.png").image("graphviz-test-example/ex2.png");
assertEquals(expected, options);
}

@Test
void toJsonMinimal() {
final String s = Options.create().engine(Engine.DOT).format(Format.PNG).toJson(false);
Expand All @@ -60,7 +77,15 @@ void toJsonEmptyImages() {
void toJsonOneImage() {
final String s = Options.create().engine(Engine.DOT).format(Format.PNG).basedir(new File("graphviz-test-example")).image("ex1.png").toJson(false);
assertEquals("{format:'svg',engine:'dot',basedir:'" + new File("graphviz-test-example").getAbsolutePath() + "',images:[" +
"{path:'" + uriPathOf(new File("graphviz-test-example/ex1.png")) + "',width:'548px',height:'100px'}]}", s);
"{path:'" + uriPathOf(new File("graphviz-test-example/ex1.png")) + "',width:'550px',height:'100px'}]}", s);
}

@Test
void toJsonTwoImages() {
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);
assertEquals("{format:'svg',engine:'dot',basedir:'" + new File("graphviz-test-example").getAbsolutePath() + "',images:["
+ "{path:'" + uriPathOf(new File("graphviz-test-example/ex1.png")) + "',width:'550px',height:'100px'},"
+ "{path:'" + uriPathOf(new File("graphviz-test-example/ex2.png")) + "',width:'900px',height:'964px'}]}", s);
}

}
2 changes: 1 addition & 1 deletion src/test/java/guru/nidi/graphviz/engine/RendererTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ void image() throws IOException {
final File out = new File("target/image.png");
final Graphviz g = Graphviz.fromGraph(graph().with(node(" ").with(Image.of("graphviz.png"))));
g.basedir(new File("graphviz-test-example")).render(Format.PNG).toFile(out);
assertThat((int) out.length(), greaterThan(20000));
assertThat((int) out.length(), greaterThan(19000));
}

@Test
Expand Down
Loading

0 comments on commit 2b35b35

Please sign in to comment.