Skip to content
Snippets Groups Projects
Commit 750a45ac authored by stepan's avatar stepan
Browse files

Optimize SVG files size

parent c5171fe5
Branches
No related tags found
No related merge requests found
......@@ -18,8 +18,8 @@ import java.util.Map;
import com.oracle.truffle.r.library.fastrGrid.GridState.GridPalette;
import com.oracle.truffle.r.library.fastrGrid.device.GridColor;
import com.oracle.truffle.r.runtime.RError;
import com.oracle.truffle.r.runtime.RInternalError;
import com.oracle.truffle.r.runtime.RError.Message;
import com.oracle.truffle.r.runtime.RInternalError;
import com.oracle.truffle.r.runtime.RRuntime;
import com.oracle.truffle.r.runtime.Utils;
import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
......
......@@ -67,9 +67,9 @@ public class SVGDevice implements GridDevice, FileGridDevice {
// saving it anywhere.
data.setLength(0);
cachedCtx = null;
append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
append("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">");
append("<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' viewBox='0 0 %.3f %.3f'>",
data.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
data.append("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
append("<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' viewBox='0 0 %.3f %.3f'>\n",
width * COORD_FACTOR,
height * COORD_FACTOR);
}
......@@ -91,45 +91,48 @@ public class SVGDevice implements GridDevice, FileGridDevice {
appendStyle(ctx);
append("<rect x='%.3f' y='%.3f' width='%.3f' height='%.3f'", leftX * COORD_FACTOR, transY(bottomY + newHeight) * COORD_FACTOR, newWidth * COORD_FACTOR, newHeight * COORD_FACTOR);
if (rotationAnticlockWise != 0) {
append("transform='rotate(%.3f %.3f,%.3f)'", toDegrees(rotationAnticlockWise), (leftX + newWidth / 2.) * COORD_FACTOR, transY(bottomY + newHeight / 2.) * COORD_FACTOR);
append(" transform='rotate(%.3f %.3f,%.3f)'", toDegrees(rotationAnticlockWise), (leftX + newWidth / 2.) * COORD_FACTOR, transY(bottomY + newHeight / 2.) * COORD_FACTOR);
}
data.append("/>"); // end of 'rect' tag
appendColorStyle(ctx);
data.append("/>\n"); // end of 'rect' tag
}
@Override
public void drawPolyLines(DrawingContext ctx, double[] x, double[] y, int startIndex, int length) {
drawPoly(ctx, x, y, startIndex, length, "style='fill:transparent'");
drawPoly(ctx, x, y, startIndex, length, "transparent");
}
@Override
public void drawPolygon(DrawingContext ctx, double[] x, double[] y, int startIndex, int length) {
drawPoly(ctx, x, y, startIndex, length, "");
drawPoly(ctx, x, y, startIndex, length, null);
}
@Override
public void drawCircle(DrawingContext ctx, double centerX, double centerY, double radius) {
appendStyle(ctx);
append("<circle cx='%.3f' cy='%.3f' r='%.3f'/>", centerX * COORD_FACTOR, transY(centerY) * COORD_FACTOR, radius * COORD_FACTOR);
append("<circle cx='%.3f' cy='%.3f' r='%.3f'", centerX * COORD_FACTOR, transY(centerY) * COORD_FACTOR, radius * COORD_FACTOR);
appendColorStyle(ctx);
data.append("/>\n");
}
@Override
public void drawRaster(double leftX, double bottomY, double width, double height, int[] pixels, int pixelsColumnsCount, ImageInterpolation interpolation) {
byte[] bitmap = Bitmap.create(pixels, pixelsColumnsCount);
String base64 = Base64.getEncoder().encodeToString(bitmap);
append("<image x='%.3f' y='%.3f' width='%.3f' height='%.3f' preserveAspectRatio='none' xlink:href='data:image/bmp;base64,%s'/>", leftX * COORD_FACTOR, transY(bottomY + height) * COORD_FACTOR,
append("<image x='%.3f' y='%.3f' width='%.3f' height='%.3f' preserveAspectRatio='none' xlink:href='data:image/bmp;base64,%s'/>\n", leftX * COORD_FACTOR,
transY(bottomY + height) * COORD_FACTOR,
width * COORD_FACTOR, height * COORD_FACTOR, base64);
}
@Override
public void drawString(DrawingContext ctx, double leftX, double bottomY, double rotationAnticlockWise, String text) {
closeStyle();
append("<text x='%.3f' y='%.3f' textLength='%.3fpx' lengthAdjust='spacingAndGlyphs' ", leftX * COORD_FACTOR, transY(bottomY) * COORD_FACTOR, getStringWidth(ctx, text) * COORD_FACTOR);
// SVG interprets the "fill" as the color of the text
data.append("style='").append(getStyleColor("fill", ctx.getColor())).append('\'');
append("<text x='%.3f' y='%.3f' textLength='%.3fpx' lengthAdjust='spacingAndGlyphs'", leftX * COORD_FACTOR, transY(bottomY) * COORD_FACTOR, getStringWidth(ctx, text) * COORD_FACTOR);
appendFontStyle(ctx);
if (rotationAnticlockWise != 0) {
append(" transform='rotate(%.3f %.3f,%.3f)'", toDegrees(rotationAnticlockWise), leftX * COORD_FACTOR, transY(bottomY) * COORD_FACTOR);
}
data.append(">").append(text).append("</text>");
data.append(">").append(text).append("</text>\n");
}
@Override
......@@ -173,16 +176,20 @@ public class SVGDevice implements GridDevice, FileGridDevice {
return 0.7 * (ctx.getFontSize() / INCH_TO_POINTS_FACTOR);
}
private void drawPoly(DrawingContext ctx, double[] x, double[] y, int startIndex, int length, String attributes) {
private void drawPoly(DrawingContext ctx, double[] x, double[] y, int startIndex, int length, String fillColorOverride) {
appendStyle(ctx);
data.append("<polyline points='");
for (int i = 0; i < length; i++) {
data.append(DECIMAL_FORMAT.format(x[i + startIndex] * COORD_FACTOR));
data.append(',');
data.append(DECIMAL_FORMAT.format(transY(y[i + startIndex]) * COORD_FACTOR));
data.append(' ');
if (i != length - 1) {
data.append(' ');
}
}
data.append("' ").append(attributes).append(" />");
data.append('\'');
appendColorStyle(ctx, fillColorOverride);
data.append("/>");
}
private void saveFile() throws DeviceCloseException {
......@@ -200,40 +207,34 @@ public class SVGDevice implements GridDevice, FileGridDevice {
}
if (cachedCtx != null) {
// see #appendStyle
append("</g>");
data.append("</g>");
}
append("</svg>");
data.append("</svg>");
}
// closes opened <g> tag if necessary
private void closeStyle() {
if (cachedCtx != null) {
cachedCtx = null;
append("</g>");
data.append("</g>");
}
}
private void appendStyle(DrawingContext ctx) {
if (cachedCtx == null || !DrawingContext.areSame(cachedCtx, ctx)) {
if (cachedCtx == null || !areSameGlobalStyles(cachedCtx, ctx)) {
if (cachedCtx != null) {
append("</g>"); // close the previous style definition
data.append("</g>"); // close the previous style definition
}
append("<g style='");
data.append("<g");
appendStyleUncached(ctx);
append("'>");
data.append('>').append('\n');
}
cachedCtx = ctx;
}
private void appendStyleUncached(DrawingContext ctx) {
byte[] lineType = ctx.getLineType();
if (lineType == GRID_LINE_BLANK) {
append("stroke:transparent");
} else {
append(getStyleColor("stroke", ctx.getColor()));
}
data.append(';').append(getStyleColor("fill", ctx.getFillColor()));
data.append(";stroke-width:").append(ctx.getLineWidth());
data.append(" style='stroke-width:").append(ctx.getLineWidth());
if (lineType != DrawingContext.GRID_LINE_SOLID && lineType != DrawingContext.GRID_LINE_BLANK) {
data.append(";stroke-dasharray:");
for (int i = 0; i < lineType.length; i++) {
......@@ -248,7 +249,34 @@ public class SVGDevice implements GridDevice, FileGridDevice {
if (ctx.getLineJoin() == GridLineJoin.MITRE) {
data.append(";stroke-miterlimit:").append(ctx.getLineMitre());
}
data.append(";font-size:").append(ctx.getFontSize()).append("px");
data.append('\'');
}
private void appendColorStyle(DrawingContext ctx) {
appendColorStyle(ctx, null);
}
private void appendColorStyle(DrawingContext ctx, String fillColorOverride) {
byte[] lineType = ctx.getLineType();
if (lineType == GRID_LINE_BLANK) {
data.append(" style='stroke:transparent");
} else {
data.append(" style='");
appendStyleColorAttrs("stroke", ctx.getColor());
}
if (fillColorOverride == null) {
data.append(';');
appendStyleColorAttrs("fill", ctx.getFillColor());
} else {
data.append(";fill=").append(fillColorOverride);
}
data.append('\'');
}
private void appendFontStyle(DrawingContext ctx) {
// Note: SVG interprets the "fill" as the color of the text
data.append(" style='font-size:").append(ctx.getFontSize()).append("px;");
appendStyleColorAttrs("fill", ctx.getColor());
if (!ctx.getFontFamily().isEmpty()) {
// Font-family strings 'mono', 'sans', and 'serif' are OK for us
data.append(";font-family:").append(ctx.getFontFamily());
......@@ -259,6 +287,7 @@ public class SVGDevice implements GridDevice, FileGridDevice {
if (ctx.getFontStyle().isItalic()) {
data.append(";font-style:italic");
}
data.append('\'');
}
private static String getSVGLineCap(GridLineEnd lineEnd) {
......@@ -287,12 +316,18 @@ public class SVGDevice implements GridDevice, FileGridDevice {
}
}
private static String getStyleColor(String prefix, GridColor color) {
return Utils.stringFormat("%s:rgb(%d,%d,%d);%s-opacity:%.3f", prefix, color.getRed(), color.getGreen(), color.getBlue(), prefix, color.getAlpha() / 255d);
private void appendStyleColorAttrs(String prefix, GridColor color) {
data.append(prefix).append(':');
if (color.getAlpha() == GridColor.OPAQUE_ALPHA) {
data.append(String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue()));
} else {
data.append("rgb(").append(color.getRed()).append(',').append(color.getGreen()).append(',').append(color.getBlue()).append(')').append(';');
data.append(prefix).append("-opacity:").append(DECIMAL_FORMAT.format(color.getAlpha() / 255d));
}
}
private void append(String fmt, Object... args) {
data.append(Utils.stringFormat(fmt + "\n", args));
data.append(Utils.stringFormat(fmt, args));
}
private double transY(double y) {
......@@ -303,6 +338,15 @@ public class SVGDevice implements GridDevice, FileGridDevice {
return (180. / Math.PI) * -rotationAnticlockWise;
}
static boolean areSameGlobalStyles(DrawingContext ctx1, DrawingContext ctx2) {
return ctx1 == ctx2 || (ctx1.getLineEnd() == ctx2.getLineEnd() &&
ctx1.getLineJoin() == ctx2.getLineJoin() &&
ctx1.getLineType() == ctx2.getLineType() &&
ctx1.getLineHeight() == ctx2.getLineHeight() &&
ctx1.getLineWidth() == ctx2.getLineWidth() &&
ctx1.getLineMitre() == ctx2.getLineMitre());
}
private static final class Bitmap {
private static final int FILE_HEADER_SIZE = 14;
private static final int IMAGE_HEADER_SIZE = 40;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment