Check-in [90124aac60]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:UPC and CODE128 generation done; See Barcode.jara
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:90124aac60d6562e380a4b3c091a88ebac2bf249
User & Date: xiekevin 2018-01-15 03:12:39
Context
2018-01-22
05:12
Work in progress of QR code image generation check-in: 8050fa743a user: xiekevin tags: trunk
2018-01-15
03:12
UPC and CODE128 generation done; See Barcode.jara check-in: 90124aac60 user: xiekevin tags: trunk
03:04
initial empty check-in check-in: 9e0a277e41 user: xiekevin tags: trunk
Changes

Added build.number.







>
>
>
1
2
3
#Build Number for ANT. Do not edit!
#Sun Jan 14 17:22:55 EST 2018
build.number=3

Added build.xml.





































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?xml version="1.0" encoding="UTF-8"?>
<project name="fanxi-barcode" default="build" basedir=".">
	<property name="dir_build" value="${basedir}/build" />
	<property name="dir_tmp" value="${basedir}/tmp" />
	<property name="dir_dist" value="${basedir}/dist" />
	<property name="dir_dist.absolute" location="${dir_dist}"/>
	<property name="dir_src" value="${basedir}/src" />
	<property name="jar_name_bin" value="fanxi-barcode" />
	<property name="jar_name_src" value="fanxi-barcode-src" />
	
	<!-- version must be an integer -->
	<property name="major" value="1" />
	<property name="minor" value="0" />
	
	<buildnumber file="build.number" />
	
	<target name="init">
		<mkdir dir="${dir_build}"/>
		<mkdir dir="${dir_dist}"/>
		<mkdir dir="${dir_tmp}"/>
	</target>
	
	<target name="clean">
		<delete dir="${dir_build}"/>
		<delete dir="${dir_tmp}"/>
		<delete dir="${dir_dist}"/>
	</target>
	
	<target name="build" depends="clean,compile">
		<jar destfile="${dir_dist}/${jar_name_bin}.jar" basedir="${dir_build}">
		</jar>

		<copy todir="${dir_build}">
			<fileset dir="${dir_src}" />
		</copy>
		<jar destfile="${dir_dist}/${jar_name_src}.jar" basedir="${dir_build}">
		</jar>		

		<delete dir="${dir_tmp}"/>
	</target>
	
	<target name="copy_src_to_tmp" depends="init">
		<copy todir="${dir_tmp}">
			<fileset dir="${dir_src}" />
		</copy>
		<replaceregexp file="${dir_tmp}/fanxi/barcode/Barcode.java"
		                   match="__BUILD__"
		                   byline="true"
		                   replace="${build.number}" />
		<replaceregexp file="${dir_tmp}/fanxi/barcode/Barcode.java"
		                   match="__MAJOR__"
		                   byline="true"
		                   replace="${major}" />
		<replaceregexp file="${dir_tmp}/fanxi/barcode/Barcode.java"
		                   match="__MINOR__"
		                   byline="true"
		                   replace="${minor}" />
	</target>
	
	<target name="compile" depends="init,copy_src_to_tmp">
		<javac srcdir="${dir_tmp}" destdir="${dir_build}" includeantruntime="false"
				debug="on" debuglevel="lines,vars,source">
		</javac>
	</target>
	
</project>

Added src/fanxi/barcode/Barcode.java.















































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package fanxi.barcode;

import java.awt.Image;
import java.io.File;

public abstract class Barcode {
	
	public static final String MAJOR	= "__MAJOR__";
	public static final String MINOR	= "__MINOR__";
	public static final String BUILD	= "__BUILD__";
	
	// dimension related parameters
	// unit is pixel
	protected int		width;
	
	protected boolean	fixedHeight		= false;
	protected int		height;
	
	protected double	ratioWH			= (double)2;
	protected int		unitPixels		= 3;
	protected int 		fontSizeUnit	= 10;
	
	protected boolean	showCode		= true;
	
	// the code string you want to encode using barcode symbology
	protected char[]	code;

	protected Barcode(char[] code) {
		this.code = code;
	}
	
	public abstract Barcode.Type type();

	public abstract void save(File file, ImageFormat format) throws Exception;

	public abstract Image getImage() throws Exception;
	
	public boolean showCode() {
		return this.showCode;
	}
	
	public Barcode showCode(boolean showCode) {
		this.showCode = showCode;
		this.computeDimensions();
		return this;
	}
	
	// set the height of label to constant value in pixel, if the input value is 0 or negative
	// the height of label is compute use the width/height ratio
	public Barcode fixedHeight(int height) {
		if( height>0 ) {
			this.fixedHeight = true;
			this.height = height;			
		}else{
			this.fixedHeight = false;
		}
		this.computeDimensions();
		return this;
	};
	
	// a module is a smallest bar or space. this method returns the width in pixel
	public int moduleWidth() {
		return this.unitPixels;
	}
	public Barcode moduleWidth(int widthInPixel) throws Exception {
		this.unitPixels = widthInPixel;
		this.computeDimensions();
		return this;		
	}
	
	public String code() {
		if( this.code==null ) {
			return null;
		}else{
			return new String(this.code);
		}
	}
	public abstract Barcode code(char[] code) throws Exception;
	public abstract Barcode code(String code) throws Exception;

	public Barcode ratioWidthToHeight(double ratio) {
		this.ratioWH = ratio;
		this.computeDimensions();
		return this;
	}
	
	protected abstract void computeDimensions();
	
	// check if the input code is a sequence of number, optionally of specific length (length=0 means
	// it can be variable length, char 48 is digit '0', char 57 is digit '9'
	public static boolean isNumbers(char[] code) {
		boolean retval = true;
		if( code==null ) return false;
		for(int i=0; i<code.length; i++) {
			if( code[i]<48 || code[i]>57 ) {
				retval = false;
				break;
			}
		}
		return retval;
	}
	
