Skip to content

Commit

Permalink
Add Okapi utility class similar to ZebraCrossing
Browse files Browse the repository at this point in the history
  • Loading branch information
melloware committed Nov 2, 2024
1 parent fcbc51a commit cfe3f4b
Show file tree
Hide file tree
Showing 5 changed files with 476 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ protected Response buildImageResponse(byte[] imageData, String mimeType, String
return response.build();
}

/**
* Builds a Response containing image data with appropriate headers.
*
* @param imageData The raw bytes of the image
* @param mimeType The MIME type of the image (e.g. "image/png")
* @param fileName The filename to use in the Content-Disposition header
* @return A Response object configured with the image data and headers
*/
protected Response buildImageResponse(String imageData, String mimeType, String fileName) {
final Response.ResponseBuilder response = Response.ok(imageData);
response.header(HttpHeaders.CONTENT_DISPOSITION, CONTENT_DISPOSITION.formatted(fileName));
response.header(HttpHeaders.CONTENT_TYPE, mimeType);
return response.build();
}

protected String stripWhitespace(String svgContent) {
// Remove unnecessary whitespace characters (spaces, tabs, newlines) from SVG content
return svgContent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,10 @@
*/
package io.quarkiverse.barcode.it;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import javax.imageio.ImageIO;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
Expand All @@ -35,11 +31,9 @@
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;

import io.quarkiverse.barcode.okapi.Okapi;
import uk.org.okapibarcode.backend.Code128;
import uk.org.okapibarcode.backend.HumanReadableLocation;
import uk.org.okapibarcode.graphics.Color;
import uk.org.okapibarcode.output.Java2DRenderer;
import uk.org.okapibarcode.output.SvgRenderer;

@Path("/okapi")
@ApplicationScoped
Expand All @@ -49,36 +43,40 @@ public class OkapiResource extends BaseImageResource {
@Path("code128/png")
@APIResponse(responseCode = "200", description = "Document downloaded", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM, schema = @Schema(type = SchemaType.STRING, format = "binary")))
public Response code128() throws IOException {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
Code128 barcode = createCode128();

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

BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
Graphics2D g2d = image.createGraphics();
Java2DRenderer renderer = new Java2DRenderer(g2d, 1, Color.WHITE, Color.BLACK);
renderer.render(barcode);
g2d.dispose();
Code128 barcode = createCode128();

ImageIO.write(image, "png", outputStream);
int width = barcode.getWidth();
int height = barcode.getHeight();

// return the image
return buildImageResponse(outputStream.toByteArray(), PNG_MIME_TYPE, "okapi-code128.png");
}
BufferedImage image = Okapi.generateBarcodePng(barcode, width, height);
byte[] bytes = Okapi.pngToBytes(image);
// return the image
return buildImageResponse(bytes, PNG_MIME_TYPE, "okapi-code128.png");
}

@GET
@Path("code128/svg")
@APIResponse(responseCode = "200", description = "Document downloaded", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM, schema = @Schema(type = SchemaType.STRING, format = "binary")))
public Response code128Svg() throws IOException {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
Code128 barcode = createCode128();
SvgRenderer renderer = new SvgRenderer(outputStream, 1, Color.WHITE, Color.BLACK, true);
renderer.render(barcode);
String svg = stripWhitespace(outputStream.toString());
return buildImageResponse(svg.getBytes(StandardCharsets.UTF_8), SVG_MIME_TYPE, "okapi-code128.svg");
}
Code128 barcode = createCode128();
String svg = stripWhitespace(Okapi.generateBarcodeSvgAsString(barcode));
return buildImageResponse(svg.getBytes(StandardCharsets.UTF_8), SVG_MIME_TYPE, "okapi-code128.svg");
}

@GET
@Path("ean13/png/base64")
@APIResponse(responseCode = "200", description = "Document downloaded", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM, schema = @Schema(type = SchemaType.STRING, format = "binary")))
public Response ean13PngBase64() throws IOException {
String image = Okapi.ean13Png("123456789012+12345", 300, 300);
return buildImageResponse(image, PNG_MIME_TYPE, "okapi-ean13.png");
}

@GET
@Path("ean13/svg/base64")
@APIResponse(responseCode = "200", description = "Document downloaded", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM, schema = @Schema(type = SchemaType.STRING, format = "binary")))
public Response ean13SvgBase64() throws IOException {
String image = Okapi.ean13Svg("123456789012+12345", 2.0);
return buildImageResponse(image, SVG_MIME_TYPE, "okapi-ean13.svg");
}

