Skip to content

Commit

Permalink
[fix] add svg support via batik
Browse files Browse the repository at this point in the history
which is apache licensed

closes #95
  • Loading branch information
atomfrede committed Sep 3, 2018
1 parent 6e52800 commit a630af3
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 28 deletions.
2 changes: 2 additions & 0 deletions core/pom.xml
Expand Up @@ -48,6 +48,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions> <executions>
<execution> <execution>
<goals> <goals>
Expand All @@ -59,6 +60,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>${maven-javadoc-plugin.version}</version>
<executions> <executions>
<execution> <execution>
<goals> <goals>
Expand Down
14 changes: 14 additions & 0 deletions javase/pom.xml
Expand Up @@ -49,6 +49,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
<version>${maven-source-plugin.version}</version>
<executions> <executions>
<execution> <execution>
<goals> <goals>
Expand All @@ -60,6 +61,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>${maven-javadoc-plugin.version}</version>
<executions> <executions>
<execution> <execution>
<goals> <goals>
Expand All @@ -81,10 +83,22 @@
<artifactId>core</artifactId> <artifactId>core</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-svggen</artifactId>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-dom</artifactId>
</dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>
</dependencies> </dependencies>


</project> </project>
53 changes: 53 additions & 0 deletions javase/src/main/java/net/glxn/qrgen/javase/MatrixToSvgWriter.java
@@ -0,0 +1,53 @@
package net.glxn.qrgen.javase;

import com.google.zxing.client.j2se.MatrixToImageConfig;
import com.google.zxing.common.BitMatrix;
import org.apache.batik.dom.GenericDOMImplementation;
import org.apache.batik.svggen.SVGGraphics2D;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;

import java.awt.*;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Path;

class MatrixToSvgWriter {

private MatrixToSvgWriter() {
// private utility class constuctor
}

private static SVGGraphics2D toSvgDocument(BitMatrix matrix, MatrixToImageConfig config) {

int width = matrix.getWidth();
int height = matrix.getHeight();

DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
String svgNS = "http://www.w3.org/2000/svg";
Document document = domImpl.createDocument(svgNS, "svg", null);

SVGGraphics2D svgGraphics = new SVGGraphics2D(document);
svgGraphics.setColor(new Color(config.getPixelOffColor()));
svgGraphics.fillRect(0,0, width, height);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
if (matrix.get(x,y)) {
svgGraphics.setColor(new Color(config.getPixelOnColor()));
svgGraphics.fillRect(x, y, 1, 1);
}
}
}

return svgGraphics;


}

static void writeToPath(BitMatrix matrix, Path file, MatrixToImageConfig matrixToImageConfig) throws IOException {
SVGGraphics2D g2 = toSvgDocument(matrix, matrixToImageConfig);

FileWriter out = new FileWriter(file.toFile());
g2.stream(out);
}
}
22 changes: 22 additions & 0 deletions javase/src/main/java/net/glxn/qrgen/javase/QRCode.java
Expand Up @@ -150,6 +150,28 @@ protected void writeToStream(OutputStream stream) throws IOException, WriterExce
MatrixToImageWriter.writeToStream(createMatrix(text), imageType.toString(), stream, matrixToImageConfig); MatrixToImageWriter.writeToStream(createMatrix(text), imageType.toString(), stream, matrixToImageConfig);
} }


public File svg() {
File file;
try {
file = createTempSvgFile();
MatrixToSvgWriter.writeToPath(createMatrix(text), file.toPath(), matrixToImageConfig);
} catch (Exception e) {
throw new QRGenerationException("Failed to create QR svg from text due to underlying exception", e);
}
return file;
}

public File svg(String name) {
File file;
try {
file = createTempSvgFile(name);
MatrixToSvgWriter.writeToPath(createMatrix(text), file.toPath(), matrixToImageConfig);
} catch (Exception e) {
throw new QRGenerationException("Failed to create QR svg from text due to underlying exception", e);
}
return file;
}

private File createTempSvgFile() throws IOException { private File createTempSvgFile() throws IOException {
return createTempSvgFile("QRCode"); return createTempSvgFile("QRCode");
} }
Expand Down
99 changes: 71 additions & 28 deletions javase/src/test/java/net/glxn/qrgen/javase/QRCodeTest.java
Expand Up @@ -20,10 +20,12 @@
import java.nio.file.Files; import java.nio.file.Files;
import java.util.Map; import java.util.Map;


import static org.assertj.core.api.Assertions.assertThat;