	// is this an ASCII char array, null and empty array are considered false
	public static boolean isAsciis(char[] chars) {
		boolean retval = true;
		if( chars==null || chars.length==0 ) return false;
		for(char c : chars) {
			if( c > 127 ) {
				retval = false;
				break;
			}
		}
		return retval;
	}
	
	public static enum ImageFormat {
		PNG, JPEG, JPG, BMP, WBMP
	}
	
	public static enum Type {
		UPC, CODE128
	}
	
	public static Barcode instance(Type type, String code) throws Exception {
		Barcode retval = null;
		switch(type) {
		case UPC:
			retval = new Upc(code);
			break;
		case CODE128:
			retval = new Code128(code);
			break;
		default:
			throw new Exception("Unsupport barcode type " + type);
		}
		return retval;
	}
	
	public static void main(String[] args) throws Exception{
		Code128 code128;
		String	code;
		Barcode	barcode, upc;
		
		code = "How many do you want? [3]: ";
		barcode = Barcode.instance(Type.CODE128, code);
		barcode.save(new File("C:\\Users\\Kevin Xie\\Documents\\code128-default.png"), ImageFormat.PNG);
		barcode.moduleWidth(1).ratioWidthToHeight(3).showCode(false);
		barcode.save(new File("C:\\Users\\Kevin Xie\\Documents\\code128-mw1-r3.png"), ImageFormat.PNG);
		barcode.moduleWidth(1).showCode(false).fixedHeight(90);
		barcode.save(new File("C:\\Users\\Kevin Xie\\Documents\\code128-mw1-r3-90.png"), ImageFormat.PNG);
		barcode.code("I don't event know you number 4168772322!").fixedHeight(60);
		barcode.save(new File("C:\\Users\\Kevin Xie\\Documents\\code128-other-60.png"), ImageFormat.PNG);
		
		code = "09520532046";
		upc = new Upc(code).showCode(false).ratioWidthToHeight(2.6);
		upc.save(new File("C:\\Users\\Kevin Xie\\Documents\\upc-" + code + ".png"), ImageFormat.PNG);

		upc = new Upc(code).showCode(false).ratioWidthToHeight(2.6).fixedHeight(60);
		upc.save(new File("C:\\Users\\Kevin Xie\\Documents\\upc-" + code + "-60.png"), ImageFormat.PNG);
		/**		

		code = "069000061008";
		upc.code(code).ratioWidthToHeight(2.5);
		upc.save(new File("C:\\Users\\Kevin Xie\\Documents\\upc-" + code + ".png"), ImageFormat.PNG);

		code = "09520532046";
		code128 = new Code128(code);
		code128.save(new File("C:\\Users\\Kevin Xie\\Documents\\code128-" + code + ".png"), ImageFormat.PNG);

		code = "HI345678";
		code128.code(code).ratioWidthToHeight(3);
		code128.save(new File("C:\\Users\\Kevin Xie\\Documents\\code128-" + code + ".png"), ImageFormat.PNG);

		code = "It's really good!";
		code128.code(code);;
		code128.save(new File("C:\\Users\\Kevin Xie\\Documents\\code128-" + code + ".png"), ImageFormat.PNG);

		code = "AaBbCc LlEeKk 99 00";
		code128.code(code);;
		code128.save(new File("C:\\Users\\Kevin Xie\\Documents\\code128-" + code + ".png"), ImageFormat.PNG);
*/	
	}
}

Added src/fanxi/barcode/Code128.java.











































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
package fanxi.barcode;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;

/** 
 * Do NOT support control characters
 * 
 * @author Kevin Xie
 */
public class Code128 extends Barcode {
	
	public static final byte VALUE_START_A = 103;
	public static final byte VALUE_START_B = 104;
	public static final byte VALUE_START_C = 105;
	
	public static final byte VALUE_SWITCH_A2B = 100;
	public static final byte VALUE_SWITCH_A2C = 99;
	public static final byte VALUE_SWITCH_B2A = 101;
	public static final byte VALUE_SWITCH_B2C = 99;
	public static final byte VALUE_SWITCH_C2A = 101;
	public static final byte VALUE_SWITCH_C2B = 100;
	
	// STOP char 11000111010 + termination bar 11
	private static final byte[] STOP = {1,1,0,0,0,1,1,1,0,1,0,1,1};
	
	// encoded values with checksum ready for print; stop charactor 
	// and termination bar 11 is not included
	private final List<Byte>	values;
	
	// dimensions
	private int unitBarcodeWidth	= 0;
	private int unitMarginSide		= 12;  // quiet zone, minimum is 10 for code 128
	private int unitMarginTop		= 5;	
	// 
	private int barcodeWidth	= 0;
	private int marginSide		= 0;
	private int marginTop		= 0;
	private int fontSize		= 0;
	private int barcodeHeight;
	
	@Override
	protected void computeDimensions() {
		unitBarcodeWidth = values.size()*11 + 13; // STOP + TERMINATION is 13
		barcodeWidth	= unitBarcodeWidth * unitPixels;
		marginSide		= unitMarginSide * unitPixels;
		marginTop		= unitMarginTop * unitPixels;
		fontSize		= fontSizeUnit * unitPixels;
		
		width			= marginSide + barcodeWidth + marginSide;
		
		height			= (this.fixedHeight? this.height : (int)Math.round(width/ratioWH));
		barcodeHeight	= height - marginTop*2 - (this.showCode? this.fontSize : 0);
	}