private Code128 createCode128() {
Expand All @@ -91,4 +89,4 @@ private Code128 createCode128() {
barcode.setContent("123456789");
return barcode;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.startsWith;

import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -38,4 +39,28 @@ public void testOkapiCode128Svg() {
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="202" height="70" version="1.1" xmlns="http://www.w3.org/2000/svg"><desc>123456789</desc><g id="barcode" fill="#000000"><rect x="0" y="0" width="202" height="70" fill="#FFFFFF"/><rect x="0.00" y="0.00" width="4.00" height="50.00"/><rect x="6.00" y="0.00" width="2.00" height="50.00"/><rect x="12.00" y="0.00" width="6.00" height="50.00"/><rect x="22.00" y="0.00" width="2.00" height="50.00"/><rect x="26.00" y="0.00" width="4.00" height="50.00"/><rect x="34.00" y="0.00" width="6.00" height="50.00"/><rect x="44.00" y="0.00" width="2.00" height="50.00"/><rect x="52.00" y="0.00" width="2.00" height="50.00"/><rect x="56.00" y="0.00" width="4.00" height="50.00"/><rect x="66.00" y="0.00" width="6.00" height="50.00"/><rect x="78.00" y="0.00" width="2.00" height="50.00"/><rect x="82.00" y="0.00" width="4.00" height="50.00"/><rect x="88.00" y="0.00" width="4.00" height="50.00"/><rect x="100.00" y="0.00" width="2.00" height="50.00"/><rect x="104.00" y="0.00" width="2.00" height="50.00"/><rect x="110.00" y="0.00" width="2.00" height="50.00"/><rect x="114.00" y="0.00" width="8.00" height="50.00"/><rect x="124.00" y="0.00" width="6.00" height="50.00"/><rect x="132.00" y="0.00" width="6.00" height="50.00"/><rect x="142.00" y="0.00" width="2.00" height="50.00"/><rect x="146.00" y="0.00" width="4.00" height="50.00"/><rect x="154.00" y="0.00" width="2.00" height="50.00"/><rect x="162.00" y="0.00" width="8.00" height="50.00"/><rect x="172.00" y="0.00" width="2.00" height="50.00"/><rect x="176.00" y="0.00" width="4.00" height="50.00"/><rect x="186.00" y="0.00" width="6.00" height="50.00"/><rect x="194.00" y="0.00" width="2.00" height="50.00"/><rect x="198.00" y="0.00" width="4.00" height="50.00"/><text x="101.00" y="66.00" text-anchor="middle" font-family="Monospaced" font-size="16.00" fill="#000000"> 123456789 </text></g></svg>
"""));
}

@Test
public void testOkapiEan13Base64Png() {
given()
.when().get("/okapi/ean13/png/base64")
.then()
.statusCode(200)
.contentType(equalTo(BaseImageResource.PNG_MIME_TYPE))
.header(CONTENT_LENGTH, Integer::parseInt, greaterThan(0))
.body(startsWith(
"data:image/png;base64,77+9UE5HDQoaCgAAAA1JSERSAAABLAAAASwIAAAAAF"));
}

@Test
public void testOkapiEan13Base64Svg() {
given()
.when().get("/okapi/ean13/svg/base64")
.then()
.statusCode(200)
.contentType(equalTo(BaseImageResource.SVG_MIME_TYPE))
.header(CONTENT_LENGTH, Integer::parseInt, greaterThan(0))
.body(is(
"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iCiAgICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8c3ZnIHdpZHRoPSIzMDIiIGhlaWdodD0iMTAwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgIDxkZXNjPjEyMzQ1Njc4OTAxMjwvZGVzYz4KICAgPGcgaWQ9ImJhcmNvZGUiIGZpbGw9IiMwMDAwMDAiPgogICAgICA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMzAyIiBoZWlnaHQ9IjEwMCIgZmlsbD0iI0ZGRkZGRiIgLz4KICAgICAgPHJlY3QgeD0iMC4wMCIgeT0iMC4wMCIgd2lkdGg9IjIuMDAiIGhlaWdodD0iOTAuMDAiIC8+CiAgICAgIDxyZWN0IHg9IjQuMDAiIHk9IjAuMDAiIHdpZHRoPSIyLjAwIiBoZWlnaHQ9IjkwLjAwIiAvPgogICAgICA8cmVjdCB4PSIxMC4wMCIgeT0iMC4wMCIgd2lkdGg9IjIuMDAiIGhlaWdodD0iODAuMDAiIC8+CiAgICAgIDxyZWN0IHg9IjE2LjAwIiB5PSIwLjAwIiB3aWR0aD0iNC4wMCIgaGVpZ2h0PSI4MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMjIuMDAiIHk9IjAuMDAiIHdpZHRoPSI4LjAwIiBoZWlnaHQ9IjgwLjAwIiAvPgogICAgICA8cmVjdCB4PSIzMi4wMCIgeT0iMC4wMCIgd2lkdGg9IjIuMDAiIGhlaWdodD0iODAuMDAiIC8+CiAgICAgIDxyZWN0IHg9IjM4LjAwIiB5PSIwLjAwIiB3aWR0aD0iNi4wMCIgaGVpZ2h0PSI4MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iNDYuMDAiIHk9IjAuMDAiIHdpZHRoPSIyLjAwIiBoZWlnaHQ9IjgwLjAwIiAvPgogICAgICA8cmVjdCB4PSI1MC4wMCIgeT0iMC4wMCIgd2lkdGg9IjQuMDAiIGhlaWdodD0iODAuMDAiIC8+CiAgICAgIDxyZWN0IHg9IjYwLjAwIiB5PSIwLjAwIiB3aWR0aD0iMi4wMCIgaGVpZ2h0PSI4MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iNzAuMDAiIHk9IjAuMDAiIHdpZHRoPSIyLjAwIiBoZWlnaHQ9IjgwLjAwIiAvPgogICAgICA8cmVjdCB4PSI3NC4wMCIgeT0iMC4wMCIgd2lkdGg9IjIuMDAiIGhlaWdodD0iODAuMDAiIC8+CiAgICAgIDxyZWN0IHg9IjgwLjAwIiB5PSIwLjAwIiB3aWR0aD0iMi4wMCIgaGVpZ2h0PSI4MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iODguMDAiIHk9IjAuMDAiIHdpZHRoPSIyLjAwIiBoZWlnaHQ9IjgwLjAwIiAvPgogICAgICA8cmVjdCB4PSI5Mi4wMCIgeT0iMC4wMCIgd2lkdGg9IjIuMDAiIGhlaWdodD0iOTAuMDAiIC8+CiAgICAgIDxyZWN0IHg9Ijk2LjAwIiB5PSIwLjAwIiB3aWR0aD0iMi4wMCIgaGVpZ2h0PSI5MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMTAwLjAwIiB5PSIwLjAwIiB3aWR0aD0iMi4wMCIgaGVpZ2h0PSI4MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMTA2LjAwIiB5PSIwLjAwIiB3aWR0aD0iMi4wMCIgaGVpZ2h0PSI4MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMTE0LjAwIiB5PSIwLjAwIiB3aWR0aD0iNi4wMCIgaGVpZ2h0PSI4MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMTIyLjAwIiB5PSIwLjAwIiB3aWR0aD0iMi4wMCIgaGVpZ2h0PSI4MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMTI4LjAwIiB5PSIwLjAwIiB3aWR0aD0iNi4wMCIgaGVpZ2h0PSI4MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMTM4LjAwIiB5PSIwLjAwIiB3aWR0aD0iMi4wMCIgaGVpZ2h0PSI4MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMTQyLjAwIiB5PSIwLjAwIiB3aWR0aD0iNC4wMCIgaGVpZ2h0PSI4MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMTUwLjAwIiB5PSIwLjAwIiB3aWR0aD0iNC4wMCIgaGVpZ2h0PSI4MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMTU2LjAwIiB5PSIwLjAwIiB3aWR0aD0iNC4wMCIgaGVpZ2h0PSI4MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMTYyLjAwIiB5PSIwLjAwIiB3aWR0aD0iNC4wMCIgaGVpZ2h0PSI4MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMTcwLjAwIiB5PSIwLjAwIiB3aWR0aD0iMi4wMCIgaGVpZ2h0PSI4MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMTc2LjAwIiB5PSIwLjAwIiB3aWR0aD0iMi4wMCIgaGVpZ2h0PSI4MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMTg0LjAwIiB5PSIwLjAwIiB3aWR0aD0iMi4wMCIgaGVpZ2h0PSI5MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMTg4LjAwIiB5PSIwLjAwIiB3aWR0aD0iMi4wMCIgaGVpZ2h0PSI5MC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMjA4LjAwIiB5PSIxNi4wMCIgd2lkdGg9IjIuMDAiIGhlaWdodD0iNzQuMDAiIC8+CiAgICAgIDxyZWN0IHg9IjIxMi4wMCIgeT0iMTYuMDAiIHdpZHRoPSI0LjAwIiBoZWlnaHQ9Ijc0LjAwIiAvPgogICAgICA8cmVjdCB4PSIyMTguMDAiIHk9IjE2LjAwIiB3aWR0aD0iNC4wMCIgaGVpZ2h0PSI3NC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMjI2LjAwIiB5PSIxNi4wMCIgd2lkdGg9IjQuMDAiIGhlaWdodD0iNzQuMDAiIC8+CiAgICAgIDxyZWN0IHg9IjIzMi4wMCIgeT0iMTYuMDAiIHdpZHRoPSIyLjAwIiBoZWlnaHQ9Ijc0LjAwIiAvPgogICAgICA8cmVjdCB4PSIyMzguMDAiIHk9IjE2LjAwIiB3aWR0aD0iMi4wMCIgaGVpZ2h0PSI3NC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMjQ0LjAwIiB5PSIxNi4wMCIgd2lkdGg9IjQuMDAiIGhlaWdodD0iNzQuMDAiIC8+CiAgICAgIDxyZWN0IHg9IjI1MC4wMCIgeT0iMTYuMDAiIHdpZHRoPSIyLjAwIiBoZWlnaHQ9Ijc0LjAwIiAvPgogICAgICA8cmVjdCB4PSIyNTQuMDAiIHk9IjE2LjAwIiB3aWR0aD0iMi4wMCIgaGVpZ2h0PSI3NC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMjY0LjAwIiB5PSIxNi4wMCIgd2lkdGg9IjIuMDAiIGhlaWdodD0iNzQuMDAiIC8+CiAgICAgIDxyZWN0IHg9IjI2OC4wMCIgeT0iMTYuMDAiIHdpZHRoPSIyLjAwIiBoZWlnaHQ9Ijc0LjAwIiAvPgogICAgICA8cmVjdCB4PSIyNzIuMDAiIHk9IjE2LjAwIiB3aWR0aD0iMi4wMCIgaGVpZ2h0PSI3NC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMjgwLjAwIiB5PSIxNi4wMCIgd2lkdGg9IjQuMDAiIGhlaWdodD0iNzQuMDAiIC8+CiAgICAgIDxyZWN0IHg9IjI4Ni4wMCIgeT0iMTYuMDAiIHdpZHRoPSIyLjAwIiBoZWlnaHQ9Ijc0LjAwIiAvPgogICAgICA8cmVjdCB4PSIyOTAuMDAiIHk9IjE2LjAwIiB3aWR0aD0iNC4wMCIgaGVpZ2h0PSI3NC4wMCIgLz4KICAgICAgPHJlY3QgeD0iMzAwLjAwIiB5PSIxNi4wMCIgd2lkdGg9IjIuMDAiIGhlaWdodD0iNzQuMDAiIC8+CiAgICAgIDx0ZXh0IHg9Ii0xMC4wMCIgeT0iOTYuMDAiIHRleHQtYW5jaG9yPSJlbmQiCiAgICAgICAgIGZvbnQtZmFtaWx5PSJIZWx2ZXRpY2EiIGZvbnQtc2l6ZT0iMTYuMDAiIGZpbGw9IiMwMDAwMDAiPgogICAgICAgICAxCiAgICAgIDwvdGV4dD4KICAgICAgPHRleHQgeD0iMTAuMDAiIHk9Ijk2LjAwIiB0ZXh0LWFuY2hvcj0ic3RhcnQiCiAgICAgICAgIHRleHRMZW5ndGg9Ijc4LjAwIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciCiAgICAgICAgIGZvbnQtZmFtaWx5PSJIZWx2ZXRpY2EiIGZvbnQtc2l6ZT0iMTYuMDAiIGZpbGw9IiMwMDAwMDAiPgogICAgICAgICAyMzQ1NjcKICAgICAgPC90ZXh0PgogICAgICA8dGV4dCB4PSIxMDIuMDAiIHk9Ijk2LjAwIiB0ZXh0LWFuY2hvcj0ic3RhcnQiCiAgICAgICAgIHRleHRMZW5ndGg9Ijc4LjAwIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciCiAgICAgICAgIGZvbnQtZmFtaWx5PSJIZWx2ZXRpY2EiIGZvbnQtc2l6ZT0iMTYuMDAiIGZpbGw9IiMwMDAwMDAiPgogICAgICAgICA4OTAxMjgKICAgICAgPC90ZXh0PgogICAgICA8dGV4dCB4PSIyNTUuMDAiIHk9IjEyLjAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIgogICAgICAgICBmb250LWZhbWlseT0iSGVsdmV0aWNhIiBmb250LXNpemU9IjE2LjAwIiBmaWxsPSIjMDAwMDAwIj4KICAgICAgICAgMTIzNDUKICAgICAgPC90ZXh0PgogICA8L2c+Cjwvc3ZnPgo="));
}
}
Loading

0 comments on commit cfe3f4b

Please sign in to comment.