Skip to content

Commit

Permalink
fix rendering for asciidoc templates
Browse files Browse the repository at this point in the history
  • Loading branch information
jtama committed Nov 21, 2024
1 parent 89f53e3 commit 69a3717
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 47 deletions.
6 changes: 5 additions & 1 deletion docs/modules/ROOT/pages/quarkus-roq-plugins.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,11 @@ To install it add:

Then create a template and add the `{#qrcode ...}` tag to it and style and size it as you want.

By default, the plugin generates a html output which is compatible with both `HTML` and `MarkDown` templates. If you want to use the plugin with an `asciidoc`, you can say so using the `asciidoc` attribute (which defaults to `false`.

[source,yaml]
----
{#qrcode value="https://luigis.com/menu/" alt="Luigi's Menu" foreground="#000066" background="#FFFFFF" width=300 height=300 /}
{#qrcode value="https://luigis.com/menu/" alt="Luigi's Menu" foreground="#000066" background="#FFFFFF" width=300 height=300 /} // Will generate HTML code
{#qrcode value="https://luigis.com/menu/" alt="Luigi's Menu" foreground="#000066" background="#FFFFFF" width=300 height=300 asciidoc=true/} // Will save the file in the static folder and generate an asciidoc image macro pointing to it
----

Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkiverse.roq.plugin.qrcode.deployment;

import io.quarkiverse.roq.plugin.qrcode.runtime.QRCodeRenderer;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.FeatureBuildItem;

Expand All @@ -12,4 +14,9 @@ FeatureBuildItem feature() {
return new FeatureBuildItem(FEATURE);
}

@BuildStep
AdditionalBeanBuildItem process() {
return new AdditionalBeanBuildItem(QRCodeRenderer.class);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package io.quarkiverse.roq.plugin.qrcode.runtime;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import jakarta.enterprise.context.ApplicationScoped;

import org.eclipse.microprofile.config.inject.ConfigProperty;

import io.quarkiverse.barcode.okapi.Okapi;
import io.quarkus.arc.Unremovable;
import uk.org.okapibarcode.backend.QrCode;
import uk.org.okapibarcode.graphics.Color;
import uk.org.okapibarcode.output.SvgRenderer;

@ApplicationScoped
@Unremovable
public class QRCodeRenderer {

private final String rootPath;

public QRCodeRenderer(@ConfigProperty(name = "quarkus.http.root-path") String rootPath) {
this.rootPath = rootPath != "/" ? rootPath : "";
}

public String encode(String value, String alt, String foreground, String background, int width, int height,
Boolean asciidoc) {
// Create and configure QR code with low error correction level
QrCode qrCode = new QrCode();
qrCode.setContent(value);
qrCode.setPreferredEccLevel(QrCode.EccLevel.L); // Low error correction provides smallest QR code size

// Strip leading # from hex color codes if present
if (foreground.startsWith("#")) {
foreground = foreground.substring(1);
}
if (background.startsWith("#")) {
background = background.substring(1);
}

// Generate SVG representation of the QR code
ByteArrayOutputStream out = new ByteArrayOutputStream();
SvgRenderer renderer = new SvgRenderer(out, 1.0, new Color(background), new Color(foreground), true);
try {
renderer.render(qrCode);
} catch (IOException e) {
throw new RuntimeException("Error rendering QR code", e);
}
// Convert SVG to base64 data URI format
byte[] svgBytes = out.toByteArray();

if (asciidoc) {
// Render the SVG QR code as an Asciidoc image
String fileName = String.format("qrcode-%s.svg", value.hashCode());
return "image::%s/static/assets/images/%s[alt=\"%s\", width=%s,height=%s]".formatted(
rootPath,
renderQRCode(fileName, svgBytes),
alt,
width,
height);
}
String base64Image = Okapi.dataUriSvg(out.toByteArray());

// Wrap the base64 image in an HTML img tag with specified dimensions
String imgTag = String.format(
"<img src=\"%s\" alt=\"%s\" width=\"%d\" height=\"%d\"/>",
base64Image, alt, width, height);
return imgTag;
}

private String renderQRCode(String fileName, byte[] content) {
try {
Path imagePath = Path.of("static/assets/images", fileName);
Files.write(imagePath, content);
return fileName;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ public static interface BarCodeEncoder {
* @param background The background color
* @param width The width in pixels
* @param height The height in pixels
* @param asciidoc Whether to render the barcode as an Asciidoc image
* @return The encoded barcode as a String (typically HTML)
*/
String encode(String value, String alt, String foreground, String background, int width, int height);
String encode(String value, String alt, String foreground, String background, int width, int height, Boolean asciidoc);
}

private String name;
Expand Down Expand Up @@ -65,21 +66,27 @@ public ParametersInfo getParameters() {
.addParameter(Parameter.builder("background").optional())
.addParameter(Parameter.builder("width").optional())
.addParameter(Parameter.builder("height").optional())
.addParameter(Parameter.builder("asciidoc").optional())
.build();
}

@Override
public Scope initializeBlock(Scope outerScope, BlockInfo block) {
TypeUtil.declareBlock(block, "value", "alt", "foreground", "background", "width", "height");
TypeUtil.declareBlock(block, "value", "alt", "foreground", "background", "width", "height", "asciidoc");
return SectionHelperFactory.super.initializeBlock(outerScope, block);
}

@Override
public CustomSectionHelper initialize(SectionInitContext context) {
TypeUtil.requireParameter(context, "value");
Map<String, Expression> params = TypeUtil.collectExpressions(context, "value", "alt", "foreground", "background",
Map<String, Expression> params = TypeUtil.collectExpressions(context,
"value",
"alt",
"foreground",
"background",
"width",
"height");
"height",
"asciidoc");
return new CustomSectionHelper(params, encoder);
}

Expand All @@ -106,10 +113,11 @@ public CompletionStage<ResultNode> resolve(SectionResolutionContext context) {
String background = TypeUtil.typecheckValue(values, "background", String.class, "white");
Integer width = TypeUtil.typecheckValue(values, "width", Integer.class, 200);
Integer height = TypeUtil.typecheckValue(values, "height", Integer.class, 200);

return new SingleResultNode(encoder.encode(value, alt, foreground, background, width, height));
Boolean asciidoc = TypeUtil.typecheckValue(values, "asciidoc", Boolean.class, false);
return new SingleResultNode(
encoder.encode(value, alt, foreground, background, width, height, asciidoc));
});
}
}

}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
package io.quarkiverse.roq.plugin.qrcode.runtime;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import io.quarkiverse.barcode.okapi.Okapi;
import io.quarkus.arc.Arc;
import io.quarkus.arc.impl.LazyValue;
import io.quarkus.qute.EngineConfiguration;
import uk.org.okapibarcode.backend.QrCode;
import uk.org.okapibarcode.backend.QrCode.EccLevel;
import uk.org.okapibarcode.graphics.Color;
import uk.org.okapibarcode.output.SvgRenderer;

/**
* Qute template section helper for generating QR codes.
Expand All @@ -17,6 +11,9 @@
@EngineConfiguration
public class RoqQrCode extends QuteBarCode {

private static final LazyValue<QRCodeRenderer> CONVERTER = new LazyValue<>(
() -> Arc.container().instance(QRCodeRenderer.class).get());

/**
* Constructs a new RoqQrCode instance.
* Registers the "qrcode" section helper with the encode method.
Expand All @@ -36,37 +33,9 @@ public RoqQrCode() {
* @param height The height of the generated image in pixels
* @return An HTML img tag containing the base64-encoded SVG QR code
*/
private static String encode(String value, String alt, String foreground, String background, int width, int height) {
// Create and configure QR code with low error correction level
QrCode qrCode = new QrCode();
qrCode.setContent(value);
qrCode.setPreferredEccLevel(EccLevel.L); // Low error correction provides smallest QR code size

// Strip leading # from hex color codes if present
if (foreground.startsWith("#")) {
foreground = foreground.substring(1);
}
if (background.startsWith("#")) {
background = background.substring(1);
}

// Generate SVG representation of the QR code
ByteArrayOutputStream out = new ByteArrayOutputStream();
SvgRenderer renderer = new SvgRenderer(out, 1.0, new Color(background), new Color(foreground), true);
try {
renderer.render(qrCode);
} catch (IOException e) {
throw new RuntimeException("Error rendering QR code", e);
}

// Convert SVG to base64 data URI format
String base64Image = Okapi.dataUriSvg(out.toByteArray());

// Wrap the base64 image in an HTML img tag with specified dimensions
String imgTag = String.format(
"<img src=\"%s\" alt=\"%s\" width=\"%d\" height=\"%d\"/>",
base64Image, alt, width, height);
return imgTag;
private static String encode(String value, String alt, String foreground, String background, int width, int height,
Boolean asciidoc) {
return CONVERTER.get().encode(value, alt, foreground, background, width, height, asciidoc);
}

}

0 comments on commit 69a3717

Please sign in to comment.