	// map a char to Code128-A value, return value -1 means the input char is not
	// in Code128-A's alphabet
	public static byte getValueA(char c) {
		byte retval = -1;
		if( c<32 ) {
			retval = (byte)(c + 64);
		}else if( c<96 ) {
			retval = (byte)(c - 32);
		}
		return retval;
	}

	// map a char to Code128-B value, return value -1 means the input char is not
	// in Code128-B's alphabet
	public static byte getValueB(char c) {
		byte retval = -1;
		if( c>=32 && c<=127 ) {
			retval = (byte)(c - 32);
		}
		return retval;
	}

	private static final byte[][] VALUES = {
		// Value 0
		{1,1,0,1,1,0,0,1,1,0,0},{1,1,0,0,1,1,0,1,1,0,0},{1,1,0,0,1,1,0,0,1,1,0},{1,0,0,1,0,0,1,1,0,0,0},{1,0,0,1,0,0,0,1,1,0,0}, // 5
		{1,0,0,0,1,0,0,1,1,0,0},{1,0,0,1,1,0,0,1,0,0,0},{1,0,0,1,1,0,0,0,1,0,0},{1,0,0,0,1,1,0,0,1,0,0},{1,1,0,0,1,0,0,1,0,0,0}, // 10
		{1,1,0,0,1,0,0,0,1,0,0},{1,1,0,0,0,1,0,0,1,0,0},{1,0,1,1,0,0,1,1,1,0,0},{1,0,0,1,1,0,1,1,1,0,0},{1,0,0,1,1,0,0,1,1,1,0},
		{1,0,1,1,1,0,0,1,1,0,0},{1,0,0,1,1,1,0,1,1,0,0},{1,0,0,1,1,1,0,0,1,1,0},{1,1,0,0,1,1,1,0,0,1,0},{1,1,0,0,1,0,1,1,1,0,0}, // 20
		{1,1,0,0,1,0,0,1,1,1,0},{1,1,0,1,1,1,0,0,1,0,0},{1,1,0,0,1,1,1,0,1,0,0},{1,1,1,0,1,1,0,1,1,1,0},{1,1,1,0,1,0,0,1,1,0,0},
		{1,1,1,0,0,1,0,1,1,0,0},{1,1,1,0,0,1,0,0,1,1,0},{1,1,1,0,1,1,0,0,1,0,0},{1,1,1,0,0,1,1,0,1,0,0},{1,1,1,0,0,1,1,0,0,1,0}, // 30
		{1,1,0,1,1,0,1,1,0,0,0},{1,1,0,1,1,0,0,0,1,1,0},{1,1,0,0,0,1,1,0,1,1,0},{1,0,1,0,0,0,1,1,0,0,0},{1,0,0,0,1,0,1,1,0,0,0},
		{1,0,0,0,1,0,0,0,1,1,0},{1,0,1,1,0,0,0,1,0,0,0},{1,0,0,0,1,1,0,1,0,0,0},{1,0,0,0,1,1,0,0,0,1,0},{1,1,0,1,0,0,0,1,0,0,0}, // 40
		{1,1,0,0,0,1,0,1,0,0,0},{1,1,0,0,0,1,0,0,0,1,0},{1,0,1,1,0,1,1,1,0,0,0},{1,0,1,1,0,0,0,1,1,1,0},{1,0,0,0,1,1,0,1,1,1,0},
		{1,0,1,1,1,0,1,1,0,0,0},{1,0,1,1,1,0,0,0,1,1,0},{1,0,0,0,1,1,1,0,1,1,0},{1,1,1,0,1,1,1,0,1,1,0},{1,1,0,1,0,0,0,1,1,1,0}, // 50
		{1,1,0,0,0,1,0,1,1,1,0},{1,1,0,1,1,1,0,1,0,0,0},{1,1,0,1,1,1,0,0,0,1,0},{1,1,0,1,1,1,0,1,1,1,0},{1,1,1,0,1,0,1,1,0,0,0},
		{1,1,1,0,1,0,0,0,1,1,0},{1,1,1,0,0,0,1,0,1,1,0},{1,1,1,0,1,1,0,1,0,0,0},{1,1,1,0,1,1,0,0,0,1,0},{1,1,1,0,0,0,1,1,0,1,0}, // 60
		{1,1,1,0,1,1,1,1,0,1,0},{1,1,0,0,1,0,0,0,0,1,0},{1,1,1,1,0,0,0,1,0,1,0},{1,0,1,0,0,1,1,0,0,0,0},{1,0,1,0,0,0,0,1,1,0,0},
		{1,0,0,1,0,1,1,0,0,0,0},{1,0,0,1,0,0,0,0,1,1,0},{1,0,0,0,0,1,0,1,1,0,0},{1,0,0,0,0,1,0,0,1,1,0},{1,0,1,1,0,0,1,0,0,0,0}, // 70
		{1,0,1,1,0,0,0,0,1,0,0},{1,0,0,1,1,0,1,0,0,0,0},{1,0,0,1,1,0,0,0,0,1,0},{1,0,0,0,0,1,1,0,1,0,0},{1,0,0,0,0,1,1,0,0,1,0},
		{1,1,0,0,0,0,1,0,0,1,0},{1,1,0,0,1,0,1,0,0,0,0},{1,1,1,1,0,1,1,1,0,1,0},{1,1,0,0,0,0,1,0,1,0,0},{1,0,0,0,1,1,1,1,0,1,0}, // 80
		{1,0,1,0,0,1,1,1,1,0,0},{1,0,0,1,0,1,1,1,1,0,0},{1,0,0,1,0,0,1,1,1,1,0},{1,0,1,1,1,1,0,0,1,0,0},{1,0,0,1,1,1,1,0,1,0,0},
		{1,0,0,1,1,1,1,0,0,1,0},{1,1,1,1,0,1,0,0,1,0,0},{1,1,1,1,0,0,1,0,1,0,0},{1,1,1,1,0,0,1,0,0,1,0},{1,1,0,1,1,0,1,1,1,1,0}, // 90
		{1,1,0,1,1,1,1,0,1,1,0},{1,1,1,1,0,1,1,0,1,1,0},{1,0,1,0,1,1,1,1,0,0,0},{1,0,1,0,0,0,1,1,1,1,0},{1,0,0,0,1,0,1,1,1,1,0},
		{1,0,1,1,1,1,0,1,0,0,0},{1,0,1,1,1,1,0,0,0,1,0},{1,1,1,1,0,1,0,1,0,0,0},{1,1,1,1,0,1,0,0,0,1,0},{1,0,1,1,1,0,1,1,1,1,0}, // 100
		{1,0,1,1,1,1,0,1,1,1,0},{1,1,1,0,1,0,1,1,1,1,0},{1,1,1,1,0,1,0,1,1,1,0},{1,1,0,1,0,0,0,0,1,0,0},{1,1,0,1,0,0,1,0,0,0,0}, // 105
		{1,1,0,1,0,0,1,1,1,0,0}		
		// Value 105	
	};
	