public class QRCodeTest { public class QRCodeTest {


@Test @Test
public void shouldGetFileFromVCardWithDefaults() throws Exception { public void shouldGetFileFromVCardWithDefaults() {
VCard johnDoe = new VCard("John Doe") VCard johnDoe = new VCard("John Doe")
.setName("John Doe") .setName("John Doe")
.setEmail("john.doe@example.org") .setEmail("john.doe@example.org")
Expand All @@ -33,11 +35,13 @@ public void shouldGetFileFromVCardWithDefaults() throws Exception {
.setPhoneNumber("1234") .setPhoneNumber("1234")
.setWebsite("www.example.org"); .setWebsite("www.example.org");
File file = QRCode.from(johnDoe).file(); File file = QRCode.from(johnDoe).file();
Assert.assertNotNull(file); assertThat(file).exists();
assertThat(file).canRead();
assertThat(file.length()).isGreaterThan(0);
} }


@Test @Test
public void shouldGetFileFromVCardWithExtendedChars() throws Exception { public void shouldGetFileFromVCardWithExtendedChars() {
VCard johnDoe = new VCard("John Doe") VCard johnDoe = new VCard("John Doe")
.setName("Björkelundsvägen") .setName("Björkelundsvägen")
.setEmail("john.doe@example.org") .setEmail("john.doe@example.org")
Expand All @@ -47,36 +51,44 @@ public void shouldGetFileFromVCardWithExtendedChars() throws Exception {
.setPhoneNumber("1234") .setPhoneNumber("1234")
.setWebsite("www.Björkelundsvägen.org"); .setWebsite("www.Björkelundsvägen.org");
File file = QRCode.from(johnDoe).file(); File file = QRCode.from(johnDoe).file();
Assert.assertNotNull(file); assertThat(file).exists();
assertThat(file).canRead();
assertThat(file.length()).isGreaterThan(0);
} }


@Test @Test
public void shouldGetBitmapFileFromText() throws Exception { public void shouldGetBitmapFileFromText() {
File file = QRCode.from("www.example.org").to(ImageType.BMP).file(); File file = QRCode.from("www.example.org").to(ImageType.BMP).file();
Assert.assertNotNull(file); assertThat(file).exists();
assertThat(file).canRead();
assertThat(file.length()).isGreaterThan(0);
} }


@Test @Test
public void shouldGetFileFromTextWithDefaults() throws Exception { public void shouldGetFileFromTextWithDefaults() {
File file = QRCode.from("Hello World").file(); File file = QRCode.from("Hello World").file();
Assert.assertNotNull(file); assertThat(file).exists();
assertThat(file).canRead();
assertThat(file.length()).isGreaterThan(0);
} }


@Test @Test
public void shouldGetFileWithNameFromTextWithDefaults() throws Exception { public void shouldGetFileWithNameFromTextWithDefaults() {
File file = QRCode.from("Hello World").file("Hello World"); File file = QRCode.from("Hello World").file("Hello World");
Assert.assertNotNull(file); assertThat(file).exists();
assertThat(file).canRead();
assertThat(file.length()).isGreaterThan(0);
Assert.assertTrue(file.getName().startsWith("Hello World")); Assert.assertTrue(file.getName().startsWith("Hello World"));
} }


@Test @Test
public void shouldGetSTREAMFromTextWithDefaults() throws Exception { public void shouldGetSTREAMFromTextWithDefaults() {
ByteArrayOutputStream stream = QRCode.from("Hello World").stream(); ByteArrayOutputStream stream = QRCode.from("Hello World").stream();
Assert.assertNotNull(stream); Assert.assertNotNull(stream);
} }


@Test @Test
public void shouldHandleLargeString() throws Exception { public void shouldHandleLargeString() {
int length = 2950; int length = 2950;
char[] chars = new char[length]; char[] chars = new char[length];
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
Expand All @@ -86,24 +98,30 @@ public void shouldHandleLargeString() throws Exception {
Assert.assertEquals(length, text.length()); Assert.assertEquals(length, text.length());


File file = QRCode.from(text).to(ImageType.PNG).file(); File file = QRCode.from(text).to(ImageType.PNG).file();
Assert.assertNotNull(file); assertThat(file).exists();
assertThat(file).canRead();
assertThat(file.length()).isGreaterThan(0);
} }


@Test @Test
public void shouldGetFileFromTextWithImageTypeOverrides() throws Exception { public void shouldGetFileFromTextWithImageTypeOverrides() {
File jpg = QRCode.from("Hello World").to(ImageType.JPG).file(); File jpg = QRCode.from("Hello World").to(ImageType.JPG).file();
Assert.assertNotNull(jpg); Assert.assertNotNull(jpg);
File gif = QRCode.from("Hello World").to(ImageType.GIF).file(); File gif = QRCode.from("Hello World").to(ImageType.GIF).file();
Assert.assertNotNull(gif); assertThat(gif).exists();
assertThat(gif).canRead();
assertThat(gif.length()).isGreaterThan(0);
} }


@Test @Test
public void shouldGetFileWithNameFromTextWithImageTypeOverrides() throws Exception { public void shouldGetFileWithNameFromTextWithImageTypeOverrides() {
File jpg = QRCode.from("Hello World").to(ImageType.JPG).file("Hello World"); File jpg = QRCode.from("Hello World").to(ImageType.JPG).file("Hello World");
Assert.assertNotNull(jpg); Assert.assertNotNull(jpg);
Assert.assertTrue(jpg.getName().startsWith("Hello World")); Assert.assertTrue(jpg.getName().startsWith("Hello World"));
File gif = QRCode.from("Hello World").to(ImageType.GIF).file("Hello World"); File gif = QRCode.from("Hello World").to(ImageType.GIF).file("Hello World");
Assert.assertNotNull(gif); assertThat(gif).exists();
assertThat(gif).canRead();
assertThat(gif.length()).isGreaterThan(0);
Assert.assertTrue(gif.getName().startsWith("Hello World")); Assert.assertTrue(gif.getName().startsWith("Hello World"));
} }


Expand All @@ -115,7 +133,7 @@ public void shouldGetStreamFromText() throws Exception {
long lengthBefore = tempFile.length(); long lengthBefore = tempFile.length();
FileOutputStream fileOutputStream = new FileOutputStream(tempFile); FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
stream.writeTo(fileOutputStream); stream.writeTo(fileOutputStream);
Assert.assertTrue(lengthBefore < tempFile.length()); assertThat(tempFile.length()).isGreaterThan(lengthBefore);
} }


@Test @Test
Expand All @@ -128,32 +146,36 @@ public void shouldWriteToSuppliedStream() throws Exception {
long lengthBefore = tempFile.length(); long lengthBefore = tempFile.length();
FileOutputStream fileOutputStream = new FileOutputStream(tempFile); FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
stream.writeTo(fileOutputStream); stream.writeTo(fileOutputStream);
Assert.assertTrue(lengthBefore < tempFile.length()); assertThat(tempFile.length()).isGreaterThan(lengthBefore);
} }


@Test @Test
public void shouldBeAbleToOverrideDimensionsToFile() throws Exception { public void shouldBeAbleToOverrideDimensionsToFile() {
long defaultSize = QRCode.from("Hello World").to(ImageType.PNG).file().length(); long defaultSize = QRCode.from("Hello World").to(ImageType.PNG).file().length();
long defaultSize2 = QRCode.from("Hello World").to(ImageType.PNG).file().length(); long defaultSize2 = QRCode.from("Hello World").to(ImageType.PNG).file().length();
File file = QRCode.from("Hello World").to(ImageType.PNG).withSize(250, 250).file(); File file = QRCode.from("Hello World").to(ImageType.PNG).withSize(250, 250).file();
Assert.assertNotNull(file); assertThat(file).exists();
Assert.assertTrue(defaultSize == defaultSize2); assertThat(file).canRead();
assertThat(file.length()).isGreaterThan(0);
Assert.assertEquals(defaultSize, defaultSize2);
Assert.assertTrue(defaultSize < file.length()); Assert.assertTrue(defaultSize < file.length());
} }


@Test @Test
public void shouldBeAbleToOverrideDimensionsToFileWithName() throws Exception { public void shouldBeAbleToOverrideDimensionsToFileWithName() {
long defaultSize = QRCode.from("Hello World").to(ImageType.PNG).file("Hello World").length(); long defaultSize = QRCode.from("Hello World").to(ImageType.PNG).file("Hello World").length();
long defaultSize2 = QRCode.from("Hello World").to(ImageType.PNG).file("Hello World").length(); long defaultSize2 = QRCode.from("Hello World").to(ImageType.PNG).file("Hello World").length();
File file = QRCode.from("Hello World").to(ImageType.PNG).withSize(250, 250).file("Hello World"); File file = QRCode.from("Hello World").to(ImageType.PNG).withSize(250, 250).file("Hello World");
Assert.assertNotNull(file); assertThat(file).exists();
Assert.assertTrue(defaultSize == defaultSize2); assertThat(file).canRead();
assertThat(file.length()).isGreaterThan(0);
Assert.assertEquals(defaultSize, defaultSize2);
Assert.assertTrue(defaultSize < file.length()); Assert.assertTrue(defaultSize < file.length());
Assert.assertTrue(file.getName().startsWith("Hello World")); Assert.assertTrue(file.getName().startsWith("Hello World"));
} }


@Test @Test
public void shouldBeAbleToSupplyEncodingHint() throws Exception { public void shouldBeAbleToSupplyEncodingHint() {
String expected = "UTF-8"; String expected = "UTF-8";
final Object[] capture = new Object[1]; final Object[] capture = new Object[1];
try { try {
Expand All @@ -166,7 +188,7 @@ public void shouldBeAbleToSupplyEncodingHint() throws Exception {
} }


@Test @Test
public void shouldBeAbleToSupplyErrorCorrectionHint() throws Exception { public void shouldBeAbleToSupplyErrorCorrectionHint() {
ErrorCorrectionLevel expected = ErrorCorrectionLevel.L; ErrorCorrectionLevel expected = ErrorCorrectionLevel.L;
final Object[] capture = new Object[1]; final Object[] capture = new Object[1];
try { try {
Expand Down Expand Up @@ -199,7 +221,28 @@ public void shouldColorOutput() throws IOException {
File file = QRCode.from("Hello World").withColor(0xFFFF0000, 0xFFFFFFAA).file(); File file = QRCode.from("Hello World").withColor(0xFFFF0000, 0xFFFFFFAA).file();
File tempFile = File.createTempFile("qr_", ".png"); File tempFile = File.createTempFile("qr_", ".png");
Files.copy(file.toPath(), new FileOutputStream(tempFile)); Files.copy(file.toPath(), new FileOutputStream(tempFile));
System.out.println(tempFile.getAbsoluteFile()); }

@Test
public void shouldGetSvgFromText() {
File file = QRCode.from("www.example.org").svg();
assertThat(file).exists();
assertThat(file).canRead();
assertThat(file.length()).isGreaterThan(0);
}
@Test
public void shouldGetSvgWithSizeFromText() {
File file = QRCode.from("www.example.com").withSize(250, 250).svg();
assertThat(file).exists();
assertThat(file).canRead();
assertThat(file.length()).isGreaterThan(0);
}
@Test
public void shouldGetSvgWithSizeAndColorFromText() {
File file = QRCode.from("www.example.com").withSize(250, 250).withColor(30, 90).svg();
assertThat(file).exists();
assertThat(file).canRead();
assertThat(file.length()).isGreaterThan(0);
} }


@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Expand Down
20 changes: 20 additions & 0 deletions pom.xml
Expand Up @@ -45,6 +45,7 @@


<properties> <properties>
<zxing.version>3.3.0</zxing.version> <zxing.version>3.3.0</zxing.version>
<batik.version>1.10</batik.version>
<junit.version>4.8.2</junit.version> <junit.version>4.8.2</junit.version>
<robolectric.version>2.2</robolectric.version> <robolectric.version>2.2</robolectric.version>
<fest-android.version>1.0.7</fest-android.version> <fest-android.version>1.0.7</fest-android.version>
Expand All @@ -53,6 +54,8 @@
<maven-compiler-plugin.target>1.7</maven-compiler-plugin.target> <maven-compiler-plugin.target>1.7</maven-compiler-plugin.target>
<maven-compiler-plugin.version>2.3.2</maven-compiler-plugin.version> <maven-compiler-plugin.version>2.3.2</maven-compiler-plugin.version>
<maven-checkstyle-plugin.version>2.12.1</maven-checkstyle-plugin.version> <maven-checkstyle-plugin.version>2.12.1</maven-checkstyle-plugin.version>
<maven-javadoc-plugin.version>3.0.1</maven-javadoc-plugin.version>
<maven-source-plugin.version>3.0.1</maven-source-plugin.version>
<nexus-staging-maven-plugin.version>1.6.4</nexus-staging-maven-plugin.version> <nexus-staging-maven-plugin.version>1.6.4</nexus-staging-maven-plugin.version>
<maven-gpg-plugin.version>1.5</maven-gpg-plugin.version> <maven-gpg-plugin.version>1.5</maven-gpg-plugin.version>
</properties> </properties>
Expand Down Expand Up @@ -133,6 +136,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
<version>${maven-source-plugin.version}</version>
<executions> <executions>
<execution> <execution>
<goals> <goals>
Expand All @@ -156,6 +160,22 @@
<artifactId>core</artifactId> <artifactId>core</artifactId>
<version>${zxing.version}</version> <version>${zxing.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-svggen</artifactId>
<version>${batik.version}</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-dom</artifactId>
<version>${batik.version}</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>2.9.1</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>com.google.android</groupId> <groupId>com.google.android</groupId>
<artifactId>android</artifactId> <artifactId>android</artifactId>
Expand Down

0 comments on commit a630af3

Please sign in to comment.