Improve performance and flexibility:

- Use Writer instead of StringBuilder
- Don't use unnecessary and inefficient String.format() method
- Allow caller to specify whether to include XML PI, doctype, and extra
whitespace.
pull/28/head
Christopher Schultz 8 years ago
parent 6b33f4fd9c
commit c1da14be6f

@ -24,6 +24,8 @@
package io.nayuki.qrcodegen; package io.nayuki.qrcodegen;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -91,7 +93,7 @@ public final class QrCode {
* @throws IllegalArgumentException if the data is too long to fit in the largest version QR Code at the ECL * @throws IllegalArgumentException if the data is too long to fit in the largest version QR Code at the ECL
*/ */
public static QrCode encodeSegments(List<QrSegment> segs, Ecc ecl) { public static QrCode encodeSegments(List<QrSegment> segs, Ecc ecl) {
return encodeSegments(segs, ecl, MIN_VERSION, MAX_VERSION, -1, true); return encodeSegments(segs, ecl, 1, 40, -1, true);
} }
@ -115,7 +117,7 @@ public final class QrCode {
public static QrCode encodeSegments(List<QrSegment> segs, Ecc ecl, int minVersion, int maxVersion, int mask, boolean boostEcl) { public static QrCode encodeSegments(List<QrSegment> segs, Ecc ecl, int minVersion, int maxVersion, int mask, boolean boostEcl) {
Objects.requireNonNull(segs); Objects.requireNonNull(segs);
Objects.requireNonNull(ecl); Objects.requireNonNull(ecl);
if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7) if (!(1 <= minVersion && minVersion <= maxVersion && maxVersion <= 40) || mask < -1 || mask > 7)
throw new IllegalArgumentException("Invalid value"); throw new IllegalArgumentException("Invalid value");
// Find the minimal version number to use // Find the minimal version number to use
@ -162,13 +164,6 @@ public final class QrCode {
/*---- Public constants ----*/
public static final int MIN_VERSION = 1;
public static final int MAX_VERSION = 40;
/*---- Instance fields ----*/ /*---- Instance fields ----*/
// Public immutable scalar parameters // Public immutable scalar parameters
@ -210,7 +205,7 @@ public final class QrCode {
public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int mask) { public QrCode(int ver, Ecc ecl, byte[] dataCodewords, int mask) {
// Check arguments // Check arguments
Objects.requireNonNull(ecl); Objects.requireNonNull(ecl);
if (ver < MIN_VERSION || ver > MAX_VERSION || mask < -1 || mask > 7) if (ver < 1 || ver > 40 || mask < -1 || mask > 7)
throw new IllegalArgumentException("Value out of range"); throw new IllegalArgumentException("Value out of range");
Objects.requireNonNull(dataCodewords); Objects.requireNonNull(dataCodewords);
@ -276,7 +271,7 @@ public final class QrCode {
* @param border the number of border modules to add, which must be non-negative * @param border the number of border modules to add, which must be non-negative
* @return a string representing this QR Code as an SVG document * @return a string representing this QR Code as an SVG document
*/ */
public String toSvgString(int border) { public String toSvgString_old(int border) {
if (border < 0) if (border < 0)
throw new IllegalArgumentException("Border must be non-negative"); throw new IllegalArgumentException("Border must be non-negative");
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@ -304,6 +299,103 @@ public final class QrCode {
return sb.toString(); return sb.toString();
} }
/**
* Returns a String containing a Scalable Vector Graphic (SVG) that depicts
* this QR Code symbol.
*
* @param border The number of border modules to add, which must be non-negative.
*
* @return A String representing this QR Code as an SVG document.
*/
public String toSvgString(int border) {
return toSvgString(border, true, true, true);
}
/**
* Returns a String containing a Scalable Vector Graphic (SVG) that depicts
* this QR Code symbol.
*
* @param border The number of border modules to add, which must be non-negative.
* @param includeXMLPI Flag to include the XML processing instruction (&lt;?xml ... ?&gt;).
* @param includeDoctype Flag to include the DOCTYPE.
* @param prettyPrint Flag to include optional newlines and indents.
*
* @return A String representing this QR Code as an SVG document.
*/
public String toSvgString(int border, boolean includeXMLPI, boolean includeDoctype, boolean prettyPrint) {
StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw);
toSvg(out, border, includeXMLPI, includeDoctype, prettyPrint);
if(prettyPrint)
out.println(); // Matches previous implementation
return sw.toString();
}
/**
* Writes a Scalable Vector Graphic (SVG) to the specified PrintWriter
* that depicts this QR Code symbol.
*
* @param border The number of border modules to add, which must be non-negative.
* @param includeXMLPI Flag to include the XML processing instruction (&lt;?xml ... ?&gt;).
* @param includeDoctype Flag to include the DOCTYPE.
* @param prettyPrint Flag to include optional newlines and indents.
*
* @return A String representing this QR Code as an SVG document.
*/
public void toSvg(PrintWriter out, int border, boolean includeXMLPI, boolean includeDoctype, boolean prettyPrint) {
if (border < 0)
throw new IllegalArgumentException("Border must be non-negative");
if(includeXMLPI)
out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
if(prettyPrint)
out.println();
if(includeDoctype)
out.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">");
if(prettyPrint)
out.println();
int dim = size + (border << 1); // Double border size by shifting
out.write("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 ");
out.print(dim);
out.write(' ');
out.print(dim);
out.write("\" stroke=\"none\">");
if(prettyPrint) {
out.println();
out.write('\t');
}
out.write("<rect width=\"100%\" height=\"100%\" fill=\"#FFFFFF\"/>");
if(prettyPrint) {
out.println();
out.write('\t');
}
out.write("<path d=\"");
boolean head = true;
for (int y = -border; y < size + border; y++) {
for (int x = -border; x < size + border; x++) {
if (getModule(x, y)) {
if (head)
head = false;
else
out.write(" ");
out.write('M');
out.print(x+border);
out.write(',');
out.print(y+border);
out.write("h1v1h-1z",0,8);
}
}
}
out.write("\" fill=\"#000000\"/>");
if(prettyPrint)
out.println();
out.write("</svg>");
}
/*---- Private helper methods for constructor: Drawing function modules ----*/ /*---- Private helper methods for constructor: Drawing function modules ----*/
@ -637,7 +729,7 @@ public final class QrCode {
// used on both the x and y axes. Each value in the resulting array is in the range [0, 177). // used on both the x and y axes. Each value in the resulting array is in the range [0, 177).
// This stateless pure function could be implemented as table of 40 variable-length lists of unsigned bytes. // This stateless pure function could be implemented as table of 40 variable-length lists of unsigned bytes.
private static int[] getAlignmentPatternPositions(int ver) { private static int[] getAlignmentPatternPositions(int ver) {
if (ver < MIN_VERSION || ver > MAX_VERSION) if (ver < 1 || ver > 40)
throw new IllegalArgumentException("Version number out of range"); throw new IllegalArgumentException("Version number out of range");
else if (ver == 1) else if (ver == 1)
return new int[]{}; return new int[]{};
@ -663,7 +755,7 @@ public final class QrCode {
// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8.
// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table.
private static int getNumRawDataModules(int ver) { private static int getNumRawDataModules(int ver) {
if (ver < MIN_VERSION || ver > MAX_VERSION) if (ver < 1 || ver > 40)
throw new IllegalArgumentException("Version number out of range"); throw new IllegalArgumentException("Version number out of range");
int size = ver * 4 + 17; int size = ver * 4 + 17;
@ -688,7 +780,7 @@ public final class QrCode {
// QR Code of the given version number and error correction level, with remainder bits discarded. // QR Code of the given version number and error correction level, with remainder bits discarded.
// This stateless pure function could be implemented as a (40*4)-cell lookup table. // This stateless pure function could be implemented as a (40*4)-cell lookup table.
static int getNumDataCodewords(int ver, Ecc ecl) { static int getNumDataCodewords(int ver, Ecc ecl) {
if (ver < MIN_VERSION || ver > MAX_VERSION) if (ver < 1 || ver > 40)
throw new IllegalArgumentException("Version number out of range"); throw new IllegalArgumentException("Version number out of range");
return getNumRawDataModules(ver) / 8 return getNumRawDataModules(ver) / 8
- ECC_CODEWORDS_PER_BLOCK[ecl.ordinal()][ver] - ECC_CODEWORDS_PER_BLOCK[ecl.ordinal()][ver]
@ -840,4 +932,69 @@ public final class QrCode {
} }
static class NullWriter extends java.io.Writer {
@Override
public void write(char[] cbuf, int off, int len)
throws java.io.IOException
{
// Do nothing
}
@Override
public void flush()
throws java.io.IOException
{
// Do nothing
}
@Override
public void close()
throws java.io.IOException
{
// Do nothing
}
}
public static void main(String[] args) {
QrCode qr = QrCode.encodeText("Hello, World", Ecc.MEDIUM);
int iterations;
if(args.length > 0)
iterations = Integer.parseInt(args[0]);
else
iterations = 99999;
System.out.println("Running benchmark with " + iterations + " iterations...");
System.out.println("QrCode.toSvgString_old:");
System.out.flush();
long elapsed = System.currentTimeMillis();
for(int i=0; i<iterations; ++i)
qr.toSvgString_old(0);
elapsed = System.currentTimeMillis() - elapsed;
System.out.println("Completed " + iterations + " conversions in " + elapsed + " ms");
System.out.flush();
System.out.println("QrCode.toSvgString:");
elapsed = System.currentTimeMillis();
for(int i=0; i<iterations; ++i)
qr.toSvgString(0);
elapsed = System.currentTimeMillis() - elapsed;
System.out.println("Completed " + iterations + " conversions in " + elapsed + " ms");
System.out.flush();
// Demonstrate that the outputs are identical:
String svg0 = qr.toSvgString_old(0);
String svg1 = qr.toSvgString(0);
if(!svg0.equals(svg1)) {
System.out.println("Differences exist:");
System.out.println(svg0);
System.out.println(svg1);
}
}
} }

Loading…
Cancel
Save