	public static enum Type {
		A,B,C
	}
	
	@Override
	public Barcode code(String code) throws Exception {
		this.code(code.toCharArray());
		return this;
	}
	
	@Override
	public Barcode code(char[] code) throws Exception {
		if( !Barcode.isAsciis(code) ) throw new Exception("Code 128 string can only contain ASCII characters");
		this.code = code;
		this.values.clear();
		
		// get values for checksum
		this.getValues(code, this.values);
		
		// do checksum
		this.checksum(this.values);
		
		this.computeDimensions();
		return this;
	}
	
	public Code128(String code) throws Exception{
		this(code.toCharArray());
	}
	
	public Code128(char[] code) throws Exception {
		super(code);
		this.values = new ArrayList<>();
		this.code(code);
	}
	
	public void checksum(List<Byte> bytes) throws Exception {
		int size = bytes.size();
		if( bytes==null || size<1 ) throw new Exception("Invalid code 128 values for checksum");
		int sum = bytes.get(0);
		for( int i=1; i<size; i++ ) {
			sum = sum + bytes.get(i)*i;
		}
		byte checksum = (byte)(sum % 103);
		bytes.add(checksum);
	}
	
	@Override
	public Barcode.Type type() {
		return Barcode.Type.CODE128;
	}
	
	@Override
	public void save(File imageFile, ImageFormat format) throws Exception {
		if( imageFile==null || format==null ) return;
		
		BufferedImage bufferedImage = new BufferedImage(this.width, this.height, BufferedImage.TYPE_INT_RGB);

		Graphics2D g = bufferedImage.createGraphics();
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.fillRect(0, 0, width, height);
		g.setColor(Color.BLACK);
		
		this.print(g);
		
		g.dispose();
		RenderedImage rendImage = bufferedImage;

		ImageIO.write(rendImage, format.toString(), imageFile);
	}
	
	@Override
	public Image getImage() {
		BufferedImage bufferedImage = new BufferedImage(this.width, this.height, BufferedImage.TYPE_INT_RGB);
		Graphics2D g = bufferedImage.createGraphics();
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.fillRect(0, 0, width, height);
		g.setColor(Color.BLACK);
		this.print(g);
		g.dispose();
		return bufferedImage;
	}

	private void print(Graphics2D g) {
		
		int x = this.marginSide;
		final int y1 = this.marginTop;
		final int y2 = y1 + this.barcodeHeight; 
		int units;

		// the stroke will always have width equal to unit pixels
		g.setStroke(new BasicStroke(this.unitPixels));
		
		// print encoded values
		int numValues = this.values.size();
		byte value;
		byte[] bars;
		for(int i=0; i<numValues; i++) {
			value = this.values.get(i);
			bars = Code128.VALUES[value];
			for(int j=0; j<bars.length; j++) {
				if(bars[j]==1) {
					g.drawLine(x, y1, x, y2);					
				}
				x = x + this.unitPixels;
			}
		}
		
		// print STOP
		for(int j=0; j<Code128.STOP.length; j++) {
			if(Code128.STOP[j]==1) {
				g.drawLine(x, y1, x, y2);					
			}
			x = x + this.unitPixels;
		}

		if(this.showCode) {
			Rectangle clip = new Rectangle(this.marginSide,y2, this.width-this.marginSide*2, this.fontSize);
			Font	  font = new Font(Font.SANS_SERIF, Font.TRUETYPE_FONT, this.fontSize);
			g.setStroke(new BasicStroke(0));
			g.setFont(font);
			PrintUtil.drawString(g, new String(this.code, 0, this.code.length), clip, PrintUtil.Alignment.CENTER);
		}
	}
	
	// given an array of ASCII characters to be encoded in code128, produce an array of bytes
	// containing the CODE128 values. This needs to be done before doing checksum computation
	// caller need to make sure the chars are ASCII array, ie. all chars are<=127
	private void getValues(char[] chars, List<Byte> bytes) throws Exception {
		// choose which type to start with that has less switch(es) between types in the middle
		Type startType = Type.A;
		int numA, numB, numC;
		numA = Code128.getNumSwitches(Type.A, chars);
		numB = Code128.getNumSwitches(Type.B, chars);
		numC = Code128.getNumSwitches(Type.C, chars);
		if( numC<=numA ) {
			// A is out
			if( numC<=numB ) {
				startType = Type.C;
			}else{
				startType = Type.B;
			}
		}else {
			// C is out
			if( numA<=numB ) {
				startType = Type.A;
			}else{
				startType = Type.B;
			}
		}
		
		this.getValues(chars, startType, bytes);
	}
	
