Skip to content

Commit b0adcea

Browse files
authored
Fix #114: Add rotation to outputs (PR #115)
1 parent 34c2efd commit b0adcea

18 files changed

+910
-50
lines changed

src/main/java/uk/org/okapibarcode/output/Java2DRenderer.java

+51-3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.awt.Polygon;
2626
import java.awt.font.FontRenderContext;
2727
import java.awt.font.TextAttribute;
28+
import java.awt.geom.AffineTransform;
2829
import java.awt.geom.Area;
2930
import java.awt.geom.Ellipse2D;
3031
import java.awt.geom.Rectangle2D;
@@ -57,6 +58,9 @@ public class Java2DRenderer implements SymbolRenderer {
5758
/** The ink (foreground) color. */
5859
private final Color ink;
5960

61+
/** The clockwise rotation of the symbol in degrees (0, 90, 180, or 270). */
62+
private final int rotation;
63+
6064
/**
6165
* Creates a new Java 2D renderer. If the specified paper color is {@code null}, the symbol is drawn without clearing the
6266
* existing {@code g2d} background.
@@ -67,18 +71,61 @@ public class Java2DRenderer implements SymbolRenderer {
6771
* @param ink the ink (foreground) color
6872
*/
6973
public Java2DRenderer(Graphics2D g2d, double magnification, Color paper, Color ink) {
74+
this(g2d, magnification, paper, ink, 0);
75+
}
76+
77+
/**
78+
* Creates a new Java 2D renderer. If the specified paper color is {@code null}, the symbol is drawn without clearing the
79+
* existing {@code g2d} background.
80+
*
81+
* @param g2d the graphics to render to
82+
* @param magnification the magnification factor to apply
83+
* @param paper the paper (background) color (may be {@code null})
84+
* @param ink the ink (foreground) color
85+
* @param rotation the clockwise rotation of the symbol in degrees (0, 90, 180, or 270)
86+
*/
87+
public Java2DRenderer(Graphics2D g2d, double magnification, Color paper, Color ink, int rotation) {
7088
this.g2d = g2d;
7189
this.magnification = magnification;
7290
this.paper = paper;
7391
this.ink = ink;
92+
this.rotation = SymbolRenderer.normalizeRotation(rotation);
7493
}
7594

7695
/** {@inheritDoc} */
7796
@Override
7897
public void render(Symbol symbol) {
7998

99+
int width = (int) (symbol.getWidth() * magnification);
100+
int height = (int) (symbol.getHeight() * magnification);
80101
int marginX = (int) (symbol.getQuietZoneHorizontal() * magnification);
81102
int marginY = (int) (symbol.getQuietZoneVertical() * magnification);
103+
int rotateHeight = height;
104+
int rotateWidth = width;
105+
106+
// render rotation clockwise
107+
AffineTransform oldTransform = null;
108+
if (rotation != 0) {
109+
oldTransform = g2d.getTransform();
110+
switch (rotation) {
111+
case 90:
112+
rotateHeight = width;
113+
rotateWidth = height;
114+
g2d.rotate(Math.PI / 2d);
115+
g2d.translate(0, -rotateWidth);
116+
break;
117+
case 180:
118+
g2d.rotate(Math.PI);
119+
g2d.translate(-width, -height);
120+
break;
121+
case 270:
122+
rotateHeight = width;
123+
rotateWidth = height;
124+
g2d.rotate(Math.PI * 1.5d);
125+
g2d.translate(-rotateHeight, 0);
126+
break;
127+
}
128+
}
82129

83130
Font f = symbol.getFont();
84131
if (f != null) {
@@ -92,10 +139,8 @@ public void render(Symbol symbol) {
92139
java.awt.Color oldColor = g2d.getColor();
93140

94141
if (paper != null) {
95-
int w = (int) (symbol.getWidth() * magnification);
96-
int h = (int) (symbol.getHeight() * magnification);
97142
g2d.setColor(new java.awt.Color(paper.red, paper.green, paper.blue));
98-
g2d.fillRect(0, 0, w, h);
143+
g2d.fillRect(0, 0, width, height);
99144
}
100145

101146
g2d.setColor(new java.awt.Color(ink.red, ink.green, ink.blue));
@@ -154,6 +199,9 @@ public void render(Symbol symbol) {
154199

155200
g2d.setFont(oldFont);
156201
g2d.setColor(oldColor);
202+
if (oldTransform != null) {
203+
g2d.setTransform(oldTransform);
204+
}
157205
}
158206

159207
private static Rectangle2D getBounds(TextBox text, Graphics2D g2d) {

src/main/java/uk/org/okapibarcode/output/PostScriptRenderer.java

+58-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ public class PostScriptRenderer implements SymbolRenderer {
5252
/** The ink (foreground) color. */
5353
private final Color ink;
5454

55+
/** The clockwise rotation of the symbol in degrees. */
56+
private final int rotation;
57+
5558
/**
5659
* Creates a new PostScript renderer.
5760
*
@@ -61,10 +64,24 @@ public class PostScriptRenderer implements SymbolRenderer {
6164
* @param ink the ink (foreground) color
6265
*/
6366
public PostScriptRenderer(OutputStream out, double magnification, Color paper, Color ink) {
67+
this(out, magnification, paper, ink, 0);
68+
}
69+
70+
/**
71+
* Creates a new PostScript renderer.
72+
*
73+
* @param out the output stream to render to
74+
* @param magnification the magnification factor to apply
75+
* @param paper the paper (background) color
76+
* @param ink the ink (foreground) color
77+
* @param rotation the clockwise rotation of the symbol in degrees (0, 90, 180, or 270)
78+
*/
79+
public PostScriptRenderer(OutputStream out, double magnification, Color paper, Color ink, int rotation) {
6480
this.out = out;
6581
this.magnification = magnification;
6682
this.paper = paper;
6783
this.ink = ink;
84+
this.rotation = SymbolRenderer.normalizeRotation(rotation);
6885
}
6986

7087
/** {@inheritDoc} */
@@ -79,6 +96,20 @@ public void render(Symbol symbol) throws IOException {
7996
int marginX = (int) (symbol.getQuietZoneHorizontal() * magnification);
8097
int marginY = (int) (symbol.getQuietZoneVertical() * magnification);
8198

99+
// render rotation clockwise
100+
int rotateHeight = height;
101+
int rotateWidth = width;
102+
String transform;
103+
switch (rotation) {
104+
case 90:
105+
case 270:
106+
rotateHeight = width;
107+
rotateWidth = height;
108+
break;
109+
default:
110+
break;
111+
}
112+
82113
String title;
83114
if (content.isEmpty()) {
84115
title = "OkapiBarcode Generated Symbol";
@@ -93,7 +124,7 @@ public void render(Symbol symbol) throws IOException {
93124
writer.append("%%Creator: OkapiBarcode\n");
94125
writer.append("%%Title: ").append(title).append('\n');
95126
writer.append("%%Pages: 0\n");
96-
writer.append("%%BoundingBox: 0 0 ").appendInt(width).append(" ").appendInt(height).append("\n");
127+
writer.append("%%BoundingBox: 0 0 ").appendInt(rotateWidth).append(" ").appendInt(rotateHeight).append("\n");
97128
writer.append("%%EndComments\n");
98129

99130
// Definitions
@@ -104,6 +135,27 @@ public void render(Symbol symbol) throws IOException {
104135
writer.append("/TR { newpath 4 1 roll exch moveto 1 index 0 rlineto 0 exch rlineto neg 0 rlineto closepath fill } bind def\n");
105136
writer.append("/TE { pop pop } bind def\n");
106137

138+
// Set orientation
139+
switch (rotation) {
140+
case 90:
141+
writer.append("gsave\n")
142+
.append(-rotation).append(" rotate\n")
143+
.append(-width).append(" 0.00 translate\n");
144+
break;
145+
case 180:
146+
writer.append("gsave\n")
147+
.append(-rotation).append(" rotate\n")
148+
.append(-width).append(" ").append(-height).append(" translate\n");
149+
break;
150+
case 270:
151+
writer.append("gsave\n")
152+
.append(-rotation).append(" rotate\n")
153+
.append("0.00 ").append(-height).append(" translate\n");
154+
break;
155+
default:
156+
break;
157+
}
158+
107159
// Background
108160
writer.append("newpath\n");
109161
writer.append(ink.red / 255.0).append(" ")
@@ -235,6 +287,11 @@ public void render(Symbol symbol) throws IOException {
235287
writer.append(" TH\n");
236288
}
237289

290+
// Restore original transformation if rotated
291+
if (rotation != 0) {
292+
writer.append("grestore\n");
293+
}
294+
238295
// Footer
239296
writer.append("\nshowpage\n");
240297
}

src/main/java/uk/org/okapibarcode/output/SvgRenderer.java

+47-3
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ public class SvgRenderer implements SymbolRenderer {
6767

6868
/** Whether or not to include the XML prolog in the output. */
6969
private final boolean xmlProlog;
70+
71+
/** The clockwise rotation of the symbol in degrees. */
72+
private final int rotation;
7073

7174
/**
7275
* Creates a new SVG renderer.
@@ -79,11 +82,27 @@ public class SvgRenderer implements SymbolRenderer {
7982
* standalone SVG documents, {@code false} for SVG content embedded directly in HTML documents)
8083
*/
8184
public SvgRenderer(OutputStream out, double magnification, Color paper, Color ink, boolean xmlProlog) {
85+
this(out, magnification, paper, ink, xmlProlog, 0);
86+
}
87+
88+
/**
89+
* Creates a new SVG renderer.
90+
*
91+
* @param out the output stream to render to
92+
* @param magnification the magnification factor to apply
93+
* @param paper the paper (background) color
94+
* @param ink the ink (foreground) color
95+
* @param xmlProlog whether or not to include the XML prolog in the output (usually {@code true} for
96+
* standalone SVG documents, {@code false} for SVG content embedded directly in HTML documents)
97+
* @param rotation the clockwise rotation of the symbol in degrees (0, 90, 180, or 270)
98+
*/
99+
public SvgRenderer(OutputStream out, double magnification, Color paper, Color ink, boolean xmlProlog, int rotation) {
82100
this.out = out;
83101
this.magnification = magnification;
84102
this.paper = paper;
85103
this.ink = ink;
86104
this.xmlProlog = xmlProlog;
105+
this.rotation = SymbolRenderer.normalizeRotation(rotation);
87106
}
88107

89108
/** {@inheritDoc} */
@@ -120,13 +139,38 @@ public void render(Symbol symbol) throws IOException {
120139
writer.append(" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
121140
}
122141

142+
// render rotation clockwise
143+
int rotateHeight = height;
144+
int rotateWidth = width;
145+
String transform;
146+
switch (rotation) {
147+
case 90:
148+
rotateHeight = width;
149+
rotateWidth = height;
150+
transform = " transform=\"rotate(" + rotation + ") translate(0,-" + rotateWidth + ")\"";
151+
break;
152+
case 180:
153+
transform = " transform=\"rotate(" + rotation + "," + (width/2) + "," + (height/2) + ")\"";
154+
break;
155+
case 270:
156+
rotateHeight = width;
157+
rotateWidth = height;
158+
transform = " transform=\"rotate(" + rotation + ") translate(-" + rotateHeight + ",0)\"";
159+
break;
160+
default:
161+
transform = "";
162+
break;
163+
}
164+
123165
// Header
124-
writer.append("<svg width=\"").appendInt(width)
125-
.append("\" height=\"").appendInt(height)
166+
writer.append("<svg width=\"").appendInt(rotateWidth)
167+
.append("\" height=\"").appendInt(rotateHeight)
126168
.append("\" version=\"1.1")
127169
.append("\" xmlns=\"http://www.w3.org/2000/svg\">\n");
128170
writer.append(" <desc>").append(clean(title)).append("</desc>\n");
129-
writer.append(" <g id=\"barcode\" fill=\"#").append(fgColour).append("\">\n");
171+
172+
173+
writer.append(" <g id=\"barcode\" fill=\"#").append(fgColour).append("\"").append(transform).append(">\n");
130174
writer.append(" <rect x=\"0\" y=\"0\" width=\"").appendInt(width)
131175
.append("\" height=\"").appendInt(height)
132176
.append("\" fill=\"#").append(bgColour).append("\" />\n");

src/main/java/uk/org/okapibarcode/output/SymbolRenderer.java

+15
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,19 @@ public interface SymbolRenderer {
3333
*/
3434
void render(Symbol symbol) throws IOException;
3535

36+
/**
37+
* Normalizes clockwise rotation values to 0, 90, 180, or 270 degrees.
38+
*
39+
* @param rotation the clockwise rotation to normalize
40+
* @return the normalized rotation (0, 90, 180, or 270)
41+
* @throws IllegalArgumentException if rotation is not a multiple of 90 degrees
42+
*/
43+
static int normalizeRotation(int rotation) {
44+
int normalized = ((rotation % 360) + 360) % 360;
45+
if (normalized % 90 != 0) {
46+
throw new IllegalArgumentException("Rotation must be a multiple of 90 degrees");
47+
}
48+
return normalized;
49+
}
50+
3651
}

0 commit comments

Comments
 (0)