	// iterate throught the char array, get each char's code128 value, insert switch value
	// if needed, append the values to bytes list
	private void getValues(char[] chars, Type startType, List<Byte> bytes) throws Exception {

		// add start value
		if(startType==Type.A)		{ bytes.add(Code128.VALUE_START_A); }
		else if(startType==Type.B)	{ bytes.add(Code128.VALUE_START_B); }
		else if(startType==Type.C)	{ bytes.add(Code128.VALUE_START_C); }
		
		int		i			= 0;
		int		digitOccurs	= 0; // the # of adjacent digits starting from current position in the array
		Type	curType		= startType;
		int		pairValue	= 0;
		while(i<chars.length) {
			switch(curType) {
			case A:
				// type A alphabet is 0-95
				if( chars[i]>95 ) {
					// current char is in type B's alphabet, need to switch to B
					curType = Type.B;
					bytes.add(Code128.VALUE_SWITCH_A2B);
					bytes.add(Code128.getValueB(chars[i]));
					i++; // next char
				}else{
					// check if there is multiple digits following ...
					digitOccurs = Code128.digitOccurrence(chars, i);
					if(digitOccurs>=4) {
						// there more than 3 digits counting start current character, need to switch to C
						if( (digitOccurs%2)!=0 ) { // odd # of digits, the current digit don't need to switch
							bytes.add(Code128.getValueA(chars[i]));
							i++; // next char
							digitOccurs--;
						}
						curType = Type.C;
						bytes.add(Code128.VALUE_SWITCH_A2C);
						for(int j=0; j<digitOccurs; j=j+2) {
							pairValue = (chars[i+j] - 48)*10 + (chars[i+j+1] - 48); // '0' has ascii value 48
							bytes.add((byte)pairValue);
						}
						i = i + digitOccurs;
					}else{
						bytes.add(Code128.getValueA(chars[i]));
						i++; // next char
					}
				}
				break;
			case B:
				// type B alphabet is 32-127
				if( chars[i]<32 ) {
					// current char is in type A's alphabet, need to switch to A
					curType = Type.A;
					bytes.add(Code128.VALUE_SWITCH_B2A);
					bytes.add(Code128.getValueA(chars[i]));
					i++; // next char
				}else{
					// check if there is multiple digits following ...
					digitOccurs = Code128.digitOccurrence(chars, i);
					if(digitOccurs>=4) {
						// there more than 3 digits counting start current character, need to switch to C
						if( (digitOccurs%2)!=0 ) { // odd # of digits, the current digit don't need to switch
							bytes.add(Code128.getValueB(chars[i]));
							i++; // next char
							digitOccurs--;
						}
						curType = Type.C;
						bytes.add(Code128.VALUE_SWITCH_B2C);
						for(int j=0; j<digitOccurs; j=j+2) {
							pairValue = (chars[i+j] - 48)*10 + (chars[i+j+1] - 48); // '0' has ascii value 48
							bytes.add((byte)pairValue);
						}
						i = i + digitOccurs;
					}else{
						bytes.add(Code128.getValueB(chars[i]));
						i++; // next char
					}
				}
				break;
			case C:
				// type C contains only pairs of digits
				digitOccurs = Code128.digitOccurrence(chars, i);
				if( digitOccurs<2 ) {
					// need to switch, but do NOT process any char in the array
					char firstNoneDigit = Code128.firstNoneDigig(chars, i);
					// choose what type to switch to based on the first none digit char
					if( firstNoneDigit<32 ) {
						curType = Type.A;
					}else if(firstNoneDigit>95){
						curType = Type.B;
					}else{
						if(startType==Type.C) {
							curType = Type.A;
						}else{
							curType=startType; // back to startType
						}
					}
					if( curType==Type.A ) {
						bytes.add(Code128.VALUE_SWITCH_C2A);
					}else {
						bytes.add(Code128.VALUE_SWITCH_C2B);
					}
				}else{
					// there more than 2 digits counting start current character
					int startIdx = i;
					for(int j=0; j<(digitOccurs-1); j=j+2) {
						pairValue = (chars[startIdx+j] - 48)*10 + (chars[startIdx+j+1] - 48); // '0' has ascii value 48
						bytes.add((byte)pairValue);
						i = i + 2; // 2 chars processed
					}
				}
				break;
			default:
				throw new Exception("Unsupported code 128 type " + curType);
			}
		}
	}	
	
/**
	// starting at offset, check if there is digits pairs following. If yes, add the switch value
	// and following 
	// return the # of chars processed
	public static int processDigitPairs(char[] chars, final int offset, Type currentType, List<Byte> bytes) {
		int		i			= offset;
		byte	codeValue;
		int		digitOccurs	= Code128.digitOccurrence(chars, i);
		if(digitOccurs>=4) {
			// there more than 3 digits counting start current character, need to switch to C
			if( (digitOccurs%2)!=0 ) { // odd # of digits, the current digit don't need to switch
				bytes.add(Code128.getValueB(chars[i]));
				i++; // next char
				digitOccurs--;
			}
			numSwitch++;
			curType = Type.C;
			bytes.add(Code128.VALUE_SWITCH_B2C);
			for(int j=0; j<digitOccurs; j=j+2) {
				pairValue = (chars[i+j] - 48)*10 + (chars[i+j+1] - 48); // '0' has ascii value 48
				bytes.add((byte)pairValue);
			}
			i = i + digitOccurs + 1;
		}else{
			bytes.add(Code128.getValueA(chars[i]));
			i++; // next char
		}
		
	}
*/	
	// Compute the # of switches between Types we need to perform, if using 
	// start character of startType 
	private static int getNumSwitches(Type startType, char[] chars) throws Exception {
		// decide which type to use as start characters
		char c;
		int i = 0, digitOccurs = 0;
		Type curType = startType;
		int numSwitch = 0;
		while(i<chars.length) {
			c = chars[i];
			switch(curType) {
			case A:
				// type A alphabet is 0-95
				if( c>95 ) {
					// current char is in type B's alphabet, need to switch to B
					numSwitch++;
					curType = Type.B;
					i++;
				}else{
					digitOccurs = Code128.digitOccurrence(chars, i);
					if(digitOccurs>=4) {
						// need to switch to C
						numSwitch++;
						curType = Type.C;
						i = i + digitOccurs;
					}
					i++;
				}
				break;
			case B:
				// type B alphabet is 32-127
				if( c<32 ) {
					// current char is in type A's alphabet, need to switch to A
					numSwitch++;
					curType = Type.A;
					i++;
				}else{
					digitOccurs = Code128.digitOccurrence(chars, i);
					if(digitOccurs>=4) {
						// need to switch to C
						numSwitch++;
						curType = Type.C;
						i = i + digitOccurs;
					}
					i++;
				}
				break;
			case C:
				// type C contains only pairs of digits
				digitOccurs = Code128.digitOccurrence(chars, i);
				if( digitOccurs<2 ) {
					// need to switch
					numSwitch++;
					char firstNoneDigit = Code128.firstNoneDigig(chars, i);
					// choose what type to switch to based on the first none digit char
					if( firstNoneDigit<32) {
						curType = Type.A;
					}else if(firstNoneDigit>95){
						curType = Type.B;
					}else{
						if(startType==Type.C) {
							curType = Type.A;
						}else{
							curType=startType;
						}
					}
				}else{
					i = i + 2*(digitOccurs/2);
					i++;
				}
				break;
			default:
				throw new Exception("Unsupported code 128 type " + curType);
			}
		}
		return numSwitch;
	}
	
	private static int digitOccurrence(char[] chars, int offset) {
		int retval = 0;
		for(int i=offset; i<chars.length; i++) {
			if( chars[i]>=48 && chars[i]<=57 ) {
				retval++;
			}else{
				break;
			}
		}
		return retval;
	}
	
	private static char firstNoneDigig(char[] chars, int offset) {
		char c = 0;
		for(int i=offset; i<chars.length; i++) {
			if( chars[i]<48 || chars[i]>57 ) {
				c = chars[i];
				break;
			}
		}
		return c;
	}
}

Added src/fanxi/barcode/PrintUtil.java.





























































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package fanxi.barcode;

import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.font.LineMetrics;

public class PrintUtil {
	public static final double	MM_TO_INCH		= 0.0393701;
	public static final int		DPI_USER_SPACE	= 72;
	public static final double	MM_TO_POINT		= PrintUtil.MM_TO_INCH * DPI_USER_SPACE;
	
	public static enum Alignment {
		CENTER, NORTH, SOUTH, EAST, WEST, NE, SE, NW, SW
	}
	
	/** Draw a string within the provided rectangle, the parts that are outside the rectangle
	 * will be clipped.  */
	public static void drawString(Graphics2D g2d, String text, Rectangle clip, PrintUtil.Alignment alignment) {
		Shape	oldClip		= g2d.getClip();
		FontMetrics	fontMetrics	= g2d.getFontMetrics();
		LineMetrics	lineMetrics = fontMetrics.getLineMetrics(text, g2d);
		float		lineAscend	= lineMetrics.getAscent();
		float		lineDescend	= lineMetrics.getDescent();
		float		lineLeading	= lineMetrics.getLeading();
		float		lineHeight	= lineMetrics.getHeight(); // this height includes leading
		float		lineWidth	= fontMetrics.stringWidth(text);
		
		// default is NW (NorthWest)
		float		x			= clip.x;
		float		y			= clip.y + lineAscend; // string anchor point
		
		/* Compute the anchor point of the text, ie, the (x,y) 
		 * where x is the begin and y is the baseline position */
		switch(alignment) {
		case CENTER:
			x = clip.x + (clip.width - lineWidth)/2;
			y = clip.y + (clip.height - lineHeight)/2 + lineAscend; // y is the baseline y-coord
			break;
		case NORTH:
			x = clip.x + (clip.width - lineWidth)/2;			
			break;
		case SOUTH:
			x = clip.x + (clip.width - lineWidth)/2;
			y = clip.y + clip.height - lineDescend;
			break;
		case EAST:
			x = clip.x + clip.width - lineWidth;
			y = clip.y + (clip.height - lineHeight)/2 + lineAscend; // y is the baseline y-coord
			break;
		case WEST:
			y = clip.y + (clip.height - lineHeight)/2 + lineAscend; // y is the baseline y-coord
			break;
		case SE:
			x = clip.x + clip.width - lineWidth;
			y = clip.y + clip.height - lineDescend;
			break;
		case NE:
			x = clip.x + clip.width - lineWidth;
			break;
		case SW:
			y = clip.y + clip.height - lineDescend;
			break;
		case NW:
		default:
			break;
		}
		
		g2d.clip(clip);
		g2d.drawString(text, x, y);
		g2d.setClip(oldClip);
	}
	
	/** Draw a string within the provided rectangle, the parts that are outside the rectangle
	 * will be clipped.  */
	public static void drawImage(Graphics2D g2d, Image image, Rectangle clip, PrintUtil.Alignment alignment) {
		Shape	oldClip		= g2d.getClip();
		int		imageWidth	= image.getWidth(null);
		int		imageHeight	= image.getHeight(null);
		
		// default is NW (NorthWest)
		int		x			= clip.x;
		int		y			= clip.y; // string anchor point
		
		/* Compute the anchor point of the image, ie, the (x,y) */
		switch(alignment) {
		case CENTER:
			x = clip.x + (clip.width - imageWidth)/2;
			y = clip.y + (clip.height - imageHeight)/2;
			break;
		case NORTH:
			x = clip.x + (clip.width - imageWidth)/2;			
			break;
		case SOUTH:
			x = clip.x + (clip.width - imageWidth)/2;
			y = clip.y + clip.height - imageHeight;
			break;
		case EAST:
			x = clip.x + clip.width - imageWidth;
			y = clip.y + (clip.height - imageHeight)/2; // y is the baseline y-coord
			break;
		case WEST:
			y = clip.y + (clip.height - imageHeight)/2; // y is the baseline y-coord
			break;
		case SE:
			x = clip.x + clip.width - imageWidth;
			y = clip.y + clip.height - imageHeight;
			break;
		case NE:
			x = clip.x + clip.width - imageWidth;
			break;
		case SW:
			y = clip.y + clip.height - imageHeight;
			break;
		case NW:
		default:
			break;
		}
		
		g2d.clip(clip);
		g2d.drawImage(image, clip.x, clip.y, clip.width, clip.height, null);
		g2d.setClip(oldClip);
	}
}

Added src/fanxi/barcode/Upc.java.





















































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
package fanxi.barcode;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;

import javax.imageio.ImageIO;

public class Upc extends Barcode {
	
	// start/stop code, bar-space-bar
	private final int[] codeSS		= {1,1,1};

	// space-bar-space-bar-space
	private final int[] splitter	= {1,1,1,1,1};
	
	private final int[][] codeDigits = {
			{3,2,1,1},	// 0
			{2,2,2,1},	// 1
			{2,1,2,2},	// 2
			{1,4,1,1},	// 3
			{1,1,3,2},	// 4
			{1,2,3,1},	// 5
			{1,1,1,4},	// 6
			{1,3,1,2},	// 7
			{1,2,1,3},	// 8
			{3,1,1,2}	// 9
	};

	// all number are in units
	// total units of UPC barcode is 
	// Start code (1-1-1)	        3
	// 1st 6 digits (7 each) 6*7 = 42
	// splitter (1-1-1-1-1)		    5
	// 2nd 6 digits (7 each) 6*7 = 42
	// End code (1-1-1)	            3		
	// Total units                 95 **
	private int unitBarcodeWidth	= 95;
	private int unitMarginSide		= 10;
	private int unitMarginTop		= 5;
	private int unitFontSize1		= 8;
	private int unitTextIndent		= 5;
	// end of configurable parameters

	// following data are computed based on above configurable parameters
	private int barcodeWidth;
	private int marginSide;
	private int marginTop;
	private int fontSize;
	private int fontSize1;
	private int textIndent;
	private int barcodeHeight;

	@Override
	protected void computeDimensions() {
		barcodeWidth	= unitBarcodeWidth * unitPixels;
		marginSide		= unitMarginSide * unitPixels;
		marginTop		= unitMarginTop * unitPixels;
		fontSize		= fontSizeUnit * unitPixels;
		fontSize1		= unitFontSize1 * unitPixels;
		textIndent		= unitTextIndent * unitPixels;
		
		width			= marginSide + barcodeWidth + marginSide;
		
		height			= (this.fixedHeight? this.height : (int)Math.round(width/ratioWH));
		barcodeHeight	= height - marginTop*2 - (this.showCode? this.fontSize + this.textIndent : 0) ;
	}
	
	public Upc(String code) throws Exception {
		super(new char[12]);
		if(code==null) throw new Exception("Num string can not be encoded");
		this.computeBarcode(code.toCharArray());
		this.computeDimensions();
	}
	
	// code's length can only be 11 (without checksum) or 12 (with checksum)
	public Upc(char[] code) 
			throws Exception 
	{
		super(new char[12]);
		this.computeBarcode(code);
		this.computeDimensions();
	}
	
	@Override
	public Barcode code(String code) throws Exception {
		if( code==null ) throw new Exception("UPC code must be provided");
		this.computeBarcode(code.toCharArray());
		this.computeDimensions();
		return this;
	}
	
	@Override
	public Barcode code(char[] code) throws Exception {
		if( code==null ) throw new Exception("UPC code must be provided");
		this.computeBarcode(code);
		this.computeDimensions();	
		return this;
	}
	
	@Override
	public Barcode.Type type() {
		return Barcode.Type.UPC;
	}
	
	// compute checksum and fill this.barcode[] array
	private void computeBarcode(char[] code) throws Exception {
		
		if( !Barcode.isNumbers(code) ) {
			throw new Exception("UPC code must be all digit numbers");
		}

		if( code.length!=11 && code.length!=12 ) {
			throw new Exception(
				"UPC code must be 11 digit number (without checksum) or "
					+ "12 digit number (with checksum)");
		}

		// put all digit to barcode
		for(int i=0; i<code.length; i++) {
			this.code[i] = code[i];
		}

		// compute the checksum
		int sumOdd = 0, sumEven = 0, sum;
		char checksum;
		for(int i=0; i<11; i=i+2) {
			sumOdd = sumOdd + this.code[i] - 48;
		}
		sumOdd = sumOdd*3;
		for(int i=1; i<11; i=i+2) {
			sumEven = sumEven + this.code[i] - 48;
		}
		sum = sumOdd + sumEven;
		checksum = (char) (10 - (sum % 10) + 48);
		this.code[11] = checksum;
		
		if( code.length==12 && this.code[11]!=code[11] ) {
			throw new Exception("checksum digit is wrong: " + this.code[11] + " vs " + code[11]);
		}
	}
	
	@Override
	public String toString() {
		return new String(this.code);
	}
	
	@Override
	public void save(File imageFile, ImageFormat format) throws Exception {
		if( imageFile==null || format==null ) return;
		
		BufferedImage bufferedImage = new BufferedImage(this.width, this.height, BufferedImage.TYPE_INT_RGB);

		Graphics2D g = bufferedImage.createGraphics();
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.fillRect(0, 0, width, height);
		g.setColor(Color.BLACK);
		
		this.printBar(g);
		
		g.dispose();
		RenderedImage rendImage = bufferedImage;

		ImageIO.write(rendImage, format.toString(), imageFile);
	}
	
	@Override
	public Image getImage() {
		BufferedImage bufferedImage = new BufferedImage(this.width, this.height, BufferedImage.TYPE_INT_RGB);
		Graphics2D g = bufferedImage.createGraphics();
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.fillRect(0, 0, width, height);
		g.setColor(Color.BLACK);
		this.printBar(g);
		g.dispose();
		return bufferedImage;
	}

	// print a set of bar-space-bar-... at position 0
	private void printBar(Graphics2D g) {
		int x = this.marginSide;
		final int y1 = this.marginTop;
		final int y2 = y1 + this.barcodeHeight; 
		boolean isBar = true;
		int units;
		char c;
		int[] bars;
		int x1=0, x2=0, x3=0, x4=0;

		// the stroke will always have width equal to unit pixels
		g.setStroke(new BasicStroke(this.unitPixels));
		
		// print start code 1-1-1, start with bar (bar-space-bar)
		for(int i=0; i<this.codeSS.length; i++) {
			units = this.codeSS[i];
			if(isBar) {
				for( int k=0; k<units; k++ ) {
					g.drawLine(x + k*this.unitPixels, y1, x + k*this.unitPixels, y2);
				}
			}
			x = x + units*this.unitPixels;
			isBar = (!isBar);
		}
		
		// print the first 6 code, six of (space-bar-space-bar)
		for(int i=0; i<6; i++) {
			if(i==1) {
				x1 = x;
			}
			c = this.code[i];
			bars = this.codeDigits[c-48];
			for(int j=0; j<bars.length; j++) {
				units = bars[j];
				if(isBar) {
					for( int k=0; k<units; k++ ) {
						g.drawLine(x + k*this.unitPixels, y1, x + k*this.unitPixels, y2);
					}
				}
				x = x + units*this.unitPixels;
				isBar = (!isBar);
			}
		}
		
		// print splitter 1-1-1-1-1 code (space-bar-space-bar-space)
		x2 = x;
		for(int i=0; i<this.splitter.length; i++) {
			units = this.splitter[i];
			if(isBar) {
				for( int k=0; k<units; k++ ) {
					g.drawLine(x + k*this.unitPixels, y1, x + k*this.unitPixels, y2);
				}
			}
			x = x + units*this.unitPixels;
			isBar = (!isBar);
		}
		x3 = x;
		
		// print the last 6 code, six of (bar-space-bar-space)
		for(int i=6; i<12; i++) {
			if( i==11 ) {
				x4 = x;
			}
			c = this.code[i];
			bars = this.codeDigits[c-48];
			for(int j=0; j<bars.length; j++) {
				units = bars[j];
				if(isBar) {
					for( int k=0; k<units; k++ ) {
						g.drawLine(x + k*this.unitPixels, y1, x + k*this.unitPixels, y2);
					}
				}
				x = x + units*this.unitPixels;
				isBar = (!isBar);
			}
		}
		
		// print stop code, (bar-space-bar)
		for(int i=0; i<this.codeSS.length; i++) {
			units = this.codeSS[i];
			if(isBar) {
				for( int k=0; k<units; k++ ) {
					g.drawLine(x + k*this.unitPixels, y1, x + k*this.unitPixels, y2);
				}
			}
			x = x + units*this.unitPixels;
			isBar = (!isBar);
		}

		if( this.showCode ) {
			Rectangle	clip		= new Rectangle(this.marginSide,y2-this.fontSize/2,
											this.width-this.marginSide*2, this.fontSize);
			Font		font		= new Font(Font.SANS_SERIF, Font.TRUETYPE_FONT, this.fontSize);
			Font		font1		= new Font(Font.SANS_SERIF, Font.TRUETYPE_FONT, this.fontSize1);
			
			g.setStroke(new BasicStroke(0));
			g.setFont(font1);
			clip.setFrame(0, y2-this.textIndent, this.marginSide-this.unitPixels, this.fontSize);
			g.setPaint(Color.WHITE);
			g.fill(clip);
			g.setPaint(Color.BLACK);
			PrintUtil.drawString(g, Character.toString(this.code[0]), clip, PrintUtil.Alignment.CENTER);
	
			g.setFont(font);
			clip.setFrame(x1, y2-this.textIndent, x2-x1, this.fontSize);
			g.setPaint(Color.WHITE);
			g.fill(clip);
			g.setPaint(Color.BLACK);
			PrintUtil.drawString(g, new String(this.code,1,5), clip, PrintUtil.Alignment.CENTER);
			
			g.setFont(font);
			clip.setFrame(x3-this.unitPixels, y2-this.textIndent, x4-x3, this.fontSize);
			g.setPaint(Color.WHITE);
			g.fill(clip);
			g.setPaint(Color.BLACK);
			PrintUtil.drawString(g, new String(this.code,6,5), clip, PrintUtil.Alignment.CENTER);
	
			g.setFont(font1);
			clip.setFrame(this.width-this.marginSide, y2-this.textIndent, this.marginSide, this.fontSize);
			g.setPaint(Color.WHITE);
			g.fill(clip);
			g.setPaint(Color.BLACK);
			PrintUtil.drawString(g, Character.toString(this.code[11]), clip, PrintUtil.Alignment.CENTER);
		}
	}
